Add edit cipher org events (#3352)

This commit is contained in:
David Perez 2024-06-25 09:12:45 -05:00 committed by GitHub
parent 949768ac95
commit d1e8ed63a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 383 additions and 17 deletions

View file

@ -9,6 +9,10 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme 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.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
@ -70,10 +74,16 @@ fun LazyListScope.vaultAddEditCardItems(
} }
item { item {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
var showNumber by rememberSaveable { mutableStateOf(value = false) }
BitwardenPasswordField( BitwardenPasswordField(
label = stringResource(id = R.string.number), label = stringResource(id = R.string.number),
value = cardState.number, value = cardState.number,
onValueChange = cardHandlers.onNumberTextChange, onValueChange = cardHandlers.onNumberTextChange,
showPassword = showNumber,
showPasswordChange = {
showNumber = !showNumber
cardHandlers.onNumberVisibilityChange(showNumber)
},
modifier = Modifier modifier = Modifier
.testTag("CardNumberEntry") .testTag("CardNumberEntry")
.fillMaxWidth() .fillMaxWidth()
@ -140,10 +150,16 @@ fun LazyListScope.vaultAddEditCardItems(
} }
item { item {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
var showSecurityCode by rememberSaveable { mutableStateOf(value = false) }
BitwardenPasswordField( BitwardenPasswordField(
label = stringResource(id = R.string.security_code), label = stringResource(id = R.string.security_code),
value = cardState.securityCode, value = cardState.securityCode,
onValueChange = cardHandlers.onSecurityCodeTextChange, onValueChange = cardHandlers.onSecurityCodeTextChange,
showPassword = showSecurityCode,
showPasswordChange = {
showSecurityCode = !showSecurityCode
cardHandlers.onSecurityCodeVisibilityChange(showSecurityCode)
},
keyboardType = KeyboardType.NumberPassword, keyboardType = KeyboardType.NumberPassword,
modifier = Modifier modifier = Modifier
.testTag("CardSecurityCodeEntry") .testTag("CardSecurityCodeEntry")
@ -259,7 +275,7 @@ fun LazyListScope.vaultAddEditCardItems(
items(commonState.customFieldData) { customItem -> items(commonState.customFieldData) { customItem ->
VaultAddEditCustomField( VaultAddEditCustomField(
customItem, customField = customItem,
onCustomFieldValueChange = commonHandlers.onCustomFieldValueChange, onCustomFieldValueChange = commonHandlers.onCustomFieldValueChange,
onCustomFieldAction = commonHandlers.onCustomFieldActionSelect, onCustomFieldAction = commonHandlers.onCustomFieldActionSelect,
modifier = Modifier modifier = Modifier
@ -273,6 +289,7 @@ fun LazyListScope.vaultAddEditCardItems(
VaultLinkedFieldType.BRAND, VaultLinkedFieldType.BRAND,
VaultLinkedFieldType.NUMBER, VaultLinkedFieldType.NUMBER,
), ),
onHiddenVisibilityChanged = commonHandlers.onHiddenFieldVisibilityChange,
) )
} }

View file

@ -37,6 +37,7 @@ import kotlinx.collections.immutable.toImmutableList
* @param onCustomFieldAction Invoked when the user chooses an action. * @param onCustomFieldAction Invoked when the user chooses an action.
* @param modifier Modifier for the UI elements. * @param modifier Modifier for the UI elements.
* @param supportedLinkedTypes The supported linked types for the vault item. * @param supportedLinkedTypes The supported linked types for the vault item.
* @param onHiddenVisibilityChanged Emits when the visibility of a hidden custom field changes.
*/ */
@Composable @Composable
@Suppress("LongMethod") @Suppress("LongMethod")
@ -46,6 +47,7 @@ fun VaultAddEditCustomField(
onCustomFieldAction: (CustomFieldAction, VaultAddEditState.Custom) -> Unit, onCustomFieldAction: (CustomFieldAction, VaultAddEditState.Custom) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
supportedLinkedTypes: ImmutableList<VaultLinkedFieldType> = persistentListOf(), supportedLinkedTypes: ImmutableList<VaultLinkedFieldType> = persistentListOf(),
onHiddenVisibilityChanged: (Boolean) -> Unit,
) { ) {
var shouldShowChooserDialog by remember { mutableStateOf(false) } var shouldShowChooserDialog by remember { mutableStateOf(false) }
var shouldShowEditDialog by remember { mutableStateOf(false) } var shouldShowEditDialog by remember { mutableStateOf(false) }
@ -91,11 +93,12 @@ fun VaultAddEditCustomField(
is VaultAddEditState.Custom.HiddenField -> { is VaultAddEditState.Custom.HiddenField -> {
CustomFieldHiddenField( CustomFieldHiddenField(
customField.name, label = customField.name,
customField.value, value = customField.value,
onValueChanged = { onValueChanged = {
onCustomFieldValueChange(customField.copy(value = it)) onCustomFieldValueChange(customField.copy(value = it))
}, },
onVisibilityChanged = onHiddenVisibilityChanged,
onEditValue = { shouldShowChooserDialog = true }, onEditValue = { shouldShowChooserDialog = true },
modifier = modifier, modifier = modifier,
) )
@ -175,12 +178,19 @@ private fun CustomFieldHiddenField(
value: String, value: String,
onValueChanged: (String) -> Unit, onValueChanged: (String) -> Unit,
onEditValue: () -> Unit, onEditValue: () -> Unit,
onVisibilityChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
var shouldShowPassword by remember { mutableStateOf(value = false) }
BitwardenPasswordFieldWithActions( BitwardenPasswordFieldWithActions(
label = label, label = label,
value = value, value = value,
onValueChange = onValueChanged, onValueChange = onValueChanged,
showPassword = shouldShowPassword,
showPasswordChange = {
shouldShowPassword = !shouldShowPassword
onVisibilityChanged(shouldShowPassword)
},
singleLine = true, singleLine = true,
modifier = modifier, modifier = modifier,
actions = { actions = {

View file

@ -403,6 +403,7 @@ fun LazyListScope.vaultAddEditIdentityItems(
VaultLinkedFieldType.LAST_NAME, VaultLinkedFieldType.LAST_NAME,
VaultLinkedFieldType.FULL_NAME, VaultLinkedFieldType.FULL_NAME,
), ),
onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange,
) )
} }

View file

@ -12,6 +12,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -301,7 +302,7 @@ fun LazyListScope.vaultAddEditLoginItems(
items(commonState.customFieldData) { customItem -> items(commonState.customFieldData) { customItem ->
VaultAddEditCustomField( VaultAddEditCustomField(
customItem, customField = customItem,
onCustomFieldValueChange = commonActionHandler.onCustomFieldValueChange, onCustomFieldValueChange = commonActionHandler.onCustomFieldValueChange,
onCustomFieldAction = commonActionHandler.onCustomFieldActionSelect, onCustomFieldAction = commonActionHandler.onCustomFieldActionSelect,
modifier = Modifier modifier = Modifier
@ -311,6 +312,7 @@ fun LazyListScope.vaultAddEditLoginItems(
VaultLinkedFieldType.PASSWORD, VaultLinkedFieldType.PASSWORD,
VaultLinkedFieldType.USERNAME, VaultLinkedFieldType.USERNAME,
), ),
onHiddenVisibilityChanged = commonActionHandler.onHiddenFieldVisibilityChange,
) )
} }
@ -436,10 +438,16 @@ private fun PasswordRow(
var shouldShowDialog by rememberSaveable { mutableStateOf(false) } var shouldShowDialog by rememberSaveable { mutableStateOf(false) }
if (canViewPassword) { if (canViewPassword) {
var shouldShowPassword by remember { mutableStateOf(false) }
BitwardenPasswordFieldWithActions( BitwardenPasswordFieldWithActions(
label = stringResource(id = R.string.password), label = stringResource(id = R.string.password),
value = password, value = password,
onValueChange = loginItemTypeHandlers.onPasswordTextChange, onValueChange = loginItemTypeHandlers.onPasswordTextChange,
showPassword = shouldShowPassword,
showPasswordChange = {
shouldShowPassword = !shouldShowPassword
loginItemTypeHandlers.onPasswordVisibilityChange(shouldShowPassword)
},
showPasswordTestTag = "ViewPasswordButton", showPasswordTestTag = "ViewPasswordButton",
passwordFieldTestTag = "LoginPasswordEntry", passwordFieldTestTag = "LoginPasswordEntry",
modifier = Modifier modifier = Modifier

View file

@ -154,12 +154,13 @@ fun LazyListScope.vaultAddEditSecureNotesItems(
} }
items(commonState.customFieldData) { customItem -> items(commonState.customFieldData) { customItem ->
VaultAddEditCustomField( VaultAddEditCustomField(
customItem, customField = customItem,
onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange, onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange,
onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect, onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange,
) )
} }

View file

@ -214,9 +214,9 @@ class VaultAddEditViewModel @Inject constructor(
private fun handleCommonActions(action: VaultAddEditAction.Common) { private fun handleCommonActions(action: VaultAddEditAction.Common) {
when (action) { when (action) {
is VaultAddEditAction.Common.CustomFieldValueChange -> handleCustomFieldValueChange( is VaultAddEditAction.Common.CustomFieldValueChange -> {
action, handleCustomFieldValueChange(action)
) }
is VaultAddEditAction.Common.FolderChange -> handleFolderTextInputChange(action) is VaultAddEditAction.Common.FolderChange -> handleFolderTextInputChange(action)
is VaultAddEditAction.Common.NameTextChange -> handleNameTextInputChange(action) is VaultAddEditAction.Common.NameTextChange -> handleNameTextInputChange(action)
@ -235,19 +235,23 @@ class VaultAddEditViewModel @Inject constructor(
is VaultAddEditAction.Common.DismissDialog -> handleDismissDialog() is VaultAddEditAction.Common.DismissDialog -> handleDismissDialog()
is VaultAddEditAction.Common.SaveClick -> handleSaveClick() is VaultAddEditAction.Common.SaveClick -> handleSaveClick()
is VaultAddEditAction.Common.TypeOptionSelect -> handleTypeOptionSelect(action) is VaultAddEditAction.Common.TypeOptionSelect -> handleTypeOptionSelect(action)
is VaultAddEditAction.Common.AddNewCustomFieldClick -> handleAddNewCustomFieldClick( is VaultAddEditAction.Common.AddNewCustomFieldClick -> {
action, handleAddNewCustomFieldClick(action)
) }
is VaultAddEditAction.Common.TooltipClick -> handleTooltipClick() is VaultAddEditAction.Common.TooltipClick -> handleTooltipClick()
is VaultAddEditAction.Common.CustomFieldActionSelect -> handleCustomFieldActionSelected( is VaultAddEditAction.Common.CustomFieldActionSelect -> {
action, handleCustomFieldActionSelected(action)
) }
is VaultAddEditAction.Common.CollectionSelect -> handleCollectionSelect(action) is VaultAddEditAction.Common.CollectionSelect -> handleCollectionSelect(action)
is VaultAddEditAction.Common.InitialAutofillDialogDismissed -> { is VaultAddEditAction.Common.InitialAutofillDialogDismissed -> {
handleInitialAutofillDialogDismissed() 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( private fun handleAddNewCustomFieldClick(
action: VaultAddEditAction.Common.AddNewCustomFieldClick, action: VaultAddEditAction.Common.AddNewCustomFieldClick,
) { ) {
@ -636,6 +654,10 @@ class VaultAddEditViewModel @Inject constructor(
is VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick -> { is VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick -> {
handleLoginClearTotpKey() 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() { private fun handleLoginAddNewUriClick() {
updateLoginContent { loginType -> updateLoginContent { loginType ->
loginType.copy( loginType.copy(
@ -961,9 +997,17 @@ class VaultAddEditViewModel @Inject constructor(
handleCardNumberTextChange(action) handleCardNumberTextChange(action)
} }
is VaultAddEditAction.ItemType.CardType.NumberVisibilityChange -> {
handleNumberVisibilityChange(action)
}
is VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange -> { is VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange -> {
handleCardSecurityCodeTextChange(action) handleCardSecurityCodeTextChange(action)
} }
is VaultAddEditAction.ItemType.CardType.SecurityCodeVisibilityChange -> {
handleSecurityCodeVisibilityChange(action)
}
} }
} }
@ -997,12 +1041,40 @@ class VaultAddEditViewModel @Inject constructor(
updateCardContent { it.copy(number = action.number) } 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( private fun handleCardSecurityCodeTextChange(
action: VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange, action: VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange,
) { ) {
updateCardContent { it.copy(securityCode = action.securityCode) } 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 //endregion Card Type Handlers
//region Internal Type Handlers //region Internal Type Handlers
@ -1024,8 +1096,9 @@ class VaultAddEditViewModel @Inject constructor(
handleGeneratorResultReceive(action) handleGeneratorResultReceive(action)
} }
is VaultAddEditAction.Internal.PasswordBreachReceive -> is VaultAddEditAction.Internal.PasswordBreachReceive -> {
handlePasswordBreachReceive(action) handlePasswordBreachReceive(action)
}
} }
} }
@ -2009,6 +2082,13 @@ sealed class VaultAddEditAction {
data class CollectionSelect( data class CollectionSelect(
val collection: VaultCollection, val collection: VaultCollection,
) : Common() ) : 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. * Represents the action to add a new URI field.
*/ */
data object AddNewUriClick : LoginType() 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() 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. * Fired when the brand input is selected.
* *
@ -2272,6 +2366,13 @@ sealed class VaultAddEditAction {
* @property securityCode The new security code text. * @property securityCode The new security code text.
*/ */
data class SecurityCodeTextChange(val securityCode: String) : CardType() 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()
} }
} }

View file

@ -15,6 +15,9 @@ import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
* @property onExpirationMonthSelected Handles the action when an expiration month is selected. * @property onExpirationMonthSelected Handles the action when an expiration month is selected.
* @property onExpirationYearTextChange Handles the action when the expiration year text is changed. * @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 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") @Suppress("MaxLineLength")
data class VaultAddEditCardTypeHandlers( data class VaultAddEditCardTypeHandlers(
@ -24,8 +27,9 @@ data class VaultAddEditCardTypeHandlers(
val onExpirationMonthSelected: (VaultCardExpirationMonth) -> Unit, val onExpirationMonthSelected: (VaultCardExpirationMonth) -> Unit,
val onExpirationYearTextChange: (String) -> Unit, val onExpirationYearTextChange: (String) -> Unit,
val onSecurityCodeTextChange: (String) -> Unit, val onSecurityCodeTextChange: (String) -> Unit,
val onSecurityCodeVisibilityChange: (Boolean) -> Unit,
) { val onNumberVisibilityChange: (Boolean) -> Unit,
) {
companion object { 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),
)
},
) )
} }
} }

View file

@ -37,6 +37,7 @@ data class VaultAddEditCommonHandlers(
val onCustomFieldValueChange: (VaultAddEditState.Custom) -> Unit, val onCustomFieldValueChange: (VaultAddEditState.Custom) -> Unit,
val onCustomFieldActionSelect: (CustomFieldAction, VaultAddEditState.Custom) -> Unit, val onCustomFieldActionSelect: (CustomFieldAction, VaultAddEditState.Custom) -> Unit,
val onCollectionSelect: (VaultCollection) -> Unit, val onCollectionSelect: (VaultCollection) -> Unit,
val onHiddenFieldVisibilityChange: (Boolean) -> Unit,
) { ) {
companion object { companion object {
@ -116,6 +117,11 @@ data class VaultAddEditCommonHandlers(
), ),
) )
}, },
onHiddenFieldVisibilityChange = {
viewModel.trySendAction(
VaultAddEditAction.Common.HiddenFieldVisibilityChange(isVisible = it),
)
},
) )
} }
} }

View file

@ -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 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 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 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") @Suppress("LongParameterList")
data class VaultAddEditLoginTypeHandlers( data class VaultAddEditLoginTypeHandlers(
@ -36,6 +38,7 @@ data class VaultAddEditLoginTypeHandlers(
val onCopyTotpKeyClick: (String) -> Unit, val onCopyTotpKeyClick: (String) -> Unit,
val onClearTotpKeyClick: () -> Unit, val onClearTotpKeyClick: () -> Unit,
val onAddNewUriClick: () -> Unit, val onAddNewUriClick: () -> Unit,
val onPasswordVisibilityChange: (Boolean) -> Unit,
) { ) {
companion object { companion object {
@ -106,6 +109,11 @@ data class VaultAddEditLoginTypeHandlers(
VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick, VaultAddEditAction.ItemType.LoginType.ClearTotpKeyClick,
) )
}, },
onPasswordVisibilityChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LoginType.PasswordVisibilityChange(it),
)
},
) )
} }
} }

View file

@ -20,6 +20,7 @@ import androidx.compose.ui.test.isDialog
import androidx.compose.ui.test.isPopup import androidx.compose.ui.test.isPopup
import androidx.compose.ui.test.onAllNodesWithContentDescription import androidx.compose.ui.test.onAllNodesWithContentDescription
import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onLast import androidx.compose.ui.test.onLast
import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithContentDescription
@ -466,6 +467,24 @@ class VaultAddEditScreenTest : BaseComposeTest() {
.assertDoesNotExist() .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 @Test
fun `in ItemType_Login state changing Username text field should trigger UsernameTextChange`() { fun `in ItemType_Login state changing Username text field should trigger UsernameTextChange`() {
composeTestRule 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 @Test
fun `in ItemType_Card the number text field should display the text provided by the state`() { fun `in ItemType_Card the number text field should display the text provided by the state`() {
mutableStateFlow.value = DEFAULT_STATE_CARD 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") @Suppress("MaxLineLength")
@Test @Test
fun `in ItemType_Card the security code text field should display the text provided by the state`() { 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 @Test
fun `clicking and changing the custom text field will send a CustomFieldValueChange event`() { fun `clicking and changing the custom text field will send a CustomFieldValueChange event`() {
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS

View file

@ -1205,6 +1205,32 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
assertEquals(expectedState, viewModel.stateFlow.value) 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") @Suppress("MaxLineLength")
@Test @Test
fun `OpenUsernameGeneratorClick should emit NavigateToGeneratorModal with username GeneratorMode`() = 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 @Nested
inner class VaultAddEditCommonActions { inner class VaultAddEditCommonActions {
private lateinit var viewModel: VaultAddEditViewModel 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 @Test
fun `TooltipClick should emit NavigateToToolTipUri`() = runTest { fun `TooltipClick should emit NavigateToToolTipUri`() = runTest {
viewModel.eventFlow.test { viewModel.eventFlow.test {