Merge pull request #2719 from cdr/add-tests-register

feat(testing): add unit tests for register
This commit is contained in:
Joe Previte 2021-02-26 16:47:51 -07:00 committed by GitHub
commit 726f694268
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 234 additions and 40 deletions

View File

@ -6,7 +6,7 @@ main() {
eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js" | grep -v "lib/vscode") eslint --max-warnings=0 --fix $(git ls-files "*.ts" "*.tsx" "*.js" | grep -v "lib/vscode")
stylelint $(git ls-files "*.css" | 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") 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 if command -v helm && helm kubeval --help > /dev/null; then
helm kubeval ci/helm-chart helm kubeval ci/helm-chart

View File

@ -153,6 +153,9 @@
"<rootDir>/release-npm-package", "<rootDir>/release-npm-package",
"<rootDir>/release-gcp", "<rootDir>/release-gcp",
"<rootDir>/release-images" "<rootDir>/release-images"
] ],
"moduleNameMapper": {
"^.+\\.(css|less)$": "<rootDir>/test/cssStub.ts"
}
} }
} }

View File

@ -1,18 +1,24 @@
import { getOptions, normalize } from "../common/util" import { getOptions, normalize, logError } from "../common/util"
const options = getOptions()
import "./pages/error.css" import "./pages/error.css"
import "./pages/global.css" import "./pages/global.css"
import "./pages/login.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`) const path = normalize(`${options.csStaticBase}/dist/serviceWorker.js`)
navigator.serviceWorker try {
.register(path, { await navigator.serviceWorker.register(path, {
scope: (options.base ?? "") + "/", 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`)
} }

View File

@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
self.addEventListener("install", () => { self.addEventListener("install", () => {
console.log("[Service Worker] install") console.log("[Service Worker] installed")
}) })
self.addEventListener("activate", (event: any) => { self.addEventListener("activate", (event: any) => {
event.waitUntil((self as any).clients.claim()) event.waitUntil((self as any).clients.claim())
console.log("[Service Worker] activated")
}) })
self.addEventListener("fetch", () => { self.addEventListener("fetch", () => {

View File

@ -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 { 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("constants", () => {
describe("getPackageJson", () => { describe("getPackageJson", () => {
let spy: jest.SpyInstance
beforeEach(() => {
spy = jest.spyOn(logger, "warn")
})
afterEach(() => { afterEach(() => {
jest.clearAllMocks() jest.clearAllMocks()
}) })
@ -24,8 +19,8 @@ describe("constants", () => {
getPackageJson("./package.json") getPackageJson("./package.json")
expect(spy).toHaveBeenCalled() expect(loggerModule.logger.warn).toHaveBeenCalled()
expect(spy).toHaveBeenCalledWith(expectedErrorMessage) expect(loggerModule.logger.warn).toHaveBeenCalledWith(expectedErrorMessage)
}) })
it("should find the package.json", () => { it("should find the package.json", () => {

5
test/cssStub.ts Normal file
View File

@ -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 = {}

View File

@ -1,9 +1,10 @@
// Note: we need to import logger from the root // Note: we need to import logger from the root
// because this is the logger used in logError in ../src/common/util // because this is the logger used in logError in ../src/common/util
import { logger } from "../node_modules/@coder/logger" import { logger } from "../node_modules/@coder/logger"
import { Emitter } from "../src/common/emitter" import { Emitter } from "../src/common/emitter"
describe("Emitter", () => { describe("emitter", () => {
let spy: jest.SpyInstance let spy: jest.SpyInstance
beforeEach(() => { beforeEach(() => {

View File

@ -33,3 +33,15 @@ export function createCookieIfDoesntExist(cookies: Array<Cookie>, cookieToStore:
} }
return cookies 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
test/register.test.ts Normal file
View File

@ -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")
})
})
})

View File

@ -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()
})
})

View File

@ -1,8 +1,4 @@
import { JSDOM } from "jsdom" 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 { import {
arrayify, arrayify,
generateUuid, generateUuid,
@ -18,14 +14,16 @@ import {
import { Cookie as CookieEnum } from "../src/node/routes/login" import { Cookie as CookieEnum } from "../src/node/routes/login"
import { hash } from "../src/node/util" import { hash } from "../src/node/util"
import { PASSWORD } from "./constants" import { PASSWORD } from "./constants"
import { checkForCookie, createCookieIfDoesntExist } from "./helpers" import { checkForCookie, createCookieIfDoesntExist, loggerModule, Cookie } from "./helpers"
const dom = new JSDOM() const dom = new JSDOM()
global.document = dom.window.document global.document = dom.window.document
// global.window = (dom.window as unknown) as Window & typeof globalThis
type LocationLike = Pick<Location, "pathname" | "origin"> 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("util", () => {
describe("normalize", () => { describe("normalize", () => {
it("should remove multiple slashes", () => { it("should remove multiple slashes", () => {
@ -229,12 +227,6 @@ describe("util", () => {
}) })
describe("logError", () => { describe("logError", () => {
let spy: jest.SpyInstance
beforeEach(() => {
spy = jest.spyOn(logger, "error")
})
afterEach(() => { afterEach(() => {
jest.clearAllMocks() jest.clearAllMocks()
}) })
@ -249,15 +241,15 @@ describe("util", () => {
logError("ui", error) logError("ui", error)
expect(spy).toHaveBeenCalled() expect(loggerModule.logger.error).toHaveBeenCalled()
expect(spy).toHaveBeenCalledWith(`ui: ${error.message} ${error.stack}`) expect(loggerModule.logger.error).toHaveBeenCalledWith(`ui: ${error.message} ${error.stack}`)
}) })
it("should log an error, even if not an instance of error", () => { it("should log an error, even if not an instance of error", () => {
logError("api", "oh no") logError("api", "oh no")
expect(spy).toHaveBeenCalled() expect(loggerModule.logger.error).toHaveBeenCalled()
expect(spy).toHaveBeenCalledWith("api: oh no") expect(loggerModule.logger.error).toHaveBeenCalledWith("api: oh no")
}) })
}) })