diff --git a/app/worker.test.ts b/app/worker.test.ts index 6822a8e..73a56f3 100644 --- a/app/worker.test.ts +++ b/app/worker.test.ts @@ -25,6 +25,7 @@ const createSelf = () => clients: { claim: jest.fn(() => Promise.resolve()), openWindow: jest.fn(() => Promise.resolve()), + matchAll: jest.fn(() => Promise.resolve([])), }, }) as unknown as ServiceWorkerGlobalScope @@ -32,12 +33,16 @@ let originalWindow: Window & typeof globalThis describe("service worker", () => { let self: ServiceWorkerGlobalScope + let controlledClients: Client[] + let uncontrolledClients: Client[] beforeEach(() => { originalWindow = global.window global.window = undefined as any indexedDB = new IDBFactory() self = createSelf() + controlledClients = [] + uncontrolledClients = [] fetchMock.mockResponse((req) => { if ( @@ -94,28 +99,71 @@ describe("service worker", () => { ) }) - test("closes notification on click", async () => { - initWorker(self) + describe("on notification click", () => { + test("the notification is closed", async () => { + initWorker(self) - const notificationHandler = getHandler("notificationclick") + const notificationHandler = getHandler("notificationclick") - const event = createNotificationEvent("tackupnow.com") - notificationHandler(event) - await waitUntilCalls(event) + const event = createNotificationEvent("tackupnow.com") + notificationHandler(event) + await waitUntilCalls(event) - expect(event.notification.close).toHaveBeenCalled() - }) + expect(event.notification.close).toHaveBeenCalled() + }) - test("opens tack up now on click", async () => { - initWorker(self) + test("opens tack up now if no clients match the destination", async () => { + initWorker(self) - const notificationHandler = getHandler("notificationclick") + const notificationHandler = getHandler("notificationclick") - const event = createNotificationEvent("tackupnow.com") - notificationHandler(event) - await waitUntilCalls(event) + const event = createNotificationEvent("tackupnow.com") + notificationHandler(event) + await waitUntilCalls(event) - expect(self.clients.openWindow).toHaveBeenCalledWith("tackupnow.com") + expect(self.clients.openWindow).toHaveBeenCalledWith("tackupnow.com") + }) + + test("focuses first matching client", async () => { + addClient("https://tackupnow.com/place", "window", true) + const matchingClient = addClient( + "https://tackupnow.com/otherplace", + "window", + true + ) + addClient("https://tackupnow.com/yetanotherotherplace", "window", true) + + initWorker(self) + + const notificationHandler = getHandler("notificationclick") + + const event = createNotificationEvent("https://tackupnow.com/otherplace") + notificationHandler(event) + await waitUntilCalls(event) + + expect(matchingClient.focus).toHaveBeenCalled() + }) + + test("focuses uncontrolled matching clients", async () => { + addClient("https://derpatious.world", "window", false) + addClient("https://tackupnow.com/1", "window", false) + const matchingClient = addClient( + "https://tackupnow.com/2", + "window", + false + ) + addClient("https://tackupnow.com/controlled", "window", true) + + initWorker(self) + + const notificationHandler = getHandler("notificationclick") + + const event = createNotificationEvent("https://tackupnow.com/2") + notificationHandler(event) + await waitUntilCalls(event) + + expect(matchingClient.focus).toHaveBeenCalled() + }) }) test("claims on activate", async () => { @@ -287,6 +335,31 @@ describe("service worker", () => { }) }) + function addClient(url: string, type: ClientTypes, controlled: boolean) { + const matchAllMock = self.clients.matchAll as jest.Mock + + matchAllMock.mockImplementation( + (args: { type: string; includeUncontrolled?: boolean }) => { + const clientList = args.includeUncontrolled + ? [...uncontrolledClients, ...controlledClients] + : controlledClients + return clientList.filter((client) => client.type === args.type) + } + ) + + const clientList = controlled ? controlledClients : uncontrolledClients + + const client: WindowClient = { + url, + type, + focus: jest.fn(() => Promise.resolve(client)) as WindowClient["focus"], + } as WindowClient + + clientList.push(client) + + return client + } + function getHandler(eventName: string) { expect(self.addEventListener).toHaveBeenCalledWith( eventName, diff --git a/app/worker.ts b/app/worker.ts index cce3cb4..2425488 100644 --- a/app/worker.ts +++ b/app/worker.ts @@ -17,7 +17,12 @@ export default function start(self: ServiceWorkerGlobalScope) { }) self.addEventListener("push", function (event: PushEvent) { - const { title, body, badge, icon, destination } = event.data?.json() + const { title, body, badge, icon, destination } = event.data?.json() ?? { + title: "It was missing yo", + badge: "image/blah", + icon: "image/blah", + destination: "https://tackupnow.com", + } event.waitUntil( self.registration.showNotification(title, { @@ -31,9 +36,25 @@ export default function start(self: ServiceWorkerGlobalScope) { self.addEventListener("notificationclick", function (event) { event.notification.close() + const destination = event.notification.data.destination event.waitUntil( - self.clients.openWindow(event.notification.data.destination) + (async () => { + const clients = await self.clients.matchAll({ + type: "window", + includeUncontrolled: true, + }) + + const existingClient = clients.find( + (client) => client.url === event.notification.data.destination + ) + + if (existingClient === undefined) { + await self.clients.openWindow(destination) + } else { + await existingClient.focus() + } + })() ) })