Merge pull request #2719 from cdr/add-tests-register
feat(testing): add unit tests for register
This commit is contained in:
commit
726f694268
@ -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
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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`)
|
||||||
}
|
}
|
||||||
|
@ -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", () => {
|
||||||
|
@ -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
5
test/cssStub.ts
Normal 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 = {}
|
@ -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(() => {
|
||||||
|
@ -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
87
test/register.test.ts
Normal 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")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
92
test/serviceWorker.test.ts
Normal file
92
test/serviceWorker.test.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
@ -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")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user