Skip to content

Commit 726f694

Browse files
authored
Merge pull request #2719 from cdr/add-tests-register
feat(testing): add unit tests for register
2 parents 63733c3 + 8c14799 commit 726f694

11 files changed

+234
-40
lines changed

ci/dev/lint.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ main() {
66

77
eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js" | grep -v "lib/vscode")
88
stylelint $(git ls-files "*.css" | grep -v "lib/vscode")
9-
tsc --noEmit
9+
tsc --noEmit --skipLibCheck
1010
shellcheck -e SC2046,SC2164,SC2154,SC1091,SC1090,SC2002 $(git ls-files "*.sh" | grep -v "lib/vscode")
1111
if command -v helm && helm kubeval --help > /dev/null; then
1212
helm kubeval ci/helm-chart

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@
153153
"<rootDir>/release-npm-package",
154154
"<rootDir>/release-gcp",
155155
"<rootDir>/release-images"
156-
]
156+
],
157+
"moduleNameMapper": {
158+
"^.+\\.(css|less)$": "<rootDir>/test/cssStub.ts"
159+
}
157160
}
158161
}

src/browser/register.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
import { getOptions, normalize } from "../common/util"
2-
3-
const options = getOptions()
1+
import { getOptions, normalize, logError } from "../common/util"
42

53
import "./pages/error.css"
64
import "./pages/global.css"
75
import "./pages/login.css"
86

9-
if ("serviceWorker" in navigator) {
7+
async function registerServiceWorker(): Promise<void> {
8+
const options = getOptions()
109
const path = normalize(`${options.csStaticBase}/dist/serviceWorker.js`)
11-
navigator.serviceWorker
12-
.register(path, {
10+
try {
11+
await navigator.serviceWorker.register(path, {
1312
scope: (options.base ?? "") + "/",
1413
})
15-
.then(() => {
16-
console.log("[Service Worker] registered")
17-
})
14+
console.log("[Service Worker] registered")
15+
} catch (error) {
16+
logError(`[Service Worker] registration`, error)
17+
}
18+
}
19+
20+
if (typeof navigator !== "undefined" && "serviceWorker" in navigator) {
21+
registerServiceWorker()
22+
} else {
23+
console.error(`[Service Worker] navigator is undefined`)
1824
}

src/browser/serviceWorker.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22

33
self.addEventListener("install", () => {
4-
console.log("[Service Worker] install")
4+
console.log("[Service Worker] installed")
55
})
66

77
self.addEventListener("activate", (event: any) => {
88
event.waitUntil((self as any).clients.claim())
9+
console.log("[Service Worker] activated")
910
})
1011

1112
self.addEventListener("fetch", () => {

test/constants.test.ts

+6-11
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
1-
// Note: we need to import logger from the root
2-
// because this is the logger used in logError in ../src/common/util
3-
import { logger } from "../node_modules/@coder/logger"
41
import { commit, getPackageJson, version } from "../src/node/constants"
2+
import { loggerModule } from "./helpers"
3+
4+
// jest.mock is hoisted above the imports so we must use `require` here.
5+
jest.mock("@coder/logger", () => require("./helpers").loggerModule)
56

67
describe("constants", () => {
78
describe("getPackageJson", () => {
8-
let spy: jest.SpyInstance
9-
10-
beforeEach(() => {
11-
spy = jest.spyOn(logger, "warn")
12-
})
13-
149
afterEach(() => {
1510
jest.clearAllMocks()
1611
})
@@ -24,8 +19,8 @@ describe("constants", () => {
2419

2520
getPackageJson("./package.json")
2621

27-
expect(spy).toHaveBeenCalled()
28-
expect(spy).toHaveBeenCalledWith(expectedErrorMessage)
22+
expect(loggerModule.logger.warn).toHaveBeenCalled()
23+
expect(loggerModule.logger.warn).toHaveBeenCalledWith(expectedErrorMessage)
2924
})
3025

3126
it("should find the package.json", () => {

test/cssStub.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Note: this is needed for the register.test.ts
2+
// This is because inside src/browser/register.ts
3+
// we import CSS files, which Jest can't handle unless we tell it how to
4+
// See: https://stackoverflow.com/a/39434579/3015595
5+
module.exports = {}

test/emitter.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// Note: we need to import logger from the root
22
// because this is the logger used in logError in ../src/common/util
33
import { logger } from "../node_modules/@coder/logger"
4+
45
import { Emitter } from "../src/common/emitter"
56

6-
describe("Emitter", () => {
7+
describe("emitter", () => {
78
let spy: jest.SpyInstance
89

910
beforeEach(() => {

test/helpers.ts

+12
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,15 @@ export function createCookieIfDoesntExist(cookies: Array<Cookie>, cookieToStore:
3333
}
3434
return cookies
3535
}
36+
37+
export const loggerModule = {
38+
field: jest.fn(),
39+
level: 2,
40+
logger: {
41+
debug: jest.fn(),
42+
error: jest.fn(),
43+
info: jest.fn(),
44+
trace: jest.fn(),
45+
warn: jest.fn(),
46+
},
47+
}

test/register.test.ts

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { JSDOM } from "jsdom"
2+
import { loggerModule } from "./helpers"
3+
4+
describe("register", () => {
5+
describe("when navigator and serviceWorker are defined", () => {
6+
const mockRegisterFn = jest.fn()
7+
8+
beforeAll(() => {
9+
const { window } = new JSDOM()
10+
global.window = (window as unknown) as Window & typeof globalThis
11+
global.document = window.document
12+
global.navigator = window.navigator
13+
global.location = window.location
14+
15+
Object.defineProperty(global.navigator, "serviceWorker", {
16+
value: {
17+
register: mockRegisterFn,
18+
},
19+
})
20+
})
21+
22+
beforeEach(() => {
23+
jest.mock("@coder/logger", () => loggerModule)
24+
})
25+
26+
afterEach(() => {
27+
mockRegisterFn.mockClear()
28+
jest.resetModules()
29+
})
30+
31+
afterAll(() => {
32+
jest.restoreAllMocks()
33+
34+
// We don't want these to stay around because it can affect other tests
35+
global.window = (undefined as unknown) as Window & typeof globalThis
36+
global.document = (undefined as unknown) as Document & typeof globalThis
37+
global.navigator = (undefined as unknown) as Navigator & typeof globalThis
38+
global.location = (undefined as unknown) as Location & typeof globalThis
39+
})
40+
41+
it("should register a ServiceWorker", () => {
42+
// Load service worker like you would in the browser
43+
require("../src/browser/register")
44+
expect(mockRegisterFn).toHaveBeenCalled()
45+
expect(mockRegisterFn).toHaveBeenCalledTimes(1)
46+
})
47+
48+
it("should log an error if something doesn't work", () => {
49+
const message = "Can't find browser"
50+
const error = new Error(message)
51+
52+
mockRegisterFn.mockImplementation(() => {
53+
throw error
54+
})
55+
56+
// Load service worker like you would in the browser
57+
require("../src/browser/register")
58+
59+
expect(mockRegisterFn).toHaveBeenCalled()
60+
expect(loggerModule.logger.error).toHaveBeenCalled()
61+
expect(loggerModule.logger.error).toHaveBeenCalledTimes(1)
62+
expect(loggerModule.logger.error).toHaveBeenCalledWith(
63+
`[Service Worker] registration: ${error.message} ${error.stack}`,
64+
)
65+
})
66+
})
67+
68+
describe("when navigator and serviceWorker are NOT defined", () => {
69+
let spy: jest.SpyInstance
70+
71+
beforeEach(() => {
72+
spy = jest.spyOn(console, "error")
73+
})
74+
75+
afterAll(() => {
76+
jest.restoreAllMocks()
77+
})
78+
79+
it("should log an error to the console", () => {
80+
// Load service worker like you would in the browser
81+
require("../src/browser/register")
82+
expect(spy).toHaveBeenCalled()
83+
expect(spy).toHaveBeenCalledTimes(1)
84+
expect(spy).toHaveBeenCalledWith("[Service Worker] navigator is undefined")
85+
})
86+
})
87+
})

test/serviceWorker.test.ts

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
interface MockEvent {
2+
claim: jest.Mock<any, any>
3+
waitUntil?: jest.Mock<any, any>
4+
}
5+
6+
interface Listener {
7+
event: string
8+
cb: (event?: MockEvent) => void
9+
}
10+
11+
describe("serviceWorker", () => {
12+
let listeners: Listener[] = []
13+
let spy: jest.SpyInstance
14+
let claimSpy: jest.Mock<any, any>
15+
let waitUntilSpy: jest.Mock<any, any>
16+
17+
function emit(event: string) {
18+
listeners
19+
.filter((listener) => listener.event === event)
20+
.forEach((listener) => {
21+
switch (event) {
22+
case "activate":
23+
listener.cb({
24+
claim: jest.fn(),
25+
waitUntil: jest.fn(() => waitUntilSpy()),
26+
})
27+
break
28+
default:
29+
listener.cb()
30+
}
31+
})
32+
}
33+
34+
beforeEach(() => {
35+
claimSpy = jest.fn()
36+
spy = jest.spyOn(console, "log")
37+
waitUntilSpy = jest.fn()
38+
39+
Object.assign(global, {
40+
self: global,
41+
addEventListener: (event: string, cb: () => void) => {
42+
listeners.push({ event, cb })
43+
},
44+
clients: {
45+
claim: claimSpy.mockResolvedValue("claimed"),
46+
},
47+
})
48+
})
49+
50+
afterEach(() => {
51+
jest.restoreAllMocks()
52+
jest.resetModules()
53+
spy.mockClear()
54+
claimSpy.mockClear()
55+
56+
// Clear all the listeners
57+
listeners = []
58+
})
59+
60+
it("should add 3 listeners: install, activate and fetch", () => {
61+
require("../src/browser/serviceWorker.ts")
62+
const listenerEventNames = listeners.map((listener) => listener.event)
63+
64+
expect(listeners).toHaveLength(3)
65+
expect(listenerEventNames).toContain("install")
66+
expect(listenerEventNames).toContain("activate")
67+
expect(listenerEventNames).toContain("fetch")
68+
})
69+
70+
it("should call the proper callbacks for 'install'", async () => {
71+
require("../src/browser/serviceWorker.ts")
72+
emit("install")
73+
expect(spy).toHaveBeenCalledWith("[Service Worker] installed")
74+
expect(spy).toHaveBeenCalledTimes(1)
75+
})
76+
77+
it("should do nothing when 'fetch' is called", async () => {
78+
require("../src/browser/serviceWorker.ts")
79+
emit("fetch")
80+
expect(spy).not.toHaveBeenCalled()
81+
})
82+
83+
it("should call the proper callbacks for 'activate'", async () => {
84+
require("../src/browser/serviceWorker.ts")
85+
emit("activate")
86+
87+
// Activate serviceWorker
88+
expect(spy).toHaveBeenCalledWith("[Service Worker] activated")
89+
expect(waitUntilSpy).toHaveBeenCalled()
90+
expect(claimSpy).toHaveBeenCalled()
91+
})
92+
})

test/util.test.ts

+8-16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import { JSDOM } from "jsdom"
2-
import { Cookie } from "playwright"
3-
// Note: we need to import logger from the root
4-
// because this is the logger used in logError in ../src/common/util
5-
import { logger } from "../node_modules/@coder/logger"
62
import {
73
arrayify,
84
generateUuid,
@@ -18,14 +14,16 @@ import {
1814
import { Cookie as CookieEnum } from "../src/node/routes/login"
1915
import { hash } from "../src/node/util"
2016
import { PASSWORD } from "./constants"
21-
import { checkForCookie, createCookieIfDoesntExist } from "./helpers"
17+
import { checkForCookie, createCookieIfDoesntExist, loggerModule, Cookie } from "./helpers"
2218

2319
const dom = new JSDOM()
2420
global.document = dom.window.document
25-
// global.window = (dom.window as unknown) as Window & typeof globalThis
2621

2722
type LocationLike = Pick<Location, "pathname" | "origin">
2823

24+
// jest.mock is hoisted above the imports so we must use `require` here.
25+
jest.mock("@coder/logger", () => require("./helpers").loggerModule)
26+
2927
describe("util", () => {
3028
describe("normalize", () => {
3129
it("should remove multiple slashes", () => {
@@ -229,12 +227,6 @@ describe("util", () => {
229227
})
230228

231229
describe("logError", () => {
232-
let spy: jest.SpyInstance
233-
234-
beforeEach(() => {
235-
spy = jest.spyOn(logger, "error")
236-
})
237-
238230
afterEach(() => {
239231
jest.clearAllMocks()
240232
})
@@ -249,15 +241,15 @@ describe("util", () => {
249241

250242
logError("ui", error)
251243

252-
expect(spy).toHaveBeenCalled()
253-
expect(spy).toHaveBeenCalledWith(`ui: ${error.message} ${error.stack}`)
244+
expect(loggerModule.logger.error).toHaveBeenCalled()
245+
expect(loggerModule.logger.error).toHaveBeenCalledWith(`ui: ${error.message} ${error.stack}`)
254246
})
255247

256248
it("should log an error, even if not an instance of error", () => {
257249
logError("api", "oh no")
258250

259-
expect(spy).toHaveBeenCalled()
260-
expect(spy).toHaveBeenCalledWith("api: oh no")
251+
expect(loggerModule.logger.error).toHaveBeenCalled()
252+
expect(loggerModule.logger.error).toHaveBeenCalledWith("api: oh no")
261253
})
262254
})
263255

0 commit comments

Comments
 (0)