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 d19f6300..b845aab5 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,89 @@ 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", + } + 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)