1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 22:33:35 +00:00

[PM-26653] - fix uri match strategy logic (#17142)

* fix uri match strategy logic

* fix variable name

* update logic and specs

* add test case
This commit is contained in:
Jordan Aasen
2025-11-02 08:14:30 -08:00
committed by GitHub
parent e68a471655
commit b102ee4bdf
2 changed files with 125 additions and 32 deletions

View File

@@ -181,10 +181,21 @@ describe("ItemMoreOptionsComponent", () => {
expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); 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", () => { describe("autofill confirmation dialog", () => {
beforeEach(() => { beforeEach(() => {
// autofill confirmation dialog is shown when feature flag is enabled and search text is present
featureFlag$.next(true); featureFlag$.next(true);
hasSearchText$.next(true); hasSearchText$.next(true);
uriMatchStrategy$.next(UriMatchStrategy.Domain);
passwordRepromptService.passwordRepromptCheck.mockResolvedValue(true); passwordRepromptService.passwordRepromptCheck.mockResolvedValue(true);
}); });
@@ -243,47 +254,122 @@ describe("ItemMoreOptionsComponent", () => {
}); });
describe("URI match strategy handling", () => { describe("URI match strategy handling", () => {
it("shows the exact match dialog when the uri match strategy is Exact", async () => { describe("when the default URI match strategy is Exact", () => {
uriMatchStrategy$.next(UriMatchStrategy.Exact); beforeEach(() => {
autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" }); 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); await component.doAutofill();
expect(dialogService.openSimpleDialog).toHaveBeenCalledWith(
expect.objectContaining({ expect(dialogService.openSimpleDialog).toHaveBeenCalledTimes(1);
title: expect.objectContaining({ key: "cannotAutofill" }), expect(dialogService.openSimpleDialog).toHaveBeenCalledWith(
content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), expect.objectContaining({
type: "info", title: expect.objectContaining({ key: "cannotAutofill" }),
}), content: expect.objectContaining({ key: "cannotAutofillExactMatch" }),
); type: "info",
expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); }),
expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); );
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 () => { describe("when the default URI match strategy is not Exact", () => {
uriMatchStrategy$.next(UriMatchStrategy.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" }); autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" });
await component.doAutofill(); await component.doAutofill();
expect(dialogService.openSimpleDialog).toHaveBeenCalledTimes(1); expect(dialogService.openSimpleDialog).not.toHaveBeenCalled();
expect(dialogService.openSimpleDialog).toHaveBeenCalledWith( });
expect.objectContaining({
title: expect.objectContaining({ key: "cannotAutofill" }), 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 () => {
content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled);
type: "info", cipherService.getFullCipherView.mockImplementation(async (c) => ({
}), ...baseCipher,
); ...c,
expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); login: {
expect(passwordRepromptService.passwordRepromptCheck).not.toHaveBeenCalled(); ...baseCipher.login,
expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); 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 () => { it("hides the 'Fill and Save' button when showAutofillConfirmation$ is true", async () => {
// Enable both feature flag and search text → makes showAutofillConfirmation$ true
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();

View File

@@ -202,8 +202,17 @@ export class ItemMoreOptionsComponent {
async doAutofill() { async doAutofill() {
const cipher = await this.cipherService.getFullCipherView(this.cipher); 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$); const uriMatchStrategy = await firstValueFrom(this.uriMatchStrategy$);
if (uriMatchStrategy === UriMatchStrategy.Exact) {
if (
showAutofillConfirmation &&
(cipherHasAllExactMatchLoginUris || uriMatchStrategy === UriMatchStrategy.Exact)
) {
await this.dialogService.openSimpleDialog({ await this.dialogService.openSimpleDialog({
title: { key: "cannotAutofill" }, title: { key: "cannotAutofill" },
content: { key: "cannotAutofillExactMatch" }, content: { key: "cannotAutofillExactMatch" },
@@ -218,8 +227,6 @@ export class ItemMoreOptionsComponent {
return; return;
} }
const showAutofillConfirmation = await firstValueFrom(this.showAutofillConfirmation$);
if (!showAutofillConfirmation) { if (!showAutofillConfirmation) {
await this.vaultPopupAutofillService.doAutofill(cipher, true, true); await this.vaultPopupAutofillService.doAutofill(cipher, true, true);
return; return;