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