73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { generateServiceWorker } from "../src/index.ts";
|
|
function worker(fetcher: (r: any) => Promise<Response>, failStore = false) {
|
|
const listeners: Record<string, (event: any) => void> = {};
|
|
const saved: string[] = [];
|
|
const cache = {
|
|
match: async () => undefined,
|
|
put: async (r: any) => {
|
|
if (failStore) throw Error("quota");
|
|
saved.push(r.url);
|
|
},
|
|
};
|
|
const self = {
|
|
location: { origin: "https://account.example.test" },
|
|
addEventListener: (name: string, fn: (event: any) => void) => (listeners[name] = fn),
|
|
skipWaiting() {},
|
|
clients: { claim: async () => {} },
|
|
};
|
|
new Function("self", "caches", "fetch", generateServiceWorker())(
|
|
self,
|
|
{ open: async () => cache, keys: async () => [], delete: async () => true },
|
|
fetcher,
|
|
);
|
|
return {
|
|
saved,
|
|
async fetch(path: string, mode = "cors") {
|
|
let result: Promise<Response> | undefined;
|
|
const req = new Request(new URL(path, self.location.origin));
|
|
Object.defineProperty(req, "mode", { value: mode });
|
|
listeners.fetch({
|
|
request: req,
|
|
respondWith: (r: Promise<Response>) => (result = r),
|
|
waitUntil: () => {},
|
|
});
|
|
return result;
|
|
},
|
|
};
|
|
}
|
|
test("handoffs and API calls bypass service worker interception", async () => {
|
|
const w = worker(async () => {
|
|
throw Error("must not intercept");
|
|
});
|
|
expect(await w.fetch("/organization-add?sso_code=one-time")).toBeUndefined();
|
|
expect(await w.fetch("/api/account")).toBeUndefined();
|
|
});
|
|
test("private responses and navigations never enter cache", async () => {
|
|
const w = worker(
|
|
async () => new Response("private", { headers: { "cache-control": "private, no-store" } }),
|
|
);
|
|
expect(await (await w.fetch("/profile"))?.text()).toBe("private");
|
|
expect(w.saved).toEqual([]);
|
|
const nav = worker(async () => new Response("personal page"));
|
|
await nav.fetch("/profile", "navigate");
|
|
expect(nav.saved).toEqual([]);
|
|
});
|
|
test("cache quota errors do not turn successful requests into network errors", async () => {
|
|
const w = worker(async () => new Response("asset"), true);
|
|
expect(await (await w.fetch("/app.js"))?.text()).toBe("asset");
|
|
});
|
|
test("offline without a cached fallback always returns a Response", async () => {
|
|
const w = worker(async () => {
|
|
throw Error("offline");
|
|
});
|
|
expect((await w.fetch("/profile", "navigate"))?.status).toBe(503);
|
|
});
|
|
test("public static assets remain cacheable", async () => {
|
|
const w = worker(
|
|
async () => new Response("asset", { headers: { "cache-control": "public, max-age=3600" } }),
|
|
);
|
|
await w.fetch("/app.js");
|
|
expect(w.saved).toHaveLength(1);
|
|
});
|