bitwarden-estensione-browser/apps/browser/src/platform/popup/browser-popup-utils.spec.ts

444 lines
15 KiB
TypeScript

import { createChromeTabMock } from "../../autofill/spec/autofill-mocks";
import { BrowserApi } from "../browser/browser-api";
import BrowserPopupUtils from "./browser-popup-utils";
describe("BrowserPopupUtils", () => {
afterEach(() => {
jest.clearAllMocks();
});
describe("inSidebar", () => {
it("should return true if the window contains the sidebar query param", () => {
const win = { location: { href: "https://jest-testing.com?uilocation=sidebar" } } as Window;
expect(BrowserPopupUtils.inSidebar(win)).toBe(true);
});
it("should return false if the window does not contain the sidebar query param", () => {
const win = { location: { href: "https://jest-testing.com?uilocation=popout" } } as Window;
expect(BrowserPopupUtils.inSidebar(win)).toBe(false);
});
});
describe("inPopout", () => {
it("should return true if the window contains the popout query param", () => {
const win = { location: { href: "https://jest-testing.com?uilocation=popout" } } as Window;
expect(BrowserPopupUtils.inPopout(win)).toBe(true);
});
it("should return false if the window does not contain the popout query param", () => {
const win = { location: { href: "https://jest-testing.com?uilocation=sidebar" } } as Window;
expect(BrowserPopupUtils.inPopout(win)).toBe(false);
});
});
describe("inSingleActionPopout", () => {
it("should return true if the window contains the singleActionPopout query param", () => {
const win = {
location: { href: "https://jest-testing.com?singleActionPopout=123" },
} as Window;
expect(BrowserPopupUtils.inSingleActionPopout(win, "123")).toBe(true);
});
it("should return false if the window does not contain the singleActionPopout query param", () => {
const win = { location: { href: "https://jest-testing.com" } } as Window;
expect(BrowserPopupUtils.inSingleActionPopout(win, "123")).toBe(false);
});
});
describe("inPopup", () => {
it("should return true if the window does not contain the popup query param", () => {
const win = { location: { href: "https://jest-testing.com" } } as Window;
expect(BrowserPopupUtils.inPopup(win)).toBe(true);
});
it("should return true if the window contains the popup query param", () => {
const win = { location: { href: "https://jest-testing.com?uilocation=popup" } } as Window;
expect(BrowserPopupUtils.inPopup(win)).toBe(true);
});
it("should return false if the window does not contain the popup query param", () => {
const win = { location: { href: "https://jest-testing.com?uilocation=sidebar" } } as Window;
expect(BrowserPopupUtils.inPopup(win)).toBe(false);
});
});
describe("getContentScrollY", () => {
it("should return the scroll position of the popup", () => {
const win = {
document: { getElementsByTagName: () => [{ scrollTop: 100 }] },
} as unknown as Window;
expect(BrowserPopupUtils.getContentScrollY(win)).toBe(100);
});
});
describe("setContentScrollY", () => {
it("should set the scroll position of the popup", async () => {
window.document.body.innerHTML = `
<main>
<div></div>
</main>
`;
await BrowserPopupUtils.setContentScrollY(window, 200);
expect(window.document.getElementsByTagName("main")[0].scrollTop).toBe(200);
});
it("should not set the scroll position of the popup if the scrollY is null", async () => {
window.document.body.innerHTML = `
<main>
<div></div>
</main>
`;
await BrowserPopupUtils.setContentScrollY(window, null);
expect(window.document.getElementsByTagName("main")[0].scrollTop).toBe(0);
});
it("will set the scroll position of the popup after the provided delay", async () => {
jest.useRealTimers();
window.document.body.innerHTML = `
<div class="scrolling-container">
<div></div>
</div>
`;
await BrowserPopupUtils.setContentScrollY(window, 300, {
delay: 200,
containerSelector: ".scrolling-container",
});
expect(window.document.querySelector(".scrolling-container").scrollTop).toBe(300);
});
});
describe("backgroundInitializationRequired", () => {
it("return true if the background page is a null value", () => {
jest.spyOn(BrowserApi, "getBackgroundPage").mockReturnValue(null);
expect(BrowserPopupUtils.backgroundInitializationRequired()).toBe(true);
});
it("return false if the background page is not a null value", () => {
jest.spyOn(BrowserApi, "getBackgroundPage").mockReturnValue({});
expect(BrowserPopupUtils.backgroundInitializationRequired()).toBe(false);
});
});
describe("inPrivateMode", () => {
it("returns false if the background requires initialization", () => {
jest.spyOn(BrowserPopupUtils, "backgroundInitializationRequired").mockReturnValue(false);
expect(BrowserPopupUtils.inPrivateMode()).toBe(false);
});
it("returns false if the manifest version is for version 3", () => {
jest.spyOn(BrowserPopupUtils, "backgroundInitializationRequired").mockReturnValue(true);
jest.spyOn(BrowserApi, "manifestVersion", "get").mockReturnValue(3);
expect(BrowserPopupUtils.inPrivateMode()).toBe(false);
});
it("returns true if the background does not require initalization and the manifest version is version 2", () => {
jest.spyOn(BrowserPopupUtils, "backgroundInitializationRequired").mockReturnValue(true);
jest.spyOn(BrowserApi, "manifestVersion", "get").mockReturnValue(2);
expect(BrowserPopupUtils.inPrivateMode()).toBe(true);
});
});
describe("openPopout", () => {
beforeEach(() => {
jest.spyOn(BrowserApi, "getWindow").mockResolvedValueOnce({
id: 1,
left: 100,
top: 100,
focused: false,
alwaysOnTop: false,
incognito: false,
width: 380,
});
jest.spyOn(BrowserApi, "createWindow").mockImplementation();
});
it("creates a window with the default window options", async () => {
const url = "popup/index.html";
jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(false);
await BrowserPopupUtils.openPopout(url);
expect(BrowserApi.createWindow).toHaveBeenCalledWith({
type: "popup",
focused: true,
width: 380,
height: 630,
left: 85,
top: 190,
url: `chrome-extension://id/${url}?uilocation=popout`,
});
});
it("skips parsing the passed extension url path if the option to do that is set", () => {
const url = "popup/index.html?uilocation=popout#/tabs/vault";
jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(false);
jest.spyOn(BrowserPopupUtils as any, "buildPopoutUrl");
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
BrowserPopupUtils.openPopout(url);
expect(BrowserPopupUtils["buildPopoutUrl"]).not.toHaveBeenCalled();
});
it("replaces any existing `uilocation=` query params within the passed extension url path to state the uilocation is a popup", async () => {
const url = "popup/index.html?uilocation=sidebar#/tabs/vault";
jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(false);
await BrowserPopupUtils.openPopout(url);
expect(BrowserApi.createWindow).toHaveBeenCalledWith({
type: "popup",
focused: true,
width: 380,
height: 630,
left: 85,
top: 190,
url: `chrome-extension://id/popup/index.html?uilocation=popout#/tabs/vault`,
});
});
it("creates a single action popout window", async () => {
const url = "popup/index.html";
jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(false);
await BrowserPopupUtils.openPopout(url, { singleActionKey: "123" });
expect(BrowserApi.createWindow).toHaveBeenCalledWith({
type: "popup",
focused: true,
width: 380,
height: 630,
left: 85,
top: 190,
url: `chrome-extension://id/${url}?uilocation=popout&singleActionPopout=123`,
});
});
it("does not create a single action popout window if it is already open", async () => {
const url = "popup/index.html";
jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(true);
await BrowserPopupUtils.openPopout(url, { singleActionKey: "123" });
expect(BrowserApi.createWindow).not.toHaveBeenCalled();
});
it("creates a window with the provided window options", async () => {
const url = "popup/index.html";
jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(false);
await BrowserPopupUtils.openPopout(url, {
windowOptions: {
type: "popup",
focused: false,
width: 100,
height: 100,
},
});
expect(BrowserApi.createWindow).toHaveBeenCalledWith({
type: "popup",
focused: false,
width: 100,
height: 100,
left: 85,
top: 190,
url: `chrome-extension://id/${url}?uilocation=popout`,
});
});
it("opens a single action window if the forceCloseExistingWindows param is true", async () => {
const url = "popup/index.html";
jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(true);
await BrowserPopupUtils.openPopout(url, {
singleActionKey: "123",
forceCloseExistingWindows: true,
});
expect(BrowserApi.createWindow).toHaveBeenCalledWith({
type: "popup",
focused: true,
width: 380,
height: 630,
left: 85,
top: 190,
url: `chrome-extension://id/${url}?uilocation=popout&singleActionPopout=123`,
});
});
});
describe("openCurrentPagePopout", () => {
it("opens a popout window for the current page", async () => {
const win = { location: { href: "https://example.com#/tabs/current" } } as Window;
jest.spyOn(BrowserPopupUtils, "openPopout").mockImplementation();
jest.spyOn(BrowserApi, "closePopup").mockImplementation();
jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(false);
await BrowserPopupUtils.openCurrentPagePopout(win);
expect(BrowserPopupUtils.openPopout).toHaveBeenCalledWith("/#/tabs/vault");
expect(BrowserApi.closePopup).not.toHaveBeenCalled();
});
it("opens a popout window for the specified URL", async () => {
const win = {} as Window;
jest.spyOn(BrowserPopupUtils, "openPopout").mockImplementation();
jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(false);
await BrowserPopupUtils.openCurrentPagePopout(win, "https://example.com#/settings");
expect(BrowserPopupUtils.openPopout).toHaveBeenCalledWith("/#/settings");
});
it("opens a popout window for the current page and closes the popup window", async () => {
const win = { location: { href: "https://example.com/#/tabs/vault" } } as Window;
jest.spyOn(BrowserPopupUtils, "openPopout").mockImplementation();
jest.spyOn(BrowserApi, "closePopup").mockImplementation();
jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(true);
await BrowserPopupUtils.openCurrentPagePopout(win);
expect(BrowserPopupUtils.openPopout).toHaveBeenCalledWith("/#/tabs/vault");
expect(BrowserApi.closePopup).toHaveBeenCalledWith(win);
});
});
describe("closeSingleActionPopout", () => {
it("closes any existing single action popouts", async () => {
const url = "popup/index.html";
jest.useFakeTimers();
jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValueOnce([
createChromeTabMock({
id: 10,
url: `chrome-extension://id/${url}?uilocation=popout&singleActionPopout=123`,
windowId: 11,
}),
createChromeTabMock({
id: 20,
url: `chrome-extension://id/${url}?uilocation=popout&singleActionPopout=123`,
windowId: 21,
}),
createChromeTabMock({
id: 30,
url: `chrome-extension://id/${url}?uilocation=popout&singleActionPopout=456`,
windowId: 31,
}),
]);
jest.spyOn(BrowserApi, "removeWindow").mockResolvedValueOnce();
await BrowserPopupUtils.closeSingleActionPopout("123");
jest.runOnlyPendingTimers();
expect(BrowserApi.removeWindow).toHaveBeenNthCalledWith(1, 11);
expect(BrowserApi.removeWindow).toHaveBeenNthCalledWith(2, 21);
expect(BrowserApi.removeWindow).not.toHaveBeenCalledWith(31);
});
});
describe("isSingleActionPopoutOpen", () => {
const windowOptions = {
id: 1,
left: 100,
top: 100,
focused: false,
alwaysOnTop: false,
incognito: false,
width: 500,
height: 800,
};
beforeEach(() => {
jest.spyOn(BrowserApi, "updateWindowProperties").mockImplementation();
jest.spyOn(BrowserApi, "removeWindow").mockImplementation();
});
it("returns false if the popoutKey is not provided", async () => {
await expect(BrowserPopupUtils["isSingleActionPopoutOpen"](undefined, {})).resolves.toBe(
false,
);
});
it("returns false if no popout windows are found", async () => {
jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValueOnce([]);
await expect(
BrowserPopupUtils["isSingleActionPopoutOpen"]("123", windowOptions),
).resolves.toBe(false);
});
it("returns false if no single action popout is found relating to the popoutKey", async () => {
jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValueOnce([
createChromeTabMock({
id: 10,
url: `chrome-extension://id/popup/index.html?uilocation=popout&singleActionPopout=123`,
}),
createChromeTabMock({
id: 20,
url: `chrome-extension://id/popup/index.html?uilocation=popout&singleActionPopout=123`,
}),
createChromeTabMock({
id: 30,
url: `chrome-extension://id/popup/index.html?uilocation=popout&singleActionPopout=456`,
}),
]);
await expect(
BrowserPopupUtils["isSingleActionPopoutOpen"]("789", windowOptions),
).resolves.toBe(false);
});
it("returns true if a single action popout is found relating to the popoutKey", async () => {
jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValueOnce([
createChromeTabMock({
id: 10,
url: `chrome-extension://id/popup/index.html?uilocation=popout&singleActionPopout=123`,
}),
createChromeTabMock({
id: 20,
url: `chrome-extension://id/popup/index.html?uilocation=popout&singleActionPopout=123`,
}),
createChromeTabMock({
id: 30,
url: `chrome-extension://id/popup/index.html?uilocation=popout&singleActionPopout=456`,
}),
]);
await expect(
BrowserPopupUtils["isSingleActionPopoutOpen"]("123", windowOptions),
).resolves.toBe(true);
expect(BrowserApi.updateWindowProperties).toHaveBeenCalledWith(2, {
focused: true,
width: 500,
height: 800,
top: 100,
left: 100,
});
expect(BrowserApi.removeWindow).toHaveBeenCalledTimes(1);
});
});
});