diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts index 92f6d007c57..55e4bf1a789 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.spec.ts @@ -320,6 +320,237 @@ describe("InlineMenuFieldQualificationService", () => { }); }); - describe("validating a username field for a login form", () => {}); + describe("qualifying a username field for a login form", () => { + describe("an invalid username field", () => { + ["username", "email"].forEach((autoCompleteType) => { + it(`has a ${autoCompleteType} 'autoCompleteType' value when structured on a page with new password fields`, () => { + const field = mock({ + type: "text", + autoCompleteType, + htmlID: "user-username", + htmlName: "user-username", + placeholder: "user-username", + }); + const passwordField = mock({ + type: "password", + autoCompleteType: "new-password", + htmlID: "user-password", + htmlName: "user-password", + placeholder: "user-password", + }); + pageDetails.fields = [field, passwordField]; + + expect( + inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails), + ).toBe(false); + }); + }); + + ["new", "change", "neue", "ändern"].forEach((keyword) => { + it(`has a keyword of ${keyword} that indicates a 'new or changed' username is being filled`, () => { + const field = mock({ + type: "text", + autoCompleteType: "", + htmlID: "user-username", + htmlName: "user-username", + placeholder: `${keyword} username`, + }); + + expect( + inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails), + ).toBe(false); + }); + }); + + describe("does not have a parent form element", () => { + beforeEach(() => { + pageDetails.forms = {}; + }); + + it("is structured on a page with multiple password fields", () => { + const field = mock({ + type: "text", + autoCompleteType: "", + htmlID: "user-username", + htmlName: "user-username", + placeholder: "user-username", + }); + const passwordField = mock({ + type: "password", + autoCompleteType: "current-password", + htmlID: "user-password", + htmlName: "user-password", + placeholder: "user-password", + }); + const secondPasswordField = mock({ + type: "password", + autoCompleteType: "current-password", + htmlID: "some-other-password", + htmlName: "some-other-password", + placeholder: "some-other-password", + }); + pageDetails.fields = [field, passwordField, secondPasswordField]; + + expect( + inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails), + ).toBe(false); + }); + }); + + describe("has a parent form element", () => { + let form: MockProxy; + + beforeEach(() => { + form = mock({ opid: "validFormId" }); + pageDetails.forms = { + validFormId: form, + }; + }); + + it("is structured on a page with no password fields and has a disabled `autoCompleteType` value", () => { + const field = mock({ + type: "text", + autoCompleteType: "off", + htmlID: "user-username", + htmlName: "user-username", + placeholder: "user-username", + form: "validFormId", + }); + pageDetails.fields = [field]; + + expect( + inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails), + ).toBe(false); + }); + + it("is structured on a page with no password fields but has other types of fields in the form", () => { + const field = mock({ + type: "text", + autoCompleteType: "", + htmlID: "user-username", + htmlName: "user-username", + placeholder: "user-username", + form: "validFormId", + }); + const otherField = mock({ + type: "number", + autoCompleteType: "", + htmlID: "some-other-field", + htmlName: "some-other-field", + placeholder: "some-other-field", + form: "validFormId", + }); + pageDetails.fields = [field, otherField]; + + expect( + inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails), + ).toBe(false); + }); + }); + }); + + describe("a valid username field", () => { + ["username", "email"].forEach((autoCompleteType) => { + it(`has a ${autoCompleteType} 'autoCompleteType' value`, () => { + const field = mock({ + type: "text", + autoCompleteType, + htmlID: "user-username", + htmlName: "user-username", + placeholder: "user-username", + }); + const passwordField = mock({ + type: "password", + autoCompleteType: "current-password", + htmlID: "user-password", + htmlName: "user-password", + placeholder: "user-password", + }); + pageDetails.fields = [field, passwordField]; + + expect( + inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails), + ).toBe(true); + }); + }); + + describe("does not have a parent form element", () => { + beforeEach(() => { + pageDetails.forms = {}; + }); + + it("is structured on a page with a single password field", () => { + const field = mock({ + type: "text", + autoCompleteType: "off", + htmlID: "user-username", + htmlName: "user-username", + placeholder: "user-username", + }); + const passwordField = mock({ + type: "password", + autoCompleteType: "current-password", + htmlID: "user-password", + htmlName: "user-password", + placeholder: "user-password", + }); + pageDetails.fields = [field, passwordField]; + + expect( + inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails), + ).toBe(true); + }); + + it("has a non-disabled autoCompleteType and is structured on a page with no other password fields", () => { + const field = mock({ + type: "text", + autoCompleteType: "", + htmlID: "user-username", + htmlName: "user-username", + placeholder: "user-username", + }); + + expect( + inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails), + ).toBe(true); + }); + }); + + describe("has a parent form element", () => { + let form: MockProxy; + + beforeEach(() => { + form = mock({ opid: "validFormId" }); + pageDetails.forms = { + validFormId: form, + }; + }); + + it("is structured on a page with a single password field", () => { + const field = mock({ + type: "text", + autoCompleteType: "", + htmlID: "user-username", + htmlName: "user-username", + placeholder: "user-username", + form: "validFormId", + }); + const passwordField = mock({ + type: "password", + autoCompleteType: "current-password", + htmlID: "user-password", + htmlName: "user-password", + placeholder: "user-password", + form: "validFormId", + }); + pageDetails.fields = [field, passwordField]; + + expect( + inlineMenuFieldQualificationService.isFieldForLoginForm(field, pageDetails), + ).toBe(true); + }); + }); + }); + }); }); }); diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts index 62963bdaeaf..df467224e13 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts @@ -140,15 +140,15 @@ export class InlineMenuFieldQualificationService if (!parentForm) { // If a formless field is present in a webpage with a single password field, we // should assume that it is part of a login workflow. - if (passwordFieldsInPageDetails.length === 1) { + const visiblePasswordFieldsInPageDetails = passwordFieldsInPageDetails.filter( + (passwordField) => passwordField.viewable, + ); + if (visiblePasswordFieldsInPageDetails.length === 1) { return true; } // If more than a single password field exists on the page, we should assume that the field // is part of an account creation form. - const visiblePasswordFieldsInPageDetails = passwordFieldsInPageDetails.filter( - (passwordField) => passwordField.viewable, - ); if (visiblePasswordFieldsInPageDetails.length > 1) { return false; } @@ -178,13 +178,6 @@ export class InlineMenuFieldQualificationService // If a single password field exists within the page details, and that password field is part of // the same form as the provided field, we should assume that the field is part of a login form. - if ( - passwordFieldsInPageDetails.length === 1 && - field.form === passwordFieldsInPageDetails[0].form - ) { - return true; - } - // If multiple visible password fields exist within the page details, we need to assume that the // provided field is part of an account creation form. const visiblePasswordFieldsInPageDetails = passwordFieldsInPageDetails.filter(