Fix jest not exiting, post subscription to worker to send to the server
Test / test (push) Has been cancelled Details

This commit is contained in:
Jeff 2024-10-18 11:38:41 -04:00
parent 49392ebc4f
commit 34372bdb89
8 changed files with 252 additions and 70 deletions

View File

@ -16,6 +16,12 @@ const dialect = new PostgresDialect({
}),
})
export const db = new Kysely<Database>({
export let db = new Kysely<Database>({
dialect,
})
export function resetDbInstance() {
db = new Kysely<Database>({
dialect,
})
}

View File

@ -1,4 +1,4 @@
import { db } from "./database"
import { db, resetDbInstance } from "./database"
import migrateToLatest, { resetAll } from "./migrate"
import {
createSubscription,
@ -34,9 +34,21 @@ jest.mock("./settings", () => ({
}))
describe("subscriptions", () => {
beforeEach(migrateToLatest)
beforeAll(() => {
resetDbInstance()
})
afterEach(resetAll)
beforeEach(async () => {
await migrateToLatest()
})
afterEach(async () => {
await resetAll()
})
afterAll(async () => {
await db.destroy()
})
test("createSubscription", async () => {
const { id } = await createSubscription(pushSubscription1)

View File

@ -31,7 +31,20 @@ function EnableButton({ onSubscribe }: { onSubscribe: () => void }) {
useEffect(() => {
if (!canSendPush) return
subscribeToPush(urlB64ToUint8Array(pushPublicKey) as any, onSubscribe)
subscribeToPush(
urlB64ToUint8Array(pushPublicKey) as any,
async (subscription) => {
console.log("I love subscribing")
await navigator.serviceWorker.ready.then((registration) => {
console.log("Trying to subscrbie to stuff")
registration.active?.postMessage({
type: "subscribed",
subscription: subscription.toJSON(),
})
})
onSubscribe()
}
)
}, [canSendPush])
return <button onClick={subscribe}>Enable Notifications</button>

View File

@ -3,14 +3,22 @@ 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({ subscription: "HI" })),
getSubscription: jest.fn(() => Promise.resolve({ subscription: "HI" })),
subscribe: jest.fn(() => Promise.resolve(createSubscription())),
getSubscription: jest.fn(() => Promise.resolve(createSubscription())),
},
},
clients: {
@ -18,10 +26,14 @@ const createSelf = () =>
},
}) 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()
@ -52,6 +64,7 @@ describe("service worker", () => {
afterEach(() => {
jest.clearAllMocks()
global.window = originalWindow
})
xtest("displays push notifications", () => {
@ -87,63 +100,149 @@ describe("service worker", () => {
expect(self.skipWaiting).toHaveBeenCalled()
})
test("puts push subscription if the subscription id is set", async () => {
initWorker(self)
await setSavedSubscription(5000)
describe("on subscription message", () => {
test("puts push subscription if the subscription id is set", async () => {
initWorker(self)
await setSavedSubscription(5000)
const changeHandler = getHandler("pushsubscriptionchange")
const messageHandler = getHandler("message")
const changeEvent = createEvent()
changeHandler(changeEvent)
await waitUntilCalls(changeEvent)
const messageEvent = createEvent({
data: {
subscription: { subscription: "YO" },
type: "subscribed",
},
})
messageHandler(messageEvent)
await waitUntilCalls(messageEvent)
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: "YO" }),
headers: {
"Content-Type": "application/json",
},
}
)
})
expect(fetch).toHaveBeenCalledWith(
`${process.env.BASE_URL}/api/subscription/5000`,
{
method: "PUT",
body: JSON.stringify({ subscription: "HI" }),
}
)
})
test("posts to subscriptions when there is no user set", async () => {
initWorker(self)
test("posts to subscriptions when there is no user set", async () => {
initWorker(self)
const messageHandler = getHandler("message")
const changeHandler = getHandler("pushsubscriptionchange")
const messageEvent = createEvent({
data: {
subscription: { subscription: "YO" },
type: "subscribed",
},
})
messageHandler(messageEvent)
await waitUntilCalls(messageEvent)
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: "YO" }),
headers: {
"Content-Type": "application/json",
},
}
)
})
expect(fetch).toHaveBeenCalledWith(
`${process.env.BASE_URL}/api/subscription/`,
{
method: "POST",
body: JSON.stringify({ subscription: "HI" }),
}
)
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,
})
})
})
test("subscription id is saved on first subscription change", async () => {
initWorker(self)
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 changeHandler = getHandler("pushsubscriptionchange")
const changeEvent = createEvent()
changeHandler(changeEvent)
await waitUntilCalls(changeEvent)
const changeEvent = createEvent()
changeHandler(changeEvent)
await waitUntilCalls(changeEvent)
expect(await getSavedSubscription()).toEqual({ id: 1, subscriptionId: 123 })
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) {

View File

@ -40,41 +40,51 @@ export default function start(self: ServiceWorkerGlobalScope) {
// )
// })
self.addEventListener("message", function (event) {
const waitEvent = event as ExtendableEvent
if ("type" in event.data && event.data.type === "subscribed") {
waitEvent.waitUntil(submitSubscription(event.data.subscription))
}
})
self.addEventListener("pushsubscriptionchange", function (event) {
const waitEvent = event as ExtendableEvent
waitEvent.waitUntil(submitSubscription(self.registration))
waitEvent.waitUntil(
(async () => {
const applicationServerKey = urlB64ToUint8Array(pushPublicKey)
const newSubscription = await self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey,
})
self.registration.pushManager.getSubscription()
await submitSubscription(newSubscription.toJSON())
})()
)
})
}
async function submitSubscription(registration: ServiceWorkerRegistration) {
async function submitSubscription(subscription: PushSubscriptionJSON) {
const db = database()
const subscription = await registration.pushManager.getSubscription()
if (subscription === null) return
const existingSubscriptionId = await db.subscriptions.get(1)
const applicationServerKey = urlB64ToUint8Array(pushPublicKey)
const newSubscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey,
})
await (existingSubscriptionId === undefined
? postSubscription(newSubscription, db)
: putSubscription(newSubscription, existingSubscriptionId.subscriptionId))
? postSubscription(subscription, db)
: putSubscription(subscription, existingSubscriptionId.subscriptionId))
}
async function postSubscription(
subscription: PushSubscription,
subscription: PushSubscriptionJSON,
db: ReturnType<typeof database>
) {
const response = await fetch(`${process.env.BASE_URL}/api/subscription/`, {
method: "POST",
body: JSON.stringify(subscription),
headers: {
"Content-Type": "application/json",
},
})
db.subscriptions.put({
@ -83,10 +93,13 @@ async function postSubscription(
})
}
function putSubscription(subscription: PushSubscription, id: number) {
function putSubscription(subscription: PushSubscriptionJSON, id: number) {
return fetch(`${process.env.BASE_URL}/api/subscription/${id}`, {
method: "PUT",
body: JSON.stringify(subscription),
headers: {
"Content-Type": "application/json",
},
})
}

View File

@ -2,7 +2,7 @@ export default function urlB64ToUint8Array(base64String: string) {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/")
const rawData = window.atob(base64)
const rawData = atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) {

View File

@ -1,5 +1,6 @@
import { test, expect, Page } from "@playwright/test"
import matchPath from "./urlMatcher"
import { messageSW } from "@remix-pwa/sw"
const { describe, beforeEach, skip, use } = test
@ -89,6 +90,23 @@ describe("when user is on iOS", () => {
await expect(yourNotificationsHeading).toBeVisible()
})
test.only("the subscription is submitted", async ({ page }) => {
await page.goto("/")
await page.getByText(/Enable Notifications/).click()
const postedMessages = await page.evaluate(() => {
return (window as any).postedMessages
})
expect(postedMessages).toContainEqual({
type: "subscribed",
subscription: {
subscription: "HI",
},
})
})
})
})
@ -212,20 +230,37 @@ function stubNotifications(
function stubServiceWorker(page: Page) {
return page.addInitScript(() => {
const serviceWorker = {}
function createSubscription(testToken = "HI") {
return {
toJSON: () => ({
subscription: testToken,
}),
}
}
let postedMessages: any[] = []
const serviceWorker = {
postMessage: (message: any) => postedMessages.push(message),
}
const registration = {
pushManager: {
getSubscription() {
return Promise.resolve({ hi: "subscription" })
return Promise.resolve(createSubscription())
},
subscribe(args: Parameters<PushManager["subscribe"]>[0]) {
return Promise.resolve({ hi: "subscription" })
return Promise.resolve(createSubscription())
},
},
active: serviceWorker,
}
Object.defineProperty(window, "postedMessages", {
value: postedMessages,
writable: true,
})
Object.defineProperty(navigator, "serviceWorker", {
value: {
ready: Promise.resolve(registration),

View File

@ -10,6 +10,10 @@ export default defineConfig({
plugins: [
remix({ ignoredRouteFiles: ["**/*.test.*"] }),
tsconfigPaths(),
remixPWA(),
remixPWA({
buildVariables: {
"process.env.BASE_URL": process.env.BASE_URL ?? "http://localhost:5173",
},
}),
],
})