import { readFileSync } from "node:fs"; import vm from "node:vm"; import test from "node:test"; import assert from "node:assert/strict"; const source = readFileSync(new URL("../js/worker.js", import.meta.url), "utf8"); function worker(fetch = async () => { throw new Error("offline"); }) { const listeners = {}; let activations = 0; const self = { registration: { scope: "https://app.example/app/" }, fetch, addEventListener: (name, fn) => { listeners[name] = fn; }, skipWaiting: async () => { activations++; }, }; vm.runInNewContext(source, { self, URL, Response }); self.RastrilloPWA.install(); return { listeners, self, activations: () => activations }; } function navigate(w, request = {}) { let promise; w.listeners.fetch({ request: { url: "https://app.example/app/inbox", method: "GET", mode: "navigate", ...request }, respondWith: (p) => { promise = p; }, }); return promise; } test("offline navigation returns a public, non-cacheable fallback", async () => { const response = await navigate(worker()); assert.equal(response.status, 503); assert.equal(response.headers.get("cache-control"), "no-store"); assert.match(await response.text(), /Connect to the internet/); }); test("HTTP authentication and server errors remain visible", async () => { for (const status of [401, 403, 500]) { const w = worker(async (_request, options) => { assert.equal(options.cache, "no-store"); return new Response("server response", { status }); }); const response = await navigate(w); assert.equal(response.status, status); assert.equal(await response.text(), "server response"); } }); test("API calls, writes, external and out-of-scope navigations are untouched", () => { const w = worker(() => { assert.fail("must not fetch"); }); for (const request of [ { mode: "cors" }, { method: "POST" }, { url: "https://other.example/app/" }, { url: "https://app.example/another-app/" }, ]) assert.equal(navigate(w, request), undefined); }); test("activation requires an explicit message from a controlled-scope page", async () => { const w = worker(); assert.equal(w.listeners.install, undefined); assert.equal(w.listeners.activate, undefined); const send = async (url, type = "rastrillo:pwa:activate") => { let pending; w.listeners.message({ data: { type }, source: { url }, waitUntil: (p) => { pending = p; } }); await pending; }; await send("https://elsewhere.example/app/"); await send("https://app.example/other/"); await send("https://app.example/app/", "unrelated"); assert.equal(w.activations(), 0); await send("https://app.example/app/"); assert.equal(w.activations(), 1); assert.throws(() => w.self.RastrilloPWA.install(), /once/); });