import "fake-indexeddb/auto" import "core-js/stable/structured-clone" import initWorker from "./worker" import Dexie, { EntityTable } from "dexie" function createSubscription(testToken = "HI") { return { toJSON: () => ({ subscription: testToken, }), } } const createSelf = () => ({ addEventListener: jest.fn(), skipWaiting: jest.fn(() => Promise.resolve()), registration: { pushManager: { subscribe: jest.fn(() => Promise.resolve(createSubscription())), getSubscription: jest.fn(() => Promise.resolve(createSubscription())), }, }, clients: { claim: jest.fn(() => Promise.resolve()), }, }) as unknown as ServiceWorkerGlobalScope let originalWindow: Window & typeof globalThis describe("service worker", () => { let self: ServiceWorkerGlobalScope beforeEach(() => { originalWindow = global.window global.window = undefined as any indexedDB = new IDBFactory() self = createSelf() // fetchMock.mockIf(/http:\/\/localhost\/api\/subscribe\/?/, (req) => { // return Promise.resolve( // req.method === "POST" // ? { // body: JSON.stringify({ subscriptionId: 123 }), // } // : req.method === "PUT" // ? { init: { status: 200 } } // : { init: { status: 405 } } // ) // }) fetchMock.mockResponse((req) => { return Promise.resolve( req.method === "POST" ? { body: JSON.stringify({ subscriptionId: 123 }), } : req.method === "PUT" ? { init: { status: 200 } } : { init: { status: 405 } } ) }) }) afterEach(() => { jest.clearAllMocks() global.window = originalWindow }) xtest("displays push notifications", () => { initWorker(self) const pushHandler = getHandler("push") const pushEvent = createEvent({ data: "HI" }) pushHandler(pushEvent) }) test("claims on activate", async () => { initWorker(self) const activateHandler = getHandler("activate") const event = createEvent() activateHandler(event) await waitUntilCalls(event) expect(self.clients.claim).toHaveBeenCalled() }) test("skips waiting on install", async () => { initWorker(self) const installHandler = getHandler("install") const event = createEvent() installHandler(event) await waitUntilCalls(event) expect(self.skipWaiting).toHaveBeenCalled() }) describe("on subscription message", () => { test("puts push subscription if the subscription id is set", async () => { initWorker(self) await setSavedSubscription(5000) const messageHandler = getHandler("message") const messageEvent = createEvent({ data: { subscription: { subscription: "YO" }, type: "subscribed", }, }) messageHandler(messageEvent) await waitUntilCalls(messageEvent) expect(fetch).toHaveBeenCalledWith( `${process.env.BASE_URL}/api/subscription/5000`, { method: "PUT", body: JSON.stringify({ subscription: "YO" }), headers: { "Content-Type": "application/json", }, } ) }) test("posts to subscriptions when there is no user set", async () => { initWorker(self) const messageHandler = getHandler("message") const messageEvent = createEvent({ data: { subscription: { subscription: "YO" }, type: "subscribed", }, }) messageHandler(messageEvent) await waitUntilCalls(messageEvent) expect(fetch).toHaveBeenCalledWith( `${process.env.BASE_URL}/api/subscription/`, { method: "POST", body: JSON.stringify({ subscription: "YO" }), headers: { "Content-Type": "application/json", }, } ) }) test("subscription id is saved on first subscription change", async () => { initWorker(self) const messageHandler = getHandler("message") const messageEvent = createEvent({ data: { subscription: { subscription: "YO" }, type: "subscribed", }, }) messageHandler(messageEvent) await waitUntilCalls(messageEvent) expect(await getSavedSubscription()).toEqual({ id: 1, subscriptionId: 123, }) }) }) describe("on subscription change", () => { test("puts push subscription if the subscription id is set", async () => { initWorker(self) await setSavedSubscription(5000) const changeHandler = getHandler("pushsubscriptionchange") const changeEvent = createEvent() changeHandler(changeEvent) await waitUntilCalls(changeEvent) expect(self.registration.pushManager.subscribe).toHaveBeenCalledWith({ userVisibleOnly: true, applicationServerKey: expect.any(Uint8Array), }) expect(fetch).toHaveBeenCalledWith( `${process.env.BASE_URL}/api/subscription/5000`, { method: "PUT", body: JSON.stringify({ subscription: "HI" }), headers: { "Content-Type": "application/json", }, } ) }) test("posts to subscriptions when there is no user set", async () => { initWorker(self) const changeHandler = getHandler("pushsubscriptionchange") const changeEvent = createEvent() changeHandler(changeEvent) await waitUntilCalls(changeEvent) expect(self.registration.pushManager.subscribe).toHaveBeenCalledWith({ userVisibleOnly: true, applicationServerKey: expect.any(Uint8Array), }) expect(fetch).toHaveBeenCalledWith( `${process.env.BASE_URL}/api/subscription/`, { method: "POST", body: JSON.stringify({ subscription: "HI" }), headers: { "Content-Type": "application/json", }, } ) }) test("subscription id is saved on first subscription change", async () => { initWorker(self) const changeHandler = getHandler("pushsubscriptionchange") const changeEvent = createEvent() changeHandler(changeEvent) await waitUntilCalls(changeEvent) expect(await getSavedSubscription()).toEqual({ id: 1, subscriptionId: 123, }) }) }) function getHandler(eventName: string) { expect(self.addEventListener).toHaveBeenCalledWith( eventName, expect.anything() ) const [, handler] = (self.addEventListener as jest.Mock).mock.calls.find( ([event]: [string]) => event === eventName ) return handler } }) async function getSavedSubscription() { const database = new Dexie("tack-up-now", { indexedDB }) as Dexie & { subscriptions: EntityTable<{ id: number; subscriptionId: number }, "id"> } database.version(1).stores({ subscriptions: "id++, subscriptionId&" }) return (await database.subscriptions.toArray())[0] } async function setSavedSubscription(subscriptionId: number) { const database = new Dexie("tack-up-now", { indexedDB }) as Dexie & { subscriptions: EntityTable<{ id: number; subscriptionId: number }, "id"> } database.version(1).stores({ subscriptions: "id++, subscriptionId&" }) await database.subscriptions.put({ id: 1, subscriptionId }) } function createPushEvent(data: string) { const pushFields = { data: { blob: () => new Blob([data], { type: "text/plain" }), json: () => JSON.parse(data), text: () => data, arrayBuffer: () => new TextEncoder().encode(data), }, } return createEvent(pushFields) } function createMessageEvent(message: any) { return createEvent({ data: message }) } function createEvent(properties?: object) { return { waitUntil: jest.fn(), ...properties, } } function waitUntilCalls(event: { waitUntil: jest.Mock> }) { return Promise.all(event.waitUntil.mock.calls.map(([promise]) => promise)) }