3.0 KiB
sw-shim
Purpose
sw-shim.ts provides createServiceWorkerShim(), a hand-rolled
in-memory implementation of ServiceWorkerGlobalScope sufficient to
register and exercise the production SW handlers in a Node test
process. The shim is a real implementation — every method does
the work it claims to do, no vi.mock is permitted by CLAUDE.md rule
2. Tests register the production runPackedResourceServiceWorker
against the shim and dispatch FetchEvent / ExtendableEvent /
MessageEvent instances built from globalThis constructors.
Features
addEventListener(type, handler)registers the handler against a type-keyedMap<string, Set<EventListener>>.dispatchEvent(event)walks the matching set and invokes each handler synchronously, returning the standardboolean.cachesis a realMap<string, Map<url, Response>>-backedCacheStorage;open/match/put/keys/deleteall behave like the platform API.clients.claim()andskipWaiting()resolve immediately and record their call counts onrecordso tests can assert lifecycle invariants like "skipWaiting was called once".- No
vi.fn/vi.mockanywhere — the entire test surface observes real method behaviour via therecordsnapshot or the cache state.
Usage: Basic
import {
createServiceWorkerShim,
runPackedResourceServiceWorker,
} from "@suaveplan/packer-browser";
const scope = createServiceWorkerShim();
runPackedResourceServiceWorker(scope);
scope.dispatchEvent(new Event("install"));
// scope.record.skipWaitingCalls === 1
Usage: Advanced
import { createServiceWorkerShim } from "@suaveplan/packer-browser";
const scope = createServiceWorkerShim();
const cache = await scope.caches.open("ephemeral");
await cache.put("/a", new Response("payload"));
const hit = await cache.match("/a");
const text = await hit?.text();
// text === "payload"
// Dispatch into a custom listener
scope.addEventListener("custom", (ev) => observed.push(ev.type));
scope.dispatchEvent(new Event("custom"));
API Reference
createServiceWorkerShim(): ServiceWorkerShim.ServiceWorkerShim:addEventListener(type, handler)/dispatchEvent(event).skipWaiting(): Promise<void>(records call).clients: { claim(): Promise<void> }(records call).caches: ShimCacheStorage.record: ShimRecord—{ skipWaitingCalls, clientsClaimCalls }.
Implementation Notes
The shim deliberately does NOT reproduce browser quirks like Cache
Storage partitioning by origin, the FetchEvent network fall-through,
or the per-message-port start() requirement. Those behaviours are
covered by the Playwright E2E suite, not the unit tests; the unit
tests are concerned with the SW handler logic ("a packed URL
intercepted by the production code path produces a cache write")
which the shim faithfully exposes. Tests construct Event, Request,
and Response objects via globalThis (happy-dom in vitest) rather
than reinventing them inside the shim.