[PM-41296] feat: Display Identities vault list when autofilling for an Identity (#7373)

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
This commit is contained in:
aj-rosadoandclaude[bot] authored and GitHub committed 2026-09-21 18:11:46 +00:00
1 parent d70e13393e
commit 5e689276bf
11 files changed
+163 -26

No files matched your search

@@ -23,6 +23,7 @@ data class AutofillSelectionData(
enum class Type {
CARD,
LOGIN,
IDENTITY,
}
/**
@@ -37,10 +37,7 @@ fun FilledData.buildVaultItemDataset(
type = when (this.originalPartition) {
is AutofillPartition.Card -> AutofillSelectionData.Type.CARD
is AutofillPartition.Login -> AutofillSelectionData.Type.LOGIN
// AutofillSelectionData.Type has no IDENTITY value yet (deliberately deferred -- it
// cascades into the vault item listing/add-edit UI flows). This is a no-op placeholder
// today since nothing yet produces an Identity-only partition.
is AutofillPartition.Identity -> AutofillSelectionData.Type.LOGIN
is AutofillPartition.Identity -> AutofillSelectionData.Type.IDENTITY
},
uri = this.uri,
)
@@ -10,4 +10,5 @@ fun AutofillSelectionData.Type.toVaultItemListingType(): VaultItemListingType =
when (this) {
AutofillSelectionData.Type.CARD -> VaultItemListingType.Card
AutofillSelectionData.Type.LOGIN -> VaultItemListingType.Login
AutofillSelectionData.Type.IDENTITY -> VaultItemListingType.Identity
}
@@ -32,6 +32,10 @@ fun AutofillSelectionData.toDefaultAddTypeContent(
),
)
}
AutofillSelectionData.Type.IDENTITY -> {
VaultAddEditState.ViewState.Content.ItemType.Identity()
}
}
return VaultAddEditState.ViewState.Content(
common = VaultAddEditState.ViewState.Content.Common(
@@ -2867,6 +2867,20 @@ class VaultItemListingViewModel @Inject constructor(
)
}
}
AutofillSelectionData.Type.IDENTITY -> {
this.map { vaultData ->
vaultData.copy(
decryptCipherListResult = vaultData.decryptCipherListResult.copy(
successes = vaultData
.decryptCipherListResult
.successes
.filter { it.type is CipherListViewType.Identity },
failures = emptyList(),
),
)
}
}
}
}
@@ -3039,19 +3053,23 @@ data class VaultItemListingState(
val appBarTitle: Text
get() = autofillSelectionData
?.let { data ->
data.uri
?.toHostOrPathOrNull()
?.let {
when (data.type) {
AutofillSelectionData.Type.CARD -> {
BitwardenString.select_a_card_for_x.asText(it)
}
AutofillSelectionData.Type.LOGIN -> {
BitwardenString.items_for_uri.asText(it)
}
}
when (data.type) {
AutofillSelectionData.Type.IDENTITY -> {
BitwardenString.choose_an_identity.asText()
}
AutofillSelectionData.Type.CARD -> {
data.uri
?.toHostOrPathOrNull()
?.let { BitwardenString.select_a_card_for_x.asText(it) }
}
AutofillSelectionData.Type.LOGIN -> {
data.uri
?.toHostOrPathOrNull()
?.let { BitwardenString.items_for_uri.asText(it) }
}
}
}
?: createCredentialRequest
?.relyingPartyIdOrNull
@@ -11,6 +11,7 @@ class AutofillSelectionDataExtensionsTest {
mapOf(
AutofillSelectionData.Type.CARD to VaultItemListingType.Card,
AutofillSelectionData.Type.LOGIN to VaultItemListingType.Login,
AutofillSelectionData.Type.IDENTITY to VaultItemListingType.Identity,
)
.forEach { (type, expected) ->
assertEquals(
@@ -39,6 +39,23 @@ class AutofillSelectionDataExtensionsTest {
)
}
@Test
fun `toDefaultAddTypeContent for an Identity type should return the correct Content`() {
assertEquals(
VaultAddEditState.ViewState.Content(
common = VaultAddEditState.ViewState.Content.Common(),
isIndividualVaultDisabled = false,
type = VaultAddEditState.ViewState.Content.ItemType.Identity(),
),
AutofillSelectionData(
type = AutofillSelectionData.Type.IDENTITY,
framework = AutofillSelectionData.Framework.AUTOFILL,
uri = null,
)
.toDefaultAddTypeContent(isIndividualVaultDisabled = false),
)
}
@Test
fun `toDefaultAddTypeContent for a Login type should return the correct Content`() {
every { UUID.randomUUID().toString() } returns "uuid"
@@ -1371,6 +1371,33 @@ class VaultItemListingScreenTest : BitwardenComposeTest() {
.assertIsDisplayed()
}
@Test
fun `topBar title should be displayed for identity autofill selection regardless of the uri`() {
mutableStateFlow.update {
it.copy(
itemListingType = VaultItemListingState.ItemListingType.Vault.Identity,
autofillSelectionData = AUTOFILL_SELECTION_DATA.copy(
type = AutofillSelectionData.Type.IDENTITY,
uri = null,
),
)
}
composeTestRule
.onNodeWithText(text = "Choose an identity")
.assertIsDisplayed()
mutableStateFlow.update {
it.copy(
autofillSelectionData = AUTOFILL_SELECTION_DATA.copy(
type = AutofillSelectionData.Type.IDENTITY,
),
)
}
composeTestRule
.onNodeWithText(text = "Choose an identity")
.assertIsDisplayed()
}
@Test
fun `on overflow item click should display menu`() {
composeTestRule
@@ -2906,6 +2906,76 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
)
}
@Suppress("MaxLineLength")
@Test
fun `vaultDataStateFlow Loaded with items and autofill filtering for Identity should update ViewState to Content with filtered data`() =
runTest {
setupMockUri()
val cipherView1 = createMockCipherListView(
number = 1,
type = CipherListViewType.Identity,
)
val cipherView2 = createMockCipherListView(
number = 2,
type = CipherListViewType.Login(
createMockLoginListView(number = 2),
),
)
val autofillSelectionData = AutofillSelectionData(
type = AutofillSelectionData.Type.IDENTITY,
framework = AutofillSelectionData.Framework.AUTOFILL,
uri = "https://www.test.com",
)
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.AutofillSelection(
autofillSelectionData = autofillSelectionData,
shouldFinishWhenComplete = true,
)
val dataState = DataState.Loaded(
data = VaultData(
decryptCipherListResult = createMockDecryptCipherListResult(
number = 1,
successes = listOf(cipherView1, cipherView2),
),
folderViewList = listOf(createMockFolderView(number = 1)),
collectionViewList = listOf(createMockCollectionView(number = 1)),
sendViewList = listOf(createMockSendView(number = 1)),
),
)
val viewModel = createVaultItemListingViewModel(
savedStateHandle = createSavedStateHandleWithVaultItemListingType(
vaultItemListingType = VaultItemListingType.Identity,
),
)
mutableVaultDataStateFlow.value = dataState
assertEquals(
createVaultItemListingState(
itemListingType = VaultItemListingState.ItemListingType.Vault.Identity,
viewState = VaultItemListingState.ViewState.Content(
displayCollectionList = emptyList(),
displayItemList = listOf(
createMockDisplayItemForCipher(
number = 1,
cipherType = CipherType.IDENTITY,
subtitle = "mockSubtitle-1",
secondSubtitleTestTag = "PasskeySite",
subtitleTestTag = "PasswordName",
isAutofill = true,
),
),
displayFolderList = emptyList(),
),
)
.copy(autofillSelectionData = autofillSelectionData),
viewModel.stateFlow.value,
)
}
@Suppress("MaxLineLength")
@Test
fun `vaultDataStateFlow Loaded with items and totp data filtering should update ViewState to Content with filtered data`() =
@@ -97,7 +97,7 @@ fun createMockDisplayItemForCipher(
secondSubtitle = secondSubtitle,
secondSubtitleTestTag = secondSubtitleTestTag,
subtitle = subtitle,
subtitleTestTag = "CipherSubTitleLabel",
subtitleTestTag = subtitleTestTag,
iconData = IconData.Local(BitwardenDrawable.ic_note),
extraIconList = persistentListOf(
IconData.Local(
@@ -145,7 +145,7 @@ fun createMockDisplayItemForCipher(
secondSubtitle = secondSubtitle,
secondSubtitleTestTag = secondSubtitleTestTag,
subtitle = subtitle,
subtitleTestTag = "CipherSubTitleLabel",
subtitleTestTag = subtitleTestTag,
iconData = IconData.Local(BitwardenDrawable.ic_payment_card),
extraIconList = persistentListOf(
IconData.Local(
@@ -197,7 +197,7 @@ fun createMockDisplayItemForCipher(
secondSubtitle = secondSubtitle,
secondSubtitleTestTag = secondSubtitleTestTag,
subtitle = subtitle,
subtitleTestTag = "CipherSubTitleLabel",
subtitleTestTag = subtitleTestTag,
iconData = IconData.Local(BitwardenDrawable.ic_id_card),
extraIconList = persistentListOf(
IconData.Local(
@@ -225,9 +225,9 @@ fun createMockDisplayItemForCipher(
ListingItemOverflowAction.VaultAction.ArchiveClick(cipherId = "mockId-$number"),
),
optionsTestTag = "CipherOptionsButton",
isAutofill = false,
isCredentialCreation = false,
shouldShowMasterPasswordReprompt = false,
isAutofill = isAutofill,
isCredentialCreation = isCredentialCreation,
shouldShowMasterPasswordReprompt = shouldShowMasterPasswordReprompt,
iconTestTag = "IdentityCipherIcon",
itemType = VaultItemListingState.DisplayItem.ItemType.Vault(type = cipherType),
)
@@ -241,7 +241,7 @@ fun createMockDisplayItemForCipher(
secondSubtitle = secondSubtitle,
secondSubtitleTestTag = secondSubtitleTestTag,
subtitle = subtitle,
subtitleTestTag = "CipherSubTitleLabel",
subtitleTestTag = subtitleTestTag,
iconData = IconData.Local(BitwardenDrawable.ic_ssh_key),
extraIconList = persistentListOf(
IconData.Local(
@@ -285,7 +285,7 @@ fun createMockDisplayItemForCipher(
secondSubtitle = secondSubtitle,
secondSubtitleTestTag = secondSubtitleTestTag,
subtitle = subtitle,
subtitleTestTag = "CipherSubTitleLabel",
subtitleTestTag = subtitleTestTag,
iconData = IconData.Local(BitwardenDrawable.ic_payment_card),
extraIconList = persistentListOf(
IconData.Local(
@@ -329,7 +329,7 @@ fun createMockDisplayItemForCipher(
secondSubtitle = secondSubtitle,
secondSubtitleTestTag = secondSubtitleTestTag,
subtitle = subtitle,
subtitleTestTag = "CipherSubTitleLabel",
subtitleTestTag = subtitleTestTag,
iconData = IconData.Local(BitwardenDrawable.ic_note),
extraIconList = persistentListOf(
IconData.Local(
@@ -373,7 +373,7 @@ fun createMockDisplayItemForCipher(
secondSubtitle = secondSubtitle,
secondSubtitleTestTag = secondSubtitleTestTag,
subtitle = subtitle,
subtitleTestTag = "CipherSubTitleLabel",
subtitleTestTag = subtitleTestTag,
iconData = IconData.Local(BitwardenDrawable.ic_note),
extraIconList = persistentListOf(
IconData.Local(
+1
View File
@@ -1161,6 +1161,7 @@ Do you want to switch to this account?</string>
<item quantity="other">Bitwarden could not decrypt %1$d vault items. Copy and share this error report with customer success to avoid additional data loss.</item>
</plurals>
<string name="select_a_card_for_x">Select a card for %s</string>
<string name="choose_an_identity">Choose an identity</string>
<string name="advanced_options">Advanced options</string>
<string name="more_about_match_detection"><annotation link="moreAboutMatchDetection">More about match detection</annotation></string>
<string name="keep_your_credential_secure">Keep your credentials secure</string>