Skip to content

feat(testing): add unit tests for register #2719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4f1f2c7
fix: clean up comment in util.test.ts
jsjoeio Feb 11, 2021
28b440a
feat: add cssStub to jest
jsjoeio Feb 11, 2021
72b05ca
refactor: create registerServiceWorker fn
jsjoeio Feb 11, 2021
06aeca0
feat(testing): add register test
jsjoeio Feb 11, 2021
a9f88af
refactor: use logger in serviceworker
jsjoeio Feb 11, 2021
44b9874
feat: test failure to register service worker
jsjoeio Feb 11, 2021
21f577d
refactor: add handleRegisterServiceWorker fn
jsjoeio Feb 11, 2021
38891de
feat(test): add test handleRegisterServiceWorker
jsjoeio Feb 11, 2021
7ef630f
refactor(register): use logError instead of logger
jsjoeio Feb 22, 2021
a44a514
refactor: use same syntax as logger spy
jsjoeio Feb 22, 2021
027e8e5
refactor: use LogModule from Asher in tests
jsjoeio Feb 23, 2021
711abd8
refactor: move loggerModule into helpers
jsjoeio Feb 23, 2021
1470ff2
refactor: dont use actual logger in helper
jsjoeio Feb 23, 2021
1c737f1
refactor: hoist jest.mock in constants
jsjoeio Feb 23, 2021
cb4185b
refactor: use loggerModule in util
jsjoeio Feb 23, 2021
e6a324b
refactor: update mocking logger in register test
jsjoeio Feb 23, 2021
b232dcb
feat(register): add test when navigator undefined
jsjoeio Feb 23, 2021
80a1800
feat: add test for catching errors in Emitter
jsjoeio Feb 10, 2021
46226ea
chore(testing): add service-worker-mock
jsjoeio Feb 18, 2021
ee0973c
feat: add logs to serviceWorker
jsjoeio Feb 18, 2021
6b56e65
feat(testing): add serviceWorker tests
jsjoeio Feb 18, 2021
8c14799
refactor: add custom mock for serviceWorker test
jsjoeio Feb 24, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/dev/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ main() {

eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js" | grep -v "lib/vscode")
stylelint $(git ls-files "*.css" | grep -v "lib/vscode")
tsc --noEmit
tsc --noEmit --skipLibCheck
shellcheck -e SC2046,SC2164,SC2154,SC1091,SC1090,SC2002 $(git ls-files "*.sh" | grep -v "lib/vscode")
if command -v helm && helm kubeval --help > /dev/null; then
helm kubeval ci/helm-chart
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
"<rootDir>/release-npm-package",
"<rootDir>/release-gcp",
"<rootDir>/release-images"
]
],
"moduleNameMapper": {
"^.+\\.(css|less)$": "<rootDir>/test/cssStub.ts"
}
}
}
24 changes: 15 additions & 9 deletions src/browser/register.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { getOptions, normalize } from "../common/util"

const options = getOptions()
import { getOptions, normalize, logError } from "../common/util"

import "./pages/error.css"
import "./pages/global.css"
import "./pages/login.css"

