Adding in tests for the custom type fields (#398)

This commit is contained in:
Oleg Semenenko 2023-12-15 12:17:29 -06:00 committed by Álison Fernandes
parent 5235310de5
commit 6f85d80f9f
7 changed files with 1183 additions and 99 deletions

View file

@ -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 = "",
)
}
}
}
}

View file

@ -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 = "",
)
}
}
}

View file

@ -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(),

View file

@ -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<CipherView?>>(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<VaultAddItemState.Custom> = 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<VaultAddItemState.Custom> = 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"

View file

@ -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"

View file

@ -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"

View file

@ -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),
)