From 4f08d5ddbe8476c492487cd4ea70524bd2ad49e5 Mon Sep 17 00:00:00 2001 From: Brian Yencho Date: Wed, 31 Jan 2024 16:28:39 -0600 Subject: [PATCH] BIT-1661: Pre-populate Add Item screen during autofill save (#913) --- .../util/SpecialCircumstanceExtensions.kt | 11 ++ .../feature/addedit/VaultAddEditScreen.kt | 18 ++- .../feature/addedit/VaultAddEditViewModel.kt | 48 ++++-- .../util/AutofillSaveItemExtensions.kt | 51 +++++++ .../util/SpecialCircumstanceExtensionsTest.kt | 35 ++++- .../feature/addedit/VaultAddEditScreenTest.kt | 23 ++- .../addedit/VaultAddEditViewModelTest.kt | 137 ++++++++++++++++-- .../util/AutofillSaveItemExtensionsTest.kt | 76 ++++++++++ 8 files changed, 363 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSaveItemExtensions.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSaveItemExtensionsTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensions.kt index 65e3db91bd..4ec48589ba 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensions.kt @@ -1,8 +1,19 @@ package com.x8bit.bitwarden.data.platform.manager.util +import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance +/** + * Returns [AutofillSaveItem] when contained in the given [SpecialCircumstance]. + */ +fun SpecialCircumstance.toAutofillSaveItemOrNull(): AutofillSaveItem? = + when (this) { + is SpecialCircumstance.AutofillSave -> this.autofillSaveItem + is SpecialCircumstance.AutofillSelection -> null + is SpecialCircumstance.ShareNewSend -> null + } + /** * Returns [AutofillSelectionData] when contained in the given [SpecialCircumstance]. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt index 762e6c4240..c92b4f43c1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreen.kt @@ -34,9 +34,12 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState +import com.x8bit.bitwarden.ui.platform.components.NavigationIcon import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData +import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager +import com.x8bit.bitwarden.ui.platform.theme.LocalExitManager import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager import com.x8bit.bitwarden.ui.platform.theme.LocalPermissionsManager import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull @@ -58,6 +61,7 @@ fun VaultAddEditScreen( viewModel: VaultAddEditViewModel = hiltViewModel(), permissionsManager: PermissionsManager = LocalPermissionsManager.current, intentManager: IntentManager = LocalIntentManager.current, + exitManager: ExitManager = LocalExitManager.current, onNavigateToManualCodeEntryScreen: () -> Unit, onNavigateToGeneratorModal: (GeneratorMode.Modal) -> Unit, onNavigateToAttachments: (cipherId: String) -> Unit, @@ -94,6 +98,7 @@ fun VaultAddEditScreen( onNavigateToMoveToOrganization(event.cipherId, true) } + VaultAddEditEvent.ExitApp -> exitManager.exitApplication() VaultAddEditEvent.NavigateBack -> onNavigateBack.invoke() is VaultAddEditEvent.NavigateToTooltipUri -> { @@ -156,11 +161,14 @@ fun VaultAddEditScreen( topBar = { BitwardenTopAppBar( title = state.screenDisplayName(), - navigationIcon = painterResource(id = R.drawable.ic_close), - navigationIconContentDescription = stringResource(id = R.string.close), - onNavigationIconClick = remember(viewModel) { - { viewModel.trySendAction(VaultAddEditAction.Common.CloseClick) } - }, + navigationIcon = NavigationIcon( + navigationIcon = painterResource(id = R.drawable.ic_close), + navigationIconContentDescription = stringResource(id = R.string.close), + onNavigationIconClick = remember(viewModel) { + { viewModel.trySendAction(VaultAddEditAction.Common.CloseClick) } + }, + ) + .takeIf { state.shouldShowCloseButton }, scrollBehavior = scrollBehavior, actions = { BitwardenTextButton( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index e6796a1b60..b47f97059a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -8,10 +8,10 @@ import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult import com.x8bit.bitwarden.data.auth.repository.model.UserState -import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager -import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance +import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSaveItemOrNull +import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull import com.x8bit.bitwarden.data.platform.repository.model.DataState import com.x8bit.bitwarden.data.platform.repository.util.takeUntilLoaded import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository @@ -84,16 +84,16 @@ class VaultAddEditViewModel @Inject constructor( val vaultAddEditType = VaultAddEditArgs(savedStateHandle).vaultAddEditType // Check for autofill data to pre-populate - val autofillSelectionData: AutofillSelectionData? = - when (val specialCircumstance = specialCircumstanceManager.specialCircumstance) { - is SpecialCircumstance.AutofillSelection -> { - specialCircumstance.autofillSelectionData - } - - else -> null - } + val autofillSaveItem = specialCircumstanceManager + .specialCircumstance + ?.toAutofillSaveItemOrNull() + val autofillSelectionData = specialCircumstanceManager + .specialCircumstance + ?.toAutofillSelectionDataOrNull() val defaultAddTypeContent = autofillSelectionData ?.toDefaultAddTypeContent() + ?: autofillSaveItem + ?.toDefaultAddTypeContent() ?: VaultAddEditState.ViewState.Content( common = VaultAddEditState.ViewState.Content.Common(), type = VaultAddEditState.ViewState.Content.ItemType.Login(), @@ -107,6 +107,9 @@ class VaultAddEditViewModel @Inject constructor( is VaultAddEditType.CloneItem -> VaultAddEditState.ViewState.Loading }, dialog = null, + // Set special conditions for autofill save + shouldShowCloseButton = autofillSaveItem == null, + shouldExitOnSave = autofillSaveItem != null, ) }, ) { @@ -985,9 +988,16 @@ class VaultAddEditViewModel @Inject constructor( } is CreateCipherResult.Success -> { - sendEvent( - event = VaultAddEditEvent.NavigateBack, - ) + if (state.shouldExitOnSave) { + specialCircumstanceManager.specialCircumstance = null + sendEvent( + event = VaultAddEditEvent.ExitApp, + ) + } else { + sendEvent( + event = VaultAddEditEvent.NavigateBack, + ) + } } } } @@ -1064,6 +1074,10 @@ class VaultAddEditViewModel @Inject constructor( } DataState.Loading -> { + // Skip loading states for add modes, since this will blow away any initial content + // or user-selected content. + if (state.isAddItemMode) return + mutableStateFlow.update { it.copy(viewState = VaultAddEditState.ViewState.Loading) } @@ -1354,6 +1368,9 @@ data class VaultAddEditState( val vaultAddEditType: VaultAddEditType, val viewState: ViewState, val dialog: DialogState?, + val shouldShowCloseButton: Boolean = true, + // Internal + val shouldExitOnSave: Boolean = false, ) : Parcelable { /** @@ -1704,6 +1721,11 @@ sealed class VaultAddEditEvent { */ data class ShowToast(val message: Text) : VaultAddEditEvent() + /** + * Leave the application. + */ + data object ExitApp : VaultAddEditEvent() + /** * Navigate back to previous screen. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSaveItemExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSaveItemExtensions.kt new file mode 100644 index 0000000000..1815f6018e --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSaveItemExtensions.kt @@ -0,0 +1,51 @@ +package com.x8bit.bitwarden.ui.vault.feature.addedit.util + +import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem +import com.x8bit.bitwarden.ui.platform.base.util.toHostOrPathOrNull +import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState +import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem +import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth +import java.util.UUID + +/** + * Returns pre-filled content that may be used for an "add" type + * [VaultAddEditState.ViewState.Content]. + */ +fun AutofillSaveItem.toDefaultAddTypeContent(): VaultAddEditState.ViewState.Content = + when (this) { + is AutofillSaveItem.Card -> { + VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common(), + type = VaultAddEditState.ViewState.Content.ItemType.Card( + number = this.number.orEmpty(), + expirationMonth = VaultCardExpirationMonth + .entries + .find { it.number == this.expirationMonth } + ?: VaultCardExpirationMonth.SELECT, + expirationYear = this.expirationYear.orEmpty(), + securityCode = this.securityCode.orEmpty(), + ), + ) + } + + is AutofillSaveItem.Login -> { + val uri = this.uri + val simpleUri = uri?.toHostOrPathOrNull() + VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common( + name = simpleUri.orEmpty(), + ), + type = VaultAddEditState.ViewState.Content.ItemType.Login( + username = this.username.orEmpty(), + password = this.password.orEmpty(), + uriList = listOf( + UriItem( + id = UUID.randomUUID().toString(), + uri = uri, + match = null, + ), + ), + ), + ) + } + } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt index 18d233a6fd..31128dc12c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/manager/util/SpecialCircumstanceExtensionsTest.kt @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.data.platform.manager.util +import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance import io.mockk.mockk @@ -10,7 +11,37 @@ import org.junit.jupiter.api.Test class SpecialCircumstanceExtensionsTest { @Test - fun `toAutofillSelectionDataOrNull should a non-null value for AutofillSelection`() { + fun `toAutofillSaveItemOrNull should return a non-null value for AutofillSave`() { + val autofillSaveItem: AutofillSaveItem = mockk() + assertEquals( + autofillSaveItem, + SpecialCircumstance + .AutofillSave( + autofillSaveItem = autofillSaveItem, + ) + .toAutofillSaveItemOrNull(), + ) + } + + @Test + fun `toAutofillSaveItemOrNull should return a null value for other types`() { + listOf( + SpecialCircumstance.AutofillSelection( + autofillSelectionData = mockk(), + shouldFinishWhenComplete = true, + ), + SpecialCircumstance.ShareNewSend( + data = mockk(), + shouldFinishWhenComplete = true, + ), + ) + .forEach { specialCircumstance -> + assertNull(specialCircumstance.toAutofillSaveItemOrNull()) + } + } + + @Test + fun `toAutofillSelectionDataOrNull should return a non-null value for AutofillSelection`() { val autofillSelectionData = AutofillSelectionData( type = AutofillSelectionData.Type.LOGIN, uri = "uri", @@ -27,7 +58,7 @@ class SpecialCircumstanceExtensionsTest { } @Test - fun `toAutofillSelectionDataOrNull should a null value for other types`() { + fun `toAutofillSelectionDataOrNull should return a null value for other types`() { listOf( SpecialCircumstance.AutofillSave( autofillSaveItem = mockk(), diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index 812f05f28c..ed885af7b4 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -35,6 +35,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode @@ -82,7 +83,9 @@ class VaultAddEditScreenTest : BaseComposeTest() { every { eventFlow } returns mutableEventFlow every { stateFlow } returns mutableStateFlow } - + private val exitManager: ExitManager = mockk { + every { exitApplication() } just runs + } private val intentManager: IntentManager = mockk { every { launchUri(any()) } just runs } @@ -101,11 +104,18 @@ class VaultAddEditScreenTest : BaseComposeTest() { onNavigateToMoveToOrganization = { id, _ -> onNavigateToMoveToOrganizationId = id }, viewModel = viewModel, permissionsManager = fakePermissionManager, + exitManager = exitManager, intentManager = intentManager, ) } } + @Test + fun `on ExitApp event should call the exitApplication of ExitManager`() { + mutableEventFlow.tryEmit(VaultAddEditEvent.ExitApp) + verify { exitManager.exitApplication() } + } + @Test fun `on NavigateBack event should invoke onNavigateBack`() { mutableEventFlow.tryEmit(VaultAddEditEvent.NavigateBack) @@ -173,6 +183,17 @@ class VaultAddEditScreenTest : BaseComposeTest() { assertEquals(GeneratorMode.Modal.Username, onNavigateToGeneratorModalType) } + @Test + fun `close button should update according to state`() { + composeTestRule.onNodeWithContentDescription("Close").assertIsDisplayed() + + mutableStateFlow.update { + it.copy(shouldShowCloseButton = false) + } + + composeTestRule.onNodeWithContentDescription("Close").assertDoesNotExist() + } + @Test fun `clicking close button should send CloseClick action`() { composeTestRule diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index 2391a5bde8..972145601d 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test +import app.cash.turbine.turbineScope import com.bitwarden.core.CipherView import com.bitwarden.core.CollectionView import com.bitwarden.core.FolderView @@ -13,6 +14,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult import com.x8bit.bitwarden.data.auth.repository.model.Organization import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType +import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl @@ -59,6 +61,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -119,7 +122,10 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ) viewModel.stateFlow.test { assertEquals( - loginInitialState.copy(viewState = VaultAddEditState.ViewState.Loading), + createVaultAddItemState( + commonContentViewState = VaultAddEditState.ViewState.Content.Common(), + typeContentViewState = createLoginTypeContentViewState(), + ), awaitItem(), ) } @@ -136,7 +142,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ), ) assertEquals( - initState.copy(viewState = VaultAddEditState.ViewState.Loading), + initState, viewModel.stateFlow.value, ) verify(exactly = 1) { @@ -145,7 +151,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { } @Test - fun `initial add state should be correct when autofill`() = runTest { + fun `initial add state should be correct when autofill selection`() = runTest { val autofillSelectionData = AutofillSelectionData( type = AutofillSelectionData.Type.LOGIN, uri = "https://www.test.com", @@ -168,7 +174,39 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ), ) assertEquals( - initState.copy(viewState = VaultAddEditState.ViewState.Loading), + initState, + viewModel.stateFlow.value, + ) + verify(exactly = 1) { + vaultRepository.vaultDataStateFlow + } + } + + @Test + fun `initial add state should be correct when autofill save`() = runTest { + val autofillSaveItem = AutofillSaveItem.Login( + username = "username", + password = "password", + uri = "https://www.test.com", + ) + specialCircumstanceManager.specialCircumstance = SpecialCircumstance.AutofillSave( + autofillSaveItem = autofillSaveItem, + ) + val autofillContentState = autofillSaveItem.toDefaultAddTypeContent() + val vaultAddEditType = VaultAddEditType.AddItem + val initState = createVaultAddItemState( + vaultAddEditType = vaultAddEditType, + commonContentViewState = autofillContentState.common, + typeContentViewState = autofillContentState.type, + ) + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initState, + vaultAddEditType = vaultAddEditType, + ), + ) + assertEquals( + initState, viewModel.stateFlow.value, ) verify(exactly = 1) { @@ -375,8 +413,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") @Test - fun `in add mode, SaveClick should show dialog, and remove it once an item is saved`() = + fun `in add mode, SaveClick should show dialog, remove it once an item is saved, and emit NavigateBack`() = runTest { val stateWithDialog = createVaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, @@ -387,36 +426,104 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { name = "mockName-1", ), ) - val stateWithName = createVaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, commonContentViewState = createCommonContentViewState( name = "mockName-1", ), ) - mutableVaultDataFlow.value = DataState.Loaded( createVaultData(), ) - val viewModel = createAddVaultItemViewModel( createSavedStateHandleWithState( state = stateWithName, vaultAddEditType = VaultAddEditType.AddItem, ), ) - coEvery { vaultRepository.createCipherInOrganization(any(), any()) } returns CreateCipherResult.Success - viewModel.stateFlow.test { - viewModel.actionChannel.trySend(VaultAddEditAction.Common.SaveClick) - assertEquals(stateWithName, awaitItem()) - assertEquals(stateWithDialog, awaitItem()) - assertEquals(stateWithName, awaitItem()) - } + turbineScope { + val stateTurbine = viewModel.stateFlow.testIn(backgroundScope) + val eventTurbine = viewModel.eventFlow.testIn(backgroundScope) + viewModel.actionChannel.trySend(VaultAddEditAction.Common.SaveClick) + + assertEquals(stateWithName, stateTurbine.awaitItem()) + assertEquals(stateWithDialog, stateTurbine.awaitItem()) + assertEquals(stateWithName, stateTurbine.awaitItem()) + + assertEquals( + VaultAddEditEvent.NavigateBack, + eventTurbine.awaitItem(), + ) + } + coVerify(exactly = 1) { + vaultRepository.createCipherInOrganization(any(), any()) + } + } + + @Suppress("MaxLineLength") + @Test + fun `in add mode during autofill, SaveClick should show dialog, remove it once an item is saved, and emit ExitApp`() = + runTest { + val autofillSaveItem = AutofillSaveItem.Login( + username = null, + password = null, + uri = null, + ) + specialCircumstanceManager.specialCircumstance = + SpecialCircumstance.AutofillSave( + autofillSaveItem = autofillSaveItem, + ) + val stateWithDialog = createVaultAddItemState( + vaultAddEditType = VaultAddEditType.AddItem, + dialogState = VaultAddEditState.DialogState.Loading( + R.string.saving.asText(), + ), + commonContentViewState = createCommonContentViewState( + name = "mockName-1", + ), + ) + .copy(shouldExitOnSave = true) + val stateWithName = createVaultAddItemState( + vaultAddEditType = VaultAddEditType.AddItem, + commonContentViewState = createCommonContentViewState( + name = "mockName-1", + ), + ) + .copy(shouldExitOnSave = true) + mutableVaultDataFlow.value = DataState.Loaded( + createVaultData(), + ) + val viewModel = createAddVaultItemViewModel( + createSavedStateHandleWithState( + state = stateWithName, + vaultAddEditType = VaultAddEditType.AddItem, + ), + ) + coEvery { + vaultRepository.createCipherInOrganization(any(), any()) + } returns CreateCipherResult.Success + + turbineScope { + val stateTurbine = viewModel.stateFlow.testIn(backgroundScope) + val eventTurbine = viewModel.eventFlow.testIn(backgroundScope) + + viewModel.actionChannel.trySend(VaultAddEditAction.Common.SaveClick) + + assertEquals(stateWithName, stateTurbine.awaitItem()) + assertEquals(stateWithDialog, stateTurbine.awaitItem()) + assertEquals(stateWithName, stateTurbine.awaitItem()) + + assertEquals( + VaultAddEditEvent.ExitApp, + eventTurbine.awaitItem(), + ) + } + assertNull(specialCircumstanceManager.specialCircumstance) coVerify(exactly = 1) { vaultRepository.createCipherInOrganization(any(), any()) } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSaveItemExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSaveItemExtensionsTest.kt new file mode 100644 index 0000000000..0b7dc9d128 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSaveItemExtensionsTest.kt @@ -0,0 +1,76 @@ +package com.x8bit.bitwarden.ui.vault.feature.addedit.util + +import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem +import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState +import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem +import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.util.UUID + +class AutofillSaveItemExtensionsTest { + @BeforeEach + fun setUp() { + mockkStatic(UUID::randomUUID) + } + + @BeforeEach + fun tearDown() { + unmockkStatic(UUID::randomUUID) + } + + @Test + fun `toDefaultAddTypeContent for a Card type should return the correct Content`() { + assertEquals( + VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common(), + type = VaultAddEditState.ViewState.Content.ItemType.Card( + number = "number", + expirationMonth = VaultCardExpirationMonth.JANUARY, + expirationYear = "2024", + securityCode = "securityCode", + ), + ), + AutofillSaveItem.Card( + number = "number", + expirationMonth = "1", + expirationYear = "2024", + securityCode = "securityCode", + ) + .toDefaultAddTypeContent(), + ) + } + + @Test + fun `toDefaultAddTypeContent for a Login type should return the correct Content`() { + every { UUID.randomUUID().toString() } returns "uuid" + assertEquals( + VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common( + name = "www.test.com", + ), + type = VaultAddEditState.ViewState.Content.ItemType.Login( + username = "username", + password = "password", + uriList = listOf( + UriItem( + id = "uuid", + uri = "https://www.test.com", + match = null, + ), + ), + ), + ), + AutofillSaveItem.Login( + username = "username", + password = "password", + uri = "https://www.test.com", + ) + .toDefaultAddTypeContent(), + ) + } +}