From 0cb0b13aec6abae7ea9abaca034e616cc2651929 Mon Sep 17 00:00:00 2001 From: Oleg Semenenko <146032743+oleg-livefront@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:18:58 -0600 Subject: [PATCH] BIT-540 Adding Custom Field Edit Actions (#723) --- .../feature/addedit/VaultAddEditCardItems.kt | 1 + .../addedit/VaultAddEditCustomField.kt | 119 ++++++++--- .../addedit/VaultAddEditIdentityItems.kt | 1 + .../feature/addedit/VaultAddEditLoginItems.kt | 1 + .../addedit/VaultAddEditSecureNotesItems.kt | 1 + .../feature/addedit/VaultAddEditViewModel.kt | 87 +++++++- .../handlers/VaultAddEditCommonHandlers.kt | 10 + .../addedit/model/CustomFieldAction.kt | 16 ++ .../feature/addedit/VaultAddEditScreenTest.kt | 128 +++++++++++- .../addedit/VaultAddEditViewModelTest.kt | 196 ++++++++++++++++++ 10 files changed, 524 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldAction.kt 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 b853b4222b..bac988d235 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 @@ -232,6 +232,7 @@ fun LazyListScope.vaultAddEditCardItems( VaultAddEditCustomField( customItem, onCustomFieldValueChange = commonHandlers.onCustomFieldValueChange, + onCustomFieldAction = commonHandlers.onCustomFieldActionSelect, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), 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 a24c0c0c81..bf56f2c218 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 @@ -3,21 +3,27 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import com.x8bit.bitwarden.R -import com.x8bit.bitwarden.ui.platform.base.util.showNotYetImplementedToast +import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialogRow import com.x8bit.bitwarden.ui.platform.components.BitwardenIconButtonWithResource import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordFieldWithActions import com.x8bit.bitwarden.ui.platform.components.BitwardenRowOfActions +import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionDialog +import com.x8bit.bitwarden.ui.platform.components.BitwardenTextEntryDialog import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch import com.x8bit.bitwarden.ui.platform.components.model.IconResource +import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -28,6 +34,7 @@ import kotlinx.collections.immutable.toImmutableList * * @param customField The field that is to be displayed. * @param onCustomFieldValueChange Invoked when the user changes the value. + * @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. */ @@ -36,15 +43,46 @@ import kotlinx.collections.immutable.toImmutableList fun VaultAddEditCustomField( customField: VaultAddEditState.Custom, onCustomFieldValueChange: (VaultAddEditState.Custom) -> Unit, + onCustomFieldAction: (CustomFieldAction, VaultAddEditState.Custom) -> Unit, modifier: Modifier = Modifier, supportedLinkedTypes: ImmutableList = persistentListOf(), ) { + var shouldShowChooserDialog by remember { mutableStateOf(false) } + var shouldShowEditDialog by remember { mutableStateOf(false) } + + if (shouldShowChooserDialog) { + CustomFieldActionDialog( + onCustomFieldAction = { action -> + shouldShowChooserDialog = false + onCustomFieldAction(action, customField) + }, + onDismissRequest = { shouldShowChooserDialog = false }, + onEditAction = { + shouldShowEditDialog = true + shouldShowChooserDialog = false + }, + ) + } + + if (shouldShowEditDialog) { + BitwardenTextEntryDialog( + title = stringResource(id = R.string.custom_field_name), + textFieldLabel = stringResource(id = R.string.name), + onDismissRequest = { shouldShowEditDialog = false }, + onConfirmClick = { name -> + onCustomFieldValueChange(customField.updateName(name)) + shouldShowEditDialog = false + }, + ) + } + when (customField) { is VaultAddEditState.Custom.BooleanField -> { CustomFieldBoolean( label = customField.name, value = customField.value, onValueChanged = { onCustomFieldValueChange(customField.copy(value = it)) }, + onEditValue = { shouldShowChooserDialog = true }, modifier = modifier, ) } @@ -56,6 +94,7 @@ fun VaultAddEditCustomField( onValueChanged = { onCustomFieldValueChange(customField.copy(value = it)) }, + onEditValue = { shouldShowChooserDialog = true }, modifier = modifier, ) } @@ -68,6 +107,7 @@ fun VaultAddEditCustomField( onValueChanged = { onCustomFieldValueChange(customField.copy(vaultLinkedFieldType = it)) }, + onEditValue = { shouldShowChooserDialog = true }, modifier = modifier, ) } @@ -78,6 +118,7 @@ fun VaultAddEditCustomField( label = customField.name, value = customField.value, onValueChanged = { onCustomFieldValueChange(customField.copy(value = it)) }, + onEditValue = { shouldShowChooserDialog = true }, modifier = modifier, ) } @@ -92,10 +133,9 @@ private fun CustomFieldBoolean( label: String, value: Boolean, onValueChanged: (Boolean) -> Unit, + onEditValue: () -> Unit, modifier: Modifier = Modifier, ) { - val context = LocalContext.current - Row( modifier = modifier .semantics(mergeDescendants = true) {} @@ -116,10 +156,7 @@ private fun CustomFieldBoolean( iconPainter = painterResource(id = R.drawable.ic_settings), contentDescription = stringResource(id = R.string.edit), ), - onClick = { - // TODO add support for custom field actions (BIT-540) - showNotYetImplementedToast(context = context) - }, + onClick = onEditValue, ) }, ) @@ -134,10 +171,9 @@ private fun CustomFieldHiddenField( label: String, value: String, onValueChanged: (String) -> Unit, + onEditValue: () -> Unit, modifier: Modifier = Modifier, ) { - val context = LocalContext.current - BitwardenPasswordFieldWithActions( label = label, value = value, @@ -150,10 +186,7 @@ private fun CustomFieldHiddenField( iconPainter = painterResource(id = R.drawable.ic_settings), contentDescription = stringResource(id = R.string.edit), ), - onClick = { - // TODO Add support for custom field actions (BIT-540) - showNotYetImplementedToast(context = context) - }, + onClick = onEditValue, ) }, ) @@ -167,10 +200,9 @@ private fun CustomFieldTextField( label: String, value: String, onValueChanged: (String) -> Unit, + onEditValue: () -> Unit, modifier: Modifier = Modifier, ) { - val context = LocalContext.current - BitwardenTextFieldWithActions( label = label, value = value, @@ -183,10 +215,7 @@ private fun CustomFieldTextField( iconPainter = painterResource(id = R.drawable.ic_settings), contentDescription = stringResource(id = R.string.edit), ), - onClick = { - // TODO add support for custom field actions (BIT-540) - showNotYetImplementedToast(context = context) - }, + onClick = onEditValue, ) }, ) @@ -199,11 +228,11 @@ private fun CustomFieldTextField( private fun CustomFieldLinkedField( selectedOption: VaultLinkedFieldType, onValueChanged: (VaultLinkedFieldType) -> Unit, + onEditValue: () -> Unit, modifier: Modifier = Modifier, - label: String = "", + label: String = stringResource(id = R.string.options), supportedLinkedTypes: ImmutableList = persistentListOf(), ) { - val context = LocalContext.current val possibleTypesWithStrings = supportedLinkedTypes.associateWith { it.label.invoke() } Row( @@ -233,12 +262,50 @@ private fun CustomFieldLinkedField( iconPainter = painterResource(id = R.drawable.ic_settings), contentDescription = stringResource(id = R.string.edit), ), - onClick = { - // TODO add support for custom field actions (BIT-540) - showNotYetImplementedToast(context = context) - }, + onClick = onEditValue, ) }, ) } } + +/** + * A dialog for editing a custom field item. + */ +@Composable +private fun CustomFieldActionDialog( + onCustomFieldAction: (CustomFieldAction) -> Unit, + onEditAction: () -> Unit, + onDismissRequest: () -> Unit, +) { + BitwardenSelectionDialog( + title = stringResource(id = R.string.options), + onDismissRequest = onDismissRequest, + ) { + CustomFieldAction + .entries + .forEach { action -> + BitwardenBasicDialogRow( + text = action.actionText.invoke(), + onClick = { + if (action == CustomFieldAction.EDIT) { + onEditAction() + } else { + onCustomFieldAction(action) + } + }, + ) + } + } +} + +/** + * A helper method that will copy over the new name to a custom field. + */ +private fun VaultAddEditState.Custom.updateName(name: String): VaultAddEditState.Custom = + when (this) { + is VaultAddEditState.Custom.BooleanField -> this.copy(name = name) + is VaultAddEditState.Custom.HiddenField -> this.copy(name = name) + is VaultAddEditState.Custom.LinkedField -> this.copy(name = name) + is VaultAddEditState.Custom.TextField -> this.copy(name = name) + } 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 b1ab9ed822..51a12048fb 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 @@ -341,6 +341,7 @@ fun LazyListScope.vaultAddEditIdentityItems( VaultAddEditCustomField( customField = customItem, onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange, + onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), 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 90fe949f40..522e62d27c 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 @@ -312,6 +312,7 @@ fun LazyListScope.vaultAddEditLoginItems( VaultAddEditCustomField( customItem, onCustomFieldValueChange = commonActionHandler.onCustomFieldValueChange, + onCustomFieldAction = commonActionHandler.onCustomFieldActionSelect, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), 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 d70fa6c37d..0693f6921d 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 @@ -138,6 +138,7 @@ fun LazyListScope.vaultAddEditSecureNotesItems( VaultAddEditCustomField( customItem, onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange, + onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), 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 1c5342fb9f..651d02fafc 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 @@ -20,6 +20,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.concat import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode +import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState @@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize +import java.util.Collections import javax.inject.Inject private const val KEY_STATE = "state" @@ -148,6 +150,9 @@ class VaultAddEditViewModel @Inject constructor( ) is VaultAddEditAction.Common.TooltipClick -> handleTooltipClick() + is VaultAddEditAction.Common.CustomFieldActionSelect -> handleCustomFieldActionSelected( + action, + ) } } @@ -297,6 +302,67 @@ class VaultAddEditViewModel @Inject constructor( } } + private fun handleCustomFieldActionSelected( + action: VaultAddEditAction.Common.CustomFieldActionSelect, + ) { + when (action.customFieldAction) { + CustomFieldAction.MOVE_UP -> { + val items = + (mutableStateFlow.value.viewState as VaultAddEditState.ViewState.Content) + .common + .customFieldData + .toMutableList() + + val index = items.lastIndexOf(action.customField) + if (index == 0) { + return + } + + Collections.swap(items, index, index - 1) + + updateCommonContent { commonContent -> + commonContent.copy( + customFieldData = items, + ) + } + } + + CustomFieldAction.MOVE_DOWN -> { + val items = + (mutableStateFlow.value.viewState as VaultAddEditState.ViewState.Content) + .common + .customFieldData + .toMutableList() + + val index = items.indexOf(action.customField) + if (index == items.lastIndex) { + return + } + + Collections.swap(items, index, index + 1) + + updateCommonContent { commonContent -> + commonContent.copy( + customFieldData = items, + ) + } + } + + CustomFieldAction.DELETE -> { + updateCommonContent { commonContent -> + commonContent.copy( + customFieldData = commonContent.customFieldData.filter { + it != action.customField + }, + ) + } + } + + // Nothing is done here since we handle this with a CustomFieldValueChange action + CustomFieldAction.EDIT -> Unit + } + } + private fun handleFolderTextInputChange( action: VaultAddEditAction.Common.FolderChange, ) { @@ -1277,13 +1343,18 @@ data class VaultAddEditState( */ abstract val itemId: String + /** + * The name of the custom field. + */ + abstract val name: String + /** * Represents the data for displaying a custom text field. */ @Parcelize data class TextField( override val itemId: String, - val name: String, + override val name: String, val value: String, ) : Custom() @@ -1293,7 +1364,7 @@ data class VaultAddEditState( @Parcelize data class HiddenField( override val itemId: String, - val name: String, + override val name: String, val value: String, ) : Custom() @@ -1303,7 +1374,7 @@ data class VaultAddEditState( @Parcelize data class BooleanField( override val itemId: String, - val name: String, + override val name: String, val value: Boolean, ) : Custom() @@ -1313,7 +1384,7 @@ data class VaultAddEditState( @Parcelize data class LinkedField( override val itemId: String, - val name: String, + override val name: String, val vaultLinkedFieldType: VaultLinkedFieldType?, ) : Custom() } @@ -1462,6 +1533,14 @@ sealed class VaultAddEditAction { */ data class CustomFieldValueChange(val customField: VaultAddEditState.Custom) : Common() + /** + * Fired when the custom field data is changed. + */ + data class CustomFieldActionSelect( + val customFieldAction: CustomFieldAction, + val customField: VaultAddEditState.Custom, + ) : Common() + /** * Represents the action to open tooltip */ 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 70b56c2dbb..2ba3de10dd 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 @@ -4,6 +4,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditAction import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel +import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType /** @@ -33,6 +34,7 @@ data class VaultAddEditCommonHandlers( val onTooltipClick: () -> Unit, val onAddNewCustomFieldClick: (CustomFieldType, String) -> Unit, val onCustomFieldValueChange: (VaultAddEditState.Custom) -> Unit, + val onCustomFieldActionSelect: (CustomFieldAction, VaultAddEditState.Custom) -> Unit, ) { companion object { @@ -97,6 +99,14 @@ data class VaultAddEditCommonHandlers( ), ) }, + onCustomFieldActionSelect = { customFieldAction, field -> + viewModel.trySendAction( + VaultAddEditAction.Common.CustomFieldActionSelect( + customFieldAction, + field, + ), + ) + }, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldAction.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldAction.kt new file mode 100644 index 0000000000..67c1d3c70d --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldAction.kt @@ -0,0 +1,16 @@ +package com.x8bit.bitwarden.ui.vault.feature.addedit.model + +import com.x8bit.bitwarden.R +import com.x8bit.bitwarden.ui.platform.base.util.Text +import com.x8bit.bitwarden.ui.platform.base.util.asText + +/** + * Represents the different actions that can be taken in a custom + * item edit menu. + */ +enum class CustomFieldAction(val actionText: Text) { + EDIT(R.string.edit.asText()), + MOVE_UP(R.string.move_up.asText()), + MOVE_DOWN(R.string.move_down.asText()), + DELETE(R.string.delete.asText()), +} 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 904993d067..9c0fc370d9 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 @@ -37,6 +37,7 @@ import com.x8bit.bitwarden.ui.util.onAllNodesWithContentDescriptionAfterScroll import com.x8bit.bitwarden.ui.util.onAllNodesWithTextAfterScroll import com.x8bit.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll +import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand @@ -1939,7 +1940,7 @@ class VaultAddEditScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( VaultAddEditAction.Common.CustomFieldValueChange( - VaultAddEditState.Custom.TextField("Test ID", "TestText", ""), + VaultAddEditState.Custom.TextField("Test ID 2", "TestText", ""), ), ) } @@ -1957,7 +1958,7 @@ class VaultAddEditScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( VaultAddEditAction.Common.CustomFieldValueChange( - VaultAddEditState.Custom.HiddenField("Test ID", "TestHidden", ""), + VaultAddEditState.Custom.HiddenField("Test ID 3", "TestHidden", ""), ), ) } @@ -1975,7 +1976,122 @@ class VaultAddEditScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( VaultAddEditAction.Common.CustomFieldValueChange( - VaultAddEditState.Custom.BooleanField("Test ID", "TestBoolean", true), + VaultAddEditState.Custom.BooleanField("Test ID 1", "TestBoolean", true), + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `clicking custom field edit icon and Edit option sends a CustomFieldValueChange action`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + composeTestRule + .onAllNodesWithContentDescriptionAfterScroll("Edit") + .onFirst() + .performClick() + + composeTestRule + .onNodeWithText("Edit") + .performClick() + + composeTestRule + .onNodeWithText("Name") + .performTextInput("NewTestBooleanName") + + composeTestRule + .onNodeWithText("Ok") + .performClick() + + verify { + viewModel.trySendAction( + VaultAddEditAction.Common.CustomFieldValueChange( + VaultAddEditState.Custom.BooleanField("Test ID 1", "NewTestBooleanName", false), + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `clicking custom field edit icon and Delete option sends a CustomFieldActionSelect delete action`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + composeTestRule + .onAllNodesWithContentDescriptionAfterScroll("Edit") + .onFirst() + .performClick() + + composeTestRule + .onNodeWithText("Delete") + .performClick() + + verify { + viewModel.trySendAction( + VaultAddEditAction.Common.CustomFieldActionSelect( + customFieldAction = CustomFieldAction.DELETE, + customField = VaultAddEditState.Custom.BooleanField( + itemId = "Test ID 1", + name = "TestBoolean", + value = false, + ), + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `clicking custom field edit icon and Move down option sends a CustomFieldActionSelect move down action`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + composeTestRule + .onAllNodesWithContentDescriptionAfterScroll("Edit") + .onFirst() + .performClick() + + composeTestRule + .onNodeWithText("Move down") + .performClick() + + verify { + viewModel.trySendAction( + VaultAddEditAction.Common.CustomFieldActionSelect( + customFieldAction = CustomFieldAction.MOVE_DOWN, + customField = VaultAddEditState.Custom.BooleanField( + itemId = "Test ID 1", + name = "TestBoolean", + value = false, + ), + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `clicking custom field edit icon and Move Up options sends a CustomFieldActionSelect move up action`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + composeTestRule + .onAllNodesWithContentDescriptionAfterScroll("Edit") + .onFirst() + .performClick() + + composeTestRule + .onNodeWithText("Move Up") + .performClick() + + verify { + viewModel.trySendAction( + VaultAddEditAction.Common.CustomFieldActionSelect( + customFieldAction = CustomFieldAction.MOVE_UP, + customField = VaultAddEditState.Custom.BooleanField( + itemId = "Test ID 1", + name = "TestBoolean", + value = false, + ), ), ) } @@ -2111,10 +2227,10 @@ class VaultAddEditScreenTest : BaseComposeTest() { viewState = VaultAddEditState.ViewState.Content( common = VaultAddEditState.ViewState.Content.Common( customFieldData = listOf( - VaultAddEditState.Custom.BooleanField("Test ID", "TestBoolean", false), - VaultAddEditState.Custom.TextField("Test ID", "TestText", "TestTextVal"), + VaultAddEditState.Custom.BooleanField("Test ID 1", "TestBoolean", false), + VaultAddEditState.Custom.TextField("Test ID 2", "TestText", "TestTextVal"), VaultAddEditState.Custom.HiddenField( - "Test ID", + "Test ID 3", "TestHidden", "TestHiddenVal", ), 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 879a68d470..2970c2d591 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 @@ -18,6 +18,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode +import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState @@ -1444,6 +1445,201 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ) } + @Test + fun `CustomFieldValueChange should update name field`() = runTest { + val initState = createVaultAddItemState( + typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + commonContentViewState = VaultAddEditState.ViewState.Content.Common( + customFieldData = listOf( + VaultAddEditState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + true, + ), + ), + ), + ) + + assertCustomFieldValueChange( + initState, + CustomFieldType.BOOLEAN, + ) + } + + @Test + fun `CustomFieldActionSelect with delete action should delete the item`() = runTest { + val customFieldData = VaultAddEditState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + true, + ) + + val initState = createVaultAddItemState( + typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + commonContentViewState = VaultAddEditState.ViewState.Content.Common( + customFieldData = listOf(customFieldData), + ), + ) + + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initState, + vaultAddEditType = VaultAddEditType.AddItem, + ), + ) + val currentContentState = + (viewModel.stateFlow.value.viewState as VaultAddEditState.ViewState.Content) + val expectedState = currentContentState + .copy( + common = currentContentState.common.copy( + customFieldData = listOf(), + ), + ) + + viewModel.actionChannel.trySend( + VaultAddEditAction.Common.CustomFieldActionSelect( + CustomFieldAction.DELETE, + customFieldData, + ), + ) + + assertEquals( + expectedState, + viewModel.stateFlow.value.viewState, + ) + } + + @Test + fun `CustomFieldActionSelect with move up action should move the item up`() = runTest { + val customFieldData = + VaultAddEditState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + true, + ) + + val initState = createVaultAddItemState( + typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + commonContentViewState = VaultAddEditState.ViewState.Content.Common( + customFieldData = listOf( + VaultAddEditState.Custom.BooleanField( + "TestId 1", + "Boolean Field", + true, + ), VaultAddEditState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + true, + ), + ), + ), + ) + + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initState, + vaultAddEditType = VaultAddEditType.AddItem, + ), + ) + val currentContentState = + (viewModel.stateFlow.value.viewState as VaultAddEditState.ViewState.Content) + val expectedState = currentContentState + .copy( + common = currentContentState.common.copy( + customFieldData = listOf( + VaultAddEditState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + true, + ), + VaultAddEditState.Custom.BooleanField( + "TestId 1", + "Boolean Field", + true, + ), + ), + ), + ) + + viewModel.actionChannel.trySend( + VaultAddEditAction.Common.CustomFieldActionSelect( + CustomFieldAction.MOVE_UP, + customFieldData, + ), + ) + + assertEquals( + expectedState, + viewModel.stateFlow.value.viewState, + ) + } + + @Test + fun `CustomFieldActionSelect with move down action should move the item down`() = runTest { + val customFieldData = + VaultAddEditState.Custom.BooleanField( + "TestId 1", + "Boolean Field", + true, + ) + + val initState = createVaultAddItemState( + typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + commonContentViewState = VaultAddEditState.ViewState.Content.Common( + customFieldData = listOf( + VaultAddEditState.Custom.BooleanField( + "TestId 1", + "Boolean Field", + true, + ), + VaultAddEditState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + true, + ), + ), + ), + ) + + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initState, + vaultAddEditType = VaultAddEditType.AddItem, + ), + ) + val currentContentState = + (viewModel.stateFlow.value.viewState as VaultAddEditState.ViewState.Content) + val expectedState = currentContentState + .copy( + common = currentContentState.common.copy( + customFieldData = listOf( + VaultAddEditState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + true, + ), + VaultAddEditState.Custom.BooleanField( + "TestId 1", + "Boolean Field", + true, + ), + ), + ), + ) + + viewModel.actionChannel.trySend( + VaultAddEditAction.Common.CustomFieldActionSelect( + CustomFieldAction.MOVE_DOWN, + customFieldData, + ), + ) + + assertEquals( + expectedState, + viewModel.stateFlow.value.viewState, + ) + } + @Test fun `TooltipClick should emit ShowToast with 'Tooltip' message`() = runTest { viewModel.eventFlow.test {