mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-24 21:41:33 +01:00
[PM-5189] Implementing jest tests for AutofillInlineMenuContentService
This commit is contained in:
parent
c2e62940e0
commit
7832784be6
@ -1,6 +1,10 @@
|
|||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
import AutofillInit from "../../../content/autofill-init";
|
import AutofillInit from "../../../content/autofill-init";
|
||||||
import { AutofillOverlayElement } from "../../../enums/autofill-overlay.enum";
|
import { AutofillOverlayElement } from "../../../enums/autofill-overlay.enum";
|
||||||
import { sendMockExtensionMessage } from "../../../spec/testing-utils";
|
import { createMutationRecordMock } from "../../../spec/autofill-mocks";
|
||||||
|
import { flushPromises, sendMockExtensionMessage } from "../../../spec/testing-utils";
|
||||||
|
import { ElementWithOpId } from "../../../types";
|
||||||
|
|
||||||
import { AutofillInlineMenuContentService } from "./autofill-inline-menu-content.service";
|
import { AutofillInlineMenuContentService } from "./autofill-inline-menu-content.service";
|
||||||
|
|
||||||
@ -9,6 +13,7 @@ describe("AutofillInlineMenuContentService", () => {
|
|||||||
let autofillInit: AutofillInit;
|
let autofillInit: AutofillInit;
|
||||||
let sendExtensionMessageSpy: jest.SpyInstance;
|
let sendExtensionMessageSpy: jest.SpyInstance;
|
||||||
let observeBodyMutationsSpy: jest.SpyInstance;
|
let observeBodyMutationsSpy: jest.SpyInstance;
|
||||||
|
const sendResponseSpy = jest.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
globalThis.document.body.innerHTML = "";
|
globalThis.document.body.innerHTML = "";
|
||||||
@ -39,7 +44,7 @@ describe("AutofillInlineMenuContentService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("extension message handlers", () => {
|
describe("extension message handlers", () => {
|
||||||
describe("closeAutofillInlineMenu", () => {
|
describe("closeAutofillInlineMenu message handler", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
observeBodyMutationsSpy.mockImplementation();
|
observeBodyMutationsSpy.mockImplementation();
|
||||||
});
|
});
|
||||||
@ -105,7 +110,7 @@ describe("AutofillInlineMenuContentService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("appendAutofillInlineMenuToDom", () => {
|
describe("appendAutofillInlineMenuToDom message handler", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
observeBodyMutationsSpy.mockImplementation();
|
observeBodyMutationsSpy.mockImplementation();
|
||||||
});
|
});
|
||||||
@ -137,7 +142,7 @@ describe("AutofillInlineMenuContentService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("toggleAutofillInlineMenuHidden", () => {
|
describe("toggleAutofillInlineMenuHidden message handler", () => {
|
||||||
it("sets the inline elements as hidden if the elements do not exist", () => {
|
it("sets the inline elements as hidden if the elements do not exist", () => {
|
||||||
sendMockExtensionMessage({
|
sendMockExtensionMessage({
|
||||||
command: "toggleAutofillInlineMenuHidden",
|
command: "toggleAutofillInlineMenuHidden",
|
||||||
@ -176,5 +181,248 @@ describe("AutofillInlineMenuContentService", () => {
|
|||||||
expect(autofillInlineMenuContentService["isListVisible"]).toBe(false);
|
expect(autofillInlineMenuContentService["isListVisible"]).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("checkIsAutofillInlineMenuButtonVisible message handler", () => {
|
||||||
|
it("returns true if the inline menu button is visible", async () => {
|
||||||
|
autofillInlineMenuContentService["isButtonVisible"] = true;
|
||||||
|
|
||||||
|
sendMockExtensionMessage(
|
||||||
|
{ command: "checkIsAutofillInlineMenuButtonVisible" },
|
||||||
|
mock<chrome.runtime.MessageSender>(),
|
||||||
|
sendResponseSpy,
|
||||||
|
);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(sendResponseSpy).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("checkIsAutofillInlineMenuListVisible message handler", () => {
|
||||||
|
it("returns true if the inline menu list is visible", async () => {
|
||||||
|
autofillInlineMenuContentService["isListVisible"] = true;
|
||||||
|
|
||||||
|
sendMockExtensionMessage(
|
||||||
|
{ command: "checkIsAutofillInlineMenuListVisible" },
|
||||||
|
mock<chrome.runtime.MessageSender>(),
|
||||||
|
sendResponseSpy,
|
||||||
|
);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(sendResponseSpy).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("handleInlineMenuElementMutationObserverUpdate", () => {
|
||||||
|
let usernameField: ElementWithOpId<HTMLInputElement>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<form id="validFormId">
|
||||||
|
<input type="text" id="username-field" placeholder="username" />
|
||||||
|
<input type="password" id="password-field" placeholder="password" />
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
usernameField = document.getElementById(
|
||||||
|
"username-field",
|
||||||
|
) as ElementWithOpId<HTMLInputElement>;
|
||||||
|
usernameField.style.setProperty("display", "block", "important");
|
||||||
|
jest.spyOn(usernameField, "removeAttribute");
|
||||||
|
jest.spyOn(usernameField.style, "setProperty");
|
||||||
|
jest
|
||||||
|
.spyOn(
|
||||||
|
autofillInlineMenuContentService as any,
|
||||||
|
"isTriggeringExcessiveMutationObserverIterations",
|
||||||
|
)
|
||||||
|
.mockReturnValue(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips handling the mutation if excessive mutation observer events are triggered", () => {
|
||||||
|
jest
|
||||||
|
.spyOn(
|
||||||
|
autofillInlineMenuContentService as any,
|
||||||
|
"isTriggeringExcessiveMutationObserverIterations",
|
||||||
|
)
|
||||||
|
.mockReturnValue(true);
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["handleInlineMenuElementMutationObserverUpdate"]([
|
||||||
|
createMutationRecordMock({ target: usernameField }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(usernameField.removeAttribute).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips handling the mutation if the record type is not for `attributes`", () => {
|
||||||
|
autofillInlineMenuContentService["handleInlineMenuElementMutationObserverUpdate"]([
|
||||||
|
createMutationRecordMock({ target: usernameField, type: "childList" }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(usernameField.removeAttribute).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes all element attributes that are not the style attribute", () => {
|
||||||
|
autofillInlineMenuContentService["handleInlineMenuElementMutationObserverUpdate"]([
|
||||||
|
createMutationRecordMock({
|
||||||
|
target: usernameField,
|
||||||
|
type: "attributes",
|
||||||
|
attributeName: "placeholder",
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(usernameField.removeAttribute).toHaveBeenCalledWith("placeholder");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes all attached style attributes and sets the default styles", () => {
|
||||||
|
autofillInlineMenuContentService["handleInlineMenuElementMutationObserverUpdate"]([
|
||||||
|
createMutationRecordMock({
|
||||||
|
target: usernameField,
|
||||||
|
type: "attributes",
|
||||||
|
attributeName: "style",
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(usernameField.removeAttribute).toHaveBeenCalledWith("style");
|
||||||
|
expect(usernameField.style.setProperty).toHaveBeenCalledWith("all", "initial", "important");
|
||||||
|
expect(usernameField.style.setProperty).toHaveBeenCalledWith(
|
||||||
|
"position",
|
||||||
|
"fixed",
|
||||||
|
"important",
|
||||||
|
);
|
||||||
|
expect(usernameField.style.setProperty).toHaveBeenCalledWith("display", "block", "important");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("handleBodyElementMutationObserverUpdate", () => {
|
||||||
|
let buttonElement: HTMLElement;
|
||||||
|
let listElement: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
document.body.innerHTML = `
|
||||||
|
<div class="overlay-button"></div>
|
||||||
|
<div class="overlay-list"></div>
|
||||||
|
`;
|
||||||
|
buttonElement = document.querySelector(".overlay-button") as HTMLElement;
|
||||||
|
listElement = document.querySelector(".overlay-list") as HTMLElement;
|
||||||
|
autofillInlineMenuContentService["buttonElement"] = buttonElement;
|
||||||
|
autofillInlineMenuContentService["listElement"] = listElement;
|
||||||
|
autofillInlineMenuContentService["isListVisible"] = true;
|
||||||
|
jest.spyOn(globalThis.document.body, "insertBefore");
|
||||||
|
jest
|
||||||
|
.spyOn(
|
||||||
|
autofillInlineMenuContentService as any,
|
||||||
|
"isTriggeringExcessiveMutationObserverIterations",
|
||||||
|
)
|
||||||
|
.mockReturnValue(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips handling the mutation if the overlay elements are not present in the DOM", () => {
|
||||||
|
autofillInlineMenuContentService["buttonElement"] = undefined;
|
||||||
|
autofillInlineMenuContentService["listElement"] = undefined;
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||||
|
|
||||||
|
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips handling the mutation if excessive mutations are being triggered", () => {
|
||||||
|
jest
|
||||||
|
.spyOn(
|
||||||
|
autofillInlineMenuContentService as any,
|
||||||
|
"isTriggeringExcessiveMutationObserverIterations",
|
||||||
|
)
|
||||||
|
.mockReturnValue(true);
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||||
|
|
||||||
|
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips re-arranging the DOM elements if the last child of the body is the overlay list and the second to last child of the body is the overlay button", () => {
|
||||||
|
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||||
|
|
||||||
|
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips re-arranging the DOM elements if the last child is the overlay button and the overlay list is not visible", () => {
|
||||||
|
listElement.remove();
|
||||||
|
autofillInlineMenuContentService["isListVisible"] = false;
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||||
|
|
||||||
|
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("positions the overlay button before the overlay list if an element has inserted itself after the button element", () => {
|
||||||
|
const injectedElement = document.createElement("div");
|
||||||
|
document.body.insertBefore(injectedElement, listElement);
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||||
|
|
||||||
|
expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith(
|
||||||
|
buttonElement,
|
||||||
|
listElement,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("positions the overlay button before the overlay list if the elements have inserted in incorrect order", () => {
|
||||||
|
document.body.appendChild(buttonElement);
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||||
|
|
||||||
|
expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith(
|
||||||
|
buttonElement,
|
||||||
|
listElement,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("positions the last child before the overlay button if it is not the overlay list", () => {
|
||||||
|
const injectedElement = document.createElement("div");
|
||||||
|
document.body.appendChild(injectedElement);
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["handleBodyElementMutationObserverUpdate"]();
|
||||||
|
|
||||||
|
expect(globalThis.document.body.insertBefore).toHaveBeenCalledWith(
|
||||||
|
injectedElement,
|
||||||
|
buttonElement,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isTriggeringExcessiveMutationObserverIterations", () => {
|
||||||
|
it("clears any existing reset timeout", () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const clearTimeoutSpy = jest.spyOn(globalThis, "clearTimeout");
|
||||||
|
autofillInlineMenuContentService["mutationObserverIterationsResetTimeout"] = setTimeout(
|
||||||
|
jest.fn(),
|
||||||
|
123,
|
||||||
|
);
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["isTriggeringExcessiveMutationObserverIterations"]();
|
||||||
|
|
||||||
|
expect(clearTimeoutSpy).toHaveBeenCalledWith(expect.anything());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will reset the number of mutationObserverIterations after two seconds", () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
autofillInlineMenuContentService["mutationObserverIterations"] = 10;
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["isTriggeringExcessiveMutationObserverIterations"]();
|
||||||
|
jest.advanceTimersByTime(2000);
|
||||||
|
|
||||||
|
expect(autofillInlineMenuContentService["mutationObserverIterations"]).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("will blur the overlay field and remove the autofill overlay if excessive mutation observer iterations are triggering", async () => {
|
||||||
|
autofillInlineMenuContentService["mutationObserverIterations"] = 101;
|
||||||
|
const closeInlineMenuSpy = jest.spyOn(
|
||||||
|
autofillInlineMenuContentService as any,
|
||||||
|
"closeInlineMenu",
|
||||||
|
);
|
||||||
|
|
||||||
|
autofillInlineMenuContentService["isTriggeringExcessiveMutationObserverIterations"]();
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(closeInlineMenuSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user