mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[PM-8027] Working through jest tests for the InlineMenuFieldQualificationService
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
import AutofillField from "../../models/autofill-field";
|
||||||
|
import AutofillPageDetails from "../../models/autofill-page-details";
|
||||||
|
|
||||||
|
export interface InlineMenuFieldQualificationsService {
|
||||||
|
isFieldForLoginForm(field: AutofillField, pageDetails: AutofillPageDetails): boolean;
|
||||||
|
}
|
||||||
@@ -184,7 +184,12 @@ describe("AutofillOverlayContentService", () => {
|
|||||||
|
|
||||||
describe("skips setup for ignored form fields", () => {
|
describe("skips setup for ignored form fields", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
autofillFieldData = mock<AutofillField>();
|
autofillFieldData = mock<AutofillField>({
|
||||||
|
type: "text",
|
||||||
|
htmlName: "username",
|
||||||
|
htmlID: "username",
|
||||||
|
placeholder: "username",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores fields that are readonly", async () => {
|
it("ignores fields that are readonly", async () => {
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
|||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
this.formFieldElements.has(formFieldElement) ||
|
this.formFieldElements.has(formFieldElement) ||
|
||||||
!this.inlineMenuFieldQualificationService.isFieldForLoginForm(autofillFieldData, pageDetails)
|
this.isIgnoredField(autofillFieldData, pageDetails)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -700,6 +700,33 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies if the field should have the autofill overlay setup on it. Currently, this is mainly
|
||||||
|
* determined by whether the field correlates with a login cipher. This method will need to be
|
||||||
|
* updated in the future to support other types of forms.
|
||||||
|
*
|
||||||
|
* @param autofillFieldData - Autofill field data captured from the form field element.
|
||||||
|
* @param pageDetails - The collected page details from the tab.
|
||||||
|
*/
|
||||||
|
private isIgnoredField(
|
||||||
|
autofillFieldData: AutofillField,
|
||||||
|
pageDetails: AutofillPageDetails,
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
autofillFieldData.readonly ||
|
||||||
|
autofillFieldData.disabled ||
|
||||||
|
!autofillFieldData.viewable ||
|
||||||
|
this.ignoredFieldTypes.has(autofillFieldData.type)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !this.inlineMenuFieldQualificationService.isFieldForLoginForm(
|
||||||
|
autofillFieldData,
|
||||||
|
pageDetails,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the autofill overlay button element. Will not attempt
|
* Creates the autofill overlay button element. Will not attempt
|
||||||
* to create the element if it already exists in the DOM.
|
* to create the element if it already exists in the DOM.
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import AutofillField from "../models/autofill-field";
|
||||||
|
import AutofillPageDetails from "../models/autofill-page-details";
|
||||||
|
|
||||||
|
import { AutoFillConstants } from "./autofill-constants";
|
||||||
|
import { InlineMenuFieldQualificationService } from "./inline-menu-field-qualification.service";
|
||||||
|
|
||||||
|
describe("InlineMenuFieldQualificationService", () => {
|
||||||
|
let pageDetails: MockProxy<AutofillPageDetails>;
|
||||||
|
let inlineMenuFieldQualificationService: InlineMenuFieldQualificationService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
pageDetails = mock<AutofillPageDetails>();
|
||||||
|
inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isFieldForLoginForm", () => {
|
||||||
|
describe("validating a password field for a login form", () => {
|
||||||
|
describe("an invalid password field", () => {
|
||||||
|
it("has a `new-password` autoCompleteType", () => {
|
||||||
|
const newPasswordField = mock<AutofillField>({
|
||||||
|
type: "password",
|
||||||
|
autoCompleteType: "new-password",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
inlineMenuFieldQualificationService.isFieldForLoginForm(newPasswordField, pageDetails),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has a type that is an excluded type", () => {
|
||||||
|
AutoFillConstants.ExcludedAutofillLoginTypes.forEach((excludedType) => {
|
||||||
|
const excludedField = mock<AutofillField>({
|
||||||
|
type: excludedType,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
inlineMenuFieldQualificationService.isFieldForLoginForm(excludedField, pageDetails),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("a valid password field", () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("validating a username field for a login form", () => {});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import AutofillField from "../models/autofill-field";
|
import AutofillField from "../models/autofill-field";
|
||||||
import AutofillPageDetails from "../models/autofill-page-details";
|
import AutofillPageDetails from "../models/autofill-page-details";
|
||||||
|
|
||||||
|
import { InlineMenuFieldQualificationsService as InlineMenuFieldQualificationsServiceInterface } from "./abstractions/inline-menu-field-qualifications.service";
|
||||||
import { AutoFillConstants } from "./autofill-constants";
|
import { AutoFillConstants } from "./autofill-constants";
|
||||||
|
|
||||||
export class InlineMenuFieldQualificationService {
|
export class InlineMenuFieldQualificationService
|
||||||
|
implements InlineMenuFieldQualificationsServiceInterface
|
||||||
|
{
|
||||||
private searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
|
private searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames);
|
||||||
private excludedAutofillLoginTypesSet = new Set(AutoFillConstants.ExcludedAutofillLoginTypes);
|
private excludedAutofillLoginTypesSet = new Set(AutoFillConstants.ExcludedAutofillLoginTypes);
|
||||||
private usernameFieldTypes = new Set(["text", "email", "number", "tel"]);
|
private usernameFieldTypes = new Set(["text", "email", "number", "tel"]);
|
||||||
@@ -14,6 +17,12 @@ export class InlineMenuFieldQualificationService {
|
|||||||
private autocompleteDisabledValues = new Set(["off", "false"]);
|
private autocompleteDisabledValues = new Set(["off", "false"]);
|
||||||
private newFieldKeywords = new Set(["new", "change", "neue", "ändern"]);
|
private newFieldKeywords = new Set(["new", "change", "neue", "ändern"]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided field as a field for a login form.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate, should be a username or password field.
|
||||||
|
* @param pageDetails - The details of the page that the field is on.
|
||||||
|
*/
|
||||||
isFieldForLoginForm(field: AutofillField, pageDetails: AutofillPageDetails): boolean {
|
isFieldForLoginForm(field: AutofillField, pageDetails: AutofillPageDetails): boolean {
|
||||||
const isCurrentPasswordField = this.isCurrentPasswordField(field);
|
const isCurrentPasswordField = this.isCurrentPasswordField(field);
|
||||||
if (isCurrentPasswordField) {
|
if (isCurrentPasswordField) {
|
||||||
@@ -28,6 +37,12 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return this.isUsernameFieldForLoginForm(field, pageDetails);
|
return this.isUsernameFieldForLoginForm(field, pageDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided field as a password field for a login form.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
* @param pageDetails - The details of the page that the field is on.
|
||||||
|
*/
|
||||||
private isPasswordFieldForLoginForm(
|
private isPasswordFieldForLoginForm(
|
||||||
field: AutofillField,
|
field: AutofillField,
|
||||||
pageDetails: AutofillPageDetails,
|
pageDetails: AutofillPageDetails,
|
||||||
@@ -91,6 +106,12 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return !this.autocompleteDisabledValues.has(field.autoCompleteType);
|
return !this.autocompleteDisabledValues.has(field.autoCompleteType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided field as a username field for a login form.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
* @param pageDetails - The details of the page that the field is on.
|
||||||
|
*/
|
||||||
private isUsernameFieldForLoginForm(
|
private isUsernameFieldForLoginForm(
|
||||||
field: AutofillField,
|
field: AutofillField,
|
||||||
pageDetails: AutofillPageDetails,
|
pageDetails: AutofillPageDetails,
|
||||||
@@ -171,7 +192,12 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return visiblePasswordFieldsInPageDetails.length === 1;
|
return visiblePasswordFieldsInPageDetails.length === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
isUsernameField = (field: AutofillField): boolean => {
|
/**
|
||||||
|
* Validates the provided field as a username field.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
*/
|
||||||
|
private isUsernameField = (field: AutofillField): boolean => {
|
||||||
if (
|
if (
|
||||||
!this.usernameFieldTypes.has(field.type) ||
|
!this.usernameFieldTypes.has(field.type) ||
|
||||||
this.isExcludedFieldType(field, this.excludedAutofillLoginTypesSet)
|
this.isExcludedFieldType(field, this.excludedAutofillLoginTypesSet)
|
||||||
@@ -182,7 +208,12 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return this.keywordsFoundInFieldData(field, AutoFillConstants.UsernameFieldNames);
|
return this.keywordsFoundInFieldData(field, AutoFillConstants.UsernameFieldNames);
|
||||||
};
|
};
|
||||||
|
|
||||||
isCurrentPasswordField = (field: AutofillField): boolean => {
|
/**
|
||||||
|
* Validates the provided field as a current password field.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
*/
|
||||||
|
private isCurrentPasswordField = (field: AutofillField): boolean => {
|
||||||
if (field.autoCompleteType === "new-password") {
|
if (field.autoCompleteType === "new-password") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -190,7 +221,12 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return this.isPasswordField(field);
|
return this.isPasswordField(field);
|
||||||
};
|
};
|
||||||
|
|
||||||
isNewPasswordField = (field: AutofillField): boolean => {
|
/**
|
||||||
|
* Validates the provided field as a new password field.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
*/
|
||||||
|
private isNewPasswordField = (field: AutofillField): boolean => {
|
||||||
if (field.autoCompleteType === "current-password") {
|
if (field.autoCompleteType === "current-password") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -198,11 +234,16 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return this.isPasswordField(field);
|
return this.isPasswordField(field);
|
||||||
};
|
};
|
||||||
|
|
||||||
isPasswordField = (field: AutofillField): boolean => {
|
/**
|
||||||
|
* Validates the provided field as a password field.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
*/
|
||||||
|
private isPasswordField = (field: AutofillField): boolean => {
|
||||||
const isInputPasswordType = field.type === "password";
|
const isInputPasswordType = field.type === "password";
|
||||||
if (
|
if (
|
||||||
!isInputPasswordType ||
|
(!isInputPasswordType &&
|
||||||
this.isExcludedFieldType(field, this.excludedAutofillLoginTypesSet) ||
|
this.isExcludedFieldType(field, this.excludedAutofillLoginTypesSet)) ||
|
||||||
this.fieldHasDisqualifyingAttributeValue(field)
|
this.fieldHasDisqualifyingAttributeValue(field)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
@@ -211,6 +252,12 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return isInputPasswordType || this.isLikePasswordField(field);
|
return isInputPasswordType || this.isLikePasswordField(field);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided field as a field to indicate if the
|
||||||
|
* field potentially acts as a password field.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
*/
|
||||||
private isLikePasswordField(field: AutofillField): boolean {
|
private isLikePasswordField(field: AutofillField): boolean {
|
||||||
if (field.type !== "text") {
|
if (field.type !== "text") {
|
||||||
return false;
|
return false;
|
||||||
@@ -226,6 +273,11 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided value to indicate if the value is like a password.
|
||||||
|
*
|
||||||
|
* @param value - The value to validate
|
||||||
|
*/
|
||||||
private valueIsLikePassword(value: string): boolean {
|
private valueIsLikePassword(value: string): boolean {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -240,6 +292,12 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return !(this.passwordFieldExcludeListString.indexOf(cleanedValue) > -1);
|
return !(this.passwordFieldExcludeListString.indexOf(cleanedValue) > -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided field to indicate if the field has a
|
||||||
|
* disqualifying attribute that would impede autofill entirely.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
*/
|
||||||
private fieldHasDisqualifyingAttributeValue(field: AutofillField): boolean {
|
private fieldHasDisqualifyingAttributeValue(field: AutofillField): boolean {
|
||||||
const checkedAttributeValues = [field.htmlID, field.htmlName, field.placeholder];
|
const checkedAttributeValues = [field.htmlID, field.htmlName, field.placeholder];
|
||||||
|
|
||||||
@@ -255,6 +313,12 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided field to indicate if the field is excluded from autofill.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
* @param excludedTypes - The set of excluded types
|
||||||
|
*/
|
||||||
private isExcludedFieldType(field: AutofillField, excludedTypes: Set<string>): boolean {
|
private isExcludedFieldType(field: AutofillField, excludedTypes: Set<string>): boolean {
|
||||||
if (excludedTypes.has(field.type)) {
|
if (excludedTypes.has(field.type)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -263,6 +327,11 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return this.isSearchField(field);
|
return this.isSearchField(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided field to indicate if the field is a search field.
|
||||||
|
*
|
||||||
|
* @param field - The field to validate
|
||||||
|
*/
|
||||||
private isSearchField(field: AutofillField): boolean {
|
private isSearchField(field: AutofillField): boolean {
|
||||||
const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder];
|
const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder];
|
||||||
for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) {
|
for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) {
|
||||||
@@ -287,11 +356,22 @@ export class InlineMenuFieldQualificationService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the provided field to indicate if the field has any of the provided keywords.
|
||||||
|
*
|
||||||
|
* @param autofillFieldData - The field data to search for keywords
|
||||||
|
* @param keywords - The keywords to search for
|
||||||
|
*/
|
||||||
private keywordsFoundInFieldData(autofillFieldData: AutofillField, keywords: string[]) {
|
private keywordsFoundInFieldData(autofillFieldData: AutofillField, keywords: string[]) {
|
||||||
const searchedString = this.getAutofillFieldDataKeywords(autofillFieldData);
|
const searchedString = this.getAutofillFieldDataKeywords(autofillFieldData);
|
||||||
return keywords.some((keyword) => searchedString.includes(keyword));
|
return keywords.some((keyword) => searchedString.includes(keyword));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the keywords from the provided autofill field data.
|
||||||
|
*
|
||||||
|
* @param autofillFieldData - The field data to search for keywords
|
||||||
|
*/
|
||||||
private getAutofillFieldDataKeywords(autofillFieldData: AutofillField) {
|
private getAutofillFieldDataKeywords(autofillFieldData: AutofillField) {
|
||||||
if (this.autofillFieldKeywordsMap.has(autofillFieldData)) {
|
if (this.autofillFieldKeywordsMap.has(autofillFieldData)) {
|
||||||
return this.autofillFieldKeywordsMap.get(autofillFieldData);
|
return this.autofillFieldKeywordsMap.get(autofillFieldData);
|
||||||
|
|||||||
Reference in New Issue
Block a user