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 = `
`; 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 = `
`; 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 = `
`; 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); }); }); });