Fix jest not exiting, post subscription to worker to send to the server
Test / test (push) Has been cancelled
Details
Test / test (push) Has been cancelled
Details
This commit is contained in:
parent
49392ebc4f
commit
34372bdb89
|
|
@ -16,6 +16,12 @@ const dialect = new PostgresDialect({
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const db = new Kysely<Database>({
|
export let db = new Kysely<Database>({
|
||||||
dialect,
|
dialect,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export function resetDbInstance() {
|
||||||
|
db = new Kysely<Database>({
|
||||||
|
dialect,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { db } from "./database"
|
import { db, resetDbInstance } from "./database"
|
||||||
import migrateToLatest, { resetAll } from "./migrate"
|
import migrateToLatest, { resetAll } from "./migrate"
|
||||||
import {
|
import {
|
||||||
createSubscription,
|
createSubscription,
|
||||||
|
|
@ -34,9 +34,21 @@ jest.mock("./settings", () => ({
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe("subscriptions", () => {
|
describe("subscriptions", () => {
|
||||||
beforeEach(migrateToLatest)
|
beforeAll(() => {
|
||||||
|
resetDbInstance()
|
||||||
|
})
|
||||||
|
|
||||||
afterEach(resetAll)
|
beforeEach(async () => {
|
||||||
|
await migrateToLatest()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await resetAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await db.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
test("createSubscription", async () => {
|
test("createSubscription", async () => {
|
||||||
const { id } = await createSubscription(pushSubscription1)
|
const { id } = await createSubscription(pushSubscription1)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,20 @@ function EnableButton({ onSubscribe }: { onSubscribe: () => void }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canSendPush) return
|
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])
|
}, [canSendPush])
|
||||||
|
|
||||||
return <button onClick={subscribe}>Enable Notifications</button>
|
return <button onClick={subscribe}>Enable Notifications</button>
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,22 @@ import "core-js/stable/structured-clone"
|
||||||
import initWorker from "./worker"
|
import initWorker from "./worker"
|
||||||
import Dexie, { EntityTable } from "dexie"
|
import Dexie, { EntityTable } from "dexie"
|
||||||
|
|
||||||
|
function createSubscription(testToken = "HI") {
|
||||||
|
return {
|
||||||
|
toJSON: () => ({
|
||||||
|
subscription: testToken,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const createSelf = () =>
|
const createSelf = () =>
|
||||||
({
|
({
|
||||||
addEventListener: jest.fn(),
|
addEventListener: jest.fn(),
|
||||||
skipWaiting: jest.fn(() => Promise.resolve()),
|
skipWaiting: jest.fn(() => Promise.resolve()),
|
||||||
registration: {
|
registration: {
|
||||||
pushManager: {
|
pushManager: {
|
||||||
subscribe: jest.fn(() => Promise.resolve({ subscription: "HI" })),
|
subscribe: jest.fn(() => Promise.resolve(createSubscription())),
|
||||||
getSubscription: jest.fn(() => Promise.resolve({ subscription: "HI" })),
|
getSubscription: jest.fn(() => Promise.resolve(createSubscription())),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
clients: {
|
clients: {
|
||||||
|
|
@ -18,10 +26,14 @@ const createSelf = () =>
|
||||||
},
|
},
|
||||||
}) as unknown as ServiceWorkerGlobalScope
|
}) as unknown as ServiceWorkerGlobalScope
|
||||||
|
|
||||||
|
let originalWindow: Window & typeof globalThis
|
||||||
|
|
||||||
describe("service worker", () => {
|
describe("service worker", () => {
|
||||||
let self: ServiceWorkerGlobalScope
|
let self: ServiceWorkerGlobalScope
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
originalWindow = global.window
|
||||||
|
global.window = undefined as any
|
||||||
indexedDB = new IDBFactory()
|
indexedDB = new IDBFactory()
|
||||||
self = createSelf()
|
self = createSelf()
|
||||||
|
|
||||||
|
|
@ -52,6 +64,7 @@ describe("service worker", () => {
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
|
global.window = originalWindow
|
||||||
})
|
})
|
||||||
|
|
||||||
xtest("displays push notifications", () => {
|
xtest("displays push notifications", () => {
|
||||||
|
|
@ -87,63 +100,149 @@ describe("service worker", () => {
|
||||||
expect(self.skipWaiting).toHaveBeenCalled()
|
expect(self.skipWaiting).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("puts push subscription if the subscription id is set", async () => {
|
describe("on subscription message", () => {
|
||||||
initWorker(self)
|
test("puts push subscription if the subscription id is set", async () => {
|
||||||
await setSavedSubscription(5000)
|
initWorker(self)
|
||||||
|
await setSavedSubscription(5000)
|
||||||
|
|
||||||
const changeHandler = getHandler("pushsubscriptionchange")
|
const messageHandler = getHandler("message")
|
||||||
|
|
||||||
const changeEvent = createEvent()
|
const messageEvent = createEvent({
|
||||||
changeHandler(changeEvent)
|
data: {
|
||||||
await waitUntilCalls(changeEvent)
|
subscription: { subscription: "YO" },
|
||||||
|
type: "subscribed",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
messageHandler(messageEvent)
|
||||||
|
await waitUntilCalls(messageEvent)
|
||||||
|
|
||||||
expect(self.registration.pushManager.subscribe).toHaveBeenCalledWith({
|
expect(fetch).toHaveBeenCalledWith(
|
||||||
userVisibleOnly: true,
|
`${process.env.BASE_URL}/api/subscription/5000`,
|
||||||
applicationServerKey: expect.any(Uint8Array),
|
{
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({ subscription: "YO" }),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(fetch).toHaveBeenCalledWith(
|
test("posts to subscriptions when there is no user set", async () => {
|
||||||
`${process.env.BASE_URL}/api/subscription/5000`,
|
initWorker(self)
|
||||||
{
|
|
||||||
method: "PUT",
|
|
||||||
body: JSON.stringify({ subscription: "HI" }),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("posts to subscriptions when there is no user set", async () => {
|
const messageHandler = getHandler("message")
|
||||||
initWorker(self)
|
|
||||||
|
|
||||||
const changeHandler = getHandler("pushsubscriptionchange")
|
const messageEvent = createEvent({
|
||||||
|
data: {
|
||||||
|
subscription: { subscription: "YO" },
|
||||||
|
type: "subscribed",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
messageHandler(messageEvent)
|
||||||
|
await waitUntilCalls(messageEvent)
|
||||||
|
|
||||||
const changeEvent = createEvent()
|
expect(fetch).toHaveBeenCalledWith(
|
||||||
changeHandler(changeEvent)
|
`${process.env.BASE_URL}/api/subscription/`,
|
||||||
await waitUntilCalls(changeEvent)
|
{
|
||||||
|
method: "POST",
|
||||||
expect(self.registration.pushManager.subscribe).toHaveBeenCalledWith({
|
body: JSON.stringify({ subscription: "YO" }),
|
||||||
userVisibleOnly: true,
|
headers: {
|
||||||
applicationServerKey: expect.any(Uint8Array),
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(fetch).toHaveBeenCalledWith(
|
test("subscription id is saved on first subscription change", async () => {
|
||||||
`${process.env.BASE_URL}/api/subscription/`,
|
initWorker(self)
|
||||||
{
|
|
||||||
method: "POST",
|
const messageHandler = getHandler("message")
|
||||||
body: JSON.stringify({ subscription: "HI" }),
|
|
||||||
}
|
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 () => {
|
describe("on subscription change", () => {
|
||||||
initWorker(self)
|
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()
|
const changeEvent = createEvent()
|
||||||
changeHandler(changeEvent)
|
changeHandler(changeEvent)
|
||||||
await waitUntilCalls(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) {
|
function getHandler(eventName: string) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
self.addEventListener("pushsubscriptionchange", function (event) {
|
||||||
const waitEvent = event as ExtendableEvent
|
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 db = database()
|
||||||
|
|
||||||
const subscription = await registration.pushManager.getSubscription()
|
|
||||||
if (subscription === null) return
|
|
||||||
|
|
||||||
const existingSubscriptionId = await db.subscriptions.get(1)
|
const existingSubscriptionId = await db.subscriptions.get(1)
|
||||||
|
|
||||||
const applicationServerKey = urlB64ToUint8Array(pushPublicKey)
|
|
||||||
const newSubscription = await registration.pushManager.subscribe({
|
|
||||||
userVisibleOnly: true,
|
|
||||||
applicationServerKey: applicationServerKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
await (existingSubscriptionId === undefined
|
await (existingSubscriptionId === undefined
|
||||||
? postSubscription(newSubscription, db)
|
? postSubscription(subscription, db)
|
||||||
: putSubscription(newSubscription, existingSubscriptionId.subscriptionId))
|
: putSubscription(subscription, existingSubscriptionId.subscriptionId))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function postSubscription(
|
async function postSubscription(
|
||||||
subscription: PushSubscription,
|
subscription: PushSubscriptionJSON,
|
||||||
db: ReturnType<typeof database>
|
db: ReturnType<typeof database>
|
||||||
) {
|
) {
|
||||||
const response = await fetch(`${process.env.BASE_URL}/api/subscription/`, {
|
const response = await fetch(`${process.env.BASE_URL}/api/subscription/`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(subscription),
|
body: JSON.stringify(subscription),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
db.subscriptions.put({
|
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}`, {
|
return fetch(`${process.env.BASE_URL}/api/subscription/${id}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
body: JSON.stringify(subscription),
|
body: JSON.stringify(subscription),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ export default function urlB64ToUint8Array(base64String: string) {
|
||||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
|
||||||
const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/")
|
const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/")
|
||||||
|
|
||||||
const rawData = window.atob(base64)
|
const rawData = atob(base64)
|
||||||
const outputArray = new Uint8Array(rawData.length)
|
const outputArray = new Uint8Array(rawData.length)
|
||||||
|
|
||||||
for (let i = 0; i < rawData.length; ++i) {
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { test, expect, Page } from "@playwright/test"
|
import { test, expect, Page } from "@playwright/test"
|
||||||
import matchPath from "./urlMatcher"
|
import matchPath from "./urlMatcher"
|
||||||
|
import { messageSW } from "@remix-pwa/sw"
|
||||||
|
|
||||||
const { describe, beforeEach, skip, use } = test
|
const { describe, beforeEach, skip, use } = test
|
||||||
|
|
||||||
|
|
@ -89,6 +90,23 @@ describe("when user is on iOS", () => {
|
||||||
|
|
||||||
await expect(yourNotificationsHeading).toBeVisible()
|
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) {
|
function stubServiceWorker(page: Page) {
|
||||||
return page.addInitScript(() => {
|
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 = {
|
const registration = {
|
||||||
pushManager: {
|
pushManager: {
|
||||||
getSubscription() {
|
getSubscription() {
|
||||||
return Promise.resolve({ hi: "subscription" })
|
return Promise.resolve(createSubscription())
|
||||||
},
|
},
|
||||||
subscribe(args: Parameters<PushManager["subscribe"]>[0]) {
|
subscribe(args: Parameters<PushManager["subscribe"]>[0]) {
|
||||||
return Promise.resolve({ hi: "subscription" })
|
return Promise.resolve(createSubscription())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
active: serviceWorker,
|
active: serviceWorker,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(window, "postedMessages", {
|
||||||
|
value: postedMessages,
|
||||||
|
writable: true,
|
||||||
|
})
|
||||||
|
|
||||||
Object.defineProperty(navigator, "serviceWorker", {
|
Object.defineProperty(navigator, "serviceWorker", {
|
||||||
value: {
|
value: {
|
||||||
ready: Promise.resolve(registration),
|
ready: Promise.resolve(registration),
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
remix({ ignoredRouteFiles: ["**/*.test.*"] }),
|
remix({ ignoredRouteFiles: ["**/*.test.*"] }),
|
||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
remixPWA(),
|
remixPWA({
|
||||||
|
buildVariables: {
|
||||||
|
"process.env.BASE_URL": process.env.BASE_URL ?? "http://localhost:5173",
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue