diff --git a/make-pdf/test/diagram-prepass.test.ts b/make-pdf/test/diagram-prepass.test.ts new file mode 100644 index 000000000..04df04c27 --- /dev/null +++ b/make-pdf/test/diagram-prepass.test.ts @@ -0,0 +1,300 @@ +/** + * Unit tests for the diagram pre-pass: fence extraction, info-string parsing, + * slot substitution, diagnostic blocks, image inlining policy, and the + * byte-level image dimension prober. No browse daemon required — the tab + * factory returns null so downscale paths are exercised as no-ops. + */ +import { describe, expect, test } from "bun:test"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import zlib from "node:zlib"; + +import { + StrictModeError, + buildDiagnosticBlock, + buildDiagramFigure, + contentWidthInches, + dimToInches, + extractDiagramFences, + inlineLocalImages, + parseInfoString, + substituteSlots, +} from "../src/diagram-prepass"; +import { imageDims } from "../src/image-size"; + +// ─── fence extraction ───────────────────────────────────────────────── + +describe("extractDiagramFences", () => { + test("extracts a mermaid fence and replaces it with a token paragraph", () => { + const md = "# T\n\n```mermaid\ngraph LR\n A --> B\n```\n\ntail"; + const { markdown, fences } = extractDiagramFences(md); + expect(fences).toHaveLength(1); + expect(fences[0].lang).toBe("mermaid"); + expect(fences[0].source).toBe("graph LR\n A --> B"); + expect(markdown).toContain(fences[0].token); + expect(markdown).not.toContain("```mermaid"); + }); + + test("extracts excalidraw fences", () => { + const md = '```excalidraw\n{"type":"excalidraw","elements":[]}\n```'; + const { fences } = extractDiagramFences(md); + expect(fences).toHaveLength(1); + expect(fences[0].lang).toBe("excalidraw"); + }); + + test("render=false keeps the fence as code and strips the flag", () => { + const md = "```mermaid render=false\ngraph LR\n X --> Y\n```"; + const { markdown, fences } = extractDiagramFences(md); + expect(fences).toHaveLength(0); + expect(markdown).toContain("```mermaid\ngraph LR"); + expect(markdown).not.toContain("render=false"); + }); + + test("title is captured from the info string", () => { + const md = '```mermaid title="Auth flow"\ngraph LR\n A --> B\n```'; + const { fences } = extractDiagramFences(md); + expect(fences[0].title).toBe("Auth flow"); + }); + + test("non-diagram fences pass through untouched", () => { + const md = "```js\nconst a = 1;\n```"; + const { markdown, fences } = extractDiagramFences(md); + expect(fences).toHaveLength(0); + expect(markdown).toBe(md); + }); + + test("a mermaid example inside a plain fence is never extracted", () => { + const md = "````\n```mermaid\ngraph LR\n```\n````"; + const { markdown, fences } = extractDiagramFences(md); + expect(fences).toHaveLength(0); + expect(markdown).toBe(md); + }); + + test("tilde fences work", () => { + const md = "~~~mermaid\ngraph TD\n A --> B\n~~~"; + const { fences } = extractDiagramFences(md); + expect(fences).toHaveLength(1); + }); + + test("unclosed fence at EOF replays verbatim", () => { + const md = "```mermaid\ngraph LR\n A --> B"; + const { markdown, fences } = extractDiagramFences(md); + expect(fences).toHaveLength(0); + expect(markdown).toBe(md); + }); + + test("multiple fences get distinct ordinals and tokens", () => { + const md = "```mermaid\nA\n```\n\nmiddle\n\n```mermaid\nB\n```"; + const { fences } = extractDiagramFences(md); + expect(fences).toHaveLength(2); + expect(fences[0].ordinal).toBe(1); + expect(fences[1].ordinal).toBe(2); + expect(fences[0].token).not.toBe(fences[1].token); + }); +}); + +describe("parseInfoString", () => { + test("plain language", () => { + expect(parseInfoString("mermaid")).toEqual({ lang: "mermaid", render: true, title: undefined }); + }); + test("render=false", () => { + expect(parseInfoString("mermaid render=false").render).toBe(false); + }); + test("single-quoted title", () => { + expect(parseInfoString("mermaid title='Hi there'").title).toBe("Hi there"); + }); +}); + +// ─── slots ──────────────────────────────────────────────────────────── + +describe("substituteSlots", () => { + test("replaces the
-wrapped token with slot HTML", () => {
+ const slots = new Map([["gstack-diagram-slot-ab-1", "
gstack-diagram-slot-ab-1
\ntail
"; + const out = substituteSlots(html, slots); + expect(out).toContain("