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});