1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +00:00
Files
browser/apps/browser/src/platform/services/browser-platform-utils.service.spec.ts
Cesar Gonzalez 51f482dde9 [PM-5880] Refactor browser platform utils service to remove window references (#7885)
* [PM-5880] Refactor Browser Platform Utils Service to Remove Window Service

* [PM-5880] Implementing BrowserClipboardService to handle clipboard logic between the BrowserPlatformUtils and offscreen document

* [PM-5880] Adjusting how readText is handled within BrowserClipboardService

* [PM-5880] Adjusting how readText is handled within BrowserClipboardService

* [PM-5880] Working through implementation of chrome offscreen API usage

* [PM-5880] Implementing jest tests for the methods added to the BrowserApi class

* [PM-5880] Implementing jest tests for the OffscreenDocument class

* [PM-5880] Working through jest tests for BrowserClipboardService

* [PM-5880] Adding typing information to the clipboard methods present within the BrowserPlatformUtilsService

* [PM-5880] Working on adding ServiceWorkerGlobalScope typing information

* [PM-5880] Updating window references when calling BrowserPlatformUtils methods

* [PM-5880] Finishing out jest tests for the BrowserClipboardService

* [PM-5880] Finishing out jest tests for the BrowserClipboardService

* [PM-5880] Implementing jest tests to validate the changes within BrowserApi

* [PM-5880] Implementing jest tests to ensure coverage within OffscreenDocument

* [PM-5880] Implementing jest tests for the BrowserPlatformUtilsService

* [PM-5880] Removing unused catch statements

* [PM-5880] Implementing jest tests for the BrowserPlatformUtilsService

* [PM-5880] Implementing jest tests for the BrowserPlatformUtilsService

* [PM-5880] Fixing broken tests
2024-03-06 16:33:38 +00:00

387 lines
14 KiB
TypeScript

import { DeviceType } from "@bitwarden/common/enums";
import { flushPromises } from "../../autofill/spec/testing-utils";
import { SafariApp } from "../../browser/safariApp";
import { BrowserApi } from "../browser/browser-api";
import BrowserClipboardService from "./browser-clipboard.service";
import BrowserPlatformUtilsService from "./browser-platform-utils.service";
describe("Browser Utils Service", () => {
let browserPlatformUtilsService: BrowserPlatformUtilsService;
const clipboardWriteCallbackSpy = jest.fn();
beforeEach(() => {
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
browserPlatformUtilsService = new BrowserPlatformUtilsService(
null,
clipboardWriteCallbackSpy,
null,
window,
);
});
describe("getBrowser", () => {
const originalUserAgent = navigator.userAgent;
// Reset the userAgent.
afterAll(() => {
Object.defineProperty(navigator, "userAgent", {
value: originalUserAgent,
});
});
beforeEach(() => {
(window as any).matchMedia = jest.fn().mockReturnValueOnce({});
});
afterEach(() => {
window.matchMedia = undefined;
(BrowserPlatformUtilsService as any).deviceCache = null;
});
it("should detect chrome", () => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
});
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.ChromeExtension);
});
it("should detect firefox", () => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0",
});
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension);
});
it("should detect opera", () => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3175.3 Safari/537.36 OPR/49.0.2695.0 (Edition developer)",
});
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.OperaExtension);
});
it("should detect edge", () => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43",
});
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.EdgeExtension);
});
it("should detect safari", () => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/602.4.8 (KHTML, like Gecko) Version/10.0.3 Safari/602.4.8",
});
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.SafariExtension);
});
it("should detect vivaldi", () => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.97 Safari/537.36 Vivaldi/1.94.1008.40",
});
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.VivaldiExtension);
});
it("returns a previously determined device using a cached value", () => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0",
});
jest.spyOn(BrowserPlatformUtilsService, "isFirefox");
browserPlatformUtilsService.getDevice();
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension);
expect(BrowserPlatformUtilsService.isFirefox).toHaveBeenCalledTimes(1);
});
});
describe("getDeviceString", () => {
it("returns a string value indicating the device type", () => {
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.ChromeExtension);
expect(browserPlatformUtilsService.getDeviceString()).toBe("chrome");
});
});
describe("isViewOpen", () => {
it("returns false if a heartbeat response is not received", async () => {
BrowserApi.sendMessageWithResponse = jest.fn().mockResolvedValueOnce(undefined);
const isViewOpen = await browserPlatformUtilsService.isViewOpen();
expect(isViewOpen).toBe(false);
});
it("returns true if a heartbeat response is received", async () => {
BrowserApi.sendMessageWithResponse = jest
.fn()
.mockImplementationOnce((subscriber) =>
Promise.resolve((subscriber === "checkVaultPopupHeartbeat") as any),
);
const isViewOpen = await browserPlatformUtilsService.isViewOpen();
expect(isViewOpen).toBe(true);
});
});
describe("copyToClipboard", () => {
const getManifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
const sendMessageToAppSpy = jest.spyOn(SafariApp, "sendMessageToApp");
const clipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy");
let triggerOffscreenCopyToClipboardSpy: jest.SpyInstance;
beforeEach(() => {
getManifestVersionSpy.mockReturnValue(2);
triggerOffscreenCopyToClipboardSpy = jest.spyOn(
browserPlatformUtilsService as any,
"triggerOffscreenCopyToClipboard",
);
});
afterEach(() => {
jest.resetAllMocks();
});
it("sends a copy to clipboard message to the desktop application if a user is using the safari browser", async () => {
const text = "test";
const clearMs = 1000;
sendMessageToAppSpy.mockResolvedValueOnce("success");
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.SafariExtension);
browserPlatformUtilsService.copyToClipboard(text, { clearMs });
await flushPromises();
expect(sendMessageToAppSpy).toHaveBeenCalledWith("copyToClipboard", text);
expect(clipboardWriteCallbackSpy).toHaveBeenCalledWith(text, clearMs);
expect(clipboardServiceCopySpy).not.toHaveBeenCalled();
expect(triggerOffscreenCopyToClipboardSpy).not.toHaveBeenCalled();
});
it("sets the copied text to a unicode placeholder when the user is using Chrome if the passed text is an empty string", async () => {
const text = "";
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.ChromeExtension);
browserPlatformUtilsService.copyToClipboard(text);
await flushPromises();
expect(clipboardServiceCopySpy).toHaveBeenCalledWith(window, "\u0000");
});
it("copies the passed text using the BrowserClipboardService", async () => {
const text = "test";
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.ChromeExtension);
browserPlatformUtilsService.copyToClipboard(text, { window: self });
await flushPromises();
expect(clipboardServiceCopySpy).toHaveBeenCalledWith(self, text);
expect(triggerOffscreenCopyToClipboardSpy).not.toHaveBeenCalled();
});
it("copies the passed text using the offscreen document if the extension is using manifest v3", async () => {
const text = "test";
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.ChromeExtension);
getManifestVersionSpy.mockReturnValue(3);
jest.spyOn(BrowserApi, "createOffscreenDocument");
jest.spyOn(BrowserApi, "sendMessageWithResponse").mockResolvedValue(undefined);
jest.spyOn(BrowserApi, "closeOffscreenDocument");
browserPlatformUtilsService.copyToClipboard(text);
await flushPromises();
expect(triggerOffscreenCopyToClipboardSpy).toHaveBeenCalledWith(text);
expect(clipboardServiceCopySpy).not.toHaveBeenCalled();
expect(BrowserApi.createOffscreenDocument).toHaveBeenCalledWith(
[chrome.offscreen.Reason.CLIPBOARD],
"Write text to the clipboard.",
);
expect(BrowserApi.sendMessageWithResponse).toHaveBeenCalledWith("offscreenCopyToClipboard", {
text,
});
expect(BrowserApi.closeOffscreenDocument).toHaveBeenCalled();
});
it("skips the clipboardWriteCallback if the clipboard is clearing", async () => {
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.ChromeExtension);
browserPlatformUtilsService.copyToClipboard("test", { window: self, clearing: true });
await flushPromises();
expect(clipboardWriteCallbackSpy).not.toHaveBeenCalled();
});
});
describe("readFromClipboard", () => {
const getManifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get");
const sendMessageToAppSpy = jest.spyOn(SafariApp, "sendMessageToApp");
const clipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read");
beforeEach(() => {
getManifestVersionSpy.mockReturnValue(2);
});
afterEach(() => {
jest.resetAllMocks();
});
it("sends a ready from clipboard message to the desktop application if a user is using the safari browser", async () => {
sendMessageToAppSpy.mockResolvedValueOnce("test");
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.SafariExtension);
const result = await browserPlatformUtilsService.readFromClipboard();
expect(sendMessageToAppSpy).toHaveBeenCalledWith("readFromClipboard");
expect(clipboardServiceReadSpy).not.toHaveBeenCalled();
expect(result).toBe("test");
});
it("reads text from the clipboard using the ClipboardService", async () => {
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.ChromeExtension);
clipboardServiceReadSpy.mockResolvedValueOnce("test");
const result = await browserPlatformUtilsService.readFromClipboard({ window: self });
expect(clipboardServiceReadSpy).toHaveBeenCalledWith(self);
expect(sendMessageToAppSpy).not.toHaveBeenCalled();
expect(result).toBe("test");
});
it("reads the clipboard text using the offscreen document", async () => {
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.ChromeExtension);
getManifestVersionSpy.mockReturnValue(3);
jest.spyOn(BrowserApi, "createOffscreenDocument");
jest.spyOn(BrowserApi, "sendMessageWithResponse").mockResolvedValue("test");
jest.spyOn(BrowserApi, "closeOffscreenDocument");
await browserPlatformUtilsService.readFromClipboard();
expect(BrowserApi.createOffscreenDocument).toHaveBeenCalledWith(
[chrome.offscreen.Reason.CLIPBOARD],
"Read text from the clipboard.",
);
expect(BrowserApi.sendMessageWithResponse).toHaveBeenCalledWith("offscreenReadFromClipboard");
expect(BrowserApi.closeOffscreenDocument).toHaveBeenCalled();
});
it("returns an empty string from the offscreen document if the response is not of type string", async () => {
jest
.spyOn(browserPlatformUtilsService, "getDevice")
.mockReturnValue(DeviceType.ChromeExtension);
getManifestVersionSpy.mockReturnValue(3);
jest.spyOn(BrowserApi, "createOffscreenDocument");
jest.spyOn(BrowserApi, "sendMessageWithResponse").mockResolvedValue(1);
jest.spyOn(BrowserApi, "closeOffscreenDocument");
const result = await browserPlatformUtilsService.readFromClipboard();
expect(result).toBe("");
});
});
});
describe("Safari Height Fix", () => {
const originalUserAgent = navigator.userAgent;
// Reset the userAgent.
afterAll(() => {
Object.defineProperty(navigator, "userAgent", {
value: originalUserAgent,
});
});
afterEach(() => {
(BrowserPlatformUtilsService as any).deviceCache = null;
});
test.each([
[
"safari 15.6.1",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15",
true,
],
[
"safari 16.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15",
true,
],
[
"safari 16.1",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
false,
],
[
"safari 16.4",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15",
false,
],
[
"safari 17.0 (future release)",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
false,
],
[
"chrome",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
false,
],
[
"firefox",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0",
false,
],
[
"opera",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3175.3 Safari/537.36 OPR/49.0.2695.0 (Edition developer)",
false,
],
[
"edge",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43",
false,
],
])("Apply fix for %s", (name, userAgent, expected) => {
Object.defineProperty(navigator, "userAgent", {
configurable: true,
value: userAgent,
});
expect(BrowserPlatformUtilsService.shouldApplySafariHeightFix(window)).toBe(expected);
});
});