// @vitest-environment jsdom import { describe, it, expect, beforeEach, vi } from 'vitest'; class FakeSW extends EventTarget { scriptURL = '/service-worker.js'; state: 'installed' | 'activated' = 'activated'; version: string | null; postMessage = vi.fn((msg: unknown, transfer?: Transferable[]) => { if ((msg as { type?: string } | null)?.type === 'GET_VERSION') { const port = transfer?.[0] as MessagePort | undefined; if (port && this.version !== null) port.postMessage({ version: this.version }); } }); constructor(version: string | null) { super(); this.version = version; } } type Reg = { active: FakeSW | null; waiting: FakeSW | null; installing: FakeSW | null; addEventListener: ReturnType; update: ReturnType; }; function mountFakeSW(init: Partial): Reg { const registration: Reg = { active: init.active ?? null, waiting: init.waiting ?? null, installing: init.installing ?? null, addEventListener: vi.fn(), update: vi.fn().mockResolvedValue(undefined) }; Object.defineProperty(navigator, 'serviceWorker', { configurable: true, value: { ready: Promise.resolve(registration), controller: registration.active, addEventListener: vi.fn() } }); return registration; } async function flush(ms = 20): Promise { await new Promise((r) => setTimeout(r, ms)); } describe('pwa store', () => { beforeEach(() => { vi.resetModules(); }); it('bleibt still, wenn waiting- und active-SW dieselbe Version melden (Zombie)', async () => { const active = new FakeSW('1776526292782'); const waiting = new FakeSW('1776526292782'); waiting.state = 'installed'; mountFakeSW({ active, waiting }); const { pwaStore } = await import('../../src/lib/client/pwa.svelte'); await pwaStore.init(); await flush(); expect(pwaStore.updateAvailable).toBe(false); expect(waiting.postMessage).toHaveBeenCalledWith({ type: 'SKIP_WAITING' }); }); it('zeigt Toast, wenn sich die Versionen unterscheiden', async () => { const active = new FakeSW('1776526292782'); const waiting = new FakeSW('1776999999999'); waiting.state = 'installed'; mountFakeSW({ active, waiting }); const { pwaStore } = await import('../../src/lib/client/pwa.svelte'); await pwaStore.init(); await flush(); expect(pwaStore.updateAvailable).toBe(true); expect(waiting.postMessage).not.toHaveBeenCalledWith({ type: 'SKIP_WAITING' }); }); it('zeigt Toast, wenn der alte active-SW keine Version liefert (Fallback)', async () => { const active = new FakeSW(null); const waiting = new FakeSW('1776999999999'); waiting.state = 'installed'; mountFakeSW({ active, waiting }); const { pwaStore } = await import('../../src/lib/client/pwa.svelte'); await pwaStore.init(); await flush(); expect(pwaStore.updateAvailable).toBe(true); }); it('reload() ohne waiting-SW macht nur location.reload()', async () => { const active = new FakeSW('1776526292782'); mountFakeSW({ active }); const { pwaStore } = await import('../../src/lib/client/pwa.svelte'); await pwaStore.init(); await flush(); const reload = vi.fn(); Object.defineProperty(window, 'location', { configurable: true, value: { ...window.location, reload } }); pwaStore.reload(); expect(reload).toHaveBeenCalled(); }); });