mirror of
https://github.com/bitwarden/browser.git
synced 2024-09-27 04:03:00 +02:00
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 })
|
||
|
);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|