From 6f2709bcaa5fc82172133322b95f523fb2ee2c7f Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 23 Apr 2021 16:31:56 -0700 Subject: [PATCH 1/2] feat: add tests for registerServiceWorker --- test/unit/register.test.ts | 94 ++++++++++++++++++++++++++++++++++++++ test/unit/util.test.ts | 2 +- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/test/unit/register.test.ts b/test/unit/register.test.ts index d19f6300..c163e0a5 100644 --- a/test/unit/register.test.ts +++ b/test/unit/register.test.ts @@ -1,5 +1,7 @@ import { JSDOM } from "jsdom" import { loggerModule } from "../utils/helpers" +import { registerServiceWorker } from "../../src/browser/register" +import { LocationLike } from "./util.test" describe("register", () => { describe("when navigator and serviceWorker are defined", () => { @@ -37,6 +39,12 @@ describe("register", () => { global.navigator = (undefined as unknown) as Navigator & typeof globalThis global.location = (undefined as unknown) as Location & typeof globalThis }) + it("test should have access to browser globals from beforeAll", () => { + expect(typeof global.window).not.toBeFalsy() + expect(typeof global.document).not.toBeFalsy() + expect(typeof global.navigator).not.toBeFalsy() + expect(typeof global.location).not.toBeFalsy() + }) it("should register a ServiceWorker", () => { // Load service worker like you would in the browser @@ -84,4 +92,90 @@ describe("register", () => { expect(spy).toHaveBeenCalledWith("[Service Worker] navigator is undefined") }) }) + describe("registerServiceWorker", () => { + let serviceWorkerPath: string + let serviceWorkerScope: string + const mockFn = jest.fn((path: string, options: { scope: string }) => { + serviceWorkerPath = path + serviceWorkerScope = options.scope + return undefined + }) + + beforeAll(() => { + const location: LocationLike = { + pathname: "", + origin: "http://localhost:8080", + // search: "?environmentId=600e0187-0909d8a00cb0a394720d4dce", + } + const { window } = new JSDOM() + global.window = (window as unknown) as Window & typeof globalThis + global.document = window.document + global.navigator = window.navigator + global.location = location as Location + + Object.defineProperty(global.navigator, "serviceWorker", { + value: { + register: mockFn, + }, + }) + }) + + afterEach(() => { + mockFn.mockClear() + jest.resetModules() + }) + + afterAll(() => { + jest.restoreAllMocks() + + // We don't want these to stay around because it can affect other tests + global.window = (undefined as unknown) as Window & typeof globalThis + global.document = (undefined as unknown) as Document & typeof globalThis + global.navigator = (undefined as unknown) as Navigator & typeof globalThis + global.location = (undefined as unknown) as Location & typeof globalThis + }) + it("should register when options.base is undefined", async () => { + // Mock getElementById + const csStaticBasePath = "/static/development/Users/jp/Dev/code-server" + const spy = jest.spyOn(document, "getElementById") + // Create a fake element and set the attribute + const mockElement = document.createElement("div") + mockElement.id = "coder-options" + mockElement.setAttribute( + "data-settings", + `{"csStaticBase":"${csStaticBasePath}","logLevel":2,"disableTelemetry":false,"disableUpdateCheck":false}`, + ) + // Return mockElement from the spy + // this way, when we call "getElementById" + // it returns the element + spy.mockImplementation(() => mockElement) + + await registerServiceWorker() + + expect(mockFn).toBeCalled() + expect(serviceWorkerPath).toMatch(`${csStaticBasePath}/dist/serviceWorker.js`) + expect(serviceWorkerScope).toMatch("/") + }) + it("should register when options.base is defined", async () => { + const csStaticBasePath = "/static/development/Users/jp/Dev/code-server" + const spy = jest.spyOn(document, "getElementById") + // Create a fake element and set the attribute + const mockElement = document.createElement("div") + mockElement.id = "coder-options" + mockElement.setAttribute( + "data-settings", + `{"base":"proxy/","csStaticBase":"${csStaticBasePath}","logLevel":2,"disableTelemetry":false,"disableUpdateCheck":false}`, + ) + // Return mockElement from the spy + // this way, when we call "getElementById" + // it returns the element + spy.mockImplementation(() => mockElement) + + await registerServiceWorker() + + expect(mockFn).toBeCalled() + expect(serviceWorkerPath).toMatch(`/dist/serviceWorker.js`) + expect(serviceWorkerScope).toMatch("/") + }) + }) }) diff --git a/test/unit/util.test.ts b/test/unit/util.test.ts index 02f3fdc5..e4d6349a 100644 --- a/test/unit/util.test.ts +++ b/test/unit/util.test.ts @@ -16,7 +16,7 @@ import { loggerModule } from "../utils/helpers" const dom = new JSDOM() global.document = dom.window.document -type LocationLike = Pick +export type LocationLike = Pick // jest.mock is hoisted above the imports so we must use `require` here. jest.mock("@coder/logger", () => require("../utils/helpers").loggerModule) From 83746c8a1f5cd97413829ec7618636ef97480cf7 Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Fri, 23 Apr 2021 16:32:12 -0700 Subject: [PATCH 2/2] refactor: remove null check in register.ts options.base Inside registerServiceWorker, we were originally using the nullash coalescing operator to check if options.base was null or undefined. However, I realized this check is not necessary. If you look at getOptions' return value, we return an object with a key "base" which is of type "string". We get that value by calling resolveBase which always returns a string. As a result, we didn't need to check if options.base was null or undefined because it never can be. --- src/browser/register.ts | 4 ++-- test/unit/register.test.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/browser/register.ts b/src/browser/register.ts index 1079d159..e99d64c6 100644 --- a/src/browser/register.ts +++ b/src/browser/register.ts @@ -4,12 +4,12 @@ import "./pages/error.css" import "./pages/global.css" import "./pages/login.css" -async function registerServiceWorker(): Promise { +export async function registerServiceWorker(): Promise { const options = getOptions() const path = normalize(`${options.csStaticBase}/dist/serviceWorker.js`) try { await navigator.serviceWorker.register(path, { - scope: (options.base ?? "") + "/", + scope: options.base + "/", }) console.log("[Service Worker] registered") } catch (error) { diff --git a/test/unit/register.test.ts b/test/unit/register.test.ts index c163e0a5..b845aab5 100644 --- a/test/unit/register.test.ts +++ b/test/unit/register.test.ts @@ -105,7 +105,6 @@ describe("register", () => { const location: LocationLike = { pathname: "", origin: "http://localhost:8080", - // search: "?environmentId=600e0187-0909d8a00cb0a394720d4dce", } const { window } = new JSDOM() global.window = (window as unknown) as Window & typeof globalThis