1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-09-27 04:03:00 +02:00
bitwarden-browser/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1048 lines
40 KiB
TypeScript
Raw Normal View History

[PM-3285] Autofill v2 Feature Branch (#5939) * [PM-3285] Autofill v2 Feature Branch * [PM-2130] - Audit, Modularize, and Refactor Core autofill.js File (#5453) * split up autofill.ts, first pass * remove modification tracking comments * lessen and localize eslint disables * additional typing and formatting * update autofill v2 with PR #5364 changes (update/i18n confirm dialogs) * update autofill v2 with PR #4155 changes (add autofill support for textarea) Co-Authored-By: Manuel <mr-manuel@outlook.it> * move commonly used string values to constants * ts cleanup * [PM-2130] Starting work to re-architect autofillv2.ts * [PM-2130] Starting work to re-architect autofillv2.ts * [PM-2130] Working through autofill collect method * [PM-2130] Marking Removal of documentUUID as dead code * [PM-2130] Refining the implementation of collect and moving broken out utils back into class implementation * [PM-2130] Applying small refactors to AutofillCollect * [PM-2130] Refining the implementation of getAutofillFieldLabelTag to help with readability of the method * [PM-2130] Implementing jest tests for AutofillCollect methods * [PM-2130] Refining implementation for AutofillCollect * [PM-2200] Unit tests for autofill content script utilities with slight refactors (#5544) * add unit tests for urlNotSecure * add test coverage command * add unit tests for canSeeElementToStyle * canSeeElementToStyle should not return true if `animateTheFilling` or `currentEl` is false * add tests for selectAllFromDoc and getElementByOpId * clean up getElementByOpId * address some typing issues * add tests for setValueForElementByEvent, setValueForElement, and doSimpleSetByQuery * clean up setValueForElement and setValueForElementByEvent * more typescript cleanup * add tests for doClickByOpId and touchAllPasswordFields * add tests for doFocusByOpId and doClickByQuery * misc fill cleanup * move functions between collect and fill utils and replace getElementForOPID for duplicate getElementByOpId * add tests for isKnownTag and isElementVisible * rename addProp and remove redundant focusElement in favor of doFocusElement * cleanup * fix checkNodeType * add tests for shiftForLeftLabel * clean up and rename checkNodeType, isKnownTag, and shiftForLeftLabel * add tests for getFormElements * clean up getFormElements * add tests for getElementAttrValue, getElementValue, getSelectElementOptions, getLabelTop, and queryDoc * clean up and rename queryDoc to queryDocument * misc cleanup and rename getElementAttrValue to getPropertyOrAttribute * rebase cleanup * prettier formatting * [PM-2130] Fixing linting issues * [PM-2130] Fixing linting issues * [PM-2130] Migrating implementation for collect methods and tests for those methods into AutofillCollect context * [PM-2130] Migrating getPropertyOrAttribute method from utils to AutofillCollect * [PM-2130] Continuing migration of methods from collect utils into AutofillCollect * [PM-2130] Rework of isViewable method to better handle behavior for how we identify if an element is currently within the viewport * [PM-2130] Filling out implementation of autofill-insert * [PM-2130] Refining AutofillInsert * [PM-2130] Implementing jest tests for AutofillCollect methods and breaking out visibility related logic to a separate service * [PM-2130] Fixing jest tests for AutofillCollect * [PM-2130] Fixing jest tests for AutofillInit * [PM-2130] Adjusting how the AutofillFieldVisibilityService class is used in AutofillCollect * [PM-2130] Working through AutofillInsert implementation * [PM-2130] Migrating methods from fill.ts to AutofillInsert * [PM-2130] Migrating methods from fill.ts to AutofillInsert * [PM-2130] Applying fix for IntersectionObserver when triggering behavior in Safari and fixing issue with how we trigger an input event shortly after filling in a field * [PM-2130] Refactoring AutofillCollect to service CollectAutofillContentService * [PM-2130] Refactoring AutofillInsert to service InsertAutofillContentService * [PM-2130] Further organization of implementation * [PM-2130] Filling out missing jest test for AutofillInit.fillForm method * [PM-2130] Migrating the last of the collect jest tests to InsertAutofillContentService * [PM-2130] Further refactoring of elements including typing information * [PM-2130] Implementing jest tests for InsertAutofillContentService * [PM-2130] Implementing jest tests for InsertAutofillContentService * [PM-2130] Organization and refactoring of methods within InsertAutofillContent * [PM-2130] Implementation of jest tests for InsertAutofillContentService * [PM-2130] Implementation of Jest Test for IntertAutofillContentService * [PM-2130] Finalizing migration of methods and jest tests from util files into Autofill serivces * [PM-2130] Cleaning up dead code comments * [PM-2130] Removing unnecessary constants * [PM-2130] Finalizing jest tests for InsertAutofillContentService * [PM-2130] Refactoring FieldVisibiltyService to DomElementVisibilityService to allow service to act in a more general manner * [PM-2130] Implementing jest tests for DomElementVisibilityService * [PM-2130] Implementing jest tests for DomElementVisibilityService * [PM-2130] Implementing jest tests for DomElementVisibilityService * [PM-2130] Implementing jest tests for DomElementVisibilityService * [PM-2130] Breaking out the callback method used to resolve the IntersectionObserver promise * [PM-2130] Adding a comment explaining a fix for Safari * [PM-2130] Adding a comment explaining a fix for Safari * [PM-2130] Applying changes required for PM-2762 to implementation, and ensuring jest tests exist to validate the behavior * [PM-2130] Removing usage of IntersectionObserver when identifying element visibility due to broken interactions with React Components * [PM-2130] Fixing issue found when attempting to capture the elementAtCenterPoint in determining file visibility * [PM-2100] Create Unit Test Suite for autofill.service.ts (#5371) * [PM-2100] Create Unit Test Suite for Autofill.service.ts * [PM-2100] Finishing out tests for the getFormsWithPasswordFields method * [PM-2100] Implementing tests for the doAutofill method within the autofill service * [PM-2100] Working through implementation of doAutofill method * [PM-2100] Working through implementation of doAutofill method * [PM-2100] Finishing implementatino of isUntrustedIframe method within autofill service * [PM-2100] Finishing implementation of doAutoFill method within autofill service * [PM-2100] Finishing implementation of doAutoFillOnTab method within autofill service * [PM-2100] Working through tests for generateFillScript * [PM-2100] Finalizing generateFillScript method testing * [PM-2100] Starting implementation of generateLoginFillScript * [PM-2100] Working through tests for generateLoginFillScript * [PM-2100] Finalizing generateLoginFillScript method testing * [PM-2100] Removing unnecessary jest config file * [PM-2100] Fixing jest tests based on changes implemented within PM-2130 * [PM-2100] Fixing autofill mocks * [PM-2100] Fixing AutofillService jest tests * [PM-2100] Handling missing tests within coverage of AutofillService * [PM-2100] Handling missing tests within coverage of AutofillService.generateLoginFillScript * [PM-2100] Writing tests for AutofillService.generateCardFillScript * [PM-2100] Finalizing tests for AutofillService.generateCardFillScript * [PM-2100] Adding additional tests to cover changes introduced by TOTOP autofill PR * [PM-2100] Adding jest tests for Autofill.generateIdentityFillScript * [PM-2100] Finalizing tests for AutofillService.generateIdentityFillScript * [PM-2100] Implementing tests for AutofillService * [PM-2100] Implementing tests for AutofillService.loadPasswordFields * [PM-2100] Implementing tests for AutofillService.findUsernameField * [PM-2100] Implementing tests for AutofillService.findTotpField * [PM-2100] Implementing tests for AutofillService.fieldPropertyIsPrefixMatch * [PM-2100] Finalizing tests for AutofillService * [PM-2100] Modyfing placement of autofill-mocks * [PM-2100] Modyfing placement of autofill-mocks * [PM-2100] Removal of jest transform declaration * [PM-2130] Fixing issue with autofill service unit tests * [PM-2130] Fixing issue with autofill service unit tests * [PM-2130] Fixing test test for when we need to handle a password reprompt --------- Co-authored-by: Manuel <mr-manuel@outlook.it> Co-authored-by: Cesar Gonzalez <cgonzalez@bitwarden.com> Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com> * [PM-3285] Migrating Changes from PM-1407 into autofill v2 refactor implementation * [PM-2747] Add Support for Feature Flag of Autofill Version (#5695) * [PM-2100] Create Unit Test Suite for Autofill.service.ts * [PM-2100] Finishing out tests for the getFormsWithPasswordFields method * [PM-2100] Implementing tests for the doAutofill method within the autofill service * [PM-2100] Working through implementation of doAutofill method * [PM-2100] Working through implementation of doAutofill method * [PM-2100] Finishing implementatino of isUntrustedIframe method within autofill service * [PM-2100] Finishing implementation of doAutoFill method within autofill service * [PM-2100] Finishing implementation of doAutoFillOnTab method within autofill service * [PM-2100] Working through tests for generateFillScript * split up autofill.ts, first pass * remove modification tracking comments * lessen and localize eslint disables * additional typing and formatting * update autofill v2 with PR #5364 changes (update/i18n confirm dialogs) * update autofill v2 with PR #4155 changes (add autofill support for textarea) Co-Authored-By: Manuel <mr-manuel@outlook.it> * move commonly used string values to constants * ts cleanup * [PM-2100] Finalizing generateFillScript method testing * [PM-2100] Starting implementation of generateLoginFillScript * [PM-2100] Working through tests for generateLoginFillScript * [PM-2100] Finalizing generateLoginFillScript method testing * [PM-2130] Starting work to re-architect autofillv2.ts * [PM-2130] Starting work to re-architect autofillv2.ts * [PM-2130] Working through autofill collect method * [PM-2130] Marking Removal of documentUUID as dead code * [PM-2130] Refining the implementation of collect and moving broken out utils back into class implementation * [PM-2130] Applying small refactors to AutofillCollect * [PM-2130] Refining the implementation of getAutofillFieldLabelTag to help with readability of the method * [PM-2130] Implementing jest tests for AutofillCollect methods * [PM-2130] Refining implementation for AutofillCollect * [PM-2200] Unit tests for autofill content script utilities with slight refactors (#5544) * add unit tests for urlNotSecure * add test coverage command * add unit tests for canSeeElementToStyle * canSeeElementToStyle should not return true if `animateTheFilling` or `currentEl` is false * add tests for selectAllFromDoc and getElementByOpId * clean up getElementByOpId * address some typing issues * add tests for setValueForElementByEvent, setValueForElement, and doSimpleSetByQuery * clean up setValueForElement and setValueForElementByEvent * more typescript cleanup * add tests for doClickByOpId and touchAllPasswordFields * add tests for doFocusByOpId and doClickByQuery * misc fill cleanup * move functions between collect and fill utils and replace getElementForOPID for duplicate getElementByOpId * add tests for isKnownTag and isElementVisible * rename addProp and remove redundant focusElement in favor of doFocusElement * cleanup * fix checkNodeType * add tests for shiftForLeftLabel * clean up and rename checkNodeType, isKnownTag, and shiftForLeftLabel * add tests for getFormElements * clean up getFormElements * add tests for getElementAttrValue, getElementValue, getSelectElementOptions, getLabelTop, and queryDoc * clean up and rename queryDoc to queryDocument * misc cleanup and rename getElementAttrValue to getPropertyOrAttribute * rebase cleanup * prettier formatting * [PM-2130] Fixing linting issues * [PM-2130] Fixing linting issues * [PM-2130] Migrating implementation for collect methods and tests for those methods into AutofillCollect context * [PM-2130] Migrating getPropertyOrAttribute method from utils to AutofillCollect * [PM-2130] Continuing migration of methods from collect utils into AutofillCollect * [PM-2130] Rework of isViewable method to better handle behavior for how we identify if an element is currently within the viewport * [PM-2130] Filling out implementation of autofill-insert * [PM-2130] Refining AutofillInsert * [PM-2130] Implementing jest tests for AutofillCollect methods and breaking out visibility related logic to a separate service * [PM-2130] Fixing jest tests for AutofillCollect * [PM-2130] Fixing jest tests for AutofillInit * [PM-2130] Adjusting how the AutofillFieldVisibilityService class is used in AutofillCollect * [PM-2130] Working through AutofillInsert implementation * [PM-2130] Migrating methods from fill.ts to AutofillInsert * [PM-2130] Migrating methods from fill.ts to AutofillInsert * [PM-2130] Applying fix for IntersectionObserver when triggering behavior in Safari and fixing issue with how we trigger an input event shortly after filling in a field * [PM-2130] Refactoring AutofillCollect to service CollectAutofillContentService * [PM-2130] Refactoring AutofillInsert to service InsertAutofillContentService * [PM-2130] Further organization of implementation * [PM-2130] Filling out missing jest test for AutofillInit.fillForm method * [PM-2130] Migrating the last of the collect jest tests to InsertAutofillContentService * [PM-2130] Further refactoring of elements including typing information * [PM-2130] Implementing jest tests for InsertAutofillContentService * [PM-2130] Implementing jest tests for InsertAutofillContentService * [PM-2130] Organization and refactoring of methods within InsertAutofillContent * [PM-2130] Implementation of jest tests for InsertAutofillContentService * [PM-2130] Implementation of Jest Test for IntertAutofillContentService * [PM-2130] Finalizing migration of methods and jest tests from util files into Autofill serivces * [PM-2130] Cleaning up dead code comments * [PM-2130] Removing unnecessary constants * [PM-2130] Finalizing jest tests for InsertAutofillContentService * [PM-2130] Refactoring FieldVisibiltyService to DomElementVisibilityService to allow service to act in a more general manner * [PM-2130] Implementing jest tests for DomElementVisibilityService * [PM-2130] Implementing jest tests for DomElementVisibilityService * [PM-2130] Implementing jest tests for DomElementVisibilityService * [PM-2130] Implementing jest tests for DomElementVisibilityService * [PM-2130] Breaking out the callback method used to resolve the IntersectionObserver promise * [PM-2100] Removing unnecessary jest config file * [PM-2100] Fixing jest tests based on changes implemented within PM-2130 * [PM-2100] Fixing autofill mocks * [PM-2100] Fixing AutofillService jest tests * [PM-2100] Handling missing tests within coverage of AutofillService * [PM-2100] Handling missing tests within coverage of AutofillService.generateLoginFillScript * [PM-2100] Writing tests for AutofillService.generateCardFillScript * [PM-2100] Finalizing tests for AutofillService.generateCardFillScript * [PM-2100] Adding additional tests to cover changes introduced by TOTOP autofill PR * [PM-2100] Adding jest tests for Autofill.generateIdentityFillScript * [PM-2100] Finalizing tests for AutofillService.generateIdentityFillScript * [PM-2100] Implementing tests for AutofillService * [PM-2130] Adding a comment explaining a fix for Safari * [PM-2130] Adding a comment explaining a fix for Safari * [PM-2100] Implementing tests for AutofillService.loadPasswordFields * [PM-2100] Implementing tests for AutofillService.findUsernameField * [PM-2100] Implementing tests for AutofillService.findTotpField * [PM-2100] Implementing tests for AutofillService.fieldPropertyIsPrefixMatch * [PM-2100] Finalizing tests for AutofillService * [PM-2747] Add Support for Feature Flag of Autofill Version * [PM-2747] Adding Support for Manifest v3 within the implementation * [PM-2747] Modifying how the feature flag for autofill is named * [PM-2747] Modifying main.background.ts to load the ConfigApiService correctly * [PM-2747] Refactoring trigger of autofill scripts to be a simple immediately invoked function * [PM-2100] Modyfing placement of autofill-mocks * [PM-2100] Modyfing placement of autofill-mocks * [PM-2100] Removal of jest transform declaration * [PM-2130] Applying changes required for PM-2762 to implementation, and ensuring jest tests exist to validate the behavior * [PM-2747] Modifying how we inject the autofill scripts to ensure we are injecting into all frames within a page * [PM-2130] Removing usage of IntersectionObserver when identifying element visibility due to broken interactions with React Components * [PM-2130] Fixing issue found when attempting to capture the elementAtCenterPoint in determining file visibility * [PM-2100] Create Unit Test Suite for autofill.service.ts (#5371) * [PM-2100] Create Unit Test Suite for Autofill.service.ts * [PM-2100] Finishing out tests for the getFormsWithPasswordFields method * [PM-2100] Implementing tests for the doAutofill method within the autofill service * [PM-2100] Working through implementation of doAutofill method * [PM-2100] Working through implementation of doAutofill method * [PM-2100] Finishing implementatino of isUntrustedIframe method within autofill service * [PM-2100] Finishing implementation of doAutoFill method within autofill service * [PM-2100] Finishing implementation of doAutoFillOnTab method within autofill service * [PM-2100] Working through tests for generateFillScript * [PM-2100] Finalizing generateFillScript method testing * [PM-2100] Starting implementation of generateLoginFillScript * [PM-2100] Working through tests for generateLoginFillScript * [PM-2100] Finalizing generateLoginFillScript method testing * [PM-2100] Removing unnecessary jest config file * [PM-2100] Fixing jest tests based on changes implemented within PM-2130 * [PM-2100] Fixing autofill mocks * [PM-2100] Fixing AutofillService jest tests * [PM-2100] Handling missing tests within coverage of AutofillService * [PM-2100] Handling missing tests within coverage of AutofillService.generateLoginFillScript * [PM-2100] Writing tests for AutofillService.generateCardFillScript * [PM-2100] Finalizing tests for AutofillService.generateCardFillScript * [PM-2100] Adding additional tests to cover changes introduced by TOTOP autofill PR * [PM-2100] Adding jest tests for Autofill.generateIdentityFillScript * [PM-2100] Finalizing tests for AutofillService.generateIdentityFillScript * [PM-2100] Implementing tests for AutofillService * [PM-2100] Implementing tests for AutofillService.loadPasswordFields * [PM-2100] Implementing tests for AutofillService.findUsernameField * [PM-2100] Implementing tests for AutofillService.findTotpField * [PM-2100] Implementing tests for AutofillService.fieldPropertyIsPrefixMatch * [PM-2100] Finalizing tests for AutofillService * [PM-2100] Modyfing placement of autofill-mocks * [PM-2100] Modyfing placement of autofill-mocks * [PM-2100] Removal of jest transform declaration * [PM-2747] Applying a fix for a race condition that can occur when loading the notification bar and autofiller script login * [PM-2747] Reverting removal of autofill npm action. Now this will force usage of autofill-v2 regardless of whether a feature flag is set or not * [PM-2747] Fixing logic error incorporated when merging in master * [PM-2130] Fixing issue with autofill service unit tests * [PM-2130] Fixing issue with autofill service unit tests * [PM-2747] Fixing issue present with notification bar merge * [PM-2130] Fixing test test for when we need to handle a password reprompt * [PM-2747] Fixing wording for webpack script * [PM-2747] Addressing stylistic changes requested from code review * [PM-2747] Addressing stylistic changes requested from code review --------- Co-authored-by: Jonathan Prusik <jprusik@classynemesis.com> Co-authored-by: Manuel <mr-manuel@outlook.it> Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com> * [PM-3285] Applying stylistic changes suggested by code review for the feature flag implementation * [PM-3285] Adding temporary console log to validate which version is being used * [PM-3285] Removing temporary console log indicating which version of autofill the user is currently loading --------- Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com> Co-authored-by: Manuel <mr-manuel@outlook.it> Co-authored-by: Jonathan Prusik <jprusik@classynemesis.com>
2023-09-07 22:33:04 +02:00
import { EVENTS } from "../constants";
import AutofillScript, { FillScript, FillScriptActions } from "../models/autofill-script";
import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types";
import CollectAutofillContentService from "./collect-autofill-content.service";
import DomElementVisibilityService from "./dom-element-visibility.service";
import InsertAutofillContentService from "./insert-autofill-content.service";
const mockLoginForm = `
<div id="root">
<form>
<input type="text" id="username" />
<input type="password" />
</form>
</div>
`;
const eventsToTest = [
EVENTS.CHANGE,
EVENTS.INPUT,
EVENTS.KEYDOWN,
EVENTS.KEYPRESS,
EVENTS.KEYUP,
"blur",
"click",
"focus",
"focusin",
"focusout",
"mousedown",
"paste",
"select",
"selectionchange",
"touchend",
"touchstart",
];
const initEventCount = Object.freeze(
eventsToTest.reduce(
(eventCounts, eventName) => ({
...eventCounts,
[eventName]: 0,
}),
{}
)
);
let confirmSpy: jest.SpyInstance<boolean, [message?: string]>;
let windowSpy: jest.SpyInstance<any>;
let savedURLs: string[] | null = ["https://bitwarden.com"];
function setMockWindowLocation({
protocol,
hostname,
}: {
protocol: "http:" | "https:";
hostname: string;
}) {
windowSpy.mockImplementation(() => ({
location: {
protocol,
hostname,
},
}));
}
describe("InsertAutofillContentService", () => {
const domElementVisibilityService = new DomElementVisibilityService();
const collectAutofillContentService = new CollectAutofillContentService(
domElementVisibilityService
);
let insertAutofillContentService: InsertAutofillContentService;
let fillScript: AutofillScript;
beforeEach(() => {
document.body.innerHTML = mockLoginForm;
confirmSpy = jest.spyOn(window, "confirm");
windowSpy = jest.spyOn(window, "window", "get");
insertAutofillContentService = new InsertAutofillContentService(
domElementVisibilityService,
collectAutofillContentService
);
fillScript = {
script: [
["click_on_opid", "username"],
["focus_by_opid", "username"],
["fill_by_opid", "username", "test"],
],
properties: {
delay_between_operations: 20,
},
metadata: {},
autosubmit: null,
savedUrls: ["https://bitwarden.com"],
untrustedIframe: false,
itemType: "login",
};
});
afterEach(() => {
jest.resetAllMocks();
windowSpy.mockRestore();
confirmSpy.mockRestore();
document.body.innerHTML = "";
});
describe("fillForm", () => {
it("returns early if the passed fill script does not have a script property", () => {
fillScript.script = [];
jest.spyOn(insertAutofillContentService as any, "fillingWithinSandboxedIframe");
jest.spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill");
jest.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill");
jest.spyOn(insertAutofillContentService as any, "runFillScriptAction");
insertAutofillContentService.fillForm(fillScript);
expect(insertAutofillContentService["fillingWithinSandboxedIframe"]).not.toHaveBeenCalled();
expect(
insertAutofillContentService["userCancelledInsecureUrlAutofill"]
).not.toHaveBeenCalled();
expect(
insertAutofillContentService["userCancelledUntrustedIframeAutofill"]
).not.toHaveBeenCalled();
expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled();
});
it("returns early if the script is filling within a sand boxed iframe", () => {
jest
.spyOn(insertAutofillContentService as any, "fillingWithinSandboxedIframe")
.mockReturnValue(true);
jest.spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill");
jest.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill");
jest.spyOn(insertAutofillContentService as any, "runFillScriptAction");
insertAutofillContentService.fillForm(fillScript);
expect(insertAutofillContentService["fillingWithinSandboxedIframe"]).toHaveBeenCalled();
expect(
insertAutofillContentService["userCancelledInsecureUrlAutofill"]
).not.toHaveBeenCalled();
expect(
insertAutofillContentService["userCancelledUntrustedIframeAutofill"]
).not.toHaveBeenCalled();
expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled();
});
it("returns early if the autofill is occurring on an insecure url and the user cancels the autofill", () => {
jest
.spyOn(insertAutofillContentService as any, "fillingWithinSandboxedIframe")
.mockReturnValue(false);
jest
.spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill")
.mockReturnValue(true);
jest.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill");
jest.spyOn(insertAutofillContentService as any, "runFillScriptAction");
insertAutofillContentService.fillForm(fillScript);
expect(insertAutofillContentService["fillingWithinSandboxedIframe"]).toHaveBeenCalled();
expect(insertAutofillContentService["userCancelledInsecureUrlAutofill"]).toHaveBeenCalled();
expect(
insertAutofillContentService["userCancelledUntrustedIframeAutofill"]
).not.toHaveBeenCalled();
expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled();
});
it("returns early if the iframe is untrusted and the user cancelled the autofill", () => {
jest
.spyOn(insertAutofillContentService as any, "fillingWithinSandboxedIframe")
.mockReturnValue(false);
jest
.spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill")
.mockReturnValue(false);
jest
.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill")
.mockReturnValue(true);
jest.spyOn(insertAutofillContentService as any, "runFillScriptAction");
insertAutofillContentService.fillForm(fillScript);
expect(insertAutofillContentService["fillingWithinSandboxedIframe"]).toHaveBeenCalled();
expect(insertAutofillContentService["userCancelledInsecureUrlAutofill"]).toHaveBeenCalled();
expect(
insertAutofillContentService["userCancelledUntrustedIframeAutofill"]
).toHaveBeenCalled();
expect(insertAutofillContentService["runFillScriptAction"]).not.toHaveBeenCalled();
});
it("runs the fill script action for all scripts found within the fill script", () => {
jest
.spyOn(insertAutofillContentService as any, "fillingWithinSandboxedIframe")
.mockReturnValue(false);
jest
.spyOn(insertAutofillContentService as any, "userCancelledInsecureUrlAutofill")
.mockReturnValue(false);
jest
.spyOn(insertAutofillContentService as any, "userCancelledUntrustedIframeAutofill")
.mockReturnValue(false);
jest.spyOn(insertAutofillContentService as any, "runFillScriptAction");
insertAutofillContentService.fillForm(fillScript);
expect(insertAutofillContentService["fillingWithinSandboxedIframe"]).toHaveBeenCalled();
expect(insertAutofillContentService["userCancelledInsecureUrlAutofill"]).toHaveBeenCalled();
expect(
insertAutofillContentService["userCancelledUntrustedIframeAutofill"]
).toHaveBeenCalled();
expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenCalledTimes(3);
expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith(
1,
fillScript.script[0],
0,
fillScript.script
);
expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith(
2,
fillScript.script[1],
1,
fillScript.script
);
expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith(
3,
fillScript.script[2],
2,
fillScript.script
);
});
});
describe("fillingWithinSandboxedIframe", () => {
afterEach(() => {
Object.defineProperty(globalThis, "window", {
value: { frameElement: null },
writable: true,
});
});
it("returns false if the `self.origin` value is not null", () => {
const result = insertAutofillContentService["fillingWithinSandboxedIframe"]();
expect(result).toBe(false);
expect(self.origin).not.toBeNull();
});
it("returns true if the frameElement has a sandbox attribute", () => {
Object.defineProperty(globalThis, "window", {
value: { frameElement: { hasAttribute: jest.fn(() => true) } },
writable: true,
});
const result = insertAutofillContentService["fillingWithinSandboxedIframe"]();
expect(result).toBe(true);
});
it("returns true if the window location hostname is empty", () => {
setMockWindowLocation({ protocol: "http:", hostname: "" });
const result = insertAutofillContentService["fillingWithinSandboxedIframe"]();
expect(result).toBe(true);
});
});
describe("userCancelledInsecureUrlAutofill", () => {
const currentHostname = "bitwarden.com";
beforeEach(() => {
savedURLs = [`https://${currentHostname}`];
});
describe("returns false if Autofill occurring...", () => {
it("when there are no saved URLs", () => {
savedURLs = [];
setMockWindowLocation({ protocol: "http:", hostname: currentHostname });
const userCancelledInsecureUrlAutofill =
insertAutofillContentService["userCancelledInsecureUrlAutofill"](savedURLs);
expect(userCancelledInsecureUrlAutofill).toBe(false);
savedURLs = null;
const userCancelledInsecureUrlAutofill2 =
insertAutofillContentService["userCancelledInsecureUrlAutofill"](savedURLs);
expect(confirmSpy).not.toHaveBeenCalled();
expect(userCancelledInsecureUrlAutofill2).toBe(false);
});
it("on http page and saved URLs contain no https values", () => {
savedURLs = ["http://bitwarden.com"];
setMockWindowLocation({ protocol: "http:", hostname: currentHostname });
const userCancelledInsecureUrlAutofill =
insertAutofillContentService["userCancelledInsecureUrlAutofill"](savedURLs);
expect(confirmSpy).not.toHaveBeenCalled();
expect(userCancelledInsecureUrlAutofill).toBe(false);
});
it("on https page with saved https URL", () => {
setMockWindowLocation({ protocol: "https:", hostname: currentHostname });
const userCancelledInsecureUrlAutofill =
insertAutofillContentService["userCancelledInsecureUrlAutofill"](savedURLs);
expect(confirmSpy).not.toHaveBeenCalled();
expect(userCancelledInsecureUrlAutofill).toBe(false);
});
it("on page with no password field", () => {
setMockWindowLocation({ protocol: "https:", hostname: currentHostname });
document.body.innerHTML = `
<div id="root">
<form>
<input type="text" id="username" />
</form>
</div>
`;
const userCancelledInsecureUrlAutofill =
insertAutofillContentService["userCancelledInsecureUrlAutofill"](savedURLs);
expect(confirmSpy).not.toHaveBeenCalled();
expect(userCancelledInsecureUrlAutofill).toBe(false);
});
it("on http page with saved https URL and user approval", () => {
setMockWindowLocation({ protocol: "http:", hostname: currentHostname });
confirmSpy.mockImplementation(jest.fn(() => true));
const userCancelledInsecureUrlAutofill =
insertAutofillContentService["userCancelledInsecureUrlAutofill"](savedURLs);
expect(confirmSpy).toHaveBeenCalled();
expect(userCancelledInsecureUrlAutofill).toBe(false);
});
});
it("returns true if Autofill occurring on http page with saved https URL and user disapproval", () => {
setMockWindowLocation({ protocol: "http:", hostname: currentHostname });
confirmSpy.mockImplementation(jest.fn(() => false));
const userCancelledInsecureUrlAutofill =
insertAutofillContentService["userCancelledInsecureUrlAutofill"](savedURLs);
expect(confirmSpy).toHaveBeenCalled();
expect(userCancelledInsecureUrlAutofill).toBe(true);
});
it("returns false if the vault item contains uris with both secure and insecure uris, but a insecure uri is being used on a insecure web page", () => {
setMockWindowLocation({ protocol: "http:", hostname: currentHostname });
savedURLs = ["http://bitwarden.com", "https://some-other-uri.com"];
const userCancelledInsecureUrlAutofill =
insertAutofillContentService["userCancelledInsecureUrlAutofill"](savedURLs);
expect(confirmSpy).not.toHaveBeenCalled();
expect(userCancelledInsecureUrlAutofill).toBe(false);
});
});
describe("userCancelledUntrustedIframeAutofill", () => {
it("returns false if Autofill occurring within a trusted iframe", () => {
fillScript.untrustedIframe = false;
const result =
insertAutofillContentService["userCancelledUntrustedIframeAutofill"](fillScript);
expect(result).toBe(false);
expect(confirmSpy).not.toHaveBeenCalled();
});
it("returns false if Autofill occurring within an untrusted iframe and the user approves", () => {
fillScript.untrustedIframe = true;
confirmSpy.mockImplementation(jest.fn(() => true));
const result =
insertAutofillContentService["userCancelledUntrustedIframeAutofill"](fillScript);
expect(result).toBe(false);
expect(confirmSpy).toHaveBeenCalled();
});
it("returns true if Autofill occurring within an untrusted iframe and the user disapproves", () => {
fillScript.untrustedIframe = true;
confirmSpy.mockImplementation(jest.fn(() => false));
const result =
insertAutofillContentService["userCancelledUntrustedIframeAutofill"](fillScript);
expect(result).toBe(true);
expect(confirmSpy).toHaveBeenCalled();
});
});
describe("runFillScriptAction", () => {
beforeEach(() => {
jest.useFakeTimers();
});
it("returns early if no opid is provided", () => {
const action = "fill_by_opid";
const opid = "";
const value = "value";
const scriptAction: FillScript = [action, opid, value];
jest.spyOn(insertAutofillContentService["autofillInsertActions"], action);
insertAutofillContentService["runFillScriptAction"](scriptAction, 0);
jest.advanceTimersByTime(20);
expect(insertAutofillContentService["autofillInsertActions"][action]).not.toHaveBeenCalled();
});
describe("given a valid fill script action and opid", () => {
const fillScriptActions: FillScriptActions[] = [
"fill_by_opid",
"click_on_opid",
"focus_by_opid",
];
fillScriptActions.forEach((action) => {
it(`triggers a ${action} action`, () => {
const opid = "opid";
const value = "value";
const scriptAction: FillScript = [action, opid, value];
jest.spyOn(insertAutofillContentService["autofillInsertActions"], action);
insertAutofillContentService["runFillScriptAction"](scriptAction, 0);
jest.advanceTimersByTime(20);
expect(
insertAutofillContentService["autofillInsertActions"][action]
).toHaveBeenCalledWith({
opid,
value,
});
});
});
});
});
describe("handleFillFieldByOpidAction", () => {
it("finds the field element by opid and inserts the value into the field", () => {
const opid = "__1";
const value = "value";
const textInput = document.querySelector('input[type="text"]') as FormElementWithAttribute;
textInput.opid = opid;
textInput.value = value;
jest.spyOn(
insertAutofillContentService["collectAutofillContentService"],
"getAutofillFieldElementByOpid"
);
jest.spyOn(insertAutofillContentService as any, "insertValueIntoField");
insertAutofillContentService["handleFillFieldByOpidAction"](opid, value);
expect(
insertAutofillContentService["collectAutofillContentService"].getAutofillFieldElementByOpid
).toHaveBeenCalledWith(opid);
expect(insertAutofillContentService["insertValueIntoField"]).toHaveBeenCalledWith(
textInput,
value
);
});
});
describe("handleClickOnFieldByOpidAction", () => {
it("clicks on the elements targeted by the passed opid", () => {
const textInput = document.querySelector('input[type="text"]') as FormElementWithAttribute;
textInput.opid = "__1";
let clickEventCount = 0;
const expectedClickEventCount = 1;
const clickEventHandler: (handledEvent: Event) => void = (handledEvent) => {
const eventTarget = handledEvent.target as HTMLInputElement;
if (eventTarget.id === "username") {
clickEventCount++;
}
};
textInput.addEventListener("click", clickEventHandler);
jest.spyOn(
insertAutofillContentService["collectAutofillContentService"],
"getAutofillFieldElementByOpid"
);
jest.spyOn(insertAutofillContentService as any, "triggerClickOnElement");
insertAutofillContentService["handleClickOnFieldByOpidAction"]("__1");
expect(
insertAutofillContentService["collectAutofillContentService"].getAutofillFieldElementByOpid
).toBeCalledWith("__1");
expect((insertAutofillContentService as any)["triggerClickOnElement"]).toHaveBeenCalledWith(
textInput
);
expect(clickEventCount).toBe(expectedClickEventCount);
textInput.removeEventListener("click", clickEventHandler);
});
it("should not trigger click when no suitable elements can be found", () => {
const textInput = document.querySelector('input[type="text"]') as FormElementWithAttribute;
let clickEventCount = 0;
const expectedClickEventCount = 0;
const clickEventHandler: (handledEvent: Event) => void = (handledEvent) => {
const eventTarget = handledEvent.target as HTMLInputElement;
if (eventTarget.id === "username") {
clickEventCount++;
}
};
textInput.addEventListener("click", clickEventHandler);
insertAutofillContentService["handleClickOnFieldByOpidAction"]("__2");
expect(clickEventCount).toEqual(expectedClickEventCount);
textInput.removeEventListener("click", clickEventHandler);
});
});
describe("handleFocusOnFieldByOpidAction", () => {
it("simulates click and focus events on the element targeted by the passed opid", () => {
const targetInput = document.querySelector('input[type="text"]') as FormElementWithAttribute;
targetInput.opid = "__0";
const elementEventCount: { [key: string]: number } = {
...initEventCount,
};
// Testing all the relevant events to ensure downstream side-effects are firing correctly
const expectedElementEventCount: { [key: string]: number } = {
...initEventCount,
click: 1,
focus: 1,
focusin: 1,
};
const eventHandlers: { [key: string]: EventListener } = {};
eventsToTest.forEach((eventType) => {
eventHandlers[eventType] = (handledEvent) => {
elementEventCount[handledEvent.type]++;
};
targetInput.addEventListener(eventType, eventHandlers[eventType]);
});
jest.spyOn(
insertAutofillContentService["collectAutofillContentService"],
"getAutofillFieldElementByOpid"
);
jest.spyOn(
insertAutofillContentService as any,
"simulateUserMouseClickAndFocusEventInteractions"
);
insertAutofillContentService["handleFocusOnFieldByOpidAction"]("__0");
expect(
insertAutofillContentService["collectAutofillContentService"].getAutofillFieldElementByOpid
).toBeCalledWith("__0");
expect(
insertAutofillContentService["simulateUserMouseClickAndFocusEventInteractions"]
).toHaveBeenCalledWith(targetInput, true);
expect(elementEventCount).toEqual(expectedElementEventCount);
});
});
describe("insertValueIntoField", () => {
it("returns early if an element is not provided", () => {
const value = "test";
const element: FormFieldElement | null = null;
jest.spyOn(insertAutofillContentService as any, "handleInsertValueAndTriggerSimulatedEvents");
insertAutofillContentService["insertValueIntoField"](element, value);
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"]
).not.toHaveBeenCalled();
});
it("returns early if a value is not provided", () => {
const value = "";
const element: FormFieldElement | null = document.querySelector('input[type="text"]');
jest.spyOn(insertAutofillContentService as any, "handleInsertValueAndTriggerSimulatedEvents");
insertAutofillContentService["insertValueIntoField"](element, value);
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"]
).not.toHaveBeenCalled();
});
it("will set the inner text of the element if a span element is passed", () => {
document.body.innerHTML = `<span id="username"></span>`;
const value = "test";
const element = document.getElementById("username") as FormFieldElement;
jest.spyOn(insertAutofillContentService as any, "handleInsertValueAndTriggerSimulatedEvents");
insertAutofillContentService["insertValueIntoField"](element, value);
expect(element.innerText).toBe(value);
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"]
).toHaveBeenCalledWith(element, expect.any(Function));
});
it("will set the `checked` attribute of any passed checkbox or radio elements", () => {
document.body.innerHTML = `<input type="checkbox" id="checkbox" /><input type="radio" id="radio" />`;
const checkboxElement = document.getElementById("checkbox") as HTMLInputElement;
const radioElement = document.getElementById("radio") as HTMLInputElement;
jest.spyOn(insertAutofillContentService as any, "handleInsertValueAndTriggerSimulatedEvents");
const possibleValues = ["true", "y", "1", "yes", "✓"];
possibleValues.forEach((value) => {
insertAutofillContentService["insertValueIntoField"](checkboxElement, value);
insertAutofillContentService["insertValueIntoField"](radioElement, value);
expect(checkboxElement.checked).toBe(true);
expect(radioElement.checked).toBe(true);
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"]
).toHaveBeenCalledWith(checkboxElement, expect.any(Function));
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"]
).toHaveBeenCalledWith(radioElement, expect.any(Function));
checkboxElement.checked = false;
radioElement.checked = false;
});
});
it("will set the `value` attribute of any passed input or textarea elements", () => {
document.body.innerHTML = `<input type="text" id="username" /><textarea id="bio"></textarea>`;
const value1 = "test";
const value2 = "test2";
const textInputElement = document.getElementById("username") as HTMLInputElement;
textInputElement.value = value1;
const textareaElement = document.getElementById("bio") as HTMLTextAreaElement;
textareaElement.value = value2;
jest.spyOn(insertAutofillContentService as any, "handleInsertValueAndTriggerSimulatedEvents");
insertAutofillContentService["insertValueIntoField"](textInputElement, value1);
expect(textInputElement.value).toBe(value1);
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"]
).toHaveBeenCalledWith(textInputElement, expect.any(Function));
insertAutofillContentService["insertValueIntoField"](textareaElement, value2);
expect(textareaElement.value).toBe(value2);
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"]
).toHaveBeenCalledWith(textareaElement, expect.any(Function));
});
});
describe("handleInsertValueAndTriggerSimulatedEvents", () => {
it("triggers pre- and post-insert events on the element while filling the value into the element", () => {
const value = "test";
const element = document.querySelector('input[type="text"]') as FormFieldElement;
jest.spyOn(insertAutofillContentService as any, "triggerPreInsertEventsOnElement");
jest.spyOn(insertAutofillContentService as any, "triggerPostInsertEventsOnElement");
jest.spyOn(insertAutofillContentService as any, "triggerFillAnimationOnElement");
const valueChangeCallback = jest.fn(
() => ((element as FillableFormFieldElement).value = value)
);
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"](
element,
valueChangeCallback
);
expect(insertAutofillContentService["triggerPreInsertEventsOnElement"]).toHaveBeenCalledWith(
element
);
expect(valueChangeCallback).toHaveBeenCalled();
expect(insertAutofillContentService["triggerPostInsertEventsOnElement"]).toHaveBeenCalledWith(
element
);
expect(insertAutofillContentService["triggerFillAnimationOnElement"]).toHaveBeenCalledWith(
element
);
expect((element as FillableFormFieldElement).value).toBe(value);
});
});
describe("triggerPreInsertEventsOnElement", () => {
it("triggers a simulated click and keyboard event on the element", () => {
const initialElementValue = "test";
document.body.innerHTML = `<input type="text" id="username" value="${initialElementValue}"/>`;
const element = document.getElementById("username") as FillableFormFieldElement;
jest.spyOn(
insertAutofillContentService as any,
"simulateUserMouseClickAndFocusEventInteractions"
);
jest.spyOn(insertAutofillContentService as any, "simulateUserKeyboardEventInteractions");
insertAutofillContentService["triggerPreInsertEventsOnElement"](element);
expect(
insertAutofillContentService["simulateUserMouseClickAndFocusEventInteractions"]
).toHaveBeenCalledWith(element);
expect(
insertAutofillContentService["simulateUserKeyboardEventInteractions"]
).toHaveBeenCalledWith(element);
expect(element.value).toBe(initialElementValue);
});
});
describe("triggerPostInsertEventsOnElement", () => {
it("triggers simulated event interactions and blurs the element after", () => {
const elementValue = "test";
document.body.innerHTML = `<input type="text" id="username" value="${elementValue}"/>`;
const element = document.getElementById("username") as FillableFormFieldElement;
jest.spyOn(element, "blur");
jest.spyOn(insertAutofillContentService as any, "simulateUserKeyboardEventInteractions");
jest.spyOn(insertAutofillContentService as any, "simulateInputElementChangedEvent");
insertAutofillContentService["triggerPostInsertEventsOnElement"](element);
expect(
insertAutofillContentService["simulateUserKeyboardEventInteractions"]
).toHaveBeenCalledWith(element);
expect(insertAutofillContentService["simulateInputElementChangedEvent"]).toHaveBeenCalledWith(
element
);
expect(element.blur).toHaveBeenCalled();
expect(element.value).toBe(elementValue);
});
});
describe("triggerFillAnimationOnElement", () => {
beforeEach(() => {
jest.useFakeTimers();
jest.clearAllTimers();
});
describe("will not trigger the animation when...", () => {
it("the element is a non-hidden hidden input type", async () => {
document.body.innerHTML = mockLoginForm + '<input type="hidden" />';
const testElement = document.querySelector(
'input[type="hidden"]'
) as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
await jest.advanceTimersByTime(200);
expect(testElement.classList.add).not.toHaveBeenCalled();
expect(testElement.classList.remove).not.toHaveBeenCalled();
});
it("the element is a non-hidden textarea", () => {
document.body.innerHTML = mockLoginForm + "<textarea></textarea>";
const testElement = document.querySelector("textarea") as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).not.toHaveBeenCalled();
expect(testElement.classList.remove).not.toHaveBeenCalled();
});
it("the element is a unsupported tag", () => {
document.body.innerHTML = mockLoginForm + '<div id="input-tag"></div>';
const testElement = document.querySelector("#input-tag") as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).not.toHaveBeenCalled();
expect(testElement.classList.remove).not.toHaveBeenCalled();
});
it("the element has a `visibility: hidden;` CSS rule applied to it", () => {
const testElement = document.querySelector(
'input[type="password"]'
) as FillableFormFieldElement;
testElement.style.visibility = "hidden";
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).not.toHaveBeenCalled();
expect(testElement.classList.remove).not.toHaveBeenCalled();
});
it("the element has a `display: none;` CSS rule applied to it", () => {
const testElement = document.querySelector(
'input[type="password"]'
) as FillableFormFieldElement;
testElement.style.display = "none";
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).not.toHaveBeenCalled();
expect(testElement.classList.remove).not.toHaveBeenCalled();
});
it("a parent of the element has an `opacity: 0;` CSS rule applied to it", () => {
document.body.innerHTML =
mockLoginForm + '<div style="opacity: 0;"><input type="email" /></div>';
const testElement = document.querySelector(
'input[type="email"]'
) as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).not.toHaveBeenCalled();
expect(testElement.classList.remove).not.toHaveBeenCalled();
});
});
describe("will trigger the animation when...", () => {
it("the element is a non-hidden password field", () => {
const testElement = document.querySelector(
'input[type="password"]'
) as FillableFormFieldElement;
jest.spyOn(
insertAutofillContentService["domElementVisibilityService"],
"isElementHiddenByCss"
);
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(
insertAutofillContentService["domElementVisibilityService"].isElementHiddenByCss
).toHaveBeenCalledWith(testElement);
expect(testElement.classList.add).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
expect(testElement.classList.remove).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
});
it("the element is a non-hidden email input", () => {
document.body.innerHTML = mockLoginForm + '<input type="email" />';
const testElement = document.querySelector(
'input[type="email"]'
) as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
expect(testElement.classList.remove).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
});
it("the element is a non-hidden text input", () => {
document.body.innerHTML = mockLoginForm + '<input type="text" />';
const testElement = document.querySelector(
'input[type="text"]'
) as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
expect(testElement.classList.remove).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
});
it("the element is a non-hidden number input", () => {
document.body.innerHTML = mockLoginForm + '<input type="number" />';
const testElement = document.querySelector(
'input[type="number"]'
) as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
expect(testElement.classList.remove).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
});
it("the element is a non-hidden tel input", () => {
document.body.innerHTML = mockLoginForm + '<input type="tel" />';
const testElement = document.querySelector('input[type="tel"]') as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
expect(testElement.classList.remove).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
});
it("the element is a non-hidden url input", () => {
document.body.innerHTML = mockLoginForm + '<input type="url" />';
const testElement = document.querySelector('input[type="url"]') as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
expect(testElement.classList.remove).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
});
it("the element is a non-hidden span", () => {
document.body.innerHTML = mockLoginForm + '<span id="input-tag"></span>';
const testElement = document.querySelector("#input-tag") as FillableFormFieldElement;
jest.spyOn(testElement.classList, "add");
jest.spyOn(testElement.classList, "remove");
insertAutofillContentService["triggerFillAnimationOnElement"](testElement);
jest.advanceTimersByTime(200);
expect(testElement.classList.add).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
expect(testElement.classList.remove).toHaveBeenCalledWith(
"com-bitwarden-browser-animated-fill"
);
});
});
});
describe("triggerClickOnElement", () => {
it("will trigger a click event on the passed element", () => {
const inputElement = document.querySelector('input[type="text"]') as HTMLElement;
jest.spyOn(inputElement, "click");
insertAutofillContentService["triggerClickOnElement"](inputElement);
expect(inputElement.click).toHaveBeenCalled();
});
});
describe("triggerFocusOnElement", () => {
it("will trigger a focus event on the passed element and attempt to reset the value", () => {
const value = "test";
const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement;
inputElement.value = "test";
jest.spyOn(inputElement, "focus");
jest.spyOn(window, "String");
insertAutofillContentService["triggerFocusOnElement"](inputElement, true);
expect(window.String).toHaveBeenCalledWith(value);
expect(inputElement.focus).toHaveBeenCalled();
expect(inputElement.value).toEqual(value);
});
it("will not attempt to reset the value but will still focus the element", () => {
const value = "test";
const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement;
inputElement.value = "test";
jest.spyOn(inputElement, "focus");
jest.spyOn(window, "String");
insertAutofillContentService["triggerFocusOnElement"](inputElement, false);
expect(window.String).not.toHaveBeenCalledWith();
expect(inputElement.focus).toHaveBeenCalled();
expect(inputElement.value).toEqual(value);
});
});
describe("simulateUserMouseClickAndFocusEventInteractions", () => {
it("will trigger click and focus events on the passed element", () => {
const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement;
jest.spyOn(insertAutofillContentService as any, "triggerClickOnElement");
jest.spyOn(insertAutofillContentService as any, "triggerFocusOnElement");
insertAutofillContentService["simulateUserMouseClickAndFocusEventInteractions"](inputElement);
expect(insertAutofillContentService["triggerClickOnElement"]).toHaveBeenCalledWith(
inputElement
);
expect(insertAutofillContentService["triggerFocusOnElement"]).toHaveBeenCalledWith(
inputElement,
false
);
});
});
describe("simulateUserKeyboardEventInteractions", () => {
it("will trigger `keydown`, `keypress`, and `keyup` events on the passed element", () => {
const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement;
jest.spyOn(inputElement, "dispatchEvent");
insertAutofillContentService["simulateUserKeyboardEventInteractions"](inputElement);
[EVENTS.KEYDOWN, EVENTS.KEYPRESS, EVENTS.KEYUP].forEach((eventName) => {
expect(inputElement.dispatchEvent).toHaveBeenCalledWith(
new KeyboardEvent(eventName, { bubbles: true })
);
});
});
});
describe("simulateInputElementChangedEvent", () => {
it("will trigger `input` and `change` events on the passed element", () => {
const inputElement = document.querySelector('input[type="text"]') as HTMLInputElement;
jest.spyOn(inputElement, "dispatchEvent");
insertAutofillContentService["simulateInputElementChangedEvent"](inputElement);
[EVENTS.INPUT, EVENTS.CHANGE].forEach((eventName) => {
expect(inputElement.dispatchEvent).toHaveBeenCalledWith(
new Event(eventName, { bubbles: true })
);
});
});
});
});