311 lines
7.9 KiB
TypeScript
311 lines
7.9 KiB
TypeScript
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<void, Parameters<ExtendableEvent["waitUntil"]>>
|
|
}) {
|
|
return Promise.all(event.waitUntil.mock.calls.map(([promise]) => promise))
|
|
}
|