if ("serviceWorker" in navigator) {
async function registerServiceWorker(): Promise<void> {
const options = getOptions()
const path = normalize(`${options.csStaticBase}/dist/serviceWorker.js`)
navigator.serviceWorker
.register(path, {
try {
await navigator.serviceWorker.register(path, {
scope: (options.base ?? "") + "/",
})
.then(() => {
console.log("[Service Worker] registered")
})
console.log("[Service Worker] registered")
} catch (error) {
logError(`[Service Worker] registration`, error)
}
}

if (typeof navigator !== "undefined" && "serviceWorker" in navigator) {
registerServiceWorker()
} else {
console.error(`[Service Worker] navigator is undefined`)
}
3 changes: 2 additions & 1 deletion src/browser/serviceWorker.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

self.addEventListener("install", () => {
console.log("[Service Worker] install")
console.log("[Service Worker] installed")
})

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

self.addEventListener("fetch", () => {
Expand Down
17 changes: 6 additions & 11 deletions test/constants.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
// Note: we need to import logger from the root
// because this is the logger used in logError in ../src/common/util
import { logger } from "../node_modules/@coder/logger"
import { commit, getPackageJson, version } from "../src/node/constants"
import { loggerModule } from "./helpers"

// jest.mock is hoisted above the imports so we must use `require` here.
jest.mock("@coder/logger", () => require("./helpers").loggerModule)

describe("constants", () => {
describe("getPackageJson", () => {
let spy: jest.SpyInstance

beforeEach(() => {
spy = jest.spyOn(logger, "warn")
})

afterEach(() => {
jest.clearAllMocks()
})
Expand All @@ -24,8 +19,8 @@ describe("constants", () => {

getPackageJson("./package.json")

expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledWith(expectedErrorMessage)
expect(loggerModule.logger.warn).toHaveBeenCalled()
expect(loggerModule.logger.warn).toHaveBeenCalledWith(expectedErrorMessage)
})

it("should find the package.json", () => {
Expand Down
5 changes: 5 additions & 0 deletions test/cssStub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Note: this is needed for the register.test.ts
// This is because inside src/browser/register.ts
// we import CSS files, which Jest can't handle unless we tell it how to
// See: https://stackoverflow.com/a/39434579/3015595
module.exports = {}
3 changes: 2 additions & 1 deletion test/emitter.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Note: we need to import logger from the root
// because this is the logger used in logError in ../src/common/util
import { logger } from "../node_modules/@coder/logger"

import { Emitter } from "../src/common/emitter"

describe("Emitter", () => {
describe("emitter", () => {
let spy: jest.SpyInstance

beforeEach(() => {
Expand Down
12 changes: 12 additions & 0 deletions test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,15 @@ export function createCookieIfDoesntExist(cookies: Array<Cookie>, cookieToStore:
}
return cookies
}

export const loggerModule = {
field: jest.fn(),
level: 2,
logger: {
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
trace: jest.fn(),
warn: jest.fn(),
},
}
87 changes: 87 additions & 0 deletions test/register.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { JSDOM } from "jsdom"
import { loggerModule } from "./helpers"

describe("register", () => {
describe("when navigator and serviceWorker are defined", () => {
const mockRegisterFn = jest.fn()

beforeAll(() => {
const { window } = new JSDOM()
global.window = (window as unknown) as Window & typeof globalThis
global.document = window.document
global.navigator = window.navigator
global.location = window.location

Object.defineProperty(global.navigator, "serviceWorker", {
value: {
register: mockRegisterFn,
},
})
})

beforeEach(() => {
jest.mock("@coder/logger", () => loggerModule)
})

afterEach(() => {
mockRegisterFn.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 a ServiceWorker", () => {
// Load service worker like you would in the browser
require("../src/browser/register")
expect(mockRegisterFn).toHaveBeenCalled()
expect(mockRegisterFn).toHaveBeenCalledTimes(1)
})

it("should log an error if something doesn't work", () => {
const message = "Can't find browser"
const error = new Error(message)

mockRegisterFn.mockImplementation(() => {
throw error
})

// Load service worker like you would in the browser
require("../src/browser/register")

expect(mockRegisterFn).toHaveBeenCalled()
expect(loggerModule.logger.error).toHaveBeenCalled()
expect(loggerModule.logger.error).toHaveBeenCalledTimes(1)
expect(loggerModule.logger.error).toHaveBeenCalledWith(
`[Service Worker] registration: ${error.message} ${error.stack}`,
)
})
})

describe("when navigator and serviceWorker are NOT defined", () => {
let spy: jest.SpyInstance

beforeEach(() => {
spy = jest.spyOn(console, "error")
})

afterAll(() => {
jest.restoreAllMocks()
})

it("should log an error to the console", () => {
// Load service worker like you would in the browser
require("../src/browser/register")
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
expect(spy).toHaveBeenCalledWith("[Service Worker] navigator is undefined")
})
})
})
92 changes: 92 additions & 0 deletions test/serviceWorker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
interface MockEvent {
claim: jest.Mock<any, any>
waitUntil?: jest.Mock<any, any>
}

interface Listener {
event: string
cb: (event?: MockEvent) => void
}

describe("serviceWorker", () => {
let listeners: Listener[] = []
let spy: jest.SpyInstance
let claimSpy: jest.Mock<any, any>
let waitUntilSpy: jest.Mock<any, any>

function emit(event: string) {
listeners
.filter((listener) => listener.event === event)
.forEach((listener) => {
switch (event) {
case "activate":
listener.cb({
claim: jest.fn(),
waitUntil: jest.fn(() => waitUntilSpy()),
})
break
default:
listener.cb()
}
})
}

beforeEach(() => {
claimSpy = jest.fn()
spy = jest.spyOn(console, "log")
waitUntilSpy = jest.fn()

Object.assign(global, {
self: global,
addEventListener: (event: string, cb: () => void) => {
listeners.push({ event, cb })
},
clients: {
claim: claimSpy.mockResolvedValue("claimed"),
},
})
})

afterEach(() => {
jest.restoreAllMocks()
jest.resetModules()
spy.mockClear()
claimSpy.mockClear()

// Clear all the listeners
listeners = []
})

it("should add 3 listeners: install, activate and fetch", () => {
require("../src/browser/serviceWorker.ts")
const listenerEventNames = listeners.map((listener) => listener.event)

expect(listeners).toHaveLength(3)
expect(listenerEventNames).toContain("install")
expect(listenerEventNames).toContain("activate")
expect(listenerEventNames).toContain("fetch")
})

it("should call the proper callbacks for 'install'", async () => {
require("../src/browser/serviceWorker.ts")
emit("install")
expect(spy).toHaveBeenCalledWith("[Service Worker] installed")
expect(spy).toHaveBeenCalledTimes(1)
})

it("should do nothing when 'fetch' is called", async () => {
require("../src/browser/serviceWorker.ts")
emit("fetch")
expect(spy).not.toHaveBeenCalled()
})

it("should call the proper callbacks for 'activate'", async () => {
require("../src/browser/serviceWorker.ts")
emit("activate")

// Activate serviceWorker
expect(spy).toHaveBeenCalledWith("[Service Worker] activated")
expect(waitUntilSpy).toHaveBeenCalled()
expect(claimSpy).toHaveBeenCalled()
})
})
24 changes: 8 additions & 16 deletions test/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import { JSDOM } from "jsdom"
import { Cookie } from "playwright"
// Note: we need to import logger from the root
// because this is the logger used in logError in ../src/common/util
import { logger } from "../node_modules/@coder/logger"
import {
arrayify,
generateUuid,
Expand All @@ -18,14 +14,16 @@ import {
import { Cookie as CookieEnum } from "../src/node/routes/login"
import { hash } from "../src/node/util"
import { PASSWORD } from "./constants"
import { checkForCookie, createCookieIfDoesntExist } from "./helpers"
import { checkForCookie, createCookieIfDoesntExist, loggerModule, Cookie } from "./helpers"

const dom = new JSDOM()
global.document = dom.window.document
// global.window = (dom.window as unknown) as Window & typeof globalThis

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

// jest.mock is hoisted above the imports so we must use `require` here.
jest.mock("@coder/logger", () => require("./helpers").loggerModule)

describe("util", () => {
describe("normalize", () => {
it("should remove multiple slashes", () => {
Expand Down Expand Up @@ -229,12 +227,6 @@ describe("util", () => {
})

describe("logError", () => {
let spy: jest.SpyInstance

beforeEach(() => {
spy = jest.spyOn(logger, "error")
})

afterEach(() => {
jest.clearAllMocks()
})
Expand All @@ -249,15 +241,15 @@ describe("util", () => {

logError("ui", error)

expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledWith(`ui: ${error.message} ${error.stack}`)
expect(loggerModule.logger.error).toHaveBeenCalled()
expect(loggerModule.logger.error).toHaveBeenCalledWith(`ui: ${error.message} ${error.stack}`)
})

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

expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledWith("api: oh no")
expect(loggerModule.logger.error).toHaveBeenCalled()
expect(loggerModule.logger.error).toHaveBeenCalledWith("api: oh no")
})
})

Expand Down