From 6f85d80f9fcfe195cf55bad9c5ad401ea95c42af Mon Sep 17 00:00:00 2001 From: Oleg Semenenko <146032743+oleg-livefront@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:17:29 -0600 Subject: [PATCH] Adding in tests for the custom type fields (#398) --- .../feature/additem/VaultAddItemViewModel.kt | 42 +- .../feature/additem/model/CustomFieldType.kt | 44 ++ .../feature/additem/VaultAddItemScreenTest.kt | 412 ++++++++++++++ .../additem/VaultAddItemViewModelTest.kt | 504 ++++++++++++++++-- .../additem/model/CustomFieldTypeTests.kt | 74 +++ .../additem/util/CipherViewExtensionsTest.kt | 86 ++- .../vault/util/VaultDataExtensionsTest.kt | 120 ++++- 7 files changed, 1183 insertions(+), 99 deletions(-) create mode 100644 app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldTypeTests.kt 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 7d95f4107b..4e35c18805 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 @@ -15,9 +15,9 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.concat -import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemAction.ItemType.SecureNotesType.TooltipClick.toCustomField import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType import com.x8bit.bitwarden.ui.vault.feature.additem.util.toViewState +import com.x8bit.bitwarden.ui.vault.feature.additem.model.toCustomField import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType @@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize -import java.util.UUID import javax.inject.Inject private const val KEY_STATE = "state" @@ -1320,43 +1319,4 @@ sealed class VaultAddItemAction { val updateCipherResult: UpdateCipherResult, ) : Internal() } - - /** - * An extension function for adding custom field types. - */ - fun CustomFieldType.toCustomField(name: String): VaultAddItemState.Custom { - return when (this) { - CustomFieldType.BOOLEAN -> { - VaultAddItemState.Custom.BooleanField( - itemId = UUID.randomUUID().toString(), - name = name, - value = false, - ) - } - - CustomFieldType.LINKED -> { - VaultAddItemState.Custom.LinkedField( - itemId = UUID.randomUUID().toString(), - name = name, - vaultLinkedFieldType = VaultLinkedFieldType.USERNAME, - ) - } - - CustomFieldType.HIDDEN -> { - VaultAddItemState.Custom.HiddenField( - itemId = UUID.randomUUID().toString(), - name = name, - value = "", - ) - } - - CustomFieldType.TEXT -> { - VaultAddItemState.Custom.TextField( - itemId = UUID.randomUUID().toString(), - name = name, - value = "", - ) - } - } - } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldType.kt index 4b992c5b2b..57574c87b9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldType.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldType.kt @@ -3,6 +3,9 @@ package com.x8bit.bitwarden.ui.vault.feature.additem.model import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState +import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType +import java.util.UUID /** * The Enum representing the Custom Field type that is being added by the user. @@ -13,3 +16,44 @@ enum class CustomFieldType(val typeText: Text) { BOOLEAN(R.string.field_type_boolean.asText()), TEXT(R.string.field_type_text.asText()), } + +/** + * A function that converts [CustomFieldType] and a string to [VaultAddItemState.Custom]. + */ +fun CustomFieldType.toCustomField( + name: String, +): VaultAddItemState.Custom { + return when (this) { + CustomFieldType.BOOLEAN -> { + VaultAddItemState.Custom.BooleanField( + itemId = UUID.randomUUID().toString(), + name = name, + value = false, + ) + } + + CustomFieldType.LINKED -> { + VaultAddItemState.Custom.LinkedField( + itemId = UUID.randomUUID().toString(), + name = name, + vaultLinkedFieldType = VaultLinkedFieldType.USERNAME, + ) + } + + CustomFieldType.HIDDEN -> { + VaultAddItemState.Custom.HiddenField( + itemId = UUID.randomUUID().toString(), + name = name, + value = "", + ) + } + + CustomFieldType.TEXT -> { + VaultAddItemState.Custom.TextField( + itemId = UUID.randomUUID().toString(), + name = name, + value = "", + ) + } + } +} 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 ad659bc90b..a6dad88640 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 @@ -20,6 +20,7 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onSiblings import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.performTouchInput import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest @@ -28,7 +29,9 @@ import com.x8bit.bitwarden.ui.util.isProgressBar import com.x8bit.bitwarden.ui.util.onAllNodesWithTextAfterScroll import com.x8bit.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll +import com.x8bit.bitwarden.ui.vault.feature.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 @@ -542,6 +545,204 @@ class VaultAddItemScreenTest : BaseComposeTest() { .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`() { + mutableStateFlow.value = DEFAULT_STATE_LOGIN + + composeTestRule + .onNodeWithTextAfterScroll(text = "New custom field") + .performClick() + + composeTestRule + .onAllNodesWithText("Cancel") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + + composeTestRule + .onNodeWithText(text = "Linked") + .performClick() + + composeTestRule + .onNodeWithText("Name") + .performTextInput("TestLinked") + + composeTestRule + .onNodeWithText("Ok") + .performClick() + + verify { + viewModel.trySendAction( + VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick( + customFieldType = CustomFieldType.LINKED, + name = "TestLinked", + ), + ) + } + } + + @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`() { // Opens the menu @@ -823,6 +1024,188 @@ class VaultAddItemScreenTest : BaseComposeTest() { .assertIsDisplayed() } + @Suppress("MaxLineLength") + @Test + fun `in ItemType_SecureNotes state clicking New Custom Field button should allow creation of Text type`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES + + 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.SecureNotesType.AddNewCustomFieldClick( + customFieldType = CustomFieldType.TEXT, + name = "TestText", + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `in ItemType_SecureNotes state clicking New Custom Field button should not display linked type`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES + + composeTestRule + .onNodeWithTextAfterScroll(text = "New custom field") + .performClick() + + composeTestRule + .onAllNodesWithText("Cancel") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + + composeTestRule + .onNodeWithText(text = "Linked") + .assertIsNotDisplayed() + } + + @Suppress("MaxLineLength") + @Test + fun `in ItemType_SecureNotes state clicking New Custom Field button should allow creation of Boolean type`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES + + 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.SecureNotesType.AddNewCustomFieldClick( + customFieldType = CustomFieldType.BOOLEAN, + name = "TestBoolean", + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `in ItemType_SecureNotes state clicking New Custom Field button should allow creation of Hidden type`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES + + 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 + .onAllNodesWithText("Ok") + .filterToOne(hasAnyAncestor(isDialog())) + .performClick() + + verify { + viewModel.trySendAction( + VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick( + customFieldType = CustomFieldType.HIDDEN, + name = "TestHidden", + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `in ItemType_SecureNotes state clicking and changing the custom text field will send a CustomFieldValueChange event`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + composeTestRule + .onNodeWithTextAfterScroll("TestText") + .performTextClearance() + + verify { + viewModel.trySendAction( + VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange( + VaultAddItemState.Custom.TextField("Test ID", "TestText", ""), + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `in ItemType_SecureNotes state clicking and changing the custom hidden field will send a CustomFieldValueChange event`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + composeTestRule + .onNodeWithTextAfterScroll("TestHidden") + .performTextClearance() + + verify { + viewModel.trySendAction( + VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange( + VaultAddItemState.Custom.HiddenField("Test ID", "TestHidden", ""), + ), + ) + } + } + + @Suppress("MaxLineLength") + @Test + fun `in ItemType_SecureNotes state clicking and changing the custom boolean field will send a CustomFieldValueChange event`() { + mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS + + composeTestRule + .onNodeWithTextAfterScroll("TestBoolean") + .performClick() + + verify { + viewModel.trySendAction( + VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange( + VaultAddItemState.Custom.BooleanField("Test ID", "TestBoolean", true), + ), + ) + } + } + //region Helper functions @Suppress("MaxLineLength") @@ -852,6 +1235,23 @@ 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(), dialog = VaultAddItemState.DialogState.Error("test".asText()), @@ -864,6 +1264,18 @@ class VaultAddItemScreenTest : BaseComposeTest() { dialog = null, ) + 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"), + ), + ), + dialog = null, + vaultAddEditType = VaultAddEditType.AddItem, + ) + private val DEFAULT_STATE_SECURE_NOTES = VaultAddItemState( vaultAddEditType = VaultAddEditType.AddItem, viewState = VaultAddItemState.ViewState.Content.SecureNotes(), 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 ce8caad81b..0e8bd1fd78 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 @@ -11,8 +11,11 @@ import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType +import com.x8bit.bitwarden.ui.vault.feature.additem.model.toCustomField import com.x8bit.bitwarden.ui.vault.feature.additem.util.toViewState import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType +import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every @@ -27,12 +30,13 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import java.util.UUID class VaultAddItemViewModelTest : BaseViewModelTest() { - private val initialState = createVaultAddLoginItemState() - private val initialSavedStateHandle = createSavedStateHandleWithState( - state = initialState, + private val loginInitialState = createVaultAddLoginItemState() + private val loginInitialSavedStateHandle = createSavedStateHandleWithState( + state = loginInitialState, vaultAddEditType = VaultAddEditType.AddItem, ) private val mutableVaultItemFlow = MutableStateFlow>(DataState.Loading) @@ -43,11 +47,14 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @BeforeEach fun setup() { mockkStatic(CIPHER_VIEW_EXTENSIONS_PATH) + mockkStatic(UUID::randomUUID) + every { UUID.randomUUID().toString() } returns TEST_ID } @AfterEach fun tearDown() { unmockkStatic(CIPHER_VIEW_EXTENSIONS_PATH) + unmockkStatic(CustomFieldType::toCustomField) } @Test @@ -59,7 +66,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { ), ) viewModel.stateFlow.test { - assertEquals(initialState, awaitItem()) + assertEquals(loginInitialState, awaitItem()) } } @@ -111,6 +118,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { fun `in add mode, SaveClick should show dialog, and remove it once an item is saved`() = runTest { val stateWithDialog = createVaultAddLoginItemState( + vaultAddEditType = VaultAddEditType.AddItem, name = "tester", dialogState = VaultAddItemState.DialogState.Loading( R.string.saving.asText(), @@ -118,6 +126,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { ) val stateWithName = createVaultAddLoginItemState( + vaultAddEditType = VaultAddEditType.AddItem, name = "tester", ) @@ -147,6 +156,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `in add mode, SaveClick should update value to loading`() = runTest { val stateWithName = createVaultAddLoginItemState( + vaultAddEditType = VaultAddEditType.AddItem, name = "tester", ) @@ -169,6 +179,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `in add mode, SaveClick createCipher error should emit ShowToast`() = runTest { val stateWithName = createVaultAddLoginItemState( + vaultAddEditType = VaultAddEditType.AddItem, name = "tester", ) @@ -266,7 +277,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `Saving item with an empty name field will cause a dialog to show up`() = runTest { - val stateWithNoName = createVaultAddSecureNotesItemState(name = "") + val stateWithNoName = createVaultAddSecureNotesItemState( + name = "", + vaultAddEditType = VaultAddEditType.AddItem, + ) val stateWithNoNameAndDialog = createVaultAddSecureNotesItemState( name = "", @@ -274,6 +288,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { R.string.validation_field_required .asText(R.string.name.asText()), ), + vaultAddEditType = VaultAddEditType.AddItem, ) val viewModel = createAddVaultItemViewModel( @@ -293,6 +308,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `HandleDialogDismiss will remove the current dialog`() = runTest { val errorState = createVaultAddLoginItemState( + vaultAddEditType = VaultAddEditType.AddItem, dialogState = VaultAddItemState.DialogState.Error( R.string.validation_field_required .asText(R.string.name.asText()), @@ -321,7 +337,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) - val expectedState = initialState.copy( + val expectedState = loginInitialState.copy( viewState = VaultAddItemState.ViewState.Content.Login(), ) @@ -345,10 +361,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) val expectedLoginItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.Login) + (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) .copy(name = "newName") - val expectedState = initialState.copy(viewState = expectedLoginItem) + val expectedState = loginInitialState.copy(viewState = expectedLoginItem) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -356,16 +372,15 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Suppress("MaxLineLength") @Test fun `UsernameTextChange should update username in LoginItem`() = runTest { - val viewModel = createAddVaultItemViewModel() val action = VaultAddItemAction.ItemType.LoginType.UsernameTextChange("newUsername") viewModel.actionChannel.trySend(action) val expectedLoginItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.Login) + (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) .copy(username = "newUsername") - val expectedState = initialState.copy(viewState = expectedLoginItem) + val expectedState = loginInitialState.copy(viewState = expectedLoginItem) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -373,64 +388,60 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Suppress("MaxLineLength") @Test fun `PasswordTextChange should update password in LoginItem`() = runTest { - val viewModel = createAddVaultItemViewModel() val action = VaultAddItemAction.ItemType.LoginType.PasswordTextChange("newPassword") viewModel.actionChannel.trySend(action) val expectedLoginItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.Login) + (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) .copy(password = "newPassword") - val expectedState = initialState.copy(viewState = expectedLoginItem) + val expectedState = loginInitialState.copy(viewState = expectedLoginItem) assertEquals(expectedState, viewModel.stateFlow.value) } @Test fun `UriTextChange should update uri in LoginItem`() = runTest { - val viewModel = createAddVaultItemViewModel() val action = VaultAddItemAction.ItemType.LoginType.UriTextChange("newUri") viewModel.actionChannel.trySend(action) val expectedLoginItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.Login) + (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) .copy(uri = "newUri") - val expectedState = initialState.copy(viewState = expectedLoginItem) + val expectedState = loginInitialState.copy(viewState = expectedLoginItem) assertEquals(expectedState, viewModel.stateFlow.value) } @Test fun `FolderChange should update folder in LoginItem`() = runTest { - val viewModel = createAddVaultItemViewModel() val action = VaultAddItemAction.ItemType.LoginType.FolderChange("newFolder".asText()) viewModel.actionChannel.trySend(action) val expectedLoginItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.Login) + (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) .copy(folderName = "newFolder".asText()) - val expectedState = initialState.copy(viewState = expectedLoginItem) + val expectedState = loginInitialState.copy(viewState = expectedLoginItem) assertEquals(expectedState, viewModel.stateFlow.value) } @Test fun `ToggleFavorite should update favorite in LoginItem`() = runTest { - val viewModel = createAddVaultItemViewModel() val action = VaultAddItemAction.ItemType.LoginType.ToggleFavorite(true) viewModel.actionChannel.trySend(action) val expectedLoginItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.Login) + (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) .copy(favorite = true) - val expectedState = initialState.copy(viewState = expectedLoginItem) + val expectedState = loginInitialState.copy(viewState = expectedLoginItem) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -439,7 +450,6 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Test fun `ToggleMasterPasswordReprompt should update masterPasswordReprompt in LoginItem`() = runTest { - val viewModel = createAddVaultItemViewModel() val action = VaultAddItemAction.ItemType.LoginType.ToggleMasterPasswordReprompt( isMasterPasswordReprompt = true, ) @@ -447,30 +457,151 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) val expectedLoginItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.Login) + (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) .copy(masterPasswordReprompt = true) - val expectedState = initialState.copy(viewState = expectedLoginItem) + val expectedState = loginInitialState.copy(viewState = expectedLoginItem) assertEquals(expectedState, viewModel.stateFlow.value) } @Test fun `NotesTextChange should update notes in LoginItem`() = runTest { - val viewModel = createAddVaultItemViewModel() val action = VaultAddItemAction.ItemType.LoginType.NotesTextChange(notes = "newNotes") viewModel.actionChannel.trySend(action) val expectedLoginItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.Login) + (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) .copy(notes = "newNotes") - val expectedState = initialState.copy(viewState = expectedLoginItem) + 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() @@ -480,10 +611,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) val expectedLoginItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.Login) + (loginInitialState.viewState as VaultAddItemState.ViewState.Content.Login) .copy(ownership = "newOwner") - val expectedState = initialState.copy(viewState = expectedLoginItem) + val expectedState = loginInitialState.copy(viewState = expectedLoginItem) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -592,18 +723,19 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @Nested inner class VaultAddSecureNotesTypeItemActions { private lateinit var viewModel: VaultAddItemViewModel - private lateinit var initialState: VaultAddItemState - private lateinit var initialSavedStateHandle: SavedStateHandle + private lateinit var secureNotesInitialState: VaultAddItemState + private lateinit var secureNotesInitialSavedStateHandle: SavedStateHandle @BeforeEach fun setup() { - initialState = createVaultAddSecureNotesItemState() - initialSavedStateHandle = createSavedStateHandleWithState( - state = initialState, + secureNotesInitialState = + createVaultAddSecureNotesItemState(vaultAddEditType = VaultAddEditType.AddItem) + secureNotesInitialSavedStateHandle = createSavedStateHandleWithState( + state = secureNotesInitialState, vaultAddEditType = VaultAddEditType.AddItem, ) viewModel = VaultAddItemViewModel( - savedStateHandle = initialSavedStateHandle, + savedStateHandle = secureNotesInitialSavedStateHandle, vaultRepository = vaultRepository, ) } @@ -615,10 +747,11 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) val expectedSecureNotesItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) + (secureNotesInitialState.viewState as + VaultAddItemState.ViewState.Content.SecureNotes) .copy(name = "newName") - val expectedState = initialState.copy(viewState = expectedSecureNotesItem) + val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -632,10 +765,11 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) val expectedSecureNotesItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) + (secureNotesInitialState.viewState as + VaultAddItemState.ViewState.Content.SecureNotes) .copy(folderName = "newFolder".asText()) - val expectedState = initialState.copy(viewState = expectedSecureNotesItem) + val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -647,10 +781,11 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) val expectedSecureNotesItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) + (secureNotesInitialState.viewState as + VaultAddItemState.ViewState.Content.SecureNotes) .copy(favorite = true) - val expectedState = initialState.copy(viewState = expectedSecureNotesItem) + val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -667,10 +802,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) val expectedSecureNotesItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) + (secureNotesInitialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) .copy(masterPasswordReprompt = true) - val expectedState = initialState.copy(viewState = expectedSecureNotesItem) + val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -684,10 +819,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) val expectedSecureNotesItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) + (secureNotesInitialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) .copy(notes = "newNotes") - val expectedState = initialState.copy(viewState = expectedSecureNotesItem) + val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) assertEquals(expectedState, viewModel.stateFlow.value) } @@ -700,14 +835,108 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { viewModel.actionChannel.trySend(action) val expectedSecureNotesItem = - (initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes) + (secureNotesInitialState.viewState as + VaultAddItemState.ViewState.Content.SecureNotes) .copy(ownership = "newOwner") - val expectedState = initialState.copy(viewState = expectedSecureNotesItem) + val expectedState = secureNotesInitialState.copy(viewState = expectedSecureNotesItem) assertEquals(expectedState, viewModel.stateFlow.value) } + @Suppress("MaxLineLength") + @Test + fun `AddNewCustomFieldClick should allow a user to add a custom boolean field in Secure notes item`() = + runTest { + assertAddNewCustomFieldClick( + initialState = secureNotesInitialState, + type = CustomFieldType.BOOLEAN, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `AddNewCustomFieldClick should allow a user to add a custom hidden field in Secure notes item`() = + runTest { + assertAddNewCustomFieldClick( + initialState = secureNotesInitialState, + type = CustomFieldType.HIDDEN, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `AddNewCustomFieldClick should allow a user to add a custom text field in Secure notes item`() = + runTest { + + assertAddNewCustomFieldClick( + initialState = secureNotesInitialState, + type = CustomFieldType.TEXT, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `CustomFieldValueChange should allow a user to update a text custom field in Secure notes item`() = + runTest { + val initState = createVaultAddSecureNotesItemState( + 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 Secure notes item`() = + runTest { + val initState = createVaultAddSecureNotesItemState( + 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 Secure notes item`() = + runTest { + val initState = createVaultAddSecureNotesItemState( + vaultAddEditType = VaultAddEditType.AddItem, + customFieldData = listOf( + VaultAddItemState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + true, + ), + ), + ) + + assertCustomFieldValueChange( + initState, + CustomFieldType.BOOLEAN, + ) + } + @Test fun `TooltipClick should emit ShowToast with 'Tooltip' message`() = runTest { viewModel.eventFlow.test { @@ -721,6 +950,8 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { } } + //region Helper functions + @Suppress("LongParameterList") private fun createVaultAddLoginItemState( vaultAddEditType: VaultAddEditType = VaultAddEditType.AddItem, @@ -732,6 +963,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { favorite: Boolean = false, masterPasswordReprompt: Boolean = false, notes: String = "", + customFieldData: List = listOf(), ownership: String = "placeholder@email.com", dialogState: VaultAddItemState.DialogState? = null, ): VaultAddItemState = @@ -744,6 +976,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { uri = uri, folderName = folder, favorite = favorite, + customFieldData = customFieldData, masterPasswordReprompt = masterPasswordReprompt, notes = notes, ownership = ownership, @@ -753,22 +986,25 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { @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.AddItem, + vaultAddEditType = vaultAddEditType, viewState = VaultAddItemState.ViewState.Content.SecureNotes( name = name, folderName = folder, favorite = favorite, masterPasswordReprompt = masterPasswordReprompt, notes = notes, + customFieldData = customFieldData, ownership = ownership, ), dialog = dialogState, @@ -790,15 +1026,181 @@ class VaultAddItemViewModelTest : BaseViewModelTest() { } private fun createAddVaultItemViewModel( - savedStateHandle: SavedStateHandle = initialSavedStateHandle, + savedStateHandle: SavedStateHandle = loginInitialSavedStateHandle, vaultRepo: VaultRepository = vaultRepository, ): VaultAddItemViewModel = VaultAddItemViewModel( savedStateHandle = savedStateHandle, vaultRepository = vaultRepo, ) + + /** + * A function to test the changes in custom fields for each type. + */ + private fun assertCustomFieldValueChange( + initialState: VaultAddItemState, + type: CustomFieldType, + ) { + lateinit var expectedCustomField: VaultAddItemState.Custom + lateinit var action: VaultAddItemAction.ItemType + lateinit var expectedState: VaultAddItemState.ViewState.Content + + when (type) { + CustomFieldType.LINKED -> { + expectedCustomField = VaultAddItemState.Custom.LinkedField( + "TestId 4", + "Linked Field", + VaultLinkedFieldType.PASSWORD, + ) + } + + CustomFieldType.HIDDEN -> { + expectedCustomField = VaultAddItemState.Custom.HiddenField( + "TestId 2", + "Test Hidden", + "Updated Test Text", + ) + } + + CustomFieldType.BOOLEAN -> { + expectedCustomField = VaultAddItemState.Custom.BooleanField( + "TestId 3", + "Boolean Field", + false, + ) + } + + CustomFieldType.TEXT -> { + expectedCustomField = VaultAddItemState.Custom.TextField( + "TestId 1", + "Test Text", + "Updated Test Text", + ) + } + } + + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initialState, + vaultAddEditType = VaultAddEditType.AddItem, + ), + ) + + 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 + } + + viewModel.actionChannel.trySend(action) + + assertEquals(expectedState, viewModel.stateFlow.value.viewState) + } + + /** + * A function to test the addition of new custom fields for each type. + */ + private fun assertAddNewCustomFieldClick( + initialState: VaultAddItemState, + type: CustomFieldType, + ) { + val viewModel = createAddVaultItemViewModel( + savedStateHandle = createSavedStateHandleWithState( + state = initialState, + vaultAddEditType = VaultAddEditType.AddItem, + ), + ) + + var name = "" + lateinit var expectedCustomField: VaultAddItemState.Custom + lateinit var action: VaultAddItemAction.ItemType + lateinit var expectedState: VaultAddItemState.ViewState.Content + + when (type) { + CustomFieldType.LINKED -> { + name = "Linked" + expectedCustomField = VaultAddItemState.Custom.LinkedField( + itemId = TEST_ID, + name = name, + vaultLinkedFieldType = VaultLinkedFieldType.USERNAME, + ) + } + + CustomFieldType.HIDDEN -> { + name = "Hidden" + expectedCustomField = VaultAddItemState.Custom.HiddenField( + itemId = TEST_ID, + name = name, + value = "", + ) + } + + CustomFieldType.BOOLEAN -> { + name = "Boolean" + expectedCustomField = VaultAddItemState.Custom.BooleanField( + itemId = TEST_ID, + name = name, + value = false, + ) + } + + CustomFieldType.TEXT -> { + name = "Text" + expectedCustomField = VaultAddItemState.Custom.TextField( + itemId = TEST_ID, + name = name, + value = "", + ) + } + } + + 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 + } + + viewModel.actionChannel.trySend(action) + assertEquals(expectedState, viewModel.stateFlow.value.viewState) + } + + //endregion Helper functions } +private const val TEST_ID = "testId" + private const val CIPHER_VIEW_EXTENSIONS_PATH: String = "com.x8bit.bitwarden.ui.vault.feature.additem.util.CipherViewExtensionsKt" diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldTypeTests.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldTypeTests.kt new file mode 100644 index 0000000000..9ae851f94a --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldTypeTests.kt @@ -0,0 +1,74 @@ +package com.x8bit.bitwarden.ui.vault.feature.additem.model + +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState +import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.util.UUID + +class CustomFieldTypeTests { + + @BeforeEach + fun setup() { + mockkStatic(UUID::randomUUID) + every { UUID.randomUUID().toString() } returns TEST_ID + } + + @AfterEach + fun tearDown() { + unmockkStatic(UUID::randomUUID) + } + + @Suppress("MaxLineLength") + @Test + fun `toCustomField should return a custom boolean type when we pass in required boolean type`() { + val name = "test" + val type = CustomFieldType.BOOLEAN + + val expected = VaultAddItemState.Custom.BooleanField(TEST_ID, "test", false) + val actual = type.toCustomField(name) + + assertEquals(expected, actual) + } + + @Test + fun `toCustomField should return a custom linked type when we pass in required linked type`() { + val name = "test" + val type = CustomFieldType.LINKED + + val expected = + VaultAddItemState.Custom.LinkedField(TEST_ID, "test", VaultLinkedFieldType.USERNAME) + val actual = type.toCustomField(name) + + assertEquals(expected, actual) + } + + @Test + fun `toCustomField should return a custom texttype when we pass in required text type`() { + val name = "test" + val type = CustomFieldType.TEXT + + val expected = VaultAddItemState.Custom.TextField(TEST_ID, "test", "") + val actual = type.toCustomField(name) + + assertEquals(expected, actual) + } + + @Test + fun `toCustomField should return a custom hidden type when we pass in required hidden type`() { + val name = "test" + val type = CustomFieldType.HIDDEN + + val expected = VaultAddItemState.Custom.HiddenField(TEST_ID, "test", "") + val actual = type.toCustomField(name) + + assertEquals(expected, actual) + } +} + +private const val TEST_ID = "testID" 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 5c497bf742..3f6f66cd34 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 @@ -4,6 +4,8 @@ import com.bitwarden.core.CardView import com.bitwarden.core.CipherRepromptType import com.bitwarden.core.CipherType import com.bitwarden.core.CipherView +import com.bitwarden.core.FieldType +import com.bitwarden.core.FieldView import com.bitwarden.core.IdentityView import com.bitwarden.core.LoginUriView import com.bitwarden.core.LoginView @@ -13,12 +15,30 @@ import com.bitwarden.core.SecureNoteView import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState +import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import java.time.Instant +import java.util.UUID class CipherViewExtensionsTest { + @BeforeEach + fun setup() { + mockkStatic(UUID::randomUUID) + every { UUID.randomUUID().toString() } returns TEST_ID + } + + @AfterEach + fun tearDown() { + unmockkStatic(UUID::randomUUID) + } + @Test fun `toViewState should create a Card ViewState`() { val cipherView = DEFAULT_CARD_CIPHER_VIEW @@ -63,7 +83,16 @@ class CipherViewExtensionsTest { ownership = "", availableFolders = emptyList(), availableOwners = emptyList(), - customFieldData = 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, + ), + ), ), result, ) @@ -84,9 +113,13 @@ class CipherViewExtensionsTest { 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(), - customFieldData = emptyList(), ), result, ) @@ -113,7 +146,32 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView( viewPassword = false, localData = null, attachments = null, - fields = emptyList(), + fields = listOf( + FieldView( + name = "TestBoolean", + value = false.toString(), + type = FieldType.BOOLEAN, + linkedId = null, + ), + FieldView( + name = "TestText", + value = "TestText", + type = FieldType.TEXT, + linkedId = null, + ), + FieldView( + name = "TestHidden", + value = "TestHidden", + type = FieldType.HIDDEN, + linkedId = null, + ), + FieldView( + name = "TestLinked", + value = null, + type = FieldType.LINKED, + linkedId = VaultLinkedFieldType.USERNAME.id, + ), + ), passwordHistory = listOf( PasswordHistoryView( password = "old_password", @@ -180,5 +238,27 @@ private val DEFAULT_LOGIN_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.cop private val DEFAULT_SECURE_NOTES_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.copy( type = CipherType.SECURE_NOTE, + fields = listOf( + FieldView( + name = "TestBoolean", + value = false.toString(), + type = FieldType.BOOLEAN, + linkedId = null, + ), + FieldView( + name = "TestText", + value = "TestText", + type = FieldType.TEXT, + linkedId = null, + ), + FieldView( + name = "TestHidden", + value = "TestHidden", + type = FieldType.HIDDEN, + linkedId = null, + ), + ), secureNote = SecureNoteView(type = SecureNoteType.GENERIC), ) + +private const val TEST_ID = "testID" 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 6114156e52..47b793e476 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 @@ -3,6 +3,8 @@ package com.x8bit.bitwarden.ui.vault.feature.vault.util import com.bitwarden.core.CipherRepromptType import com.bitwarden.core.CipherType import com.bitwarden.core.CipherView +import com.bitwarden.core.FieldType +import com.bitwarden.core.FieldView import com.bitwarden.core.LoginUriView import com.bitwarden.core.LoginView import com.bitwarden.core.PasswordHistoryView @@ -16,6 +18,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState +import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType import io.mockk.every import io.mockk.mockkStatic import io.mockk.unmockkStatic @@ -197,9 +200,18 @@ class VaultDataExtensionsTest { 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", - customFieldData = emptyList(), ) val result = loginItemType.toCipherView() @@ -225,7 +237,32 @@ class VaultDataExtensionsTest { ), favorite = true, reprompt = CipherRepromptType.NONE, - fields = emptyList(), + fields = listOf( + FieldView( + name = "TestBoolean", + value = "false", + type = FieldType.BOOLEAN, + linkedId = null, + ), + FieldView( + name = "TestText", + value = "TestText", + type = FieldType.TEXT, + linkedId = null, + ), + FieldView( + name = "TestHidden", + value = "TestHidden", + type = FieldType.HIDDEN, + linkedId = null, + ), + FieldView( + name = "TestLinked", + value = null, + type = FieldType.LINKED, + linkedId = VaultLinkedFieldType.USERNAME.id, + ), + ), passwordHistory = listOf( PasswordHistoryView( password = "old_password", @@ -248,6 +285,11 @@ class VaultDataExtensionsTest { 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 result = secureNotesItemType.toCipherView() @@ -273,7 +315,26 @@ class VaultDataExtensionsTest { viewPassword = true, localData = null, attachments = null, - fields = emptyList(), + fields = listOf( + FieldView( + name = "TestBoolean", + value = "false", + type = FieldType.BOOLEAN, + linkedId = null, + ), + FieldView( + name = "TestText", + value = "TestText", + type = FieldType.TEXT, + linkedId = null, + ), + FieldView( + name = "TestHidden", + value = "TestHidden", + type = FieldType.HIDDEN, + linkedId = null, + ), + ), passwordHistory = null, creationDate = Instant.MIN, deletedDate = null, @@ -333,7 +394,38 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView( viewPassword = false, localData = null, attachments = null, - fields = emptyList(), + fields = listOf( + FieldView( + name = "text", + value = "value", + type = FieldType.TEXT, + linkedId = null, + ), + FieldView( + name = "hidden", + value = "value", + type = FieldType.HIDDEN, + linkedId = null, + ), + FieldView( + name = "boolean", + value = "true", + type = FieldType.BOOLEAN, + linkedId = null, + ), + FieldView( + name = "linked username", + value = null, + type = FieldType.LINKED, + linkedId = 100U, + ), + FieldView( + name = "linked password", + value = null, + type = FieldType.LINKED, + linkedId = 101U, + ), + ), passwordHistory = listOf( PasswordHistoryView( password = "old_password", @@ -364,5 +456,25 @@ private val DEFAULT_LOGIN_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.cop private val DEFAULT_SECURE_NOTES_CIPHER_VIEW: CipherView = DEFAULT_BASE_CIPHER_VIEW.copy( type = CipherType.SECURE_NOTE, + fields = listOf( + FieldView( + name = "text", + value = "value", + type = FieldType.TEXT, + linkedId = null, + ), + FieldView( + name = "hidden", + value = "value", + type = FieldType.HIDDEN, + linkedId = null, + ), + FieldView( + name = "boolean", + value = "true", + type = FieldType.BOOLEAN, + linkedId = null, + ), + ), secureNote = SecureNoteView(type = SecureNoteType.GENERIC), )