push.test.ts
3.2 kB · typescript · 72 lines
1import { describe, expect, test } from "bun:test";2import { cache, IMMUTABLE, kind, mine, REVALIDATE, rules, spread, sweep, typing, type Lister } from "./push.ts";3import type { Manifest } from "./ssg/build.ts";45const SHOP = rules(["(^|/)lib-[^/]+\\.js$", "(^|/)lib-[^/]+\\.css$", "(^|/)js/[^/]+\\.js$", "\\.wasm$", "-[0-9a-f]{8}\\.[^./]+$"]);67const NET = rules(["(^|/)lib-[^/]+\\.js$", "(^|/)lib-[^/]+\\.css$", "\\.wasm$", "-[0-9a-f]{8}\\.[^./]+$"]);89const GUARD = ["cdn/", "art/", "automator/automator.json", "stats/stats.json"];1011const MANIFEST: Manifest = {12 "/": { hash: "a", at: "2026-09-21", outputs: ["index.html", "props.json"], types: { "props.json": "application/json" } },13 "/shop/": { hash: "b", at: "2026-09-21", outputs: ["shop/index.html"] },14 "@ui/tokens-8a0bcf6d.css": { hash: "c", at: "2026-09-21", outputs: ["ui/tokens-8a0bcf6d.css"] },15};1617const page = (contents: string[], prefixes: string[]) => ({18 contents: contents.map((key) => ({ key })),19 commonPrefixes: prefixes.map((prefix) => ({ prefix })),20 isTruncated: false,21 nextContinuationToken: null,22});2324const TREE: Record<string, ReturnType<typeof page>> = {25 "site/": page(["site/index.html"], ["site/art/", "site/cdn/", "site/shop/", "site/ui/"]),26 "site/shop/": page(["site/shop/index.html"], []),27 "site/ui/": page(["site/ui/tokens-8a0bcf6d.css"], []),28 "site/art/": page(["site/art/never.png"], []),29 "site/cdn/": page(["site/cdn/never.js"], []),30};3132const fake: Lister = { list: async ({ prefix }) => TREE[prefix] ?? page([], []) };3334describe("push", () => {35 test("a hashed name is immutable for a year, every other name revalidates", () => {36 expect(cache("js/main.js", SHOP)).toBe(IMMUTABLE);37 expect(cache("ui/tokens-8a0bcf6d.css", SHOP)).toBe(IMMUTABLE);38 expect(cache("index.html", SHOP)).toBe(REVALIDATE);39 expect(cache("js/main.js", NET)).toBe(REVALIDATE);40 });4142 test("a type declared in the manifest wins over the extension", () => {43 expect(typing(MANIFEST).get("props.json")).toBe("application/json");44 expect(typing(MANIFEST).has("index.html")).toBe(false);45 expect(kind("index.html")).toBe("text/html; charset=utf-8");46 });4748 test("every output points back at the record that wrote it", () => {49 expect([...spread(MANIFEST)]).toEqual([50 ["index.html", "/"],51 ["props.json", "/"],52 ["shop/index.html", "/shop/"],53 ["ui/tokens-8a0bcf6d.css", "@ui/tokens-8a0bcf6d.css"],54 ]);55 });5657 test("the guard owns a prefix and a single key alike", () => {58 expect(mine("shop/index.html", GUARD)).toBe(true);59 expect(mine("cdn/reel.js", GUARD)).toBe(false);60 expect(mine("stats/stats.json", GUARD)).toBe(false);61 expect(mine("stats/index.html", GUARD)).toBe(true);62 });6364 test("a sweep strips the prefix and never descends into a guarded folder", async () => {65 expect(await sweep(fake, "site/", GUARD)).toEqual(["index.html", "shop/index.html", "ui/tokens-8a0bcf6d.css"]);66 });6768 test("a sweep at the bucket root walks from the empty prefix", async () => {69 const flat: Lister = { list: async ({ prefix }) => (prefix === "" ? page(["index.html"], ["cdn/"]) : page([], [])) };70 expect(await sweep(flat, "", ["cdn/"])).toEqual(["index.html"]);71 });72});