| 1 | import { execFileSync } from "node:child_process"; |
| 2 | import { mkdtempSync, readFileSync, rmSync } from "node:fs"; |
| 3 | import { createHash } from "node:crypto"; |
| 4 | import { tmpdir } from "node:os"; |
| 5 | import { join } from "node:path"; |
| 6 | import { describe, expect, it } from "vitest"; |
| 7 | import { TERMINAL_SCREENSHOT } from "./media-manifest"; |
| 8 | import { TERMINAL_CAPTURE_FRAMES, TERMINAL_CAPTURE_META } from "./terminal-capture.generated"; |
| 9 | |
| 10 | // The homepage terminal is live text drawn from real PTY cells. These checks |
| 11 | // keep the generated runs tied to the capture files, cell for cell. |
| 12 | const webRoot = new URL("../", import.meta.url); |
| 13 | |
| 14 | describe("terminal capture", () => { |
| 15 | it("keeps the generated module current with the capture files", () => { |
| 16 | const out = execFileSync(process.execPath, ["scripts/render-terminal-capture.mjs", "--check"], { |
| 17 | cwd: webRoot, |
| 18 | encoding: "utf8", |
| 19 | }); |
| 20 | expect(out).toContain("is current"); |
| 21 | }); |
| 22 | |
| 23 | it("keeps every frame's text identical to its captured cells", () => { |
| 24 | for (const frame of Object.values(TERMINAL_CAPTURE_FRAMES)) { |
| 25 | const capture = JSON.parse(readFileSync(new URL(`../../${frame.file}`, import.meta.url), "utf8")) as { |
| 26 | rows: number; |
| 27 | cols: number; |
| 28 | cells: { text: string }[][]; |
| 29 | }; |
| 30 | expect(frame.lines).toHaveLength(capture.rows); |
| 31 | frame.lines.forEach((runs, row) => { |
| 32 | const text = runs.map(([t]) => t).join(""); |
| 33 | expect(text, `${frame.file} row ${row}`).toBe(capture.cells[row].map((c) => c.text || " ").join("")); |
| 34 | expect([...text]).toHaveLength(capture.cols); |
| 35 | }); |
| 36 | } |
| 37 | }); |
| 38 | |
| 39 | it("names its source and the build it came from", () => { |
| 40 | expect(TERMINAL_CAPTURE_META.source).toContain("website_current_terminal_capture"); |
| 41 | expect(TERMINAL_CAPTURE_META.baseCommit).toBe(TERMINAL_SCREENSHOT.sourceCommit); |
| 42 | expect(`web/lib/terminal-captures/website-home-100x24.json`).toBe(TERMINAL_SCREENSHOT.capture); |
| 43 | }); |
| 44 | |
| 45 | it("keeps the native workbar and every recorded frame tied to their capture hashes", () => { |
| 46 | const manifest = JSON.parse(readFileSync(new URL("./terminal-captures/manifest.json", import.meta.url), "utf8")); |
| 47 | expect(manifest.binarySha256).toMatch(/^[0-9a-f]{64}$/); |
| 48 | expect(manifest.conditions.submittedPrompts).toBe(0); |
| 49 | expect(manifest.conditions.fixtureHistory).toBe(false); |
| 50 | for (const frame of manifest.frames) { |
| 51 | const bytes = readFileSync(new URL(`./terminal-captures/${frame.file}`, import.meta.url)); |
| 52 | expect(createHash("sha256").update(bytes).digest("hex"), frame.file).toBe(frame.sha256); |
| 53 | const capture = JSON.parse(bytes.toString()); |
| 54 | expect(capture.rows).toBe(frame.rows); |
| 55 | expect(capture.cols).toBe(frame.cols); |
| 56 | } |
| 57 | const workbar = TERMINAL_CAPTURE_FRAMES.workbar.lines.map((runs) => runs.map(([text]) => text).join("")).join("\n"); |
| 58 | expect(workbar).toContain("no to-dos yet"); |
| 59 | expect(workbar).toContain("~/my-project"); |
| 60 | expect(workbar).not.toContain(".tmp"); |
| 61 | }); |
| 62 | |
| 63 | it("exports native geometry and every whale braille dot without relying on font fallback", () => { |
| 64 | const directory = mkdtempSync(join(tmpdir(), "native-terminal-svg-")); |
| 65 | try { |
| 66 | const output = join(directory, "home.svg"); |
| 67 | execFileSync(process.execPath, ["scripts/render-terminal-capture.mjs", "--svg", output], { cwd: webRoot }); |
| 68 | const svg = readFileSync(output, "utf8"); |
| 69 | expect(svg).toContain('viewBox="0 0 1000 480"'); |
| 70 | const text = TERMINAL_CAPTURE_FRAMES.home.lines.flatMap((runs) => runs.map(([text]) => text)).join(""); |
| 71 | let dots = 0; |
| 72 | for (const glyph of text) { |
| 73 | const code = glyph.codePointAt(0)!; |
| 74 | if (code >= 0x2800 && code <= 0x28ff) dots += (code - 0x2800).toString(2).replaceAll("0", "").length; |
| 75 | } |
| 76 | expect(dots).toBeGreaterThan(0); |
| 77 | expect(svg.match(/<circle /g)).toHaveLength(dots); |
| 78 | expect(svg).not.toContain("<script"); |
| 79 | } finally { |
| 80 | rmSync(directory, { recursive: true, force: true }); |
| 81 | } |
| 82 | }); |
| 83 | |
| 84 | it("renders as one labelled image inside a keyboard-scrollable region", () => { |
| 85 | const component = readFileSync(new URL("../components/terminal-capture.tsx", import.meta.url), "utf8"); |
| 86 | expect(component).toContain('role="region"'); |
| 87 | expect(component).toContain("tabIndex={0}"); |
| 88 | expect(component).toContain('role="img"'); |
| 89 | expect(component).toContain("aria-label={label}"); |
| 90 | }); |
| 91 | }); |
| 92 |