From b102ee4bdf5c2211cdf8b95f3e442eff93fb9da5 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Sun, 2 Nov 2025 08:14:30 -0800 Subject: [PATCH] [PM-26653] - fix uri match strategy logic (#17142) * fix uri match strategy logic * fix variable name * update logic and specs * add test case --- .../item-more-options.component.spec.ts | 144 ++++++++++++++---- .../item-more-options.component.ts | 13 +- 2 files changed, 125 insertions(+), 32 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts index 66bbafb7c31..5fcc4f78eb3 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts @@ -181,10 +181,21 @@ describe("ItemMoreOptionsComponent", () => { expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); }); + it("does not show the exact match dialog when the default match strategy is Exact and autofill confirmation is not to be shown", async () => { + // autofill confirmation dialog is not shown when either the feature flag is disabled or search text is not present + uriMatchStrategy$.next(UriMatchStrategy.Exact); + autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com/path" }); + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); + }); + describe("autofill confirmation dialog", () => { beforeEach(() => { + // autofill confirmation dialog is shown when feature flag is enabled and search text is present featureFlag$.next(true); hasSearchText$.next(true); + uriMatchStrategy$.next(UriMatchStrategy.Domain); passwordRepromptService.passwordRepromptCheck.mockResolvedValue(true); }); @@ -243,47 +254,122 @@ describe("ItemMoreOptionsComponent", () => { }); describe("URI match strategy handling", () => { - it("shows the exact match dialog when the uri match strategy is Exact", async () => { - uriMatchStrategy$.next(UriMatchStrategy.Exact); - autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" }); + describe("when the default URI match strategy is Exact", () => { + beforeEach(() => { + uriMatchStrategy$.next(UriMatchStrategy.Exact); + }); - await component.doAutofill(); + it("shows the exact match dialog and not the password dialog", async () => { + autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" }); - expect(dialogService.openSimpleDialog).toHaveBeenCalledTimes(1); - expect(dialogService.openSimpleDialog).toHaveBeenCalledWith( - expect.objectContaining({ - title: expect.objectContaining({ key: "cannotAutofill" }), - content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), - type: "info", - }), - ); - expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); - expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).toHaveBeenCalledTimes(1); + expect(dialogService.openSimpleDialog).toHaveBeenCalledWith( + expect.objectContaining({ + title: expect.objectContaining({ key: "cannotAutofill" }), + content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), + type: "info", + }), + ); + expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); + expect(passwordRepromptService.passwordRepromptCheck).not.toHaveBeenCalled(); + expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); + }); }); - it("shows the exact match dialog and not the password reprompt dialog when the uri match strategy is Exact and the item has master password reprompt enabled", async () => { - uriMatchStrategy$.next(UriMatchStrategy.Exact); + describe("when the default URI match strategy is not Exact", () => { + beforeEach(() => { + mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled); + uriMatchStrategy$.next(UriMatchStrategy.Domain); + }); + it("does not show the exact match dialog", async () => { + cipherService.getFullCipherView.mockImplementation(async (c) => ({ + ...baseCipher, + ...c, + login: { + ...baseCipher.login, + uris: [ + { uri: "https://one.example.com", match: UriMatchStrategy.Exact }, + { uri: "https://page.example.com", match: UriMatchStrategy.Domain }, + ], + }, + })); + + autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); + + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); + }); + + it("shows the exact match dialog when the cipher has a single uri with a match strategy of Exact", async () => { + cipherService.getFullCipherView.mockImplementation(async (c) => ({ + ...baseCipher, + ...c, + login: { + ...baseCipher.login, + uris: [{ uri: "https://one.example.com", match: UriMatchStrategy.Exact }], + }, + })); + + autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" }); + + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).toHaveBeenCalledWith( + expect.objectContaining({ + title: expect.objectContaining({ key: "cannotAutofill" }), + content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), + type: "info", + }), + ); + expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); + expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); + }); + }); + + it("does not show the exact match dialog when the cipher has no uris", async () => { + mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled); + cipherService.getFullCipherView.mockImplementation(async (c) => ({ + ...baseCipher, + ...c, + login: { + ...baseCipher.login, + uris: [], + }, + })); + autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" }); await component.doAutofill(); - expect(dialogService.openSimpleDialog).toHaveBeenCalledTimes(1); - expect(dialogService.openSimpleDialog).toHaveBeenCalledWith( - expect.objectContaining({ - title: expect.objectContaining({ key: "cannotAutofill" }), - content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), - type: "info", - }), - ); - expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); - expect(passwordRepromptService.passwordRepromptCheck).not.toHaveBeenCalled(); - expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); + }); + + it("does not show the exact match dialog when the cipher has a uri with a match strategy of Exact and a uri with a match strategy of Domain", async () => { + mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled); + cipherService.getFullCipherView.mockImplementation(async (c) => ({ + ...baseCipher, + ...c, + login: { + ...baseCipher.login, + uris: [ + { uri: "https://one.example.com", match: UriMatchStrategy.Exact }, + { uri: "https://page.example.com", match: UriMatchStrategy.Domain }, + ], + }, + })); + + autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); + + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); }); }); it("hides the 'Fill and Save' button when showAutofillConfirmation$ is true", async () => { - // Enable both feature flag and search text → makes showAutofillConfirmation$ true - fixture.detectChanges(); await fixture.whenStable(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 6bc9d3aa207..7bbef3f79a7 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -202,8 +202,17 @@ export class ItemMoreOptionsComponent { async doAutofill() { const cipher = await this.cipherService.getFullCipherView(this.cipher); + const uris = cipher.login?.uris ?? []; + const cipherHasAllExactMatchLoginUris = + uris.length > 0 && uris.every((u) => u.uri && u.match === UriMatchStrategy.Exact); + + const showAutofillConfirmation = await firstValueFrom(this.showAutofillConfirmation$); const uriMatchStrategy = await firstValueFrom(this.uriMatchStrategy$); - if (uriMatchStrategy === UriMatchStrategy.Exact) { + + if ( + showAutofillConfirmation && + (cipherHasAllExactMatchLoginUris || uriMatchStrategy === UriMatchStrategy.Exact) + ) { await this.dialogService.openSimpleDialog({ title: { key: "cannotAutofill" }, content: { key: "cannotAutofillExactMatch" }, @@ -218,8 +227,6 @@ export class ItemMoreOptionsComponent { return; } - const showAutofillConfirmation = await firstValueFrom(this.showAutofillConfirmation$); - if (!showAutofillConfirmation) { await this.vaultPopupAutofillService.doAutofill(cipher, true, true); return;