import { describe, it } from "node:test"; import assert from "node:assert/strict"; import type { IR, IRNode, IRChild, StyleMap, BBox } from "../src/normalize/ir.js"; import type { FontGraph } from "../src/infer/fonts.js"; import { generatePreviewHtml } from "../src/generate/preview.js"; import { generateCss, collectNodeRules, assembleCss, computeBands } from "../src/generate/css.js"; const VPS = [375, 1280]; const CANONICAL = 1280; function computed(over: StyleMap = {}): StyleMap { return { display: "block", position: "static", visibility: "visible", listStyleType: "disc", listStylePosition: "outside", ...over }; } function node(id: string, tag: string, cs: StyleMap, children: IRChild[] = [], attrs: Record = {}): IRNode { const computedByVp: Record = {}; const bboxByVp: Record = {}; const visibleByVp: Record = {}; for (const vp of VPS) { computedByVp[vp] = { ...cs }; bboxByVp[vp] = { x: 0, y: 0, width: vp, height: 100 }; visibleByVp[vp] = true; } return { id, tag, attrs, visibleByVp, bboxByVp, computedByVp, children }; } function irWith(root: IRNode): IR { return { doc: { sourceUrl: "https://example.test/preview", title: "Preview Fixture", lang: "en", charset: "UTF-8", metaViewport: "width=device-width, initial-scale=1", viewports: VPS, sampleViewports: VPS, canonicalViewport: CANONICAL, perViewport: Object.fromEntries(VPS.map((vp) => [vp, { scrollHeight: 800, scrollWidth: vp, htmlBg: "rgb(255, 255, 255)", bodyBg: "rgb(255, 255, 255)", bodyColor: "rgb(0, 0, 0)", bodyFont: "Arial" }])), nodeCount: 4, keyframes: [], }, root, }; } // A @font-face block whose src points at a root-absolute cloned asset path — exactly the // shape buildFontGraph emits. The preview must relativize the /assets/ prefix. const FONT_GRAPH: FontGraph = { entries: [], css: `@font-face {\n font-family: "Demo";\n src: url("/assets/cloned/fonts/abc123.woff2") format("woff2");\n}`, }; /** Build a small page IR: body → heading (with own styles) + (asset) + text. */ function fixtureIr(): IR { const heading = node("n1", "h1", computed({ color: "rgb(10, 20, 30)", fontSize: "40px" }), [{ text: "Hello preview" }]); const img = node("n2", "img", computed(), [], { src: "https://example.test/logo.png", alt: "logo" }); const root = node("n0", "body", computed(), [heading, img]); return irWith(root); } /** Asset map keyed as buildAssetMap keys it (source URL + pathname) → root-absolute local path. */ function assetMap(): Map { return new Map([ ["https://example.test/logo.png", "/assets/cloned/images/logo.png"], ["/logo.png", "/assets/cloned/images/logo.png"], ]); } function emit(ir: IR, am: Map): string { return generatePreviewHtml({ ir, assetMap: am, fontGraph: FONT_GRAPH, tokensCss: ":root { --x: 1; }", sourceUrl: ir.doc.sourceUrl }); } describe("preview.html static artifact", () => { it("emits a self-contained HTML document with an inline