[PM-32808] feat: Add Add/Edit support for Driver's License item type (#6908)

This commit is contained in:
Patrick Honkonen
2026-05-13 14:20:03 +00:00
committed by GitHub
parent f6d56fa234
commit ecc4fa6dea
10 changed files with 719 additions and 19 deletions
@@ -36,6 +36,7 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditBankAcc
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCardTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLicenseTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditSshKeyTypeHandlers
@@ -54,6 +55,7 @@ fun CoachMarkScope<AddEditItemCoachMark>.VaultAddEditContent(
cardItemTypeHandlers: VaultAddEditCardTypeHandlers,
sshKeyItemTypeHandlers: VaultAddEditSshKeyTypeHandlers,
bankAccountItemTypeHandlers: VaultAddEditBankAccountTypeHandlers,
licenseItemTypeHandlers: VaultAddEditLicenseTypeHandlers,
isCardScannerEnabled: Boolean,
cardHolderNameFocusRequester: FocusRequester,
modifier: Modifier = Modifier,
@@ -288,7 +290,12 @@ fun CoachMarkScope<AddEditItemCoachMark>.VaultAddEditContent(
)
}
is VaultAddEditState.ViewState.Content.ItemType.License -> Unit
is VaultAddEditState.ViewState.Content.ItemType.License -> {
vaultAddEditLicenseItems(
licenseState = state.type,
licenseHandlers = licenseItemTypeHandlers,
)
}
is VaultAddEditState.ViewState.Content.ItemType.Passport -> Unit
}
@@ -0,0 +1,186 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.bitwarden.ui.platform.components.button.BitwardenTextSelectionButton
import com.bitwarden.ui.platform.components.field.BitwardenPasswordField
import com.bitwarden.ui.platform.components.field.BitwardenTextField
import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.bitwarden.ui.platform.components.model.CardStyle
import com.bitwarden.ui.platform.resource.BitwardenString
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLicenseTypeHandlers
/**
* The UI for adding and editing a license cipher.
*/
@Suppress("LongMethod")
fun LazyListScope.vaultAddEditLicenseItems(
licenseState: VaultAddEditState.ViewState.Content.ItemType.License,
licenseHandlers: VaultAddEditLicenseTypeHandlers,
) {
item {
Spacer(modifier = Modifier.height(16.dp))
BitwardenListHeaderText(
label = stringResource(id = BitwardenString.license_details),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.padding(horizontal = 16.dp),
)
}
item {
Spacer(modifier = Modifier.height(8.dp))
BitwardenTextField(
label = stringResource(id = BitwardenString.first_name),
value = licenseState.firstName,
onValueChange = licenseHandlers.onFirstNameTextChange,
textFieldTestTag = "LicenseFirstNameEntry",
cardStyle = CardStyle.Top(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenTextField(
label = stringResource(id = BitwardenString.middle_name),
value = licenseState.middleName,
onValueChange = licenseHandlers.onMiddleNameTextChange,
textFieldTestTag = "LicenseMiddleNameEntry",
cardStyle = CardStyle.Middle(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenTextField(
label = stringResource(id = BitwardenString.last_name),
value = licenseState.lastName,
onValueChange = licenseHandlers.onLastNameTextChange,
textFieldTestTag = "LicenseLastNameEntry",
cardStyle = CardStyle.Middle(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenPasswordField(
label = stringResource(id = BitwardenString.license_number),
value = licenseState.licenseNumber,
onValueChange = licenseHandlers.onLicenseNumberTextChange,
passwordFieldTestTag = "LicenseLicenseNumberEntry",
showPasswordTestTag = "LicenseShowLicenseNumberButton",
cardStyle = CardStyle.Middle(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenTextSelectionButton(
label = stringResource(id = BitwardenString.date_of_birth),
selectedOption = licenseState.dateOfBirth,
// TODO: Open a native Material date picker (separate ticket TBD).
onClick = {},
textFieldTestTag = "LicenseDateOfBirthEntry",
cardStyle = CardStyle.Middle(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenTextField(
label = stringResource(id = BitwardenString.issuing_country),
value = licenseState.issuingCountry,
onValueChange = licenseHandlers.onIssuingCountryTextChange,
textFieldTestTag = "LicenseIssuingCountryEntry",
cardStyle = CardStyle.Middle(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenTextField(
label = stringResource(id = BitwardenString.issuing_state),
value = licenseState.issuingState,
onValueChange = licenseHandlers.onIssuingStateTextChange,
textFieldTestTag = "LicenseIssuingStateEntry",
cardStyle = CardStyle.Middle(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenTextField(
label = stringResource(id = BitwardenString.issuing_authority),
value = licenseState.issuingAuthority,
onValueChange = licenseHandlers.onIssuingAuthorityTextChange,
textFieldTestTag = "LicenseIssuingAuthorityEntry",
cardStyle = CardStyle.Middle(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenTextSelectionButton(
label = stringResource(id = BitwardenString.issue_date),
selectedOption = licenseState.issueDate,
// TODO: Open a native Material date picker (separate ticket TBD).
onClick = {},
textFieldTestTag = "LicenseIssueDateEntry",
cardStyle = CardStyle.Middle(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenTextSelectionButton(
label = stringResource(id = BitwardenString.expiration_date),
selectedOption = licenseState.expirationDate,
// TODO: Open a native Material date picker (separate ticket TBD).
onClick = {},
textFieldTestTag = "LicenseExpirationDateEntry",
cardStyle = CardStyle.Middle(),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
item {
BitwardenTextField(
label = stringResource(id = BitwardenString.license_class),
value = licenseState.licenseClass,
onValueChange = licenseHandlers.onLicenseClassTextChange,
textFieldTestTag = "LicenseLicenseClassEntry",
cardStyle = CardStyle.Bottom,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
}
@@ -86,6 +86,7 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditBankAcc
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCardTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLicenseTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditSshKeyTypeHandlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditUserVerificationHandlers
@@ -241,6 +242,10 @@ fun VaultAddEditScreen(
VaultAddEditBankAccountTypeHandlers.create(viewModel = viewModel)
}
val licenseItemTypeHandlers = remember(viewModel) {
VaultAddEditLicenseTypeHandlers.create(viewModel = viewModel)
}
val archiveClickAction = { viewModel.trySendAction(VaultAddEditAction.Common.ArchiveClick) }
val unarchiveClickAction = { viewModel.trySendAction(VaultAddEditAction.Common.UnarchiveClick) }
@@ -423,6 +428,7 @@ fun VaultAddEditScreen(
cardItemTypeHandlers = cardItemTypeHandlers,
sshKeyItemTypeHandlers = sshKeyItemTypeHandlers,
bankAccountItemTypeHandlers = bankAccountItemTypeHandlers,
licenseItemTypeHandlers = licenseItemTypeHandlers,
isCardScannerEnabled = state.isCardScannerEnabled,
cardHolderNameFocusRequester = cardHolderNameFocusRequester,
lazyListState = lazyListState,
@@ -314,6 +314,10 @@ class VaultAddEditViewModel @Inject constructor(
handleBankAccountTypeActions(action)
}
is VaultAddEditAction.ItemType.LicenseType -> {
handleLicenseTypeActions(action)
}
is VaultAddEditAction.Internal -> handleInternalActions(action)
}
}
@@ -1746,6 +1750,49 @@ class VaultAddEditViewModel @Inject constructor(
//endregion Bank Account Type Handlers
//region License Type Handlers
@Suppress("LongMethod")
private fun handleLicenseTypeActions(
action: VaultAddEditAction.ItemType.LicenseType,
) {
when (action) {
is VaultAddEditAction.ItemType.LicenseType.FirstNameTextChange -> {
updateLicenseContent { it.copy(firstName = action.firstName) }
}
is VaultAddEditAction.ItemType.LicenseType.MiddleNameTextChange -> {
updateLicenseContent { it.copy(middleName = action.middleName) }
}
is VaultAddEditAction.ItemType.LicenseType.LastNameTextChange -> {
updateLicenseContent { it.copy(lastName = action.lastName) }
}
is VaultAddEditAction.ItemType.LicenseType.LicenseNumberTextChange -> {
updateLicenseContent { it.copy(licenseNumber = action.licenseNumber) }
}
is VaultAddEditAction.ItemType.LicenseType.IssuingCountryTextChange -> {
updateLicenseContent { it.copy(issuingCountry = action.country) }
}
is VaultAddEditAction.ItemType.LicenseType.IssuingStateTextChange -> {
updateLicenseContent { it.copy(issuingState = action.state) }
}
is VaultAddEditAction.ItemType.LicenseType.IssuingAuthorityTextChange -> {
updateLicenseContent { it.copy(issuingAuthority = action.authority) }
}
is VaultAddEditAction.ItemType.LicenseType.LicenseClassTextChange -> {
updateLicenseContent { it.copy(licenseClass = action.licenseClass) }
}
}
}
//endregion License Type Handlers
//region Internal Type Handlers
private fun handleInternalActions(action: VaultAddEditAction.Internal) {
@@ -2513,6 +2560,16 @@ class VaultAddEditViewModel @Inject constructor(
}
}
private inline fun updateLicenseContent(
crossinline block: (VaultAddEditState.ViewState.Content.ItemType.License) ->
VaultAddEditState.ViewState.Content.ItemType.License,
) {
updateContent { currentContent ->
(currentContent.type as? VaultAddEditState.ViewState.Content.ItemType.License)
?.let { currentContent.copy(type = block(it)) }
}
}
@Suppress("MaxLineLength")
private suspend fun VaultAddEditState.ViewState.Content.createCipherForAddAndCloneItemStates(): CreateCipherResult {
return common.selectedOwner?.collections
@@ -2619,7 +2676,7 @@ data class VaultAddEditState(
VaultItemCipherType.SECURE_NOTE -> BitwardenString.new_note.asText()
VaultItemCipherType.SSH_KEY -> BitwardenString.new_ssh_key.asText()
VaultItemCipherType.BANK_ACCOUNT -> BitwardenString.new_bank_account.asText()
VaultItemCipherType.DRIVERS_LICENSE -> BitwardenString.new_drivers_license.asText()
VaultItemCipherType.DRIVERS_LICENSE -> BitwardenString.new_license.asText()
VaultItemCipherType.PASSPORT -> BitwardenString.new_passport.asText()
}
@@ -2630,7 +2687,7 @@ data class VaultAddEditState(
VaultItemCipherType.SECURE_NOTE -> BitwardenString.edit_note.asText()
VaultItemCipherType.SSH_KEY -> BitwardenString.edit_ssh_key.asText()
VaultItemCipherType.BANK_ACCOUNT -> BitwardenString.edit_bank_account.asText()
VaultItemCipherType.DRIVERS_LICENSE -> BitwardenString.edit_drivers_license.asText()
VaultItemCipherType.DRIVERS_LICENSE -> BitwardenString.edit_license.asText()
VaultItemCipherType.PASSPORT -> BitwardenString.edit_passport.asText()
}
}
@@ -2724,7 +2781,7 @@ data class VaultAddEditState(
SECURE_NOTES(BitwardenString.type_secure_note),
SSH_KEYS(BitwardenString.type_ssh_key),
BANK_ACCOUNT(BitwardenString.type_bank_account),
DRIVERS_LICENSE(BitwardenString.type_license),
LICENSE(BitwardenString.type_license),
PASSPORT(BitwardenString.type_passport),
}
@@ -3067,7 +3124,7 @@ data class VaultAddEditState(
val licenseClass: String = "",
) : ItemType() {
override val itemTypeOption: ItemTypeOption
get() = ItemTypeOption.DRIVERS_LICENSE
get() = ItemTypeOption.LICENSE
override val isSdkSupported: Boolean get() = false
@@ -4082,6 +4139,52 @@ sealed class VaultAddEditAction {
*/
data class BankContactPhoneTextChange(val phone: String) : BankAccountType()
}
/**
* Represents actions specific to the License type.
*/
sealed class LicenseType : ItemType() {
/**
* Fired when the first name text input is changed.
*/
data class FirstNameTextChange(val firstName: String) : LicenseType()
/**
* Fired when the middle name text input is changed.
*/
data class MiddleNameTextChange(val middleName: String) : LicenseType()
/**
* Fired when the last name text input is changed.
*/
data class LastNameTextChange(val lastName: String) : LicenseType()
/**
* Fired when the license number text input is changed.
*/
data class LicenseNumberTextChange(val licenseNumber: String) : LicenseType()
/**
* Fired when the issuing country text input is changed.
*/
data class IssuingCountryTextChange(val country: String) : LicenseType()
/**
* Fired when the issuing state/province text input is changed.
*/
data class IssuingStateTextChange(val state: String) : LicenseType()
/**
* Fired when the issuing authority text input is changed.
*/
data class IssuingAuthorityTextChange(val authority: String) : LicenseType()
/**
* Fired when the license class text input is changed.
*/
data class LicenseClassTextChange(val licenseClass: String) : LicenseType()
}
}
/**
@@ -0,0 +1,100 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit.handlers
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditAction
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
/**
* A collection of handler functions for managing user interactions on the License portion of the
* Add/Edit cipher screen.
*
* @property onFirstNameTextChange Handles changes to the first name text input.
* @property onMiddleNameTextChange Handles changes to the middle name text input.
* @property onLastNameTextChange Handles changes to the last name text input.
* @property onLicenseNumberTextChange Handles changes to the license number text input.
* @property onIssuingCountryTextChange Handles changes to the issuing country text input.
* @property onIssuingStateTextChange Handles changes to the issuing state/province text input.
* @property onIssuingAuthorityTextChange Handles changes to the issuing authority text input.
* @property onLicenseClassTextChange Handles changes to the license class text input.
*/
@Suppress("LongParameterList")
data class VaultAddEditLicenseTypeHandlers(
val onFirstNameTextChange: (String) -> Unit,
val onMiddleNameTextChange: (String) -> Unit,
val onLastNameTextChange: (String) -> Unit,
val onLicenseNumberTextChange: (String) -> Unit,
val onIssuingCountryTextChange: (String) -> Unit,
val onIssuingStateTextChange: (String) -> Unit,
val onIssuingAuthorityTextChange: (String) -> Unit,
val onLicenseClassTextChange: (String) -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
/**
* Creates an instance of [VaultAddEditLicenseTypeHandlers] by binding actions to
* the provided [VaultAddEditViewModel].
*/
@Suppress("LongMethod")
fun create(
viewModel: VaultAddEditViewModel,
): VaultAddEditLicenseTypeHandlers =
VaultAddEditLicenseTypeHandlers(
onFirstNameTextChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.FirstNameTextChange(
firstName = it,
),
)
},
onMiddleNameTextChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.MiddleNameTextChange(
middleName = it,
),
)
},
onLastNameTextChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.LastNameTextChange(
lastName = it,
),
)
},
onLicenseNumberTextChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.LicenseNumberTextChange(
licenseNumber = it,
),
)
},
onIssuingCountryTextChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.IssuingCountryTextChange(
country = it,
),
)
},
onIssuingStateTextChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.IssuingStateTextChange(
state = it,
),
)
},
onIssuingAuthorityTextChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.IssuingAuthorityTextChange(
authority = it,
),
)
},
onLicenseClassTextChange = {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.LicenseClassTextChange(
licenseClass = it,
),
)
},
)
}
}
@@ -1692,7 +1692,7 @@ data class VaultItemState(
VaultItemCipherType.SECURE_NOTE -> BitwardenString.view_note.asText()
VaultItemCipherType.SSH_KEY -> BitwardenString.view_ssh_key.asText()
VaultItemCipherType.BANK_ACCOUNT -> BitwardenString.view_bank_account.asText()
VaultItemCipherType.DRIVERS_LICENSE -> BitwardenString.view_drivers_license.asText()
VaultItemCipherType.DRIVERS_LICENSE -> BitwardenString.view_license.asText()
VaultItemCipherType.PASSPORT -> BitwardenString.view_passport.asText()
}
@@ -2779,6 +2779,138 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {
}
}
@Test
fun `in ItemType_License changing first name should trigger FirstNameTextChange`() {
mutableStateFlow.value = DEFAULT_STATE_LICENSE
composeTestRule
.onNodeWithTextAfterScroll(text = "First name")
.performTextInput(text = "Missy")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.FirstNameTextChange(
firstName = "Missy",
),
)
}
}
@Test
fun `in ItemType_License changing middle name should trigger MiddleNameTextChange`() {
mutableStateFlow.value = DEFAULT_STATE_LICENSE
composeTestRule
.onNodeWithTextAfterScroll(text = "Middle name")
.performTextInput(text = "Anne")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.MiddleNameTextChange(
middleName = "Anne",
),
)
}
}
@Test
fun `in ItemType_License changing last name should trigger LastNameTextChange`() {
mutableStateFlow.value = DEFAULT_STATE_LICENSE
composeTestRule
.onNodeWithTextAfterScroll(text = "Last name")
.performTextInput(text = "Katner")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.LastNameTextChange(
lastName = "Katner",
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in ItemType_License changing the license number text field should trigger LicenseNumberTextChange`() {
mutableStateFlow.value = DEFAULT_STATE_LICENSE
composeTestRule
.onNodeWithTextAfterScroll(text = "License number")
.performTextInput(text = "K123-456-789")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.LicenseNumberTextChange(
licenseNumber = "K123-456-789",
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in ItemType_License changing the issuing country text field should trigger IssuingCountryTextChange`() {
mutableStateFlow.value = DEFAULT_STATE_LICENSE
composeTestRule
.onNodeWithTextAfterScroll(text = "Issuing country")
.performTextInput(text = "USA")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.IssuingCountryTextChange(
country = "USA",
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in ItemType_License changing the issuing state text field should trigger IssuingStateTextChange`() {
mutableStateFlow.value = DEFAULT_STATE_LICENSE
composeTestRule
.onNodeWithTextAfterScroll(text = "Issuing state / province")
.performTextInput(text = "Wisconsin")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.IssuingStateTextChange(
state = "Wisconsin",
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in ItemType_License changing the issuing authority text field should trigger IssuingAuthorityTextChange`() {
mutableStateFlow.value = DEFAULT_STATE_LICENSE
composeTestRule
.onNodeWithTextAfterScroll(text = "Issuing authority")
.performTextInput(text = "DMV")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.IssuingAuthorityTextChange(
authority = "DMV",
),
)
}
}
@Test
fun `in ItemType_License changing license class should trigger LicenseClassTextChange`() {
mutableStateFlow.value = DEFAULT_STATE_LICENSE
composeTestRule
.onNodeWithTextAfterScroll(text = "License class")
.performTextInput(text = "Class D")
verify {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.LicenseClassTextChange(
licenseClass = "Class D",
),
)
}
}
@Test
fun `clicking Add field button should allow creation of Linked type`() {
mutableStateFlow.value = DEFAULT_STATE_LOGIN
@@ -4889,6 +5021,22 @@ class VaultAddEditScreenTest : BitwardenComposeTest() {
isCardScannerEnabled = false,
)
private val DEFAULT_STATE_LICENSE = VaultAddEditState(
vaultAddEditType = VaultAddEditType.AddItem,
cipherType = VaultItemCipherType.DRIVERS_LICENSE,
viewState = VaultAddEditState.ViewState.Content(
common = VaultAddEditState.ViewState.Content.Common(),
type = VaultAddEditState.ViewState.Content.ItemType.License(),
isIndividualVaultDisabled = false,
),
dialog = null,
bottomSheetState = null,
shouldShowCoachMarkTour = false,
defaultUriMatchType = UriMatchTypeModel.EXACT,
hasPremium = false,
isCardScannerEnabled = false,
)
private val DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS = VaultAddEditState(
viewState = VaultAddEditState.ViewState.Content(
common = VaultAddEditState.ViewState.Content.Common(
@@ -2408,9 +2408,8 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `in add mode, SaveClick with a Drivers License item should emit ShowSnackbar without saving`() =
fun `in add mode, SaveClick with a License item should emit ShowSnackbar without saving`() =
runTest {
mutableVaultDataFlow.value = DataState.Loaded(createVaultData())
val licenseState = createVaultAddItemState(
@@ -2522,12 +2521,11 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
assertTrue(itemType.vaultLinkedFieldTypes.isEmpty())
}
@Suppress("MaxLineLength")
@Test
fun `ItemType DriversLicense should expose DRIVERS_LICENSE itemTypeOption and not be SDK supported`() {
fun `ItemType License should expose DRIVERS_LICENSE itemTypeOption and not be SDK supported`() {
val itemType = VaultAddEditState.ViewState.Content.ItemType.License()
assertEquals(
VaultAddEditState.ItemTypeOption.DRIVERS_LICENSE,
VaultAddEditState.ItemTypeOption.LICENSE,
itemType.itemTypeOption,
)
assertFalse(itemType.isSdkSupported)
@@ -2554,7 +2552,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
baseAddState.copy(cipherType = VaultItemCipherType.BANK_ACCOUNT).screenDisplayName,
)
assertEquals(
BitwardenString.new_drivers_license.asText(),
BitwardenString.new_license.asText(),
baseAddState.copy(cipherType = VaultItemCipherType.DRIVERS_LICENSE).screenDisplayName,
)
assertEquals(
@@ -2574,7 +2572,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
baseEditState.copy(cipherType = VaultItemCipherType.BANK_ACCOUNT).screenDisplayName,
)
assertEquals(
BitwardenString.edit_drivers_license.asText(),
BitwardenString.edit_license.asText(),
baseEditState.copy(cipherType = VaultItemCipherType.DRIVERS_LICENSE).screenDisplayName,
)
assertEquals(
@@ -2594,7 +2592,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
baseCloneState.copy(cipherType = VaultItemCipherType.BANK_ACCOUNT).screenDisplayName,
)
assertEquals(
BitwardenString.new_drivers_license.asText(),
BitwardenString.new_license.asText(),
baseCloneState.copy(cipherType = VaultItemCipherType.DRIVERS_LICENSE).screenDisplayName,
)
assertEquals(
@@ -4189,6 +4187,159 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
}
}
@Nested
inner class VaultAddEditLicenseTypeItemActions {
private lateinit var viewModel: VaultAddEditViewModel
private lateinit var vaultAddItemInitialState: VaultAddEditState
private lateinit var licenseInitialSavedStateHandle: SavedStateHandle
@BeforeEach
fun setup() {
mutableVaultDataFlow.value = DataState.Loaded(
createVaultData(cipherListView = createMockCipherListView(1)),
)
vaultAddItemInitialState = createVaultAddItemState(
vaultItemCipherType = VaultItemCipherType.DRIVERS_LICENSE,
typeContentViewState =
VaultAddEditState.ViewState.Content.ItemType.License(),
)
licenseInitialSavedStateHandle = createSavedStateHandleWithState(
state = vaultAddItemInitialState,
vaultAddEditType = VaultAddEditType.AddItem,
vaultItemCipherType = VaultItemCipherType.DRIVERS_LICENSE,
)
viewModel = createAddVaultItemViewModel(
savedStateHandle = licenseInitialSavedStateHandle,
)
}
private fun expectedLicense(
block: VaultAddEditState.ViewState.Content.ItemType.License.() ->
VaultAddEditState.ViewState.Content.ItemType.License,
): VaultAddEditState =
createVaultAddItemState(
vaultItemCipherType = VaultItemCipherType.DRIVERS_LICENSE,
typeContentViewState = VaultAddEditState
.ViewState
.Content
.ItemType
.License()
.block(),
)
@Test
fun `FirstNameTextChange should update first name`() = runTest {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.FirstNameTextChange(
firstName = "Missy",
),
)
assertEquals(
expectedLicense { copy(firstName = "Missy") },
viewModel.stateFlow.value,
)
}
@Test
fun `MiddleNameTextChange should update middle name`() = runTest {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.MiddleNameTextChange(
middleName = "Anne",
),
)
assertEquals(
expectedLicense { copy(middleName = "Anne") },
viewModel.stateFlow.value,
)
}
@Test
fun `LastNameTextChange should update last name`() = runTest {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.LastNameTextChange(
lastName = "Katner",
),
)
assertEquals(
expectedLicense { copy(lastName = "Katner") },
viewModel.stateFlow.value,
)
}
@Test
fun `LicenseNumberTextChange should update license number`() = runTest {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.LicenseNumberTextChange(
licenseNumber = "K123-456-789",
),
)
assertEquals(
expectedLicense { copy(licenseNumber = "K123-456-789") },
viewModel.stateFlow.value,
)
}
@Test
fun `IssuingCountryTextChange should update issuing country`() = runTest {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.IssuingCountryTextChange(
country = "USA",
),
)
assertEquals(
expectedLicense { copy(issuingCountry = "USA") },
viewModel.stateFlow.value,
)
}
@Test
fun `IssuingStateTextChange should update issuing state`() = runTest {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.IssuingStateTextChange(
state = "Wisconsin",
),
)
assertEquals(
expectedLicense { copy(issuingState = "Wisconsin") },
viewModel.stateFlow.value,
)
}
@Test
fun `IssuingAuthorityTextChange should update issuing authority`() = runTest {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.IssuingAuthorityTextChange(
authority = "DMV",
),
)
assertEquals(
expectedLicense { copy(issuingAuthority = "DMV") },
viewModel.stateFlow.value,
)
}
@Test
fun `LicenseClassTextChange should update license class`() = runTest {
viewModel.trySendAction(
VaultAddEditAction.ItemType.LicenseType.LicenseClassTextChange(
licenseClass = "Class D",
),
)
assertEquals(
expectedLicense { copy(licenseClass = "Class D") },
viewModel.stateFlow.value,
)
}
}
@Test
fun `NumberVisibilityChange should log an event when in edit mode and password is visible`() =
runTest {
@@ -3831,7 +3831,7 @@ class VaultViewModelTest : BaseViewModelTest() {
@Suppress("MaxLineLength")
@Test
fun `SelectAddItemType action should not exclude bank account, drivers license, or passport when NewItemTypes flag is enabled`() {
fun `SelectAddItemType action should not exclude bank account, license, or passport when NewItemTypes flag is enabled`() {
mutableNewItemTypesFlagFlow.value = true
val viewModel = createViewModel()
+3 -4
View File
@@ -1306,19 +1306,18 @@ Do you want to switch to this account?</string>
<string name="subscription_error">Subscription error</string>
<string name="trouble_loading_subscription">We couldnt load your subscription details. Please try again.</string>
<string name="view_bank_account">View bank account</string>
<string name="view_drivers_license">View license</string>
<string name="view_license">View license</string>
<string name="view_passport">View passport</string>
<string name="new_bank_account">New bank account</string>
<string name="new_drivers_license">New license</string>
<string name="new_license">New license</string>
<string name="new_passport">New passport</string>
<string name="edit_bank_account">Edit bank account</string>
<string name="edit_drivers_license">Edit license</string>
<string name="edit_license">Edit license</string>
<string name="edit_passport">Edit passport</string>
<string name="type_bank_account">Bank account</string>
<string name="type_license">License</string>
<string name="type_passport">Passport</string>
<string name="bank_accounts">Bank accounts</string>
<string name="drivers_licenses">Drivers licenses</string>
<string name="passports">Passports</string>
<string name="bank_name">Bank name</string>
<string name="name_on_account">Name on account</string>