mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 16:53:34 +00:00
* [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>
1048 lines
40 KiB
TypeScript
1048 lines
40 KiB
TypeScript
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 })
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|