diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditItemContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditItemContent.kt index 7ea25f4556..0b6b460694 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditItemContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditItemContent.kt @@ -20,11 +20,11 @@ import kotlinx.collections.immutable.toImmutableList */ @Composable fun AddEditItemContent( - viewState: VaultAddItemState.ViewState.Content, + state: VaultAddItemState.ViewState.Content, isAddItemMode: Boolean, onTypeOptionClicked: (VaultAddItemState.ItemTypeOption) -> Unit, + commonTypeHandlers: VaultAddItemCommonTypeHandlers, loginItemTypeHandlers: VaultAddLoginItemTypeHandlers, - secureNotesTypeHandlers: VaultAddSecureNotesItemTypeHandlers, modifier: Modifier = Modifier, ) { LazyColumn( @@ -42,35 +42,37 @@ fun AddEditItemContent( item { Spacer(modifier = Modifier.height(8.dp)) TypeOptionsItem( - content = viewState, + itemType = state.type, onTypeOptionClicked = onTypeOptionClicked, modifier = Modifier.padding(horizontal = 16.dp), ) } } - when (viewState) { - is VaultAddItemState.ViewState.Content.Login -> { + when (state.type) { + is VaultAddItemState.ViewState.Content.ItemType.Login -> { addEditLoginItems( - state = viewState, + commonState = state.common, + loginState = state.type, isAddItemMode = isAddItemMode, + commonActionHandler = commonTypeHandlers, loginItemTypeHandlers = loginItemTypeHandlers, ) } - is VaultAddItemState.ViewState.Content.Card -> { + is VaultAddItemState.ViewState.Content.ItemType.Card -> { // TODO(BIT-507): Create UI for card-type item creation } - is VaultAddItemState.ViewState.Content.Identity -> { + is VaultAddItemState.ViewState.Content.ItemType.Identity -> { // TODO(BIT-667): Create UI for identity-type item creation } - is VaultAddItemState.ViewState.Content.SecureNotes -> { + is VaultAddItemState.ViewState.Content.ItemType.SecureNotes -> { addEditSecureNotesItems( - state = viewState, + commonState = state.common, isAddItemMode = isAddItemMode, - secureNotesTypeHandlers = secureNotesTypeHandlers, + commonTypeHandlers = commonTypeHandlers, ) } } @@ -83,7 +85,7 @@ fun AddEditItemContent( @Composable private fun TypeOptionsItem( - content: VaultAddItemState.ViewState.Content, + itemType: VaultAddItemState.ViewState.Content.ItemType, onTypeOptionClicked: (VaultAddItemState.ItemTypeOption) -> Unit, modifier: Modifier, ) { @@ -93,7 +95,7 @@ private fun TypeOptionsItem( BitwardenMultiSelectButton( label = stringResource(id = R.string.type), options = optionsWithStrings.values.toImmutableList(), - selectedOption = stringResource(id = content.displayStringResId), + selectedOption = stringResource(id = itemType.displayStringResId), onOptionSelected = { selectedOption -> val selectedOptionId = optionsWithStrings .entries diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt index 36c555af4f..08456aaaea 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt @@ -34,16 +34,18 @@ import kotlinx.collections.immutable.toImmutableList */ @Suppress("LongMethod") fun LazyListScope.addEditLoginItems( - state: VaultAddItemState.ViewState.Content.Login, + commonState: VaultAddItemState.ViewState.Content.Common, + loginState: VaultAddItemState.ViewState.Content.ItemType.Login, isAddItemMode: Boolean, + commonActionHandler: VaultAddItemCommonTypeHandlers, loginItemTypeHandlers: VaultAddLoginItemTypeHandlers, ) { item { Spacer(modifier = Modifier.height(8.dp)) BitwardenTextField( label = stringResource(id = R.string.name), - value = state.name, - onValueChange = loginItemTypeHandlers.onNameTextChange, + value = commonState.name, + onValueChange = commonActionHandler.onNameTextChange, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -54,7 +56,7 @@ fun LazyListScope.addEditLoginItems( Spacer(modifier = Modifier.height(8.dp)) BitwardenTextFieldWithActions( label = stringResource(id = R.string.username), - value = state.username, + value = loginState.username, onValueChange = loginItemTypeHandlers.onUsernameTextChange, actions = { BitwardenIconButtonWithResource( @@ -73,7 +75,7 @@ fun LazyListScope.addEditLoginItems( Spacer(modifier = Modifier.height(8.dp)) BitwardenPasswordFieldWithActions( label = stringResource(id = R.string.password), - value = state.password, + value = loginState.password, onValueChange = loginItemTypeHandlers.onPasswordTextChange, modifier = Modifier .padding(horizontal = 16.dp), @@ -131,7 +133,7 @@ fun LazyListScope.addEditLoginItems( Spacer(modifier = Modifier.height(8.dp)) BitwardenTextFieldWithActions( label = stringResource(id = R.string.uri), - value = state.uri, + value = loginState.uri, onValueChange = loginItemTypeHandlers.onUriTextChange, actions = { BitwardenIconButtonWithResource( @@ -171,9 +173,9 @@ fun LazyListScope.addEditLoginItems( Spacer(modifier = Modifier.height(8.dp)) BitwardenMultiSelectButton( label = stringResource(id = R.string.folder), - options = state.availableFolders.map { it.invoke() }.toImmutableList(), - selectedOption = state.folderName.invoke(), - onOptionSelected = loginItemTypeHandlers.onFolderTextChange, + options = commonState.availableFolders.map { it.invoke() }.toImmutableList(), + selectedOption = commonState.folderName.invoke(), + onOptionSelected = commonActionHandler.onFolderTextChange, modifier = Modifier.padding(horizontal = 16.dp), ) } @@ -184,8 +186,8 @@ fun LazyListScope.addEditLoginItems( label = stringResource( id = R.string.favorite, ), - isChecked = state.favorite, - onCheckedChange = loginItemTypeHandlers.onToggleFavorite, + isChecked = commonState.favorite, + onCheckedChange = commonActionHandler.onToggleFavorite, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -196,13 +198,13 @@ fun LazyListScope.addEditLoginItems( Spacer(modifier = Modifier.height(16.dp)) BitwardenSwitchWithActions( label = stringResource(id = R.string.password_prompt), - isChecked = state.masterPasswordReprompt, - onCheckedChange = loginItemTypeHandlers.onToggleMasterPasswordReprompt, + isChecked = commonState.masterPasswordReprompt, + onCheckedChange = commonActionHandler.onToggleMasterPasswordReprompt, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), actions = { - IconButton(onClick = loginItemTypeHandlers.onTooltipClick) { + IconButton(onClick = commonActionHandler.onTooltipClick) { Icon( painter = painterResource(id = R.drawable.ic_tooltip), tint = MaterialTheme.colorScheme.onSurface, @@ -230,8 +232,8 @@ fun LazyListScope.addEditLoginItems( BitwardenTextField( singleLine = false, label = stringResource(id = R.string.notes), - value = state.notes, - onValueChange = loginItemTypeHandlers.onNotesTextChange, + value = commonState.notes, + onValueChange = commonActionHandler.onNotesTextChange, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -248,10 +250,10 @@ fun LazyListScope.addEditLoginItems( ) } - items(state.customFieldData) { customItem -> + items(commonState.customFieldData) { customItem -> AddEditCustomField( customItem, - onCustomFieldValueChange = loginItemTypeHandlers.onCustomFieldValueChange, + onCustomFieldValueChange = commonActionHandler.onCustomFieldValueChange, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -265,7 +267,7 @@ fun LazyListScope.addEditLoginItems( item { Spacer(modifier = Modifier.height(16.dp)) AddEditCustomFieldsButton( - onFinishNamingClick = loginItemTypeHandlers.onAddNewCustomFieldClick, + onFinishNamingClick = commonActionHandler.onAddNewCustomFieldClick, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -287,9 +289,9 @@ fun LazyListScope.addEditLoginItems( Spacer(modifier = Modifier.height(8.dp)) BitwardenMultiSelectButton( label = stringResource(id = R.string.who_owns_this_item), - options = state.availableOwners.toImmutableList(), - selectedOption = state.ownership, - onOptionSelected = loginItemTypeHandlers.onOwnershipTextChange, + options = commonState.availableOwners.toImmutableList(), + selectedOption = commonState.ownership, + onOptionSelected = commonActionHandler.onOwnershipTextChange, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt index 6f9ca74380..88714b8858 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt @@ -28,16 +28,16 @@ import kotlinx.collections.immutable.toImmutableList */ @Suppress("LongMethod") fun LazyListScope.addEditSecureNotesItems( - state: VaultAddItemState.ViewState.Content.SecureNotes, + commonState: VaultAddItemState.ViewState.Content.Common, isAddItemMode: Boolean, - secureNotesTypeHandlers: VaultAddSecureNotesItemTypeHandlers, + commonTypeHandlers: VaultAddItemCommonTypeHandlers, ) { item { Spacer(modifier = Modifier.height(8.dp)) BitwardenTextField( label = stringResource(id = R.string.name), - value = state.name, - onValueChange = secureNotesTypeHandlers.onNameTextChange, + value = commonState.name, + onValueChange = commonTypeHandlers.onNameTextChange, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -58,9 +58,9 @@ fun LazyListScope.addEditSecureNotesItems( Spacer(modifier = Modifier.height(8.dp)) BitwardenMultiSelectButton( label = stringResource(id = R.string.folder), - options = state.availableFolders.map { it.invoke() }.toImmutableList(), - selectedOption = state.folderName.invoke(), - onOptionSelected = secureNotesTypeHandlers.onFolderTextChange, + options = commonState.availableFolders.map { it.invoke() }.toImmutableList(), + selectedOption = commonState.folderName.invoke(), + onOptionSelected = commonTypeHandlers.onFolderTextChange, modifier = Modifier .padding(horizontal = 16.dp), ) @@ -70,8 +70,8 @@ fun LazyListScope.addEditSecureNotesItems( Spacer(modifier = Modifier.height(16.dp)) BitwardenSwitch( label = stringResource(id = R.string.favorite), - isChecked = state.favorite, - onCheckedChange = secureNotesTypeHandlers.onToggleFavorite, + isChecked = commonState.favorite, + onCheckedChange = commonTypeHandlers.onToggleFavorite, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -82,13 +82,13 @@ fun LazyListScope.addEditSecureNotesItems( Spacer(modifier = Modifier.height(16.dp)) BitwardenSwitchWithActions( label = stringResource(id = R.string.password_prompt), - isChecked = state.masterPasswordReprompt, - onCheckedChange = secureNotesTypeHandlers.onToggleMasterPasswordReprompt, + isChecked = commonState.masterPasswordReprompt, + onCheckedChange = commonTypeHandlers.onToggleMasterPasswordReprompt, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), actions = { - IconButton(onClick = secureNotesTypeHandlers.onTooltipClick) { + IconButton(onClick = commonTypeHandlers.onTooltipClick) { Icon( painter = painterResource(id = R.drawable.ic_tooltip), tint = MaterialTheme.colorScheme.onSurface, @@ -116,8 +116,8 @@ fun LazyListScope.addEditSecureNotesItems( BitwardenTextField( singleLine = false, label = stringResource(id = R.string.notes), - value = state.notes, - onValueChange = secureNotesTypeHandlers.onNotesTextChange, + value = commonState.notes, + onValueChange = commonTypeHandlers.onNotesTextChange, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -133,11 +133,10 @@ fun LazyListScope.addEditSecureNotesItems( .padding(horizontal = 16.dp), ) } - - items(state.customFieldData) { customItem -> + items(commonState.customFieldData) { customItem -> AddEditCustomField( customItem, - onCustomFieldValueChange = secureNotesTypeHandlers.onCustomFieldValueChange, + onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -147,7 +146,7 @@ fun LazyListScope.addEditSecureNotesItems( item { Spacer(modifier = Modifier.height(16.dp)) AddEditCustomFieldsButton( - onFinishNamingClick = secureNotesTypeHandlers.onAddNewCustomFieldClick, + onFinishNamingClick = commonTypeHandlers.onAddNewCustomFieldClick, options = persistentListOf( CustomFieldType.TEXT, CustomFieldType.HIDDEN, @@ -174,9 +173,9 @@ fun LazyListScope.addEditSecureNotesItems( Spacer(modifier = Modifier.height(8.dp)) BitwardenMultiSelectButton( label = stringResource(id = R.string.who_owns_this_item), - options = state.availableOwners.toImmutableList(), - selectedOption = state.ownership, - onOptionSelected = secureNotesTypeHandlers.onOwnershipTextChange, + options = commonState.availableOwners.toImmutableList(), + selectedOption = commonState.ownership, + onOptionSelected = commonTypeHandlers.onOwnershipTextChange, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddSecureNotesItemTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemCommonTypeHandlers.kt similarity index 72% rename from app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddSecureNotesItemTypeHandlers.kt rename to app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemCommonTypeHandlers.kt index 7fc6805525..7d06344fff 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddSecureNotesItemTypeHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemCommonTypeHandlers.kt @@ -4,8 +4,8 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType /** - * A collection of handler functions specifically tailored for managing actions - * within the context of adding secure note items to a vault. + * A collection of handler functions for managing actions common + * within the context of adding items to a vault. * * @property onNameTextChange Handles the action when the name text is changed. * @property onFolderTextChange Handles the action when the folder text is changed. @@ -20,7 +20,7 @@ import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType * @property onCustomFieldValueChange Handles the action when the field's value changes */ @Suppress("LongParameterList") -class VaultAddSecureNotesItemTypeHandlers( +class VaultAddItemCommonTypeHandlers( val onNameTextChange: (String) -> Unit, val onFolderTextChange: (String) -> Unit, val onToggleFavorite: (Boolean) -> Unit, @@ -34,54 +34,54 @@ class VaultAddSecureNotesItemTypeHandlers( companion object { /** - * Creates an instance of [VaultAddSecureNotesItemTypeHandlers] by binding actions + * Creates an instance of [VaultAddItemCommonTypeHandlers] by binding actions * to the provided [VaultAddItemViewModel]. */ @Suppress("LongMethod") - fun create(viewModel: VaultAddItemViewModel): VaultAddSecureNotesItemTypeHandlers { - return VaultAddSecureNotesItemTypeHandlers( + fun create(viewModel: VaultAddItemViewModel): VaultAddItemCommonTypeHandlers { + return VaultAddItemCommonTypeHandlers( onNameTextChange = { newName -> viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.NameTextChange(newName), + VaultAddItemAction.Common.NameTextChange(newName), ) }, onFolderTextChange = { newFolder -> viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.FolderChange( + VaultAddItemAction.Common.FolderChange( newFolder.asText(), ), ) }, onToggleFavorite = { isFavorite -> viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite(isFavorite), + VaultAddItemAction.Common.ToggleFavorite(isFavorite), ) }, onToggleMasterPasswordReprompt = { isMasterPasswordReprompt -> viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt( + VaultAddItemAction.Common.ToggleMasterPasswordReprompt( isMasterPasswordReprompt, ), ) }, onNotesTextChange = { newNotes -> viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange(newNotes), + VaultAddItemAction.Common.NotesTextChange(newNotes), ) }, onOwnershipTextChange = { newOwnership -> viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange(newOwnership), + VaultAddItemAction.Common.OwnershipChange(newOwnership), ) }, onTooltipClick = { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.TooltipClick, + VaultAddItemAction.Common.TooltipClick, ) }, onAddNewCustomFieldClick = { newCustomFieldType, name -> viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick( + VaultAddItemAction.Common.AddNewCustomFieldClick( newCustomFieldType, name, ), @@ -89,7 +89,7 @@ class VaultAddSecureNotesItemTypeHandlers( }, onCustomFieldValueChange = { newValue -> viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange( + VaultAddItemAction.Common.CustomFieldValueChange( newValue, ), ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt index 284ec0a398..e54d815854 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt @@ -55,14 +55,14 @@ fun VaultAddItemScreen( VaultAddLoginItemTypeHandlers.create(viewModel = viewModel) } - val secureNotesTypeHandlers = remember(viewModel) { - VaultAddSecureNotesItemTypeHandlers.create(viewModel = viewModel) + val commonTypeHandlers = remember(viewModel) { + VaultAddItemCommonTypeHandlers.create(viewModel = viewModel) } VaultAddEditItemDialogs( dialogState = state.dialog, onDismissRequest = remember(viewModel) { - { viewModel.trySendAction(VaultAddItemAction.DismissDialog) } + { viewModel.trySendAction(VaultAddItemAction.Common.DismissDialog) } }, ) @@ -77,14 +77,14 @@ fun VaultAddItemScreen( navigationIcon = painterResource(id = R.drawable.ic_close), navigationIconContentDescription = stringResource(id = R.string.close), onNavigationIconClick = remember(viewModel) { - { viewModel.trySendAction(VaultAddItemAction.CloseClick) } + { viewModel.trySendAction(VaultAddItemAction.Common.CloseClick) } }, scrollBehavior = scrollBehavior, actions = { BitwardenTextButton( label = stringResource(id = R.string.save), onClick = remember(viewModel) { - { viewModel.trySendAction(VaultAddItemAction.SaveClick) } + { viewModel.trySendAction(VaultAddItemAction.Common.SaveClick) } }, ) }, @@ -94,13 +94,13 @@ fun VaultAddItemScreen( when (val viewState = state.viewState) { is VaultAddItemState.ViewState.Content -> { AddEditItemContent( - viewState = viewState, + state = viewState, isAddItemMode = state.isAddItemMode, onTypeOptionClicked = remember(viewModel) { - { viewModel.trySendAction(VaultAddItemAction.TypeOptionSelect(it)) } + { viewModel.trySendAction(VaultAddItemAction.Common.TypeOptionSelect(it)) } }, loginItemTypeHandlers = loginItemTypeHandlers, - secureNotesTypeHandlers = secureNotesTypeHandlers, + commonTypeHandlers = commonTypeHandlers, modifier = Modifier .imePadding() .padding(innerPadding) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModel.kt index 4e35c18805..8215e08145 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModel.kt @@ -1,7 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.additem import android.os.Parcelable -import androidx.annotation.StringRes import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.bitwarden.core.CipherView @@ -55,7 +54,11 @@ class VaultAddItemViewModel @Inject constructor( VaultAddItemState( vaultAddEditType = vaultAddEditType, viewState = when (vaultAddEditType) { - VaultAddEditType.AddItem -> VaultAddItemState.ViewState.Content.Login() + VaultAddEditType.AddItem -> VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common(), + type = VaultAddItemState.ViewState.Content.ItemType.Login(), + ) + is VaultAddEditType.EditItem -> VaultAddItemState.ViewState.Loading }, dialog = null, @@ -82,54 +85,86 @@ class VaultAddItemViewModel @Inject constructor( override fun handleAction(action: VaultAddItemAction) { when (action) { - is VaultAddItemAction.SaveClick -> { - handleSaveClick() - } - - is VaultAddItemAction.CloseClick -> { - handleCloseClick() - } - - is VaultAddItemAction.DismissDialog -> { - handleDismissDialog() - } - - is VaultAddItemAction.TypeOptionSelect -> { - handleTypeOptionSelect(action) - } - - is VaultAddItemAction.ItemType.LoginType -> { - handleAddLoginTypeAction(action) - } - - is VaultAddItemAction.ItemType.SecureNotesType -> { - handleAddSecureNoteTypeAction(action) - } - + is VaultAddItemAction.Common -> handleCommonActions(action) + is VaultAddItemAction.ItemType.LoginType -> handleAddLoginTypeAction(action) is VaultAddItemAction.Internal -> handleInternalActions(action) } } - private fun handleInternalActions(action: VaultAddItemAction.Internal) { - when (action) { - is VaultAddItemAction.Internal.CreateCipherResultReceive -> { - handleCreateCipherResultReceive(action) - } - - is VaultAddItemAction.Internal.UpdateCipherResultReceive -> { - handleUpdateCipherResultReceive(action) - } - - is VaultAddItemAction.Internal.VaultDataReceive -> handleVaultDataReceive(action) - } - } - //endregion Initialization and Overrides - //region Top Level Handlers + //region Common Handlers + + private fun handleCommonActions(action: VaultAddItemAction.Common) { + when (action) { + is VaultAddItemAction.Common.CustomFieldValueChange -> handleCustomFieldValueChange( + action, + ) + + is VaultAddItemAction.Common.FolderChange -> handleFolderTextInputChange(action) + is VaultAddItemAction.Common.NameTextChange -> handleNameTextInputChange(action) + is VaultAddItemAction.Common.NotesTextChange -> handleNotesTextInputChange(action) + is VaultAddItemAction.Common.OwnershipChange -> handleOwnershipTextInputChange(action) + is VaultAddItemAction.Common.ToggleFavorite -> handleToggleFavorite(action) + is VaultAddItemAction.Common.ToggleMasterPasswordReprompt -> { + handleToggleMasterPasswordReprompt(action) + } + + is VaultAddItemAction.Common.CloseClick -> handleCloseClick() + is VaultAddItemAction.Common.DismissDialog -> handleDismissDialog() + is VaultAddItemAction.Common.SaveClick -> handleSaveClick() + is VaultAddItemAction.Common.TypeOptionSelect -> handleTypeOptionSelect(action) + is VaultAddItemAction.Common.AddNewCustomFieldClick -> handleAddNewCustomFieldClick( + action, + ) + + is VaultAddItemAction.Common.TooltipClick -> handleTooltipClick() + } + } + + private fun handleTypeOptionSelect(action: VaultAddItemAction.Common.TypeOptionSelect) { + when (action.typeOption) { + VaultAddItemState.ItemTypeOption.LOGIN -> handleSwitchToAddLoginItem() + VaultAddItemState.ItemTypeOption.CARD -> handleSwitchToAddCardItem() + VaultAddItemState.ItemTypeOption.IDENTITY -> handleSwitchToAddIdentityItem() + VaultAddItemState.ItemTypeOption.SECURE_NOTES -> handleSwitchToAddSecureNotesItem() + } + } + + private fun handleSwitchToAddLoginItem() { + updateContent { currentContent -> + currentContent.copy( + type = VaultAddItemState.ViewState.Content.ItemType.Login(), + ) + } + } + + private fun handleSwitchToAddSecureNotesItem() { + updateContent { currentContent -> + currentContent.copy( + type = VaultAddItemState.ViewState.Content.ItemType.SecureNotes, + ) + } + } + + private fun handleSwitchToAddCardItem() { + updateContent { currentContent -> + currentContent.copy( + type = VaultAddItemState.ViewState.Content.ItemType.Card, + ) + } + } + + private fun handleSwitchToAddIdentityItem() { + updateContent { currentContent -> + currentContent.copy( + type = VaultAddItemState.ViewState.Content.ItemType.Identity, + ) + } + } private fun handleSaveClick() = onContent { content -> - if (content.name.isBlank()) { + if (content.common.name.isBlank()) { mutableStateFlow.update { it.copy( dialog = VaultAddItemState.DialogState.Error( @@ -179,36 +214,91 @@ class VaultAddItemViewModel @Inject constructor( } } - //endregion Top Level Handlers + private fun handleAddNewCustomFieldClick( + action: VaultAddItemAction.Common.AddNewCustomFieldClick, + ) { + val newCustomData: VaultAddItemState.Custom = + action.customFieldType.toCustomField(action.name) - //region Type Option Handlers - - private fun handleTypeOptionSelect(action: VaultAddItemAction.TypeOptionSelect) { - when (action.typeOption) { - VaultAddItemState.ItemTypeOption.LOGIN -> handleSwitchToAddLoginItem() - VaultAddItemState.ItemTypeOption.CARD -> handleSwitchToAddCardItem() - VaultAddItemState.ItemTypeOption.IDENTITY -> handleSwitchToAddIdentityItem() - VaultAddItemState.ItemTypeOption.SECURE_NOTES -> handleSwitchToAddSecureNotesItem() + updateCommonContent { loginType -> + loginType.copy(customFieldData = loginType.customFieldData + newCustomData) } } - private fun handleSwitchToAddLoginItem() { - updateContent { VaultAddItemState.ViewState.Content.Login() } + private fun handleCustomFieldValueChange( + action: VaultAddItemAction.Common.CustomFieldValueChange, + ) { + updateCommonContent { commonContent -> + commonContent.copy( + customFieldData = commonContent.customFieldData.map { customField -> + if (customField.itemId == action.customField.itemId) { + action.customField + } else { + customField + } + }, + ) + } } - private fun handleSwitchToAddSecureNotesItem() { - updateContent { VaultAddItemState.ViewState.Content.SecureNotes() } + private fun handleFolderTextInputChange( + action: VaultAddItemAction.Common.FolderChange, + ) { + updateCommonContent { commonContent -> + commonContent.copy(folderName = action.folder) + } } - private fun handleSwitchToAddCardItem() { - updateContent { VaultAddItemState.ViewState.Content.Card() } + private fun handleToggleFavorite( + action: VaultAddItemAction.Common.ToggleFavorite, + ) { + updateCommonContent { commonContent -> + commonContent.copy(favorite = action.isFavorite) + } } - private fun handleSwitchToAddIdentityItem() { - updateContent { VaultAddItemState.ViewState.Content.Identity() } + private fun handleToggleMasterPasswordReprompt( + action: VaultAddItemAction.Common.ToggleMasterPasswordReprompt, + ) { + updateCommonContent { commonContent -> + commonContent.copy(masterPasswordReprompt = action.isMasterPasswordReprompt) + } } - //endregion Type Option Handlers + private fun handleNotesTextInputChange( + action: VaultAddItemAction.Common.NotesTextChange, + ) { + updateCommonContent { commonContent -> + commonContent.copy(notes = action.notes) + } + } + + private fun handleOwnershipTextInputChange( + action: VaultAddItemAction.Common.OwnershipChange, + ) { + updateCommonContent { commonContent -> + commonContent.copy(ownership = action.ownership) + } + } + + private fun handleNameTextInputChange( + action: VaultAddItemAction.Common.NameTextChange, + ) { + updateCommonContent { commonContent -> + commonContent.copy(name = action.name) + } + } + + private fun handleTooltipClick() { + // TODO Add the text for the prompt (BIT-1079) + sendEvent( + event = VaultAddItemEvent.ShowToast( + message = "Not yet implemented", + ), + ) + } + + //endregion Common Handlers //region Add Login Item Type Handlers @@ -217,10 +307,6 @@ class VaultAddItemViewModel @Inject constructor( action: VaultAddItemAction.ItemType.LoginType, ) { when (action) { - is VaultAddItemAction.ItemType.LoginType.NameTextChange -> { - handleLoginNameTextInputChange(action) - } - is VaultAddItemAction.ItemType.LoginType.UsernameTextChange -> { handleLoginUsernameTextInputChange(action) } @@ -233,26 +319,6 @@ class VaultAddItemViewModel @Inject constructor( handleLoginUriTextInputChange(action) } - is VaultAddItemAction.ItemType.LoginType.FolderChange -> { - handleLoginFolderTextInputChange(action) - } - - is VaultAddItemAction.ItemType.LoginType.ToggleFavorite -> { - handleLoginToggleFavorite(action) - } - - is VaultAddItemAction.ItemType.LoginType.ToggleMasterPasswordReprompt -> { - handleLoginToggleMasterPasswordReprompt(action) - } - - is VaultAddItemAction.ItemType.LoginType.NotesTextChange -> { - handleLoginNotesTextInputChange(action) - } - - is VaultAddItemAction.ItemType.LoginType.OwnershipChange -> { - handleLoginOwnershipTextInputChange(action) - } - is VaultAddItemAction.ItemType.LoginType.OpenUsernameGeneratorClick -> { handleLoginOpenUsernameGeneratorClick() } @@ -276,26 +342,6 @@ class VaultAddItemViewModel @Inject constructor( is VaultAddItemAction.ItemType.LoginType.AddNewUriClick -> { handleLoginAddNewUriClick() } - - is VaultAddItemAction.ItemType.LoginType.TooltipClick -> { - handleLoginTooltipClick() - } - - is VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick -> { - handleLoginAddNewCustomFieldClick(action) - } - - is VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange -> { - handleLoginCustomFieldValueChange(action) - } - } - } - - private fun handleLoginNameTextInputChange( - action: VaultAddItemAction.ItemType.LoginType.NameTextChange, - ) { - updateLoginContent { loginType -> - loginType.copy(name = action.name) } } @@ -323,46 +369,6 @@ class VaultAddItemViewModel @Inject constructor( } } - private fun handleLoginFolderTextInputChange( - action: VaultAddItemAction.ItemType.LoginType.FolderChange, - ) { - updateLoginContent { loginType -> - loginType.copy(folderName = action.folder) - } - } - - private fun handleLoginToggleFavorite( - action: VaultAddItemAction.ItemType.LoginType.ToggleFavorite, - ) { - updateLoginContent { loginType -> - loginType.copy(favorite = action.isFavorite) - } - } - - private fun handleLoginToggleMasterPasswordReprompt( - action: VaultAddItemAction.ItemType.LoginType.ToggleMasterPasswordReprompt, - ) { - updateLoginContent { loginType -> - loginType.copy(masterPasswordReprompt = action.isMasterPasswordReprompt) - } - } - - private fun handleLoginNotesTextInputChange( - action: VaultAddItemAction.ItemType.LoginType.NotesTextChange, - ) { - updateLoginContent { loginType -> - loginType.copy(notes = action.notes) - } - } - - private fun handleLoginOwnershipTextInputChange( - action: VaultAddItemAction.ItemType.LoginType.OwnershipChange, - ) { - updateLoginContent { loginType -> - loginType.copy(ownership = action.ownership) - } - } - private fun handleLoginOpenUsernameGeneratorClick() { viewModelScope.launch { sendEvent( @@ -423,179 +429,27 @@ class VaultAddItemViewModel @Inject constructor( } } - private fun handleLoginTooltipClick() { - viewModelScope.launch { - sendEvent( - event = VaultAddItemEvent.ShowToast( - message = "Tooltip", - ), - ) - } - } - - private fun handleLoginAddNewCustomFieldClick( - action: VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick, - ) { - val newCustomData: VaultAddItemState.Custom = - action.customFieldType.toCustomField(action.name) - - updateLoginContent { loginType -> - loginType.copy(customFieldData = loginType.customFieldData + newCustomData) - } - } - - private fun handleLoginCustomFieldValueChange( - action: VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange, - ) { - updateLoginContent { login -> - login.copy( - customFieldData = login.customFieldData.map { customField -> - if (customField.itemId == action.customField.itemId) { - action.customField - } else { - customField - } - }, - ) - } - } - //endregion Add Login Item Type Handlers - //region Secure Note Item Type Handlers - - private fun handleAddSecureNoteTypeAction( - action: VaultAddItemAction.ItemType.SecureNotesType, - ) { - when (action) { - is VaultAddItemAction.ItemType.SecureNotesType.NameTextChange -> { - handleSecureNoteNameTextInputChange(action) - } - - is VaultAddItemAction.ItemType.SecureNotesType.FolderChange -> { - handleSecureNoteFolderTextInputChange(action) - } - - is VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite -> { - handleSecureNoteToggleFavorite(action) - } - - is VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt -> { - handleSecureNoteToggleMasterPasswordReprompt(action) - } - - is VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange -> { - handleSecureNoteNotesTextInputChange(action) - } - - is VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange -> { - handleSecureNoteOwnershipTextInputChange(action) - } - - is VaultAddItemAction.ItemType.SecureNotesType.TooltipClick -> { - handleSecureNoteTooltipClick() - } - - is VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick -> { - handleSecureNoteAddNewCustomFieldClick(action) - } - - is VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange -> { - handleSecureNoteCustomFieldValueChange(action) - } - } - } - - private fun handleSecureNoteNameTextInputChange( - action: VaultAddItemAction.ItemType.SecureNotesType.NameTextChange, - ) { - updateSecureNoteContent { secureNoteType -> - secureNoteType.copy(name = action.name) - } - } - - private fun handleSecureNoteFolderTextInputChange( - action: VaultAddItemAction.ItemType.SecureNotesType.FolderChange, - ) { - updateSecureNoteContent { secureNoteType -> - secureNoteType.copy(folderName = action.folderName) - } - } - - private fun handleSecureNoteToggleFavorite( - action: VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite, - ) { - updateSecureNoteContent { secureNoteType -> - secureNoteType.copy(favorite = action.isFavorite) - } - } - - private fun handleSecureNoteToggleMasterPasswordReprompt( - action: VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt, - ) { - updateSecureNoteContent { secureNoteType -> - secureNoteType.copy(masterPasswordReprompt = action.isMasterPasswordReprompt) - } - } - - private fun handleSecureNoteNotesTextInputChange( - action: VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange, - ) { - updateSecureNoteContent { secureNoteType -> - secureNoteType.copy(notes = action.note) - } - } - - private fun handleSecureNoteOwnershipTextInputChange( - action: VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange, - ) { - updateSecureNoteContent { secureNoteType -> - secureNoteType.copy(ownership = action.ownership) - } - } - - private fun handleSecureNoteTooltipClick() { - // TODO Add the text for the prompt (BIT-1079) - sendEvent( - event = VaultAddItemEvent.ShowToast( - message = "Not yet implemented", - ), - ) - } - - private fun handleSecureNoteAddNewCustomFieldClick( - action: VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick, - ) { - val newCustomData: VaultAddItemState.Custom = - action.customFieldType.toCustomField(action.name) - - updateSecureNoteContent { secureNotesType -> - secureNotesType.copy(customFieldData = secureNotesType.customFieldData + newCustomData) - } - } - - private fun handleSecureNoteCustomFieldValueChange( - action: VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange, - ) { - updateSecureNoteContent { secureNote -> - secureNote.copy( - customFieldData = secureNote.customFieldData.map { customField -> - if (customField.itemId == action.customField.itemId) { - action.customField - } else { - customField - } - }, - ) - } - } - - //endregion Secure Notes Item Type Handlers - //region Internal Type Handlers - @Suppress("MaxLineLength") - private fun handleCreateCipherResultReceive(action: VaultAddItemAction.Internal.CreateCipherResultReceive) { + private fun handleInternalActions(action: VaultAddItemAction.Internal) { + when (action) { + is VaultAddItemAction.Internal.CreateCipherResultReceive -> { + handleCreateCipherResultReceive(action) + } + + is VaultAddItemAction.Internal.UpdateCipherResultReceive -> { + handleUpdateCipherResultReceive(action) + } + + is VaultAddItemAction.Internal.VaultDataReceive -> handleVaultDataReceive(action) + } + } + + private fun handleCreateCipherResultReceive( + action: VaultAddItemAction.Internal.CreateCipherResultReceive, + ) { mutableStateFlow.update { it.copy(dialog = null) } @@ -714,20 +568,23 @@ class VaultAddItemViewModel @Inject constructor( mutableStateFlow.update { it.copy(viewState = updatedContent) } } - private inline fun updateLoginContent( - crossinline block: ( - VaultAddItemState.ViewState.Content.Login, - ) -> VaultAddItemState.ViewState.Content.Login, + private inline fun updateCommonContent( + crossinline block: (VaultAddItemState.ViewState.Content.Common) -> + VaultAddItemState.ViewState.Content.Common, ) { - updateContent { (it as? VaultAddItemState.ViewState.Content.Login)?.let(block) } + updateContent { currentContent -> + currentContent.copy(common = block(currentContent.common)) + } } - private inline fun updateSecureNoteContent( - crossinline block: ( - VaultAddItemState.ViewState.Content.SecureNotes, - ) -> VaultAddItemState.ViewState.Content.SecureNotes, + private inline fun updateLoginContent( + crossinline block: (VaultAddItemState.ViewState.Content.ItemType.Login) -> + VaultAddItemState.ViewState.Content.ItemType.Login, ) { - updateContent { (it as? VaultAddItemState.ViewState.Content.SecureNotes)?.let(block) } + updateContent { currentContent -> + (currentContent.type as? VaultAddItemState.ViewState.Content.ItemType.Login) + ?.let { currentContent.copy(type = block(it)) } + } } //endregion Utility Functions @@ -796,186 +653,106 @@ data class VaultAddItemState( * Represents a loaded content state for the [VaultAddItemScreen]. */ @Parcelize - sealed class Content : ViewState() { + data class Content( + val common: Common, + val type: ItemType, + ) : ViewState() { + /** - * The original cipher from the vault that the user is editing. + * Content data that is common for all item types. * + * @property originalCipher The original cipher from the vault that the user is editing. * This is only present when editing a pre-existing cipher. - */ - @IgnoredOnParcel - abstract val originalCipher: CipherView? - - /** - * Represents the resource ID for the display string. This is an abstract property - * that must be overridden by each subclass to provide the appropriate string resource - * ID for display purposes. - */ - @get:StringRes - abstract val displayStringResId: Int - - /** - * Represents the name for the item type. This is an abstract property that must be - * overridden to save the item - */ - abstract val name: String - - /** - * Indicates if a master password reprompt is required. - */ - abstract val masterPasswordReprompt: Boolean - - /** - * Indicates whether this item is marked as a favorite. - */ - abstract val favorite: Boolean - - /** - * Additional custom fields associated with the item. - */ - abstract val customFieldData: List - - /** - * Any additional notes or comments associated with the item. - */ - abstract val notes: String - - /** - * The folder that this item belongs too. - */ - abstract val folderName: Text - - /** - * The list of folders that this item could be added too. - */ - abstract val availableFolders: List - - /** - * The ownership email associated with the item. - */ - abstract val ownership: String - - /** - * A list of available owners. - */ - abstract val availableOwners: List - - /** - * Represents the login item information. - * - * @property username The username required for the login item. - * @property password The password required for the login item. - * @property uri The URI associated with the login item. + * @property name Represents the name for the item type. This is an abstract property + * that must be overridden to save the item. + * @property masterPasswordReprompt Indicates if a master password reprompt is required. + * @property favorite Indicates whether this item is marked as a favorite. + * @property customFieldData Additional custom fields associated with the item. + * @property notes Any additional notes or comments associated with the item. + * @property folderName The folder that this item belongs to. + * @property availableFolders The list of folders that this item could be added too. + * @property ownership The ownership email associated with the item. + * @property availableOwners A list of available owners. */ @Parcelize - data class Login( + data class Common( @IgnoredOnParcel - override val originalCipher: CipherView? = null, - override val name: String = "", - override val folderName: Text = DEFAULT_FOLDER, - // TODO: Update this property to get available owners from the data layer (BIT-501) - override val availableFolders: List = listOf( + val originalCipher: CipherView? = null, + val name: String = "", + val masterPasswordReprompt: Boolean = false, + val favorite: Boolean = false, + val customFieldData: List = emptyList(), + val notes: String = "", + val folderName: Text = DEFAULT_FOLDER, + val availableFolders: List = listOf( "Folder 1".asText(), "Folder 2".asText(), "Folder 3".asText(), ), - override val favorite: Boolean = false, - override val masterPasswordReprompt: Boolean = false, - override val customFieldData: List = emptyList(), - override val notes: String = "", // TODO: Update this property to get available owners from the data layer (BIT-501) - override val ownership: String = DEFAULT_OWNERSHIP, - override val availableOwners: List = listOf("a@b.com", "c@d.com"), - val username: String = "", - val password: String = "", - val uri: String = "", - ) : Content() { - override val displayStringResId: Int get() = ItemTypeOption.LOGIN.labelRes + val ownership: String = DEFAULT_OWNERSHIP, + // TODO: Update this property to get available owners from the data layer (BIT-501) + val availableOwners: List = listOf("a@b.com", "c@d.com"), + ) : Parcelable { + companion object { + private val DEFAULT_FOLDER: Text = R.string.folder_none.asText() + private const val DEFAULT_OWNERSHIP: String = "placeholder@email.com" + } } /** - * Represents the `Card` item type. + * Content data specific to an item type. */ @Parcelize - data class Card( - @IgnoredOnParcel - override val originalCipher: CipherView? = null, - override val name: String = "", - override val folderName: Text = DEFAULT_FOLDER, - // TODO: Update this property to get available owners from the data layer (BIT-501) - override val availableFolders: List = listOf( - "Folder 1".asText(), - "Folder 2".asText(), - "Folder 3".asText(), - ), - override val favorite: Boolean = false, - override val masterPasswordReprompt: Boolean = false, - override val customFieldData: List = emptyList(), - override val notes: String = "", - // TODO: Update this property to get available owners from the data layer (BIT-501) - override val ownership: String = DEFAULT_OWNERSHIP, - override val availableOwners: List = listOf("a@b.com", "c@d.com"), - ) : Content() { - override val displayStringResId: Int get() = ItemTypeOption.CARD.labelRes - } + sealed class ItemType : Parcelable { - /** - * Represents the `Identity` item type. - */ - @Parcelize - data class Identity( - @IgnoredOnParcel - override val originalCipher: CipherView? = null, - override val name: String = "", - override val folderName: Text = DEFAULT_FOLDER, - // TODO: Update this property to get available owners from the data layer (BIT-501) - override val availableFolders: List = listOf( - "Folder 1".asText(), - "Folder 2".asText(), - "Folder 3".asText(), - ), - override val favorite: Boolean = false, - override val masterPasswordReprompt: Boolean = false, - override val customFieldData: List = emptyList(), - override val notes: String = "", - // TODO: Update this property to get available owners from the data layer (BIT-501) - override val ownership: String = DEFAULT_OWNERSHIP, - override val availableOwners: List = listOf("a@b.com", "c@d.com"), - ) : Content() { - override val displayStringResId: Int get() = ItemTypeOption.IDENTITY.labelRes - } + /** + * Represents the resource ID for the display string. This is an abstract property + * that must be overridden by each subclass to provide the appropriate string + * resource for display purposes. + */ + abstract val displayStringResId: Int - /** - * Represents the `SecureNotes` item type. - * - * @property folderName The folder used for the SecureNotes item - * @property availableFolders A list of available folders. - */ - @Parcelize - data class SecureNotes( - @IgnoredOnParcel - override val originalCipher: CipherView? = null, - override val name: String = "", - override val folderName: Text = DEFAULT_FOLDER, - // TODO: Update this property to get available owners from the data layer (BIT-501) - override val availableFolders: List = listOf( - "Folder 1".asText(), - "Folder 2".asText(), - "Folder 3".asText(), - ), - override val favorite: Boolean = false, - override val masterPasswordReprompt: Boolean = false, - override val customFieldData: List = emptyList(), - override val notes: String = "", - // TODO: Update this property to get available owners from the data layer (BIT-501) - override val ownership: String = DEFAULT_OWNERSHIP, - override val availableOwners: List = listOf("a@b.com", "c@d.com"), - ) : Content() { - override val displayStringResId: Int get() = ItemTypeOption.SECURE_NOTES.labelRes - } + /** + * Represents the login item information. + * + * @property username The username required for the login item. + * @property password The password required for the login item. + * @property uri The URI associated with the login item. + */ + @Parcelize + data class Login( + val username: String = "", + val password: String = "", + val uri: String = "", + ) : ItemType() { + override val displayStringResId: Int get() = ItemTypeOption.LOGIN.labelRes + } - companion object { - private val DEFAULT_FOLDER: Text = R.string.folder_none.asText() - private const val DEFAULT_OWNERSHIP: String = "placeholder@email.com" + /** + * Represents the `Card` item type. + */ + @Parcelize + data object Card : ItemType() { + override val displayStringResId: Int get() = ItemTypeOption.CARD.labelRes + } + + /** + * Represents the `Identity` item type. + */ + @Parcelize + data object Identity : ItemType() { + override val displayStringResId: Int get() = ItemTypeOption.IDENTITY.labelRes + } + + /** + * Represents the `SecureNotes` item type. + */ + @Parcelize + data object SecureNotes : ItemType() { + override val displayStringResId: Int + get() = ItemTypeOption.SECURE_NOTES.labelRes + } } } } @@ -1074,31 +851,98 @@ sealed class VaultAddItemEvent { sealed class VaultAddItemAction { /** - * Represents the action when the save button is clicked. + * Represents actions common across all item types. */ - data object SaveClick : VaultAddItemAction() + sealed class Common : VaultAddItemAction() { + + /** + * Represents the action when the save button is clicked. + */ + data object SaveClick : Common() + + /** + * User clicked close. + */ + data object CloseClick : Common() + + /** + * The user has clicked to dismiss the dialog. + */ + data object DismissDialog : Common() + + /** + * Represents the action when a type option is selected. + * + * @property typeOption The selected type option. + */ + data class TypeOptionSelect( + val typeOption: VaultAddItemState.ItemTypeOption, + ) : Common() + + /** + * Fired when the name text input is changed. + * + * @property name The new name text. + */ + data class NameTextChange(val name: String) : Common() + + /** + * Fired when the folder text input is changed. + * + * @property folder The new folder text. + */ + data class FolderChange(val folder: Text) : Common() + + /** + * Fired when the Favorite toggle is changed. + * + * @property isFavorite The new state of the Favorite toggle. + */ + data class ToggleFavorite(val isFavorite: Boolean) : Common() + + /** + * Fired when the Master Password Reprompt toggle is changed. + * + * @property isMasterPasswordReprompt The new state of the Master + * Password Re-prompt toggle. + */ + data class ToggleMasterPasswordReprompt(val isMasterPasswordReprompt: Boolean) : Common() + + /** + * Fired when the notes text input is changed. + * + * @property notes The new notes text. + */ + data class NotesTextChange(val notes: String) : Common() + + /** + * Fired when the ownership text input is changed. + * + * @property ownership The new ownership text. + */ + data class OwnershipChange(val ownership: String) : Common() + + /** + * Represents the action to add a new custom field. + */ + data class AddNewCustomFieldClick( + val customFieldType: CustomFieldType, + val name: String, + ) : Common() + + /** + * Fired when the custom field data is changed. + */ + data class CustomFieldValueChange(val customField: VaultAddItemState.Custom) : Common() + + /** + * Represents the action to open tooltip + */ + data object TooltipClick : Common() + } /** - * User clicked close. - */ - data object CloseClick : VaultAddItemAction() - - /** - * The user has clicked to dismiss the dialog. - */ - data object DismissDialog : VaultAddItemAction() - - /** - * Represents the action when a type option is selected. - * - * @property typeOption The selected type option. - */ - data class TypeOptionSelect( - val typeOption: VaultAddItemState.ItemTypeOption, - ) : VaultAddItemAction() - - /** - * Represents actions specific to the item types. + * Represents actions specific to an item type. */ sealed class ItemType : VaultAddItemAction() { @@ -1106,12 +950,6 @@ sealed class VaultAddItemAction { * Represents actions specific to the Login type. */ sealed class LoginType : ItemType() { - /** - * Fired when the name text input is changed. - * - * @property name The new name text. - */ - data class NameTextChange(val name: String) : LoginType() /** * Fired when the username text input is changed. @@ -1134,59 +972,6 @@ sealed class VaultAddItemAction { */ data class UriTextChange(val uri: String) : LoginType() - /** - * Fired when the folder text input is changed. - * - * @property folder The new folder text. - */ - data class FolderChange(val folder: Text) : LoginType() - - /** - * Fired when the Favorite toggle is changed. - * - * @property isFavorite The new state of the Favorite toggle. - */ - data class ToggleFavorite(val isFavorite: Boolean) : LoginType() - - /** - * Fired when the Master Password Reprompt toggle is changed. - * - * @property isMasterPasswordReprompt The new state of the Master - * Password Re-prompt toggle. - */ - data class ToggleMasterPasswordReprompt( - val isMasterPasswordReprompt: Boolean, - ) : LoginType() - - /** - * Fired when the notes text input is changed. - * - * @property notes The new notes text. - */ - data class NotesTextChange(val notes: String) : LoginType() - - /** - * Fired when the ownership text input is changed. - * - * @property ownership The new ownership text. - */ - data class OwnershipChange(val ownership: String) : LoginType() - - /** - * Represents the action to add a new custom field. - */ - data class AddNewCustomFieldClick( - val customFieldType: CustomFieldType, - val name: String, - ) : LoginType() - - /** - * Fired when the custom field data is changed. - */ - data class CustomFieldValueChange( - val customField: VaultAddItemState.Custom, - ) : LoginType() - /** * Represents the action to open the username generator. */ @@ -1216,81 +1001,6 @@ sealed class VaultAddItemAction { * Represents the action to add a new URI field. */ data object AddNewUriClick : LoginType() - - /** - * Represents the action to open tooltip - */ - data object TooltipClick : LoginType() - } - - /** - * Represents actions specific to the SecureNotes type. - */ - sealed class SecureNotesType : ItemType() { - /** - * Fired when the name text input is changed. - * - * @property name The new name text. - */ - data class NameTextChange(val name: String) : SecureNotesType() - - /** - * Fired when the folder text input is changed. - * - * @property folderName The new folder text. - */ - data class FolderChange(val folderName: Text) : SecureNotesType() - - /** - * Fired when the Favorite toggle is changed. - * - * @property isFavorite The new state of the Favorite toggle. - */ - data class ToggleFavorite(val isFavorite: Boolean) : SecureNotesType() - - /** - * Fired when the Master Password Reprompt toggle is changed. - * - * @property isMasterPasswordReprompt The new state of the Master - * Password Re-prompt toggle. - */ - data class ToggleMasterPasswordReprompt( - val isMasterPasswordReprompt: Boolean, - ) : SecureNotesType() - - /** - * Fired when the note text input is changed. - * - * @property note The new note text. - */ - data class NotesTextChange(val note: String) : SecureNotesType() - - /** - * Fired when the ownership text input is changed. - * - * @property ownership The new ownership text. - */ - data class OwnershipChange(val ownership: String) : SecureNotesType() - - /** - * Represents the action to open tooltip - */ - data object TooltipClick : SecureNotesType() - - /** - * Represents the action to add a new custom field. - */ - data class AddNewCustomFieldClick( - val customFieldType: CustomFieldType, - val name: String, - ) : SecureNotesType() - - /** - * Fired when the custom field data is changed. - */ - data class CustomFieldValueChange( - val customField: VaultAddItemState.Custom, - ) : SecureNotesType() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddLoginItemTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddLoginItemTypeHandlers.kt index f27c71ddc7..010478fa36 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddLoginItemTypeHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddLoginItemTypeHandlers.kt @@ -1,22 +1,13 @@ package com.x8bit.bitwarden.ui.vault.feature.additem -import com.x8bit.bitwarden.ui.platform.base.util.asText -import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType - /** * A collection of handler functions specifically tailored for managing actions * within the context of adding login items to a vault. * - * @property onNameTextChange Handles the action when the name text is changed. * @property onUsernameTextChange Handles the action when the username text is changed. * @property onPasswordTextChange Handles the action when the password text is changed. * @property onUriTextChange Handles the action when the URI text is changed. - * @property onFolderTextChange Handles the action when the folder text is changed. - * @property onToggleFavorite Handles the action when the favorite toggle is changed. - * @property onToggleMasterPasswordReprompt Handles the action when the master password * reprompt toggle is changed. - * @property onNotesTextChange Handles the action when the notes text is changed. - * @property onOwnershipTextChange Handles the action when the ownership text is changed. * @property onOpenUsernameGeneratorClick Handles the action when the username generator * button is clicked. * @property onPasswordCheckerClick Handles the action when the password checker @@ -26,31 +17,18 @@ import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType * @property onSetupTotpClick Handles the action when the setup TOTP button is clicked. * @property onUriSettingsClick Handles the action when the URI settings button is clicked. * @property onAddNewUriClick Handles the action when the add new URI button is clicked. - * @property onTooltipClick Handles the action when the tooltip button is clicked. - * @property onAddNewCustomFieldClick Handles the action when the add new custom field - * button is clicked. - * @property onCustomFieldValueChange Handles the action when the field's value changes */ @Suppress("LongParameterList") class VaultAddLoginItemTypeHandlers( - val onNameTextChange: (String) -> Unit, val onUsernameTextChange: (String) -> Unit, val onPasswordTextChange: (String) -> Unit, val onUriTextChange: (String) -> Unit, - val onFolderTextChange: (String) -> Unit, - val onToggleFavorite: (Boolean) -> Unit, - val onToggleMasterPasswordReprompt: (Boolean) -> Unit, - val onNotesTextChange: (String) -> Unit, - val onOwnershipTextChange: (String) -> Unit, val onOpenUsernameGeneratorClick: () -> Unit, val onPasswordCheckerClick: () -> Unit, val onOpenPasswordGeneratorClick: () -> Unit, val onSetupTotpClick: () -> Unit, val onUriSettingsClick: () -> Unit, val onAddNewUriClick: () -> Unit, - val onTooltipClick: () -> Unit, - val onAddNewCustomFieldClick: (CustomFieldType, String) -> Unit, - val onCustomFieldValueChange: (VaultAddItemState.Custom) -> Unit, ) { companion object { @@ -64,11 +42,6 @@ class VaultAddLoginItemTypeHandlers( @Suppress("LongMethod") fun create(viewModel: VaultAddItemViewModel): VaultAddLoginItemTypeHandlers { return VaultAddLoginItemTypeHandlers( - onNameTextChange = { newName -> - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.NameTextChange(newName), - ) - }, onUsernameTextChange = { newUsername -> viewModel.trySendAction( VaultAddItemAction.ItemType.LoginType.UsernameTextChange(newUsername), @@ -84,33 +57,6 @@ class VaultAddLoginItemTypeHandlers( VaultAddItemAction.ItemType.LoginType.UriTextChange(newUri), ) }, - onFolderTextChange = { newFolder -> - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.FolderChange(newFolder.asText()), - ) - }, - onToggleFavorite = { isFavorite -> - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.ToggleFavorite(isFavorite), - ) - }, - onToggleMasterPasswordReprompt = { isMasterPasswordReprompt -> - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.ToggleMasterPasswordReprompt( - isMasterPasswordReprompt, - ), - ) - }, - onNotesTextChange = { newNotes -> - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.NotesTextChange(newNotes), - ) - }, - onOwnershipTextChange = { newOwnership -> - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.OwnershipChange(newOwnership), - ) - }, onOpenUsernameGeneratorClick = { viewModel.trySendAction( VaultAddItemAction.ItemType.LoginType.OpenUsernameGeneratorClick, @@ -135,24 +81,6 @@ class VaultAddLoginItemTypeHandlers( onAddNewUriClick = { viewModel.trySendAction(VaultAddItemAction.ItemType.LoginType.AddNewUriClick) }, - onTooltipClick = { - viewModel.trySendAction(VaultAddItemAction.ItemType.LoginType.TooltipClick) - }, - onAddNewCustomFieldClick = { customFieldType, name -> - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick( - customFieldType = customFieldType, - name = name, - ), - ) - }, - onCustomFieldValueChange = { customField -> - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange( - customField = customField, - ), - ) - }, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensions.kt index 210a114485..34b57dda4d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensions.kt @@ -15,55 +15,36 @@ import java.util.UUID * Transforms [CipherView] into [VaultAddItemState.ViewState]. */ fun CipherView.toViewState(): VaultAddItemState.ViewState = - when (type) { - CipherType.LOGIN -> { - val loginView = requireNotNull(this.login) - VaultAddItemState.ViewState.Content.Login( - originalCipher = this, - name = this.name, - username = loginView.username.orEmpty(), - password = loginView.password.orEmpty(), - uri = loginView.uris?.firstOrNull()?.uri.orEmpty(), - favorite = this.favorite, - masterPasswordReprompt = this.reprompt == CipherRepromptType.PASSWORD, - notes = this.notes.orEmpty(), - // TODO: Update these properties to pull folder from data layer (BIT-501) - folderName = this.folderId?.asText() ?: R.string.folder_none.asText(), - availableFolders = emptyList(), - // TODO: Update this property to pull owner from data layer (BIT-501) - ownership = "", - // TODO: Update this property to pull available owners from data layer (BIT-501) - availableOwners = emptyList(), - customFieldData = this.fields.orEmpty().map { it.toCustomField() }, - ) - } + VaultAddItemState.ViewState.Content( + type = when (type) { + CipherType.LOGIN -> { + VaultAddItemState.ViewState.Content.ItemType.Login( + username = login?.username.orEmpty(), + password = login?.password.orEmpty(), + uri = login?.uris?.firstOrNull()?.uri.orEmpty(), + ) + } - CipherType.SECURE_NOTE -> { - VaultAddItemState.ViewState.Content.SecureNotes( - originalCipher = this, - name = this.name, - favorite = this.favorite, - masterPasswordReprompt = this.reprompt == CipherRepromptType.PASSWORD, - notes = this.notes.orEmpty(), - // TODO: Update these properties to pull folder from data layer (BIT-501) - folderName = this.folderId?.asText() ?: R.string.folder_none.asText(), - availableFolders = emptyList(), - // TODO: Update this property to pull owner from data layer (BIT-501) - ownership = "", - // TODO: Update this property to pull available owners from data layer (BIT-501) - availableOwners = emptyList(), - customFieldData = this.fields.orEmpty().map { it.toCustomField() }, - ) - } - - CipherType.CARD -> VaultAddItemState.ViewState.Error( - message = "Not yet implemented.".asText(), - ) - - CipherType.IDENTITY -> VaultAddItemState.ViewState.Error( - message = "Not yet implemented.".asText(), - ) - } + CipherType.SECURE_NOTE -> VaultAddItemState.ViewState.Content.ItemType.SecureNotes + CipherType.CARD -> VaultAddItemState.ViewState.Content.ItemType.Card + CipherType.IDENTITY -> VaultAddItemState.ViewState.Content.ItemType.Identity + }, + common = VaultAddItemState.ViewState.Content.Common( + originalCipher = this, + name = this.name, + favorite = this.favorite, + masterPasswordReprompt = this.reprompt == CipherRepromptType.PASSWORD, + notes = this.notes.orEmpty(), + // TODO: Update these properties to pull folder from data layer (BIT-501) + folderName = this.folderId?.asText() ?: R.string.folder_none.asText(), + availableFolders = emptyList(), + // TODO: Update this property to pull owner from data layer (BIT-501) + ownership = "", + // TODO: Update this property to pull available owners from data layer (BIT-501) + availableOwners = emptyList(), + customFieldData = this.fields.orEmpty().map { it.toCustomField() }, + ), + ) private fun FieldView.toCustomField() = when (this.type) { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt index 9014dc119d..8c0d65be9c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt @@ -100,53 +100,53 @@ fun VaultData.toViewState(): VaultState.ViewState = } /** - * Transforms a [VaultAddItemState.ViewState.Content] into [CipherView]. + * Transforms a [VaultAddItemState.ViewState.ItemType] into [CipherView]. */ fun VaultAddItemState.ViewState.Content.toCipherView(): CipherView = CipherView( // Pulled from original cipher when editing, otherwise uses defaults - id = this.originalCipher?.id, - collectionIds = this.originalCipher?.collectionIds.orEmpty(), - key = this.originalCipher?.key, - edit = this.originalCipher?.edit ?: true, - viewPassword = this.originalCipher?.viewPassword ?: true, - localData = this.originalCipher?.localData, - attachments = this.originalCipher?.attachments, - organizationUseTotp = this.originalCipher?.organizationUseTotp ?: false, - passwordHistory = this.originalCipher?.passwordHistory, - creationDate = this.originalCipher?.creationDate ?: Instant.now(), - deletedDate = this.originalCipher?.deletedDate, - revisionDate = this.originalCipher?.revisionDate ?: Instant.now(), + id = common.originalCipher?.id, + collectionIds = common.originalCipher?.collectionIds.orEmpty(), + key = common.originalCipher?.key, + edit = common.originalCipher?.edit ?: true, + viewPassword = common.originalCipher?.viewPassword ?: true, + localData = common.originalCipher?.localData, + attachments = common.originalCipher?.attachments, + organizationUseTotp = common.originalCipher?.organizationUseTotp ?: false, + passwordHistory = common.originalCipher?.passwordHistory, + creationDate = common.originalCipher?.creationDate ?: Instant.now(), + deletedDate = common.originalCipher?.deletedDate, + revisionDate = common.originalCipher?.revisionDate ?: Instant.now(), // Type specific section - type = this.toCipherType(), - identity = this.toIdentityView(), - secureNote = this.toSecureNotesView(), - login = this.toLoginView(), - card = this.toCardView(), + type = type.toCipherType(), + identity = type.toIdentityView(), + secureNote = type.toSecureNotesView(), + login = type.toLoginView(common = common), + card = type.toCardView(), // Fields we always grab from the UI - name = this.name, - notes = this.notes, - favorite = this.favorite, + name = common.name, + notes = common.notes, + favorite = common.favorite, // TODO Use real folder ID (BIT-528) - folderId = this.originalCipher?.folderId, + folderId = common.originalCipher?.folderId, // TODO Use real organization ID (BIT-780) - organizationId = this.originalCipher?.organizationId, - reprompt = this.toCipherRepromptType(), - fields = this.customFieldData.map { it.toFieldView() }, + organizationId = common.originalCipher?.organizationId, + reprompt = common.toCipherRepromptType(), + fields = common.customFieldData.map { it.toFieldView() }, ) -private fun VaultAddItemState.ViewState.Content.toCipherType(): CipherType = +private fun VaultAddItemState.ViewState.Content.ItemType.toCipherType(): CipherType = when (this) { - is VaultAddItemState.ViewState.Content.Card -> CipherType.CARD - is VaultAddItemState.ViewState.Content.Identity -> CipherType.IDENTITY - is VaultAddItemState.ViewState.Content.Login -> CipherType.LOGIN - is VaultAddItemState.ViewState.Content.SecureNotes -> CipherType.SECURE_NOTE + is VaultAddItemState.ViewState.Content.ItemType.Card -> CipherType.CARD + is VaultAddItemState.ViewState.Content.ItemType.Identity -> CipherType.IDENTITY + is VaultAddItemState.ViewState.Content.ItemType.Login -> CipherType.LOGIN + is VaultAddItemState.ViewState.Content.ItemType.SecureNotes -> CipherType.SECURE_NOTE } -private fun VaultAddItemState.ViewState.Content.toCardView(): CardView? = - (this as? VaultAddItemState.ViewState.Content.Card)?.let { +private fun VaultAddItemState.ViewState.Content.ItemType.toCardView(): CardView? = + (this as? VaultAddItemState.ViewState.Content.ItemType.Card)?.let { // TODO Create real CardView from Content (BIT-668) CardView( cardholderName = null, @@ -158,8 +158,8 @@ private fun VaultAddItemState.ViewState.Content.toCardView(): CardView? = ) } -private fun VaultAddItemState.ViewState.Content.toIdentityView(): IdentityView? = - (this as? VaultAddItemState.ViewState.Content.Identity)?.let { +private fun VaultAddItemState.ViewState.Content.ItemType.toIdentityView(): IdentityView? = + (this as? VaultAddItemState.ViewState.Content.ItemType.Identity)?.let { // TODO Create real IdentityView from Content (BIT-508) IdentityView( title = null, @@ -183,12 +183,14 @@ private fun VaultAddItemState.ViewState.Content.toIdentityView(): IdentityView? ) } -private fun VaultAddItemState.ViewState.Content.toLoginView(): LoginView? = - (this as? VaultAddItemState.ViewState.Content.Login)?.let { +private fun VaultAddItemState.ViewState.Content.ItemType.toLoginView( + common: VaultAddItemState.ViewState.Content.Common, +): LoginView? = + (this as? VaultAddItemState.ViewState.Content.ItemType.Login)?.let { LoginView( username = it.username, password = it.password, - passwordRevisionDate = it.originalCipher?.login?.passwordRevisionDate, + passwordRevisionDate = common.originalCipher?.login?.passwordRevisionDate, uris = listOf( // TODO Implement URI list (BIT-1094) LoginUriView( @@ -198,18 +200,18 @@ private fun VaultAddItemState.ViewState.Content.toLoginView(): LoginView? = ), ), // TODO Implement TOTP (BIT-1066) - totp = it.originalCipher?.login?.totp, - autofillOnPageLoad = it.originalCipher?.login?.autofillOnPageLoad, + totp = common.originalCipher?.login?.totp, + autofillOnPageLoad = common.originalCipher?.login?.autofillOnPageLoad, ) } -private fun VaultAddItemState.ViewState.Content.toSecureNotesView(): SecureNoteView? = - (this as? VaultAddItemState.ViewState.Content.SecureNotes)?.let { +private fun VaultAddItemState.ViewState.Content.ItemType.toSecureNotesView(): SecureNoteView? = + (this as? VaultAddItemState.ViewState.Content.ItemType.SecureNotes)?.let { SecureNoteView(type = SecureNoteType.GENERIC) } -private fun VaultAddItemState.ViewState.Content.toCipherRepromptType(): CipherRepromptType = - if (this.masterPasswordReprompt) { +private fun VaultAddItemState.ViewState.Content.Common.toCipherRepromptType(): CipherRepromptType = + if (masterPasswordReprompt) { CipherRepromptType.PASSWORD } else { CipherRepromptType.NONE diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt index a6dad88640..b71a7ea157 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt @@ -31,7 +31,6 @@ import com.x8bit.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType -import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -79,7 +78,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.CloseClick, + VaultAddItemAction.Common.CloseClick, ) } } @@ -92,7 +91,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.SaveClick, + VaultAddItemAction.Common.SaveClick, ) } } @@ -108,7 +107,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.DismissDialog, + VaultAddItemAction.Common.DismissDialog, ) } } @@ -139,7 +138,12 @@ class VaultAddItemScreenTest : BaseComposeTest() { composeTestRule.onNodeWithText(message).assertIsNotDisplayed() mutableStateFlow.update { - it.copy(viewState = VaultAddItemState.ViewState.Content.Login()) + it.copy( + viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common(), + type = VaultAddItemState.ViewState.Content.ItemType.Login(), + ), + ) } composeTestRule.onNodeWithText(message).assertIsNotDisplayed() @@ -162,7 +166,12 @@ class VaultAddItemScreenTest : BaseComposeTest() { composeTestRule.onNode(isProgressBar).assertDoesNotExist() mutableStateFlow.update { - it.copy(viewState = VaultAddItemState.ViewState.Content.Login()) + it.copy( + viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common(), + type = VaultAddItemState.ViewState.Content.ItemType.Login(), + ), + ) } composeTestRule.onNode(isProgressBar).assertDoesNotExist() } @@ -183,7 +192,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.TypeOptionSelect(VaultAddItemState.ItemTypeOption.LOGIN), + VaultAddItemAction.Common.TypeOptionSelect(VaultAddItemState.ItemTypeOption.LOGIN), ) } } @@ -194,41 +203,18 @@ class VaultAddItemScreenTest : BaseComposeTest() { .onNodeWithContentDescriptionAfterScroll(label = "Type, Login") .assertIsDisplayed() - mutableStateFlow.update { it.copy(viewState = VaultAddItemState.ViewState.Content.Card()) } + mutableStateFlow.update { it.copy( + viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common(), + type = VaultAddItemState.ViewState.Content.ItemType.Card, + ), + ) } composeTestRule .onNodeWithContentDescriptionAfterScroll(label = "Type, Card") .assertIsDisplayed() } - @Test - fun `in ItemType_Login state changing Name text field should trigger NameTextChange`() { - composeTestRule - .onNodeWithTextAfterScroll(text = "Name") - .performTextInput(text = "TestName") - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.NameTextChange(name = "TestName"), - ) - } - } - - @Test - fun `in ItemType_Login the name control should display the text provided by the state`() { - composeTestRule - .onNodeWithTextAfterScroll(text = "Name") - .assertTextContains("") - - mutableStateFlow.update { currentState -> - updateLoginType(currentState) { copy(name = "NewName") } - } - - composeTestRule - .onNodeWithTextAfterScroll(text = "Name") - .assertTextContains("NewName") - } - @Test fun `in ItemType_Login state changing Username text field should trigger UsernameTextChange`() { composeTestRule @@ -399,191 +385,9 @@ class VaultAddItemScreenTest : BaseComposeTest() { } } - @Test - fun `in ItemType_Login state clicking a Folder Option should send FolderChange action`() { - // Opens the menu - composeTestRule - .onNodeWithContentDescriptionAfterScroll(label = "Folder, No Folder") - .performClick() - - // Choose the option from the menu - composeTestRule - .onAllNodesWithText(text = "Folder 1") - .onLast() - .performScrollTo() - .performClick() - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.FolderChange("Folder 1".asText()), - ) - } - } - - @Test - fun `in ItemType_Login the folder control should display the text provided by the state`() { - composeTestRule - .onNodeWithContentDescriptionAfterScroll(label = "Folder, No Folder") - .assertIsDisplayed() - - mutableStateFlow.update { currentState -> - updateLoginType(currentState) { copy(folderName = "Folder 2".asText()) } - } - - composeTestRule - .onNodeWithContentDescriptionAfterScroll(label = "Folder, Folder 2") - .assertIsDisplayed() - } - @Suppress("MaxLineLength") @Test - fun `in ItemType_Login state, toggling the favorite toggle should send ToggleFavorite action`() { - composeTestRule - .onNodeWithTextAfterScroll("Favorite") - .performClick() - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.ToggleFavorite( - isFavorite = true, - ), - ) - } - } - - @Test - fun `in ItemType_Login the favorite toggle should be enabled or disabled according to state`() { - composeTestRule - .onNodeWithTextAfterScroll("Favorite") - .assertIsOff() - - mutableStateFlow.update { currentState -> - updateLoginType(currentState) { copy(favorite = true) } - } - - composeTestRule - .onNodeWithTextAfterScroll("Favorite") - .assertIsOn() - } - - @Suppress("MaxLineLength") - @Test - fun `in ItemType_Login state, toggling the Master password re-prompt toggle should send ToggleMasterPasswordReprompt action`() { - composeTestRule - .onNodeWithTextAfterScroll("Master password re-prompt") - .performTouchInput { - click(position = Offset(x = 1f, y = center.y)) - } - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.ToggleMasterPasswordReprompt( - isMasterPasswordReprompt = true, - ), - ) - } - } - - @Suppress("MaxLineLength") - @Test - fun `in ItemType_Login the master password re-prompt toggle should be enabled or disabled according to state`() { - composeTestRule - .onNodeWithTextAfterScroll("Master password re-prompt") - .assertIsOff() - - mutableStateFlow.update { currentState -> - updateLoginType(currentState) { copy(masterPasswordReprompt = true) } - } - - composeTestRule - .onNodeWithTextAfterScroll("Master password re-prompt") - .assertIsOn() - } - - @Suppress("MaxLineLength") - @Test - fun `in ItemType_Login state, toggling the Master password re-prompt tooltip button should send TooltipClick action`() { - composeTestRule - .onNodeWithContentDescriptionAfterScroll(label = "Master password re-prompt help") - .performClick() - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.TooltipClick, - ) - } - } - - @Test - fun `in ItemType_Login state changing Notes text field should trigger NotesTextChange`() { - composeTestRule - .onAllNodesWithTextAfterScroll("Notes") - .filterToOne(hasSetTextAction()) - .performTextInput("TestNotes") - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.NotesTextChange("TestNotes"), - ) - } - } - - @Test - fun `in ItemType_Login the Notes control should display the text provided by the state`() { - composeTestRule - .onAllNodesWithTextAfterScroll("Notes") - .filterToOne(hasSetTextAction()) - .performTextInput("") - - mutableStateFlow.update { currentState -> - updateLoginType(currentState) { copy(notes = "NewNote") } - } - - composeTestRule - .onAllNodesWithTextAfterScroll("Notes") - .filterToOne(hasSetTextAction()) - .assertTextContains("NewNote") - } - - @Suppress("MaxLineLength") - @Test - fun `in ItemType_Login state clicking New Custom Field button should allow creation of Text type`() { - mutableStateFlow.value = DEFAULT_STATE_LOGIN - - composeTestRule - .onNodeWithTextAfterScroll(text = "New custom field") - .performClick() - - composeTestRule - .onAllNodesWithText("Cancel") - .filterToOne(hasAnyAncestor(isDialog())) - .assertIsDisplayed() - - composeTestRule - .onNodeWithText(text = "Text") - .performClick() - - composeTestRule - .onNodeWithText("Name") - .performTextInput("TestText") - - composeTestRule - .onNodeWithText("Ok") - .performClick() - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick( - customFieldType = CustomFieldType.TEXT, - name = "TestText", - ), - ) - } - } - - @Suppress("MaxLineLength") - @Test - fun `in ItemType_Login state clicking New Custom Field button should allow creation of Linked type`() { + fun `clicking New Custom Field button should allow creation of Linked type`() { mutableStateFlow.value = DEFAULT_STATE_LOGIN composeTestRule @@ -609,7 +413,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick( + VaultAddItemAction.Common.AddNewCustomFieldClick( customFieldType = CustomFieldType.LINKED, name = "TestLinked", ), @@ -617,134 +421,8 @@ class VaultAddItemScreenTest : BaseComposeTest() { } } - @Suppress("MaxLineLength") @Test - fun `in ItemType_Login state clicking New Custom Field button should allow creation of Boolean type`() { - mutableStateFlow.value = DEFAULT_STATE_LOGIN - - composeTestRule - .onNodeWithTextAfterScroll(text = "New custom field") - .performClick() - - composeTestRule - .onAllNodesWithText("Cancel") - .filterToOne(hasAnyAncestor(isDialog())) - .assertIsDisplayed() - - composeTestRule - .onNodeWithText(text = "Boolean") - .performClick() - - composeTestRule - .onNodeWithText("Name") - .performTextInput("TestBoolean") - - composeTestRule - .onNodeWithText("Ok") - .performClick() - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick( - customFieldType = CustomFieldType.BOOLEAN, - name = "TestBoolean", - ), - ) - } - } - - @Suppress("MaxLineLength") - @Test - fun `in ItemType_Login state clicking New Custom Field button should allow creation of Hidden type`() { - mutableStateFlow.value = DEFAULT_STATE_LOGIN - - composeTestRule - .onNodeWithTextAfterScroll(text = "New custom field") - .performClick() - - composeTestRule - .onAllNodesWithText("Cancel") - .filterToOne(hasAnyAncestor(isDialog())) - .assertIsDisplayed() - - composeTestRule - .onNodeWithText(text = "Hidden") - .performClick() - - composeTestRule - .onNodeWithText("Name") - .performTextInput("TestHidden") - - composeTestRule - .onNodeWithText("Ok") - .performClick() - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick( - customFieldType = CustomFieldType.HIDDEN, - name = "TestHidden", - ), - ) - } - } - - @Suppress("MaxLineLength") - @Test - fun `in ItemType_Login state clicking and changing the custom text field will send a CustomFieldValueChange event`() { - mutableStateFlow.value = DEFAULT_STATE_LOGIN_CUSTOM_FIELDS - - composeTestRule - .onNodeWithTextAfterScroll("TestText") - .performTextClearance() - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange( - VaultAddItemState.Custom.TextField("Test ID", "TestText", ""), - ), - ) - } - } - - @Suppress("MaxLineLength") - @Test - fun `in ItemType_Login state clicking and changing the custom hidden field will send a CustomFieldValueChange event`() { - mutableStateFlow.value = DEFAULT_STATE_LOGIN_CUSTOM_FIELDS - - composeTestRule - .onNodeWithTextAfterScroll("TestHidden") - .performTextClearance() - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange( - VaultAddItemState.Custom.HiddenField("Test ID", "TestHidden", ""), - ), - ) - } - } - - @Suppress("MaxLineLength") - @Test - fun `in ItemType_Login state clicking and changing the custom boolean field will send a CustomFieldValueChange event`() { - mutableStateFlow.value = DEFAULT_STATE_LOGIN_CUSTOM_FIELDS - - composeTestRule - .onNodeWithTextAfterScroll("TestBoolean") - .performClick() - - verify { - viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange( - VaultAddItemState.Custom.BooleanField("Test ID", "TestBoolean", true), - ), - ) - } - } - - @Test - fun `in ItemType_Login state clicking a Ownership option should send OwnershipChange action`() { + fun `clicking a Ownership option should send OwnershipChange action`() { // Opens the menu composeTestRule .onNodeWithContentDescriptionAfterScroll( @@ -761,13 +439,13 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.LoginType.OwnershipChange("a@b.com"), + VaultAddItemAction.Common.OwnershipChange("a@b.com"), ) } } @Test - fun `in ItemType_Login the Ownership control should display the text provided by the state`() { + fun `the Ownership control should display the text provided by the state`() { composeTestRule .onNodeWithContentDescriptionAfterScroll( label = "Who owns this item?, placeholder@email.com", @@ -775,7 +453,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { .assertIsDisplayed() mutableStateFlow.update { currentState -> - updateLoginType(currentState) { copy(ownership = "Owner 2") } + updateCommonContent(currentState) { copy(ownership = "Owner 2") } } composeTestRule @@ -784,7 +462,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } @Test - fun `in ItemType_SecureNotes state changing Name text field should trigger NameTextChange`() { + fun `changing Name text field should trigger NameTextChange`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -793,13 +471,13 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.NameTextChange(name = "TestName"), + VaultAddItemAction.Common.NameTextChange(name = "TestName"), ) } } @Test - fun `in ItemType_SecureNotes the name control should display the text provided by the state`() { + fun `the name control should display the text provided by the state`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -807,7 +485,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { .assertTextContains("") mutableStateFlow.update { currentState -> - updateSecureNotesType(currentState) { copy(name = "NewName") } + updateCommonContent(currentState) { copy(name = "NewName") } } composeTestRule @@ -816,7 +494,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { } @Test - fun `in ItemType_SecureNotes state clicking a Folder Option should send FolderChange action`() { + fun `clicking a Folder Option should send FolderChange action`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES // Opens the menu @@ -833,14 +511,14 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.FolderChange("Folder 1".asText()), + VaultAddItemAction.Common.FolderChange("Folder 1".asText()), ) } } @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes the folder control should display the text provided by the state`() { + fun `the folder control should display the text provided by the state`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -848,7 +526,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { .assertIsDisplayed() mutableStateFlow.update { currentState -> - updateSecureNotesType(currentState) { copy(folderName = "Folder 2".asText()) } + updateCommonContent(currentState) { copy(folderName = "Folder 2".asText()) } } composeTestRule @@ -858,7 +536,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state, toggling the favorite toggle should send ToggleFavorite action`() { + fun `toggling the favorite toggle should send ToggleFavorite action`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -867,16 +545,15 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite( + VaultAddItemAction.Common.ToggleFavorite( isFavorite = true, ), ) } } - @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes the favorite toggle should be enabled or disabled according to state`() { + fun `the favorite toggle should be enabled or disabled according to state`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -884,7 +561,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { .assertIsOff() mutableStateFlow.update { currentState -> - updateSecureNotesType(currentState) { copy(favorite = true) } + updateCommonContent(currentState) { copy(favorite = true) } } composeTestRule @@ -894,7 +571,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state, toggling the Master password re-prompt toggle should send ToggleMasterPasswordReprompt action`() { + fun `toggling the Master password re-prompt toggle should send ToggleMasterPasswordReprompt action`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -905,7 +582,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt( + VaultAddItemAction.Common.ToggleMasterPasswordReprompt( isMasterPasswordReprompt = true, ), ) @@ -914,7 +591,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes the master password re-prompt toggle should be enabled or disabled according to state`() { + fun `the master password re-prompt toggle should be enabled or disabled according to state`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -922,7 +599,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { .assertIsOff() mutableStateFlow.update { currentState -> - updateSecureNotesType(currentState) { copy(masterPasswordReprompt = true) } + updateCommonContent(currentState) { copy(masterPasswordReprompt = true) } } composeTestRule @@ -930,9 +607,8 @@ class VaultAddItemScreenTest : BaseComposeTest() { .assertIsOn() } - @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state, toggling the Master password re-prompt tooltip button should send TooltipClick action`() { + fun `toggling the Master password re-prompt tooltip button should send TooltipClick action`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -941,13 +617,13 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.TooltipClick, + VaultAddItemAction.Common.TooltipClick, ) } } @Test - fun `in ItemType_SecureNotes state changing Notes text field should trigger NotesTextChange`() { + fun `changing Notes text field should trigger NotesTextChange`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -957,14 +633,13 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange("TestNotes"), + VaultAddItemAction.Common.NotesTextChange("TestNotes"), ) } } - @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes the Notes control should display the text provided by the state`() { + fun `the Notes control should display the text provided by the state`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -973,7 +648,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { .assertTextContains("") mutableStateFlow.update { currentState -> - updateSecureNotesType(currentState) { copy(notes = "NewNote") } + updateCommonContent(currentState) { copy(notes = "NewNote") } } composeTestRule @@ -984,7 +659,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state clicking a Ownership option should send OwnershipChange action`() { + fun `Ownership option should send OwnershipChange action`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES // Opens the menu @@ -1001,7 +676,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange("a@b.com"), + VaultAddItemAction.Common.OwnershipChange("a@b.com"), ) } } @@ -1016,7 +691,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { .assertIsDisplayed() mutableStateFlow.update { currentState -> - updateSecureNotesType(currentState) { copy(ownership = "Owner 2") } + updateCommonContent(currentState) { copy(ownership = "Owner 2") } } composeTestRule @@ -1026,7 +701,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state clicking New Custom Field button should allow creation of Text type`() { + fun `clicking New Custom Field button should allow creation of Text type`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -1052,7 +727,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick( + VaultAddItemAction.Common.AddNewCustomFieldClick( customFieldType = CustomFieldType.TEXT, name = "TestText", ), @@ -1062,7 +737,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state clicking New Custom Field button should not display linked type`() { + fun `clicking New Custom Field button should not display linked type`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -1081,7 +756,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state clicking New Custom Field button should allow creation of Boolean type`() { + fun `clicking New Custom Field button should allow creation of Boolean type`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -1107,7 +782,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick( + VaultAddItemAction.Common.AddNewCustomFieldClick( customFieldType = CustomFieldType.BOOLEAN, name = "TestBoolean", ), @@ -1115,9 +790,8 @@ class VaultAddItemScreenTest : BaseComposeTest() { } } - @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state clicking New Custom Field button should allow creation of Hidden type`() { + fun `clicking New Custom Field button should allow creation of Hidden type`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES composeTestRule @@ -1144,7 +818,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick( + VaultAddItemAction.Common.AddNewCustomFieldClick( customFieldType = CustomFieldType.HIDDEN, name = "TestHidden", ), @@ -1154,7 +828,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state 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 composeTestRule @@ -1163,7 +837,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange( + VaultAddItemAction.Common.CustomFieldValueChange( VaultAddItemState.Custom.TextField("Test ID", "TestText", ""), ), ) @@ -1172,7 +846,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state clicking and changing the custom hidden field will send a CustomFieldValueChange event`() { + fun `clicking and changing the custom hidden field will send a CustomFieldValueChange event`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS composeTestRule @@ -1181,7 +855,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange( + VaultAddItemAction.Common.CustomFieldValueChange( VaultAddItemState.Custom.HiddenField("Test ID", "TestHidden", ""), ), ) @@ -1190,7 +864,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_SecureNotes state clicking and changing the custom boolean field will send a CustomFieldValueChange event`() { + fun `clicking and changing the custom boolean field will send a CustomFieldValueChange event`() { mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS composeTestRule @@ -1199,7 +873,7 @@ class VaultAddItemScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( - VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange( + VaultAddItemAction.Common.CustomFieldValueChange( VaultAddItemState.Custom.BooleanField("Test ID", "TestBoolean", true), ), ) @@ -1211,22 +885,34 @@ class VaultAddItemScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") private fun updateLoginType( currentState: VaultAddItemState, - transform: VaultAddItemState.ViewState.Content.Login.() -> VaultAddItemState.ViewState.Content.Login, + transform: VaultAddItemState.ViewState.Content.ItemType.Login.() -> + VaultAddItemState.ViewState.Content.ItemType.Login, ): VaultAddItemState { val updatedType = when (val viewState = currentState.viewState) { - is VaultAddItemState.ViewState.Content.Login -> viewState.transform() + is VaultAddItemState.ViewState.Content -> { + when (val type = viewState.type) { + is VaultAddItemState.ViewState.Content.ItemType.Login -> { + viewState.copy( + type = type.transform(), + ) + } + else -> viewState + } + } else -> viewState } return currentState.copy(viewState = updatedType) } @Suppress("MaxLineLength") - private fun updateSecureNotesType( + private fun updateCommonContent( currentState: VaultAddItemState, - transform: VaultAddItemState.ViewState.Content.SecureNotes.() -> VaultAddItemState.ViewState.Content.SecureNotes, + transform: VaultAddItemState.ViewState.Content.Common.() + -> VaultAddItemState.ViewState.Content.Common, ): VaultAddItemState { val updatedType = when (val viewState = currentState.viewState) { - is VaultAddItemState.ViewState.Content.SecureNotes -> viewState.transform() + is VaultAddItemState.ViewState.Content -> + viewState.copy(common = viewState.common.transform()) else -> viewState } return currentState.copy(viewState = updatedType) @@ -1235,42 +921,35 @@ class VaultAddItemScreenTest : BaseComposeTest() { //endregion Helper functions companion object { - private val DEFAULT_STATE_LOGIN_CUSTOM_FIELDS = VaultAddItemState( - viewState = VaultAddItemState.ViewState.Content.Login( - customFieldData = listOf( - VaultAddItemState.Custom.BooleanField("Test ID", "TestBoolean", false), - VaultAddItemState.Custom.TextField("Test ID", "TestText", "TestTextVal"), - VaultAddItemState.Custom.HiddenField("Test ID", "TestHidden", "TestHiddenVal"), - VaultAddItemState.Custom.LinkedField( - "LinkedID", - "TestLinked", - VaultLinkedFieldType.USERNAME, - ), - ), - ), - dialog = null, - vaultAddEditType = VaultAddEditType.AddItem, - ) - private val DEFAULT_STATE_LOGIN_DIALOG = VaultAddItemState( - viewState = VaultAddItemState.ViewState.Content.Login(), + viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common(), + type = VaultAddItemState.ViewState.Content.ItemType.Login(), + ), dialog = VaultAddItemState.DialogState.Error("test".asText()), vaultAddEditType = VaultAddEditType.AddItem, ) private val DEFAULT_STATE_LOGIN = VaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, - viewState = VaultAddItemState.ViewState.Content.Login(), + viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common(), + type = VaultAddItemState.ViewState.Content.ItemType.Login(), + ), dialog = null, ) + @Suppress("MaxLineLength") private val DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS = VaultAddItemState( - viewState = VaultAddItemState.ViewState.Content.SecureNotes( - customFieldData = listOf( - VaultAddItemState.Custom.BooleanField("Test ID", "TestBoolean", false), - VaultAddItemState.Custom.TextField("Test ID", "TestText", "TestTextVal"), - VaultAddItemState.Custom.HiddenField("Test ID", "TestHidden", "TestHiddenVal"), + viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common( + customFieldData = listOf( + VaultAddItemState.Custom.BooleanField("Test ID", "TestBoolean", false), + VaultAddItemState.Custom.TextField("Test ID", "TestText", "TestTextVal"), + VaultAddItemState.Custom.HiddenField("Test ID", "TestHidden", "TestHiddenVal"), + ), ), + type = VaultAddItemState.ViewState.Content.ItemType.SecureNotes, ), dialog = null, vaultAddEditType = VaultAddEditType.AddItem, @@ -1278,7 +957,10 @@ class VaultAddItemScreenTest : BaseComposeTest() { private val DEFAULT_STATE_SECURE_NOTES = VaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, - viewState = VaultAddItemState.ViewState.Content.SecureNotes(), + viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common(), + type = VaultAddItemState.ViewState.Content.ItemType.SecureNotes, + ), dialog = null, ) } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModelTest.kt index 0e8bd1fd78..81d9f112b4 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModelTest.kt @@ -34,7 +34,9 @@ import java.util.UUID class VaultAddItemViewModelTest : BaseViewModelTest() { - private val loginInitialState = createVaultAddLoginItemState() + private val loginInitialState = createVaultAddItemState( + typeContentViewState = createLoginTypeContentViewState(), + ) private val loginInitialSavedStateHandle = createSavedStateHandleWithState( state = loginInitialState, vaultAddEditType = VaultAddEditType.AddItem, @@ -66,14 +68,17 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { ), ) viewModel.stateFlow.test { - assertEquals(loginInitialState, awaitItem()) + assertEquals( + loginInitialState, + awaitItem(), + ) } } @Test fun `initial add state should be correct`() = runTest { val vaultAddEditType = VaultAddEditType.AddItem - val initState = createVaultAddLoginItemState(vaultAddEditType = vaultAddEditType) + val initState = createVaultAddItemState(vaultAddEditType = vaultAddEditType) val viewModel = createAddVaultItemViewModel( savedStateHandle = createSavedStateHandleWithState( state = initState, @@ -89,7 +94,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `initial edit state should be correct`() = runTest { val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID) - val initState = createVaultAddLoginItemState(vaultAddEditType = vaultAddEditType) + val initState = createVaultAddItemState(vaultAddEditType = vaultAddEditType) val viewModel = createAddVaultItemViewModel( savedStateHandle = createSavedStateHandleWithState( state = initState, @@ -109,7 +114,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { fun `CloseClick should emit NavigateBack`() = runTest { val viewModel = createAddVaultItemViewModel() viewModel.eventFlow.test { - viewModel.actionChannel.trySend(VaultAddItemAction.CloseClick) + viewModel.actionChannel.trySend(VaultAddItemAction.Common.CloseClick) assertEquals(VaultAddItemEvent.NavigateBack, awaitItem()) } } @@ -117,17 +122,21 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `in add mode, SaveClick should show dialog, and remove it once an item is saved`() = runTest { - val stateWithDialog = createVaultAddLoginItemState( + val stateWithDialog = createVaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, - name = "tester", dialogState = VaultAddItemState.DialogState.Loading( R.string.saving.asText(), ), + commonContentViewState = createCommonContentViewState( + name = "mockName", + ), ) - val stateWithName = createVaultAddLoginItemState( + val stateWithName = createVaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, - name = "tester", + commonContentViewState = createCommonContentViewState( + name = "mockName", + ), ) val viewModel = createAddVaultItemViewModel( @@ -142,7 +151,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { } returns CreateCipherResult.Success viewModel.stateFlow.test { - viewModel.actionChannel.trySend(VaultAddItemAction.SaveClick) + viewModel.actionChannel.trySend(VaultAddItemAction.Common.SaveClick) assertEquals(stateWithName, awaitItem()) assertEquals(stateWithDialog, awaitItem()) assertEquals(stateWithName, awaitItem()) @@ -155,9 +164,11 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `in add mode, SaveClick should update value to loading`() = runTest { - val stateWithName = createVaultAddLoginItemState( + val stateWithName = createVaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, - name = "tester", + commonContentViewState = createCommonContentViewState( + name = "mockName", + ), ) val viewModel = createAddVaultItemViewModel( @@ -171,16 +182,18 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { vaultRepository.createCipher(any()) } returns CreateCipherResult.Success viewModel.eventFlow.test { - viewModel.actionChannel.trySend(VaultAddItemAction.SaveClick) + viewModel.actionChannel.trySend(VaultAddItemAction.Common.SaveClick) assertEquals(VaultAddItemEvent.NavigateBack, awaitItem()) } } @Test fun `in add mode, SaveClick createCipher error should emit ShowToast`() = runTest { - val stateWithName = createVaultAddLoginItemState( + val stateWithName = createVaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, - name = "tester", + commonContentViewState = createCommonContentViewState( + name = "mockName", + ), ) val viewModel = createAddVaultItemViewModel( @@ -194,7 +207,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { vaultRepository.createCipher(any()) } returns CreateCipherResult.Error viewModel.eventFlow.test { - viewModel.actionChannel.trySend(VaultAddItemAction.SaveClick) + viewModel.actionChannel.trySend(VaultAddItemAction.Common.SaveClick) assertEquals(VaultAddItemEvent.ShowToast("Save Item Failure"), awaitItem()) } } @@ -204,17 +217,21 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { runTest { val cipherView = mockk() val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID) - val stateWithDialog = createVaultAddLoginItemState( + val stateWithDialog = createVaultAddItemState( vaultAddEditType = vaultAddEditType, - name = "tester", dialogState = VaultAddItemState.DialogState.Loading( R.string.saving.asText(), ), + commonContentViewState = createCommonContentViewState( + name = "mockName", + ), ) - val stateWithName = createVaultAddLoginItemState( + val stateWithName = createVaultAddItemState( vaultAddEditType = vaultAddEditType, - name = "tester", + commonContentViewState = createCommonContentViewState( + name = "mockName", + ), ) every { cipherView.toViewState() } returns stateWithName.viewState mutableVaultItemFlow.value = DataState.Loaded(cipherView) @@ -232,7 +249,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.stateFlow.test { assertEquals(stateWithName, awaitItem()) - viewModel.actionChannel.trySend(VaultAddItemAction.SaveClick) + viewModel.actionChannel.trySend(VaultAddItemAction.Common.SaveClick) assertEquals(stateWithDialog, awaitItem()) assertEquals(stateWithName, awaitItem()) } @@ -247,9 +264,11 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { fun `in edit mode, SaveClick createCipher error should emit ShowToast`() = runTest { val cipherView = mockk() val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID) - val stateWithName = createVaultAddLoginItemState( + val stateWithName = createVaultAddItemState( vaultAddEditType = vaultAddEditType, - name = "tester", + commonContentViewState = createCommonContentViewState( + name = "mockName", + ), ) every { cipherView.toViewState() } returns stateWithName.viewState @@ -266,7 +285,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { ) viewModel.eventFlow.test { - viewModel.actionChannel.trySend(VaultAddItemAction.SaveClick) + viewModel.actionChannel.trySend(VaultAddItemAction.Common.SaveClick) assertEquals(VaultAddItemEvent.ShowToast("Save Item Failure"), awaitItem()) } @@ -277,18 +296,16 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `Saving item with an empty name field will cause a dialog to show up`() = runTest { - val stateWithNoName = createVaultAddSecureNotesItemState( - name = "", - vaultAddEditType = VaultAddEditType.AddItem, + val stateWithNoName = createVaultAddItemState( + commonContentViewState = createCommonContentViewState(name = ""), ) - val stateWithNoNameAndDialog = createVaultAddSecureNotesItemState( - name = "", + val stateWithNoNameAndDialog = createVaultAddItemState( + commonContentViewState = createCommonContentViewState(name = ""), dialogState = VaultAddItemState.DialogState.Error( R.string.validation_field_required .asText(R.string.name.asText()), ), - vaultAddEditType = VaultAddEditType.AddItem, ) val viewModel = createAddVaultItemViewModel( @@ -299,7 +316,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { ) coEvery { vaultRepository.createCipher(any()) } returns CreateCipherResult.Success viewModel.stateFlow.test { - viewModel.actionChannel.trySend(VaultAddItemAction.SaveClick) + viewModel.actionChannel.trySend(VaultAddItemAction.Common.SaveClick) assertEquals(stateWithNoName, awaitItem()) assertEquals(stateWithNoNameAndDialog, awaitItem()) } @@ -307,7 +324,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `HandleDialogDismiss will remove the current dialog`() = runTest { - val errorState = createVaultAddLoginItemState( + val errorState = createVaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, dialogState = VaultAddItemState.DialogState.Error( R.string.validation_field_required @@ -324,7 +341,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { coEvery { vaultRepository.createCipher(any()) } returns CreateCipherResult.Success viewModel.stateFlow.test { - viewModel.actionChannel.trySend(VaultAddItemAction.DismissDialog) + viewModel.actionChannel.trySend(VaultAddItemAction.Common.DismissDialog) assertEquals(errorState, awaitItem()) assertEquals(null, awaitItem().dialog) } @@ -333,15 +350,23 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `TypeOptionSelect LOGIN should switch to LoginItem`() = runTest { val viewModel = createAddVaultItemViewModel() - val action = VaultAddItemAction.TypeOptionSelect(VaultAddItemState.ItemTypeOption.LOGIN) + val action = VaultAddItemAction.Common.TypeOptionSelect( + VaultAddItemState.ItemTypeOption.LOGIN, + ) viewModel.actionChannel.trySend(action) val expectedState = loginInitialState.copy( - viewState = VaultAddItemState.ViewState.Content.Login(), + viewState = VaultAddItemState.ViewState.Content( + common = createCommonContentViewState(), + type = createLoginTypeContentViewState(), + ), ) - assertEquals(expectedState, viewModel.stateFlow.value) + assertEquals( + expectedState, + viewModel.stateFlow.value, + ) } @Nested @@ -353,35 +378,17 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel = createAddVaultItemViewModel() } - @Test - fun `NameTextChange should update name in LoginItem`() = runTest { - val viewModel = createAddVaultItemViewModel() - val action = VaultAddItemAction.ItemType.LoginType.NameTextChange("newName") - - viewModel.actionChannel.trySend(action) - - val expectedLoginItem = - (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) - .copy(name = "newName") - - val expectedState = loginInitialState.copy(viewState = expectedLoginItem) - - assertEquals(expectedState, viewModel.stateFlow.value) - } - @Suppress("MaxLineLength") @Test fun `UsernameTextChange should update username in LoginItem`() = runTest { val action = VaultAddItemAction.ItemType.LoginType.UsernameTextChange("newUsername") - + val expectedState = createVaultAddItemState( + typeContentViewState = createLoginTypeContentViewState( + username = "newUsername", + ), + ) viewModel.actionChannel.trySend(action) - val expectedLoginItem = - (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) - .copy(username = "newUsername") - - val expectedState = loginInitialState.copy(viewState = expectedLoginItem) - assertEquals(expectedState, viewModel.stateFlow.value) } @@ -392,11 +399,11 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) - val expectedLoginItem = - (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) - .copy(password = "newPassword") - - val expectedState = loginInitialState.copy(viewState = expectedLoginItem) + val expectedState = createVaultAddItemState( + typeContentViewState = createLoginTypeContentViewState( + password = "newPassword", + ), + ) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -407,214 +414,11 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) - val expectedLoginItem = - (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) - .copy(uri = "newUri") - - val expectedState = loginInitialState.copy(viewState = expectedLoginItem) - - assertEquals(expectedState, viewModel.stateFlow.value) - } - - @Test - fun `FolderChange should update folder in LoginItem`() = runTest { - val action = VaultAddItemAction.ItemType.LoginType.FolderChange("newFolder".asText()) - - viewModel.actionChannel.trySend(action) - - val expectedLoginItem = - (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) - .copy(folderName = "newFolder".asText()) - - val expectedState = loginInitialState.copy(viewState = expectedLoginItem) - - assertEquals(expectedState, viewModel.stateFlow.value) - } - - @Test - fun `ToggleFavorite should update favorite in LoginItem`() = runTest { - val action = VaultAddItemAction.ItemType.LoginType.ToggleFavorite(true) - - viewModel.actionChannel.trySend(action) - - val expectedLoginItem = - (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) - .copy(favorite = true) - - val expectedState = loginInitialState.copy(viewState = expectedLoginItem) - - assertEquals(expectedState, viewModel.stateFlow.value) - } - - @Suppress("MaxLineLength") - @Test - fun `ToggleMasterPasswordReprompt should update masterPasswordReprompt in LoginItem`() = - runTest { - val action = VaultAddItemAction.ItemType.LoginType.ToggleMasterPasswordReprompt( - isMasterPasswordReprompt = true, - ) - - viewModel.actionChannel.trySend(action) - - val expectedLoginItem = - (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) - .copy(masterPasswordReprompt = true) - - val expectedState = loginInitialState.copy(viewState = expectedLoginItem) - - assertEquals(expectedState, viewModel.stateFlow.value) - } - - @Test - fun `NotesTextChange should update notes in LoginItem`() = runTest { - val action = VaultAddItemAction.ItemType.LoginType.NotesTextChange(notes = "newNotes") - - viewModel.actionChannel.trySend(action) - - val expectedLoginItem = - (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) - .copy(notes = "newNotes") - - val expectedState = loginInitialState.copy(viewState = expectedLoginItem) - - assertEquals(expectedState, viewModel.stateFlow.value) - } - - @Suppress("MaxLineLength") - @Test - fun `AddNewCustomFieldClick should allow a user to add a custom text field in Login item`() = - runTest { - assertAddNewCustomFieldClick( - initialState = loginInitialState, - type = CustomFieldType.TEXT, - ) - } - - @Suppress("MaxLineLength") - @Test - fun `AddNewCustomFieldClick should allow a user to add a custom boolean field in Login item`() = - runTest { - assertAddNewCustomFieldClick( - initialState = loginInitialState, - type = CustomFieldType.BOOLEAN, - ) - } - - @Suppress("MaxLineLength") - @Test - fun `AddNewCustomFieldClick should allow a user to add a custom hidden field in Login item`() = - runTest { - assertAddNewCustomFieldClick( - initialState = loginInitialState, - type = CustomFieldType.HIDDEN, - ) - } - - @Suppress("MaxLineLength") - @Test - fun `AddNewCustomFieldClick should allow a user to add a custom linked field in Login item`() = - runTest { - assertAddNewCustomFieldClick( - initialState = loginInitialState, - type = CustomFieldType.LINKED, - ) - } - - @Suppress("MaxLineLength") - @Test - fun `CustomFieldValueChange should allow a user to update a text custom field in Login item`() = - runTest { - val initState = createVaultAddLoginItemState( - vaultAddEditType = VaultAddEditType.AddItem, - customFieldData = listOf( - VaultAddItemState.Custom.TextField( - "TestId 1", - "Test Text", - "Test Text", - ), - ), - ) - - assertCustomFieldValueChange( - initState, - CustomFieldType.TEXT, - ) - } - - @Test - fun `CustomFieldValueChange should update hidden custom fields in Login item`() = - runTest { - val initState = createVaultAddLoginItemState( - vaultAddEditType = VaultAddEditType.AddItem, - customFieldData = listOf( - VaultAddItemState.Custom.HiddenField( - "TestId 2", - "Test Text", - "Test Text", - ), - ), - ) - - assertCustomFieldValueChange( - initState, - CustomFieldType.HIDDEN, - ) - } - - @Suppress("MaxLineLength") - @Test - fun `CustomFieldValueChange should update boolean custom fields in Login item`() = - runTest { - val initState = createVaultAddLoginItemState( - vaultAddEditType = VaultAddEditType.AddItem, - customFieldData = listOf( - VaultAddItemState.Custom.BooleanField( - "TestId 3", - "Boolean Field", - true, - ), - ), - ) - - assertCustomFieldValueChange( - initState, - CustomFieldType.BOOLEAN, - ) - } - - @Test - fun `CustomFieldValueChange should update linked custom fields in Login item`() = - runTest { - val initState = createVaultAddLoginItemState( - vaultAddEditType = VaultAddEditType.AddItem, - customFieldData = listOf( - VaultAddItemState.Custom.LinkedField( - "TestId 4", - "Linked Field", - VaultLinkedFieldType.USERNAME, - ), - ), - ) - - assertCustomFieldValueChange( - initState, - CustomFieldType.LINKED, - ) - } - - @Test - fun `OwnershipChange should update ownership in LoginItem`() = runTest { - val viewModel = createAddVaultItemViewModel() - val action = - VaultAddItemAction.ItemType.LoginType.OwnershipChange(ownership = "newOwner") - - viewModel.actionChannel.trySend(action) - - val expectedLoginItem = - (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) - .copy(ownership = "newOwner") - - val expectedState = loginInitialState.copy(viewState = expectedLoginItem) + val expectedState = createVaultAddItemState( + typeContentViewState = createLoginTypeContentViewState( + uri = "newUri", + ), + ) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -704,34 +508,19 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { assertEquals(VaultAddItemEvent.ShowToast("Add New URI"), awaitItem()) } } - - @Test - fun `TooltipClick should emit ShowToast with 'Tooltip' message`() = runTest { - val viewModel = createAddVaultItemViewModel() - - viewModel.eventFlow.test { - viewModel - .actionChannel - .trySend( - VaultAddItemAction.ItemType.LoginType.TooltipClick, - ) - assertEquals(VaultAddItemEvent.ShowToast("Tooltip"), awaitItem()) - } - } } @Nested - inner class VaultAddSecureNotesTypeItemActions { + inner class VaultAddItemCommonActions { private lateinit var viewModel: VaultAddItemViewModel - private lateinit var secureNotesInitialState: VaultAddItemState + private lateinit var vaultAddItemInitialState: VaultAddItemState private lateinit var secureNotesInitialSavedStateHandle: SavedStateHandle @BeforeEach fun setup() { - secureNotesInitialState = - createVaultAddSecureNotesItemState(vaultAddEditType = VaultAddEditType.AddItem) + vaultAddItemInitialState = createVaultAddItemState() secureNotesInitialSavedStateHandle = createSavedStateHandleWithState( - state = secureNotesInitialState, + state = vaultAddItemInitialState, vaultAddEditType = VaultAddEditType.AddItem, ) viewModel = VaultAddItemViewModel( @@ -741,105 +530,118 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { } @Test - fun `NameTextChange should update name in SecureNotesItem`() = runTest { - val action = VaultAddItemAction.ItemType.SecureNotesType.NameTextChange("newName") + fun `NameTextChange should update name`() = runTest { + val action = VaultAddItemAction.Common.NameTextChange("newName") viewModel.actionChannel.trySend(action) - val expectedSecureNotesItem = - (secureNotesInitialState.viewState as - VaultAddItemState.ViewState.Content.SecureNotes) - .copy(name = "newName") - - val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) + val expectedState = vaultAddItemInitialState.copy( + viewState = VaultAddItemState.ViewState.Content( + common = createCommonContentViewState( + name = "newName", + ), + type = createLoginTypeContentViewState(), + ), + ) assertEquals(expectedState, viewModel.stateFlow.value) } @Test - fun `FolderChange should update folder in SecureNotesItem`() = runTest { - val action = VaultAddItemAction.ItemType.SecureNotesType.FolderChange( + fun `FolderChange should update folder`() = runTest { + val action = VaultAddItemAction.Common.FolderChange( "newFolder".asText(), ) viewModel.actionChannel.trySend(action) - val expectedSecureNotesItem = - (secureNotesInitialState.viewState as - VaultAddItemState.ViewState.Content.SecureNotes) - .copy(folderName = "newFolder".asText()) - - val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) + val expectedState = vaultAddItemInitialState.copy( + viewState = VaultAddItemState.ViewState.Content( + common = createCommonContentViewState( + folder = "newFolder".asText(), + ), + type = createLoginTypeContentViewState(), + ), + ) assertEquals(expectedState, viewModel.stateFlow.value) } @Test - fun `ToggleFavorite should update favorite in SecureNotesItem`() = runTest { - val action = VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite(true) + fun `ToggleFavorite should update favorite`() = runTest { + val action = VaultAddItemAction.Common.ToggleFavorite(true) viewModel.actionChannel.trySend(action) - val expectedSecureNotesItem = - (secureNotesInitialState.viewState as - VaultAddItemState.ViewState.Content.SecureNotes) - .copy(favorite = true) - - val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) + val expectedState = vaultAddItemInitialState.copy( + viewState = VaultAddItemState.ViewState.Content( + common = createCommonContentViewState( + favorite = true, + ), + type = createLoginTypeContentViewState(), + ), + ) assertEquals(expectedState, viewModel.stateFlow.value) } @Suppress("MaxLineLength") @Test - fun `ToggleMasterPasswordReprompt should update masterPasswordReprompt in SecureNotesItem`() = + fun `ToggleMasterPasswordReprompt should update masterPasswordReprompt`() = runTest { val action = - VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt( + VaultAddItemAction.Common.ToggleMasterPasswordReprompt( isMasterPasswordReprompt = true, ) viewModel.actionChannel.trySend(action) - val expectedSecureNotesItem = - (secureNotesInitialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) - .copy(masterPasswordReprompt = true) - - val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) + val expectedState = vaultAddItemInitialState.copy( + viewState = VaultAddItemState.ViewState.Content( + common = createCommonContentViewState( + masterPasswordReprompt = true, + ), + type = createLoginTypeContentViewState(), + ), + ) assertEquals(expectedState, viewModel.stateFlow.value) } @Suppress("MaxLineLength") @Test - fun `NotesTextChange should update notes in SecureNotesItem`() = runTest { + fun `NotesTextChange should update notes`() = runTest { val action = - VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange(note = "newNotes") + VaultAddItemAction.Common.NotesTextChange(notes = "newNotes") viewModel.actionChannel.trySend(action) - val expectedSecureNotesItem = - (secureNotesInitialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) - .copy(notes = "newNotes") - - val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) + val expectedState = vaultAddItemInitialState.copy( + viewState = VaultAddItemState.ViewState.Content( + common = createCommonContentViewState( + notes = "newNotes", + ), + type = createLoginTypeContentViewState(), + ), + ) assertEquals(expectedState, viewModel.stateFlow.value) } @Test - fun `OwnershipChange should update ownership in SecureNotesItem`() = runTest { - val action = - VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange(ownership = "newOwner") + fun `OwnershipChange should update ownership`() = runTest { + val action = VaultAddItemAction.Common.OwnershipChange(ownership = "newOwner") viewModel.actionChannel.trySend(action) - val expectedSecureNotesItem = - (secureNotesInitialState.viewState as - VaultAddItemState.ViewState.Content.SecureNotes) - .copy(ownership = "newOwner") - - val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) + val expectedState = vaultAddItemInitialState.copy( + viewState = VaultAddItemState.ViewState.Content( + common = createCommonContentViewState( + ownership = "newOwner", + ), + type = createLoginTypeContentViewState(), + ), + ) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -849,7 +651,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { fun `AddNewCustomFieldClick should allow a user to add a custom boolean field in Secure notes item`() = runTest { assertAddNewCustomFieldClick( - initialState = secureNotesInitialState, + initialState = vaultAddItemInitialState, type = CustomFieldType.BOOLEAN, ) } @@ -859,7 +661,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { fun `AddNewCustomFieldClick should allow a user to add a custom hidden field in Secure notes item`() = runTest { assertAddNewCustomFieldClick( - initialState = secureNotesInitialState, + initialState = vaultAddItemInitialState, type = CustomFieldType.HIDDEN, ) } @@ -870,25 +672,30 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { runTest { assertAddNewCustomFieldClick( - initialState = secureNotesInitialState, + initialState = vaultAddItemInitialState, type = CustomFieldType.TEXT, ) } @Suppress("MaxLineLength") @Test - fun `CustomFieldValueChange should allow a user to update a text custom field in Secure notes item`() = + fun `CustomFieldValueChange should allow a user to update a text custom field`() = runTest { - val initState = createVaultAddSecureNotesItemState( - vaultAddEditType = VaultAddEditType.AddItem, - customFieldData = listOf( - VaultAddItemState.Custom.TextField( - "TestId 1", - "Test Text", - "Test Text", + + val initState = + createVaultAddItemState( + vaultAddEditType = VaultAddEditType.AddItem, + typeContentViewState = VaultAddItemState.ViewState.Content.ItemType.SecureNotes, + commonContentViewState = VaultAddItemState.ViewState.Content.Common( + customFieldData = listOf( + VaultAddItemState.Custom.TextField( + "TestId 1", + "Test Text", + "Test Text", + ), + ), ), - ), - ) + ) assertCustomFieldValueChange( initState, @@ -897,18 +704,22 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { } @Test - fun `CustomFieldValueChange should update hidden custom fields in Secure notes item`() = + @Suppress("MaxLineLength") + fun `CustomFieldValueChange should update hidden custom fields`() = runTest { - val initState = createVaultAddSecureNotesItemState( - vaultAddEditType = VaultAddEditType.AddItem, - customFieldData = listOf( - VaultAddItemState.Custom.HiddenField( - "TestId 2", - "Test Text", - "Test Text", + val initState = + createVaultAddItemState( + typeContentViewState = VaultAddItemState.ViewState.Content.ItemType.SecureNotes, + commonContentViewState = VaultAddItemState.ViewState.Content.Common( + customFieldData = listOf( + VaultAddItemState.Custom.HiddenField( + "TestId 2", + "Test Text", + "Test Text", + ), + ), ), - ), - ) + ) assertCustomFieldValueChange( initState, @@ -918,18 +729,21 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Suppress("MaxLineLength") @Test - fun `CustomFieldValueChange should update boolean custom fields in Secure notes item`() = + fun `CustomFieldValueChange should update boolean custom fields`() = runTest { - val initState = createVaultAddSecureNotesItemState( - vaultAddEditType = VaultAddEditType.AddItem, - customFieldData = listOf( - VaultAddItemState.Custom.BooleanField( - "TestId 3", - "Boolean Field", - true, + val initState = + createVaultAddItemState( + typeContentViewState = VaultAddItemState.ViewState.Content.ItemType.SecureNotes, + commonContentViewState = VaultAddItemState.ViewState.Content.Common( + customFieldData = listOf( + VaultAddItemState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + true, + ), + ), ), - ), - ) + ) assertCustomFieldValueChange( initState, @@ -943,7 +757,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel .actionChannel .trySend( - VaultAddItemAction.ItemType.SecureNotesType.TooltipClick, + VaultAddItemAction.Common.TooltipClick, ) assertEquals(VaultAddItemEvent.ShowToast("Not yet implemented"), awaitItem()) } @@ -952,62 +766,51 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { //region Helper functions - @Suppress("LongParameterList") - private fun createVaultAddLoginItemState( + @Suppress("MaxLineLength") + private fun createVaultAddItemState( vaultAddEditType: VaultAddEditType = VaultAddEditType.AddItem, + commonContentViewState: VaultAddItemState.ViewState.Content.Common = createCommonContentViewState(), + typeContentViewState: VaultAddItemState.ViewState.Content.ItemType = createLoginTypeContentViewState(), + dialogState: VaultAddItemState.DialogState? = null, + ): VaultAddItemState = + VaultAddItemState( + vaultAddEditType = vaultAddEditType, + viewState = VaultAddItemState.ViewState.Content( + common = commonContentViewState, + type = typeContentViewState, + ), + dialog = dialogState, + ) + + @Suppress("LongParameterList") + private fun createCommonContentViewState( name: String = "", - username: String = "", - password: String = "", - uri: String = "", folder: Text = R.string.folder_none.asText(), favorite: Boolean = false, masterPasswordReprompt: Boolean = false, notes: String = "", customFieldData: List = listOf(), ownership: String = "placeholder@email.com", - dialogState: VaultAddItemState.DialogState? = null, - ): VaultAddItemState = - VaultAddItemState( - vaultAddEditType = vaultAddEditType, - viewState = VaultAddItemState.ViewState.Content.Login( - name = name, - username = username, - password = password, - uri = uri, - folderName = folder, - favorite = favorite, - customFieldData = customFieldData, - masterPasswordReprompt = masterPasswordReprompt, - notes = notes, - ownership = ownership, - ), - dialog = dialogState, + ): VaultAddItemState.ViewState.Content.Common = + VaultAddItemState.ViewState.Content.Common( + name = name, + folderName = folder, + favorite = favorite, + customFieldData = customFieldData, + masterPasswordReprompt = masterPasswordReprompt, + notes = notes, + ownership = ownership, ) - @Suppress("LongParameterList") - private fun createVaultAddSecureNotesItemState( - vaultAddEditType: VaultAddEditType.AddItem, - name: String = "", - folder: Text = "No Folder".asText(), - favorite: Boolean = false, - masterPasswordReprompt: Boolean = false, - notes: String = "", - customFieldData: List = listOf(), - ownership: String = "placeholder@email.com", - dialogState: VaultAddItemState.DialogState? = null, - ): VaultAddItemState = - VaultAddItemState( - vaultAddEditType = vaultAddEditType, - viewState = VaultAddItemState.ViewState.Content.SecureNotes( - name = name, - folderName = folder, - favorite = favorite, - masterPasswordReprompt = masterPasswordReprompt, - notes = notes, - customFieldData = customFieldData, - ownership = ownership, - ), - dialog = dialogState, + private fun createLoginTypeContentViewState( + username: String = "", + password: String = "", + uri: String = "", + ): VaultAddItemState.ViewState.Content.ItemType.Login = + VaultAddItemState.ViewState.Content.ItemType.Login( + username = username, + password = password, + uri = uri, ) private fun createSavedStateHandleWithState( @@ -1042,7 +845,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { type: CustomFieldType, ) { lateinit var expectedCustomField: VaultAddItemState.Custom - lateinit var action: VaultAddItemAction.ItemType + lateinit var action: VaultAddItemAction.Common lateinit var expectedState: VaultAddItemState.ViewState.Content when (type) { @@ -1086,27 +889,15 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { ), ) - when (val state = - viewModel.stateFlow.value.viewState as VaultAddItemState.ViewState.Content) { - is VaultAddItemState.ViewState.Content.Login -> { - action = VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange( - expectedCustomField, - ) - expectedState = state.copy(customFieldData = listOf(expectedCustomField)) - } - - is VaultAddItemState.ViewState.Content.SecureNotes -> { - action = - VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange( - expectedCustomField, - ) - expectedState = state.copy(customFieldData = listOf(expectedCustomField)) - } - // TODO: Create UI for card-type item creation (BIT-507) - is VaultAddItemState.ViewState.Content.Card -> Unit - // TODO: Create UI for identity-type item creation (BIT-667) - is VaultAddItemState.ViewState.Content.Identity -> Unit - } + val currentContentState = + (viewModel.stateFlow.value.viewState as VaultAddItemState.ViewState.Content) + action = VaultAddItemAction.Common.CustomFieldValueChange(expectedCustomField) + expectedState = currentContentState + .copy( + common = currentContentState.common.copy( + customFieldData = listOf(expectedCustomField), + ), + ) viewModel.actionChannel.trySend(action) @@ -1129,7 +920,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { var name = "" lateinit var expectedCustomField: VaultAddItemState.Custom - lateinit var action: VaultAddItemAction.ItemType + lateinit var action: VaultAddItemAction.Common lateinit var expectedState: VaultAddItemState.ViewState.Content when (type) { @@ -1170,27 +961,15 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { } } - when ( - val state = - viewModel.stateFlow.value.viewState as VaultAddItemState.ViewState.Content) { - is VaultAddItemState.ViewState.Content.Login -> { - action = VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick(type, name) - expectedState = state.copy(customFieldData = listOf(expectedCustomField)) - } - - is VaultAddItemState.ViewState.Content.SecureNotes -> { - action = - VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick( - customFieldType = type, - name = name, - ) - expectedState = state.copy(customFieldData = listOf(expectedCustomField)) - } - // TODO: Create UI for card-type item creation (BIT-507) - is VaultAddItemState.ViewState.Content.Card -> Unit - // TODO: Create UI for identity-type item creation (BIT-667) - is VaultAddItemState.ViewState.Content.Identity -> Unit - } + val currentContentState = + (viewModel.stateFlow.value.viewState as VaultAddItemState.ViewState.Content) + action = VaultAddItemAction.Common.AddNewCustomFieldClick(type, name) + expectedState = currentContentState + .copy( + common = currentContentState.common.copy( + customFieldData = listOf(expectedCustomField), + ), + ) viewModel.actionChannel.trySend(action) assertEquals(expectedState, viewModel.stateFlow.value.viewState) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensionsTest.kt index 3f6f66cd34..62bbb42558 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensionsTest.kt @@ -46,7 +46,30 @@ class CipherViewExtensionsTest { val result = cipherView.toViewState() assertEquals( - VaultAddItemState.ViewState.Error(message = "Not yet implemented.".asText()), + VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common( + originalCipher = cipherView, + name = "cipher", + folderName = R.string.folder_none.asText(), + favorite = false, + masterPasswordReprompt = true, + notes = "Lots of notes", + ownership = "", + customFieldData = listOf( + VaultAddItemState.Custom.BooleanField(TEST_ID, "TestBoolean", false), + VaultAddItemState.Custom.TextField(TEST_ID, "TestText", "TestText"), + VaultAddItemState.Custom.HiddenField(TEST_ID, "TestHidden", "TestHidden"), + VaultAddItemState.Custom.LinkedField( + TEST_ID, + "TestLinked", + VaultLinkedFieldType.USERNAME, + ), + ), + availableFolders = emptyList(), + availableOwners = emptyList(), + ), + type = VaultAddItemState.ViewState.Content.ItemType.Card, + ), result, ) } @@ -58,7 +81,30 @@ class CipherViewExtensionsTest { val result = cipherView.toViewState() assertEquals( - VaultAddItemState.ViewState.Error(message = "Not yet implemented.".asText()), + VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common( + originalCipher = cipherView, + name = "cipher", + folderName = R.string.folder_none.asText(), + favorite = false, + masterPasswordReprompt = true, + notes = "Lots of notes", + ownership = "", + customFieldData = listOf( + VaultAddItemState.Custom.BooleanField(TEST_ID, "TestBoolean", false), + VaultAddItemState.Custom.TextField(TEST_ID, "TestText", "TestText"), + VaultAddItemState.Custom.HiddenField(TEST_ID, "TestHidden", "TestHidden"), + VaultAddItemState.Custom.LinkedField( + TEST_ID, + "TestLinked", + VaultLinkedFieldType.USERNAME, + ), + ), + availableFolders = emptyList(), + availableOwners = emptyList(), + ), + type = VaultAddItemState.ViewState.Content.ItemType.Identity, + ), result, ) } @@ -70,29 +116,33 @@ class CipherViewExtensionsTest { val result = cipherView.toViewState() assertEquals( - VaultAddItemState.ViewState.Content.Login( - originalCipher = cipherView, - name = "cipher", - username = "username", - password = "password", - uri = "www.example.com", - folderName = R.string.folder_none.asText(), - favorite = false, - masterPasswordReprompt = true, - notes = "Lots of notes", - ownership = "", - availableFolders = emptyList(), - availableOwners = emptyList(), - customFieldData = listOf( - VaultAddItemState.Custom.BooleanField(TEST_ID, "TestBoolean", false), - VaultAddItemState.Custom.TextField(TEST_ID, "TestText", "TestText"), - VaultAddItemState.Custom.HiddenField(TEST_ID, "TestHidden", "TestHidden"), - VaultAddItemState.Custom.LinkedField( - TEST_ID, - "TestLinked", - VaultLinkedFieldType.USERNAME, + VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common( + originalCipher = cipherView, + name = "cipher", + folderName = R.string.folder_none.asText(), + favorite = false, + masterPasswordReprompt = true, + notes = "Lots of notes", + ownership = "", + availableFolders = emptyList(), + availableOwners = emptyList(), + customFieldData = listOf( + VaultAddItemState.Custom.BooleanField(TEST_ID, "TestBoolean", false), + VaultAddItemState.Custom.TextField(TEST_ID, "TestText", "TestText"), + VaultAddItemState.Custom.HiddenField(TEST_ID, "TestHidden", "TestHidden"), + VaultAddItemState.Custom.LinkedField( + TEST_ID, + "TestLinked", + VaultLinkedFieldType.USERNAME, + ), ), ), + type = VaultAddItemState.ViewState.Content.ItemType.Login( + username = "username", + password = "password", + uri = "www.example.com", + ), ), result, ) @@ -105,21 +155,24 @@ class CipherViewExtensionsTest { val result = cipherView.toViewState() assertEquals( - VaultAddItemState.ViewState.Content.SecureNotes( - originalCipher = cipherView, - name = "cipher", - folderName = R.string.folder_none.asText(), - favorite = false, - masterPasswordReprompt = true, - notes = "Lots of notes", - ownership = "", - customFieldData = listOf( - VaultAddItemState.Custom.BooleanField(TEST_ID, "TestBoolean", false), - VaultAddItemState.Custom.TextField(TEST_ID, "TestText", "TestText"), - VaultAddItemState.Custom.HiddenField(TEST_ID, "TestHidden", "TestHidden"), + VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common( + originalCipher = cipherView, + name = "cipher", + folderName = R.string.folder_none.asText(), + favorite = false, + masterPasswordReprompt = true, + notes = "Lots of notes", + ownership = "", + customFieldData = listOf( + VaultAddItemState.Custom.BooleanField(TEST_ID, "TestBoolean", false), + VaultAddItemState.Custom.TextField(TEST_ID, "TestText", "TestText"), + VaultAddItemState.Custom.HiddenField(TEST_ID, "TestHidden", "TestHidden"), + ), + availableFolders = emptyList(), + availableOwners = emptyList(), ), - availableFolders = emptyList(), - availableOwners = emptyList(), + type = VaultAddItemState.ViewState.Content.ItemType.SecureNotes, ), result, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt index 47b793e476..29210c92ea 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt @@ -131,16 +131,20 @@ class VaultDataExtensionsTest { fun `toCipherView should transform Login ItemType to CipherView`() { mockkStatic(Instant::class) every { Instant.now() } returns Instant.MIN - val loginItemType = VaultAddItemState.ViewState.Content.Login( - name = "mockName-1", - username = "mockUsername-1", - password = "mockPassword-1", - uri = "mockUri-1", - folderName = "mockFolder-1".asText(), - favorite = false, - masterPasswordReprompt = false, - notes = "mockNotes-1", - ownership = "mockOwnership-1", + val loginItemType = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common( + name = "mockName-1", + folderName = "mockFolder-1".asText(), + favorite = false, + masterPasswordReprompt = false, + notes = "mockNotes-1", + ownership = "mockOwnership-1", + ), + type = VaultAddItemState.ViewState.Content.ItemType.Login( + username = "mockUsername-1", + password = "mockPassword-1", + uri = "mockUri-1", + ), ) val result = loginItemType.toCipherView() @@ -191,30 +195,34 @@ class VaultDataExtensionsTest { @Test fun `toCipherView should transform Login ItemType to CipherView with original cipher`() { val cipherView = DEFAULT_LOGIN_CIPHER_VIEW - val loginItemType = VaultAddItemState.ViewState.Content.Login( - originalCipher = cipherView, - name = "mockName-1", - username = "mockUsername-1", - password = "mockPassword-1", - uri = "mockUri-1", - folderName = "mockFolder-1".asText(), - favorite = true, - masterPasswordReprompt = false, - customFieldData = listOf( - VaultAddItemState.Custom.BooleanField("testId", "TestBoolean", false), - VaultAddItemState.Custom.TextField("testId", "TestText", "TestText"), - VaultAddItemState.Custom.HiddenField("testId", "TestHidden", "TestHidden"), - VaultAddItemState.Custom.LinkedField( - "testId", - "TestLinked", - VaultLinkedFieldType.USERNAME, + val viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common( + originalCipher = cipherView, + name = "mockName-1", + folderName = "mockFolder-1".asText(), + favorite = true, + masterPasswordReprompt = false, + customFieldData = listOf( + VaultAddItemState.Custom.BooleanField("testId", "TestBoolean", false), + VaultAddItemState.Custom.TextField("testId", "TestText", "TestText"), + VaultAddItemState.Custom.HiddenField("testId", "TestHidden", "TestHidden"), + VaultAddItemState.Custom.LinkedField( + "testId", + "TestLinked", + VaultLinkedFieldType.USERNAME, + ), ), + notes = "mockNotes-1", + ownership = "mockOwnership-1", + ), + type = VaultAddItemState.ViewState.Content.ItemType.Login( + username = "mockUsername-1", + password = "mockPassword-1", + uri = "mockUri-1", ), - notes = "mockNotes-1", - ownership = "mockOwnership-1", ) - val result = loginItemType.toCipherView() + val result = viewState.toCipherView() assertEquals( @Suppress("MaxLineLength") @@ -278,21 +286,24 @@ class VaultDataExtensionsTest { fun `toCipherView should transform SecureNotes ItemType to CipherView`() { mockkStatic(Instant::class) every { Instant.now() } returns Instant.MIN - val secureNotesItemType = VaultAddItemState.ViewState.Content.SecureNotes( - name = "mockName-1", - folderName = "mockFolder-1".asText(), - favorite = false, - masterPasswordReprompt = false, - notes = "mockNotes-1", - ownership = "mockOwnership-1", - customFieldData = listOf( - VaultAddItemState.Custom.BooleanField("testId", "TestBoolean", false), - VaultAddItemState.Custom.TextField("testId", "TestText", "TestText"), - VaultAddItemState.Custom.HiddenField("testId", "TestHidden", "TestHidden"), + val viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common( + name = "mockName-1", + folderName = "mockFolder-1".asText(), + favorite = false, + masterPasswordReprompt = false, + notes = "mockNotes-1", + ownership = "mockOwnership-1", + customFieldData = listOf( + VaultAddItemState.Custom.BooleanField("testId", "TestBoolean", false), + VaultAddItemState.Custom.TextField("testId", "TestText", "TestText"), + VaultAddItemState.Custom.HiddenField("testId", "TestHidden", "TestHidden"), + ), ), + type = VaultAddItemState.ViewState.Content.ItemType.SecureNotes, ) - val result = secureNotesItemType.toCipherView() + val result = viewState.toCipherView() assertEquals( CipherView( @@ -347,18 +358,21 @@ class VaultDataExtensionsTest { @Test fun `toCipherView should transform SecureNotes ItemType to CipherView with original cipher`() { val cipherView = DEFAULT_SECURE_NOTES_CIPHER_VIEW - val secureNotesItemType = VaultAddItemState.ViewState.Content.SecureNotes( - originalCipher = cipherView, - name = "mockName-1", - folderName = "mockFolder-1".asText(), - favorite = false, - masterPasswordReprompt = true, - notes = "mockNotes-1", - ownership = "mockOwnership-1", - customFieldData = emptyList(), + val viewState = VaultAddItemState.ViewState.Content( + common = VaultAddItemState.ViewState.Content.Common( + originalCipher = cipherView, + name = "mockName-1", + folderName = "mockFolder-1".asText(), + favorite = false, + masterPasswordReprompt = true, + notes = "mockNotes-1", + ownership = "mockOwnership-1", + customFieldData = emptyList(), + ), + type = VaultAddItemState.ViewState.Content.ItemType.SecureNotes, ) - val result = secureNotesItemType.toCipherView() + val result = viewState.toCipherView() assertEquals( cipherView.copy(