diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCardItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCardItems.kt index f5fdd1867..20647ab26 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCardItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCardItems.kt @@ -9,6 +9,10 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag @@ -70,10 +74,16 @@ fun LazyListScope.vaultAddEditCardItems( } item { Spacer(modifier = Modifier.height(8.dp)) + var showNumber by rememberSaveable { mutableStateOf(value = false) } BitwardenPasswordField( label = stringResource(id = R.string.number), value = cardState.number, onValueChange = cardHandlers.onNumberTextChange, + showPassword = showNumber, + showPasswordChange = { + showNumber = !showNumber + cardHandlers.onNumberVisibilityChange(showNumber) + }, modifier = Modifier .testTag("CardNumberEntry") .fillMaxWidth() @@ -140,10 +150,16 @@ fun LazyListScope.vaultAddEditCardItems( } item { Spacer(modifier = Modifier.height(8.dp)) + var showSecurityCode by rememberSaveable { mutableStateOf(value = false) } BitwardenPasswordField( label = stringResource(id = R.string.security_code), value = cardState.securityCode, onValueChange = cardHandlers.onSecurityCodeTextChange, + showPassword = showSecurityCode, + showPasswordChange = { + showSecurityCode = !showSecurityCode + cardHandlers.onSecurityCodeVisibilityChange(showSecurityCode) + }, keyboardType = KeyboardType.NumberPassword, modifier = Modifier .testTag("CardSecurityCodeEntry") @@ -259,7 +275,7 @@ fun LazyListScope.vaultAddEditCardItems( items(commonState.customFieldData) { customItem -> VaultAddEditCustomField( - customItem, + customField = customItem, onCustomFieldValueChange = commonHandlers.onCustomFieldValueChange, onCustomFieldAction = commonHandlers.onCustomFieldActionSelect, modifier = Modifier @@ -273,6 +289,7 @@ fun LazyListScope.vaultAddEditCardItems( VaultLinkedFieldType.BRAND, VaultLinkedFieldType.NUMBER, ), + onHiddenVisibilityChanged = commonHandlers.onHiddenFieldVisibilityChange, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt index 666e4834d..1a9f1aeb6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt @@ -37,6 +37,7 @@ import kotlinx.collections.immutable.toImmutableList * @param onCustomFieldAction Invoked when the user chooses an action. * @param modifier Modifier for the UI elements. * @param supportedLinkedTypes The supported linked types for the vault item. + * @param onHiddenVisibilityChanged Emits when the visibility of a hidden custom field changes. */ @Composable @Suppress("LongMethod") @@ -46,6 +47,7 @@ fun VaultAddEditCustomField( onCustomFieldAction: (CustomFieldAction, VaultAddEditState.Custom) -> Unit, modifier: Modifier = Modifier, supportedLinkedTypes: ImmutableList = persistentListOf(), + onHiddenVisibilityChanged: (Boolean) -> Unit, ) { var shouldShowChooserDialog by remember { mutableStateOf(false) } var shouldShowEditDialog by remember { mutableStateOf(false) } @@ -91,11 +93,12 @@ fun VaultAddEditCustomField( is VaultAddEditState.Custom.HiddenField -> { CustomFieldHiddenField( - customField.name, - customField.value, + label = customField.name, + value = customField.value, onValueChanged = { onCustomFieldValueChange(customField.copy(value = it)) }, + onVisibilityChanged = onHiddenVisibilityChanged, onEditValue = { shouldShowChooserDialog = true }, modifier = modifier, ) @@ -175,12 +178,19 @@ private fun CustomFieldHiddenField( value: String, onValueChanged: (String) -> Unit, onEditValue: () -> Unit, + onVisibilityChanged: (Boolean) -> Unit, modifier: Modifier = Modifier, ) { + var shouldShowPassword by remember { mutableStateOf(value = false) } BitwardenPasswordFieldWithActions( label = label, value = value, onValueChange = onValueChanged, + showPassword = shouldShowPassword, + showPasswordChange = { + shouldShowPassword = !shouldShowPassword + onVisibilityChanged(shouldShowPassword) + }, singleLine = true, modifier = modifier, actions = { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditIdentityItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditIdentityItems.kt index 7c94fe8c6..8e9a9d9d3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditIdentityItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditIdentityItems.kt @@ -403,6 +403,7 @@ fun LazyListScope.vaultAddEditIdentityItems( VaultLinkedFieldType.LAST_NAME, VaultLinkedFieldType.FULL_NAME, ), + onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditLoginItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditLoginItems.kt index 0aaac0d56..1cb062954 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditLoginItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditLoginItems.kt @@ -12,6 +12,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -301,7 +302,7 @@ fun LazyListScope.vaultAddEditLoginItems( items(commonState.customFieldData) { customItem -> VaultAddEditCustomField( - customItem, + customField = customItem, onCustomFieldValueChange = commonActionHandler.onCustomFieldValueChange, onCustomFieldAction = commonActionHandler.onCustomFieldActionSelect, modifier = Modifier @@ -311,6 +312,7 @@ fun LazyListScope.vaultAddEditLoginItems( VaultLinkedFieldType.PASSWORD, VaultLinkedFieldType.USERNAME, ), + onHiddenVisibilityChanged = commonActionHandler.onHiddenFieldVisibilityChange, ) } @@ -436,10 +438,16 @@ private fun PasswordRow( var shouldShowDialog by rememberSaveable { mutableStateOf(false) } if (canViewPassword) { + var shouldShowPassword by remember { mutableStateOf(false) } BitwardenPasswordFieldWithActions( label = stringResource(id = R.string.password), value = password, onValueChange = loginItemTypeHandlers.onPasswordTextChange, + showPassword = shouldShowPassword, + showPasswordChange = { + shouldShowPassword = !shouldShowPassword + loginItemTypeHandlers.onPasswordVisibilityChange(shouldShowPassword) + }, showPasswordTestTag = "ViewPasswordButton", passwordFieldTestTag = "LoginPasswordEntry", modifier = Modifier diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditSecureNotesItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditSecureNotesItems.kt index 8c05c6f76..5f1ed57b0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditSecureNotesItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditSecureNotesItems.kt @@ -154,12 +154,13 @@ fun LazyListScope.vaultAddEditSecureNotesItems( } items(commonState.customFieldData) { customItem -> VaultAddEditCustomField( - customItem, + customField = customItem, onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange, onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), + onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index dda1ba595..53989ec5f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -214,9 +214,9 @@ class VaultAddEditViewModel @Inject constructor( private fun handleCommonActions(action: VaultAddEditAction.Common) { when (action) { - is VaultAddEditAction.Common.CustomFieldValueChange -> handleCustomFieldValueChange( - action, - ) + is VaultAddEditAction.Common.CustomFieldValueChange -> { + handleCustomFieldValueChange(action) + } is VaultAddEditAction.Common.FolderChange -> handleFolderTextInputChange(action) is VaultAddEditAction.Common.NameTextChange -> handleNameTextInputChange(action) @@ -235,19 +235,23 @@ class VaultAddEditViewModel @Inject constructor( is VaultAddEditAction.Common.DismissDialog -> handleDismissDialog() is VaultAddEditAction.Common.SaveClick -> handleSaveClick() is VaultAddEditAction.Common.TypeOptionSelect -> handleTypeOptionSelect(action) - is VaultAddEditAction.Common.AddNewCustomFieldClick -> handleAddNewCustomFieldClick( - action, - ) + is VaultAddEditAction.Common.AddNewCustomFieldClick -> { + handleAddNewCustomFieldClick(action) + } is VaultAddEditAction.Common.TooltipClick -> handleTooltipClick() - is VaultAddEditAction.Common.CustomFieldActionSelect -> handleCustomFieldActionSelected( - action, - ) + is VaultAddEditAction.Common.CustomFieldActionSelect -> { + handleCustomFieldActionSelected(action) + } is VaultAddEditAction.Common.CollectionSelect -> handleCollectionSelect(action) is VaultAddEditAction.Common.InitialAutofillDialogDismissed -> { handleInitialAutofillDialogDismissed() } + + is VaultAddEditAction.Common.HiddenFieldVisibilityChange -> { + handleHiddenFieldVisibilityChange(action) + } } } @@ -422,6 +426,20 @@ class VaultAddEditViewModel @Inject constructor( } } + private fun handleHiddenFieldVisibilityChange( + action: VaultAddEditAction.Common.HiddenFieldVisibilityChange, + ) { + onEdit { + if (action.isVisible) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledHiddenFieldVisible( + cipherId = it.vaultItemId, + ), + ) + } + } + } + private fun handleAddNewCustomFieldClick( action: VaultAddEditAction.Common.AddNewCustomFieldClick, ) { @@ -636,6 +654,10 @@ class VaultAddEditViewModel @Inject constructor( is VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick -> { handleLoginClearTotpKey() } + + is VaultAddEditAction.ItemType.LoginType.PasswordVisibilityChange -> { + handlePasswordVisibilityChange(action) + } } } @@ -736,6 +758,20 @@ class VaultAddEditViewModel @Inject constructor( } } + private fun handlePasswordVisibilityChange( + action: VaultAddEditAction.ItemType.LoginType.PasswordVisibilityChange, + ) { + onEdit { + if (action.isVisible) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledPasswordVisible( + cipherId = it.vaultItemId, + ), + ) + } + } + } + private fun handleLoginAddNewUriClick() { updateLoginContent { loginType -> loginType.copy( @@ -961,9 +997,17 @@ class VaultAddEditViewModel @Inject constructor( handleCardNumberTextChange(action) } + is VaultAddEditAction.ItemType.CardType.NumberVisibilityChange -> { + handleNumberVisibilityChange(action) + } + is VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange -> { handleCardSecurityCodeTextChange(action) } + + is VaultAddEditAction.ItemType.CardType.SecurityCodeVisibilityChange -> { + handleSecurityCodeVisibilityChange(action) + } } } @@ -997,12 +1041,40 @@ class VaultAddEditViewModel @Inject constructor( updateCardContent { it.copy(number = action.number) } } + private fun handleNumberVisibilityChange( + action: VaultAddEditAction.ItemType.CardType.NumberVisibilityChange, + ) { + onEdit { + if (action.isVisible) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledCardNumberVisible( + cipherId = it.vaultItemId, + ), + ) + } + } + } + private fun handleCardSecurityCodeTextChange( action: VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange, ) { updateCardContent { it.copy(securityCode = action.securityCode) } } + private fun handleSecurityCodeVisibilityChange( + action: VaultAddEditAction.ItemType.CardType.SecurityCodeVisibilityChange, + ) { + onEdit { + if (action.isVisible) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledCardCodeVisible( + cipherId = it.vaultItemId, + ), + ) + } + } + } + //endregion Card Type Handlers //region Internal Type Handlers @@ -1024,8 +1096,9 @@ class VaultAddEditViewModel @Inject constructor( handleGeneratorResultReceive(action) } - is VaultAddEditAction.Internal.PasswordBreachReceive -> + is VaultAddEditAction.Internal.PasswordBreachReceive -> { handlePasswordBreachReceive(action) + } } } @@ -2009,6 +2082,13 @@ sealed class VaultAddEditAction { data class CollectionSelect( val collection: VaultCollection, ) : Common() + + /** + * The user has changed the visibility state of a hidden field. + * + * @property isVisible the new visibility state of the hidden field. + */ + data class HiddenFieldVisibilityChange(val isVisible: Boolean) : Common() } /** @@ -2085,6 +2165,13 @@ sealed class VaultAddEditAction { * Represents the action to add a new URI field. */ data object AddNewUriClick : LoginType() + + /** + * Fired when the password's visibility has changed. + * + * @property isVisible The new password visibility state. + */ + data class PasswordVisibilityChange(val isVisible: Boolean) : LoginType() } /** @@ -2240,6 +2327,13 @@ sealed class VaultAddEditAction { */ data class NumberTextChange(val number: String) : CardType() + /** + * Fired when the number's visibility has changed. + * + * @property isVisible The new number visibility state. + */ + data class NumberVisibilityChange(val isVisible: Boolean) : CardType() + /** * Fired when the brand input is selected. * @@ -2272,6 +2366,13 @@ sealed class VaultAddEditAction { * @property securityCode The new security code text. */ data class SecurityCodeTextChange(val securityCode: String) : CardType() + + /** + * Fired when the security code's visibility has changed. + * + * @property isVisible The new code visibility state. + */ + data class SecurityCodeVisibilityChange(val isVisible: Boolean) : CardType() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditCardTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditCardTypeHandlers.kt index f50503033..859dd0fb0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditCardTypeHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditCardTypeHandlers.kt @@ -15,6 +15,9 @@ import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth * @property onExpirationMonthSelected Handles the action when an expiration month is selected. * @property onExpirationYearTextChange Handles the action when the expiration year text is changed. * @property onSecurityCodeTextChange Handles the action when the expiration year text is changed. + * @property onSecurityCodeVisibilityChange Handles the action when the security code visibility + * changes. + * @property onNumberVisibilityChange Handles the action when the number visibility changes. */ @Suppress("MaxLineLength") data class VaultAddEditCardTypeHandlers( @@ -24,8 +27,9 @@ data class VaultAddEditCardTypeHandlers( val onExpirationMonthSelected: (VaultCardExpirationMonth) -> Unit, val onExpirationYearTextChange: (String) -> Unit, val onSecurityCodeTextChange: (String) -> Unit, - - ) { + val onSecurityCodeVisibilityChange: (Boolean) -> Unit, + val onNumberVisibilityChange: (Boolean) -> Unit, +) { companion object { /** @@ -76,6 +80,18 @@ data class VaultAddEditCardTypeHandlers( ), ) }, + onSecurityCodeVisibilityChange = { + viewModel.trySendAction( + VaultAddEditAction.ItemType.CardType.SecurityCodeVisibilityChange( + isVisible = it, + ), + ) + }, + onNumberVisibilityChange = { + viewModel.trySendAction( + VaultAddEditAction.ItemType.CardType.NumberVisibilityChange(isVisible = it), + ) + }, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditCommonHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditCommonHandlers.kt index 9ba4c0e79..ba253a65c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditCommonHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditCommonHandlers.kt @@ -37,6 +37,7 @@ data class VaultAddEditCommonHandlers( val onCustomFieldValueChange: (VaultAddEditState.Custom) -> Unit, val onCustomFieldActionSelect: (CustomFieldAction, VaultAddEditState.Custom) -> Unit, val onCollectionSelect: (VaultCollection) -> Unit, + val onHiddenFieldVisibilityChange: (Boolean) -> Unit, ) { companion object { @@ -116,6 +117,11 @@ data class VaultAddEditCommonHandlers( ), ) }, + onHiddenFieldVisibilityChange = { + viewModel.trySendAction( + VaultAddEditAction.Common.HiddenFieldVisibilityChange(isVisible = it), + ) + }, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditLoginTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditLoginTypeHandlers.kt index 5af6b4e0c..7f214bf16 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditLoginTypeHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/handlers/VaultAddEditLoginTypeHandlers.kt @@ -22,6 +22,8 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem * @property onCopyTotpKeyClick Handles the action when the copy TOTP text button is clicked. * @property onClearTotpKeyClick Handles the action when the clear TOTP text button is clicked. * @property onAddNewUriClick Handles the action when the add new URI button is clicked. + * @property onPasswordVisibilityChange Handles the action when the password visibility button is + * clicked. */ @Suppress("LongParameterList") data class VaultAddEditLoginTypeHandlers( @@ -36,6 +38,7 @@ data class VaultAddEditLoginTypeHandlers( val onCopyTotpKeyClick: (String) -> Unit, val onClearTotpKeyClick: () -> Unit, val onAddNewUriClick: () -> Unit, + val onPasswordVisibilityChange: (Boolean) -> Unit, ) { companion object { @@ -106,6 +109,11 @@ data class VaultAddEditLoginTypeHandlers( VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick, ) }, + onPasswordVisibilityChange = { + viewModel.trySendAction( + VaultAddEditAction.ItemType.LoginType.PasswordVisibilityChange(it), + ) + }, ) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index 4818fac88..252af5d84 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.isPopup import androidx.compose.ui.test.onAllNodesWithContentDescription import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onChildren import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onLast import androidx.compose.ui.test.onNodeWithContentDescription @@ -466,6 +467,24 @@ class VaultAddEditScreenTest : BaseComposeTest() { .assertDoesNotExist() } + @Suppress("MaxLineLength") + @Test + fun `in ItemType_Login state changing password visibility state should send PasswordVisibilityChange`() { + composeTestRule + .onNodeWithTextAfterScroll(text = "Password") + .assertExists() + composeTestRule + .onNodeWithContentDescriptionAfterScroll(label = "Show") + .assertExists() + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction( + VaultAddEditAction.ItemType.LoginType.PasswordVisibilityChange(isVisible = true), + ) + } + } + @Test fun `in ItemType_Login state changing Username text field should trigger UsernameTextChange`() { composeTestRule @@ -1671,6 +1690,30 @@ class VaultAddEditScreenTest : BaseComposeTest() { } } + @Test + fun `in ItemType_Card changing number visibility should trigger NumberVisibilityChange`() { + mutableStateFlow.value = DEFAULT_STATE_CARD.copy( + viewState = VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common(), + type = VaultAddEditState.ViewState.Content.ItemType.Card(number = "12345"), + isIndividualVaultDisabled = false, + ), + ) + composeTestRule + .onNodeWithTextAfterScroll(text = "Number") + .assertExists() + .onChildren() + .filterToOne(hasContentDescription(value = "Show")) + .assertExists() + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction( + VaultAddEditAction.ItemType.CardType.NumberVisibilityChange(isVisible = true), + ) + } + } + @Test fun `in ItemType_Card the number text field should display the text provided by the state`() { mutableStateFlow.value = DEFAULT_STATE_CARD @@ -1831,6 +1874,30 @@ class VaultAddEditScreenTest : BaseComposeTest() { } } + @Test + fun `in ItemType_Card changing code visibility should trigger SecurityCodeVisibilityChange`() { + mutableStateFlow.value = DEFAULT_STATE_CARD.copy( + viewState = VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common(), + type = VaultAddEditState.ViewState.Content.ItemType.Card(number = "12345"), + isIndividualVaultDisabled = false, + ), + ) + composeTestRule + .onNodeWithTextAfterScroll(text = "Security code") + .assertExists() + .onChildren() + .filterToOne(hasContentDescription(value = "Show")) + .assertExists() + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction( + VaultAddEditAction.ItemType.CardType.SecurityCodeVisibilityChange(isVisible = true), + ) + } + } + @Suppress("MaxLineLength") @Test fun `in ItemType_Card the security code text field should display the text provided by the state`() { @@ -2373,6 +2440,40 @@ class VaultAddEditScreenTest : BaseComposeTest() { } } + @Test + fun `changing hidden field visibility state should send HiddenFieldVisibilityChange`() { + mutableStateFlow.update { + it.copy( + viewState = VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common( + customFieldData = listOf( + VaultAddEditState.Custom.HiddenField( + itemId = "itemId", + name = "Hidden item", + value = "I am hiding", + ), + ), + ), + type = VaultAddEditState.ViewState.Content.ItemType.Login(), + isIndividualVaultDisabled = false, + ), + ) + } + composeTestRule + .onNodeWithTextAfterScroll(text = "Hidden item") + .assertExists() + composeTestRule + .onAllNodesWithContentDescriptionAfterScroll(label = "Show") + .onLast() + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction( + VaultAddEditAction.Common.HiddenFieldVisibilityChange(isVisible = true), + ) + } + } + @Test fun `clicking and changing the custom text field will send a CustomFieldValueChange event`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index abe753d84..8ac16d9b2 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -1205,6 +1205,32 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { assertEquals(expectedState, viewModel.stateFlow.value) } + @Suppress("MaxLineLength") + @Test + fun `PasswordVisibilityChange should log an event when in edit mode and password is visible`() = + runTest { + val vaultAddEditType = VaultAddEditType.EditItem(vaultItemId = "vault_item_id") + val viewModel = createAddVaultItemViewModel( + savedStateHandle = loginInitialSavedStateHandle.apply { + set("state", loginInitialState.copy(vaultAddEditType = vaultAddEditType)) + set("vault_add_edit_type", vaultAddEditType) + }, + ) + viewModel.trySendAction( + VaultAddEditAction.ItemType.LoginType.PasswordVisibilityChange( + isVisible = true, + ), + ) + + verify(exactly = 1) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledPasswordVisible( + cipherId = "vault_item_id", + ), + ) + } + } + @Suppress("MaxLineLength") @Test fun `OpenUsernameGeneratorClick should emit NavigateToGeneratorModal with username GeneratorMode`() = @@ -1920,6 +1946,53 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { } } + @Test + fun `NumberVisibilityChange should log an event when in edit mode and password is visible`() = + runTest { + val vaultAddEditType = VaultAddEditType.EditItem(vaultItemId = "vault_item_id") + val viewModel = createAddVaultItemViewModel( + savedStateHandle = loginInitialSavedStateHandle.apply { + set("state", loginInitialState.copy(vaultAddEditType = vaultAddEditType)) + set("vault_add_edit_type", vaultAddEditType) + }, + ) + viewModel.trySendAction( + VaultAddEditAction.ItemType.CardType.NumberVisibilityChange(isVisible = true), + ) + + verify(exactly = 1) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledCardNumberVisible( + cipherId = "vault_item_id", + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `SecurityCodeVisibilityChange should log an event when in edit mode and password is visible`() = + runTest { + val vaultAddEditType = VaultAddEditType.EditItem(vaultItemId = "vault_item_id") + val viewModel = createAddVaultItemViewModel( + savedStateHandle = loginInitialSavedStateHandle.apply { + set("state", loginInitialState.copy(vaultAddEditType = vaultAddEditType)) + set("vault_add_edit_type", vaultAddEditType) + }, + ) + viewModel.trySendAction( + VaultAddEditAction.ItemType.CardType.SecurityCodeVisibilityChange(isVisible = true), + ) + + verify(exactly = 1) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledCardCodeVisible( + cipherId = "vault_item_id", + ), + ) + } + } + @Nested inner class VaultAddEditCommonActions { private lateinit var viewModel: VaultAddEditViewModel @@ -2371,6 +2444,30 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `HiddenFieldVisibilityChange should log an event when in edit mode and password is visible`() = + runTest { + val vaultAddEditType = VaultAddEditType.EditItem(vaultItemId = "vault_item_id") + val viewModel = createAddVaultItemViewModel( + savedStateHandle = loginInitialSavedStateHandle.apply { + set("state", loginInitialState.copy(vaultAddEditType = vaultAddEditType)) + set("vault_add_edit_type", vaultAddEditType) + }, + ) + viewModel.trySendAction( + VaultAddEditAction.Common.HiddenFieldVisibilityChange(isVisible = true), + ) + + verify(exactly = 1) { + organizationEventManager.trackEvent( + event = OrganizationEvent.CipherClientToggledHiddenFieldVisible( + cipherId = "vault_item_id", + ) + ) + } + } + @Test fun `TooltipClick should emit NavigateToToolTipUri`() = runTest { viewModel.eventFlow.test {