import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { ROOT } from "../lib/common.ts"; import { DEFAULT_BANNER, FONT_CHARS, initials, type NuguPalette, renderBanner, } from "./banner.ts"; import { extensionDirs } from "./banners.ts"; const palette = JSON.parse( readFileSync(join(ROOT, "assets", "nugu.json"), "utf8"), ) as NuguPalette; /** Geometry only: strip every colour so two renders can be compared for shape. */ const shape = (svg: string) => svg.replaceAll(/fill="[^"]*"/g, "").replaceAll(/#(?:dark|light)/g, ""); describe("README banner", () => { it("executes CLI entrypoint and rejects an unknown extension", () => { const result = spawnSync( process.execPath, [join(ROOT, "scripts/readme/banners.ts"), "__missing_banner_cli_probe__"], { cwd: ROOT, encoding: "utf8", timeout: 10_000 }, ); expect(result.error).toBeUndefined(); expect(result.status).not.toBe(0); expect(result.stderr).toContain( "unknown extension: __missing_banner_cli_probe__", ); }); it("derives initials from the first and last hyphen part", () => { expect(initials("klaus")).toEqual(["k"]); expect(initials("git-safe")).toEqual(["g", "s"]); expect(initials("the-system")).toEqual(["t", "s"]); expect(initials("nu-bang")).toEqual(["n", "b"]); }); it("is deterministic and theme-complete", () => { const a = renderBanner("ultra", palette); expect(renderBanner("ultra", palette)).toBe(a); expect(a).toContain('id="dark"'); expect(a).toContain('id="light"'); expect(a).toContain("prefers-color-scheme: light"); expect(a).toMatch(/^")).toBe(true); expect(a).not.toContain(" { const v0 = renderBanner("ultra", palette, { version: 0 }); const v5 = renderBanner("ultra", palette, { version: 5 }); expect(v5).not.toBe(v0); expect(shape(v5)).toBe(shape(v0)); }); it("changes geometry with the seed", () => { const a = renderBanner("ultra", palette, { seed: 1 }); const b = renderBanner("ultra", palette, { seed: 2 }); expect(shape(a)).not.toBe(shape(b)); }); it("uses only palette-derived colours", () => { const svg = renderBanner("the-system", palette); const fills = [...svg.matchAll(/fill="([^"]+)"/g)].map((m) => m[1]); const allowed = new Set([ ...Object.values(palette.dark.content), ...Object.values(palette.dark.ui), ...Object.values(palette.light.content), ...Object.values(palette.light.ui), ]); for (const fill of fills) expect( fill === undefined || allowed.has(fill) || fill.startsWith("hsl("), ).toBe(true); }); it("can set every extension name in its pixel font", () => { for (const name of extensionDirs()) for (const ch of name) expect(FONT_CHARS.has(ch), `${name}: ${ch}`).toBe(true); }); it("keeps the agreed defaults", () => { expect(DEFAULT_BANNER).toEqual({ seed: 193_581, mutation: 4, version: 0, drift: 0.5, cell: 10, }); }); });