build.test.ts

3.7 kB · typescript · 108 lines

1import { expect, test } from "bun:test";2import { readFileSync } from "node:fs";3import { join } from "node:path";4import { globals, walk, type Output, type Site, type Spec } from "./build.ts";56/* SITE */78const site = () =>9  ({10    config: {11      title: "Demo",12      root: "https://demo.test",13      llms: {14        about: "A demo tree.",15        links: [16          { href: "/raw/README.md", name: "README", note: "the readme" },17          { href: "/git/", name: "Code", note: "the tree" },18          { href: "/nowhere/", name: "Nowhere" },19        ],20      },21    },22    routes: [23      { route: "/", name: "Home", at: "2026-01-01" },24      { route: "/404.html", kind: "missing", hidden: true },25      { route: "/git/", kind: "gitdir", name: "demo", sitemap: true, at: "2026-02-02" },26      {27        route: "/git/README.md",28        kind: "gitfile",29        name: "README.md",30        hidden: true,31        sitemap: true,32        at: "2026-03-03",33        urls: [{ route: "/git/README.md" }, { route: "/raw/README.md" }],34      },35    ],36  }) as unknown as Site;3738const made = async () => {39  const out = await globals(site(), {} as unknown as Spec);40  const find = (path: string) => (out.find((one: Output) => one.path === path)!.bytes as string);41  return { sitemap: find("sitemap.xml"), robots: find("robots.txt"), llms: find("llms.txt") };42};4344/* SITEMAP */4546test("the sitemap carries every git route and every raw path with its own date", async () => {47  const { sitemap } = await made();48  expect(sitemap).toContain("<loc>https://demo.test/git/</loc><lastmod>2026-02-02</lastmod>");49  expect(sitemap).toContain("<loc>https://demo.test/git/README.md</loc><lastmod>2026-03-03</lastmod>");50  expect(sitemap).toContain("<loc>https://demo.test/raw/README.md</loc><lastmod>2026-03-03</lastmod>");51  expect(sitemap).toContain("<loc>https://demo.test/</loc><lastmod>2026-01-01</lastmod>");52  expect(sitemap).not.toContain("404.html");53  expect(sitemap.match(/<url>/g)!.length).toBe(4);54});5556/* ROBOTS */5758test("robots names the crawlers it welcomes and points at the sitemap", async () => {59  const { robots } = await made();60  for (const agent of ["GPTBot", "ClaudeBot", "Claude-Web", "CCBot", "Google-Extended", "anthropic-ai", "PerplexityBot"]) {61    expect(robots).toContain(`User-agent: ${agent}\nAllow: /`);62  }63  expect(robots).toContain("User-agent: *\nAllow: /");64  expect(robots).toContain("Sitemap: https://demo.test/sitemap.xml");65});6667/* LLMS */6869test("llms.txt says what the site is and links only what the site publishes", async () => {70  const { llms } = await made();71  expect(llms).toContain("# Demo");72  expect(llms).toContain("A demo tree.");73  expect(llms).toContain("- [README](https://demo.test/raw/README.md): the readme");74  expect(llms).toContain("- [Code](https://demo.test/git/): the tree");75  expect(llms).not.toContain("Nowhere");76});7778/* CSS */7980const selectors = (css: string) => {81  const out: string[] = [];82  let depth = 0;83  let buf = "";84  for (const c of css.replace(/\/\*[\s\S]*?\*\//g, "")) {85    if (c === "{") {86      if (depth === 0) out.push(buf.trim().replace(/\s+/g, " "));87      depth++;88      buf = "";89    } else if (c === "}") {90      depth--;91      buf = "";92    } else if (depth === 0) buf += c;93  }94  return out;95};9697test("the kit css never repeats a top-level selector with another rule between", () => {98  const home = join(import.meta.dir, "..", "ui");99  for (const file of walk(home, false).filter((f) => f.endsWith(".css"))) {100    const list = selectors(readFileSync(file, "utf8"));101    const last = new Map<string, number>();102    list.forEach((sel, n) => {103      const was = last.get(sel);104      if (was !== undefined && list.slice(was + 1, n).some((other) => other !== sel)) throw new Error(`${file}: '${sel}' at ${was} and ${n}`);105      last.set(sel, n);106    });107  }108});