/** * Publisher Pipeline Tests (Section 1 of TESTS.md) * * Tests Y.Doc extraction, HTML generation, post-processing, * and idempotency of the publish pipeline. */ import { describe, it, expect } from "vitest"; import { renderArticleHTML, type PublishMeta } from "../src/publisher/html-renderer.js"; import { createTestYDoc, FULL_DOC_OPTIONS, MINIMAL_DOC_JSON } from "./helpers.js"; function buildMeta(options: typeof FULL_DOC_OPTIONS): PublishMeta { const authors = options.authors ?? []; const affiliations = options.affiliations ?? []; return { title: options.title || "Untitled", subtitle: options.subtitle || undefined, description: options.description || "", authors: authors.map((a) => ({ name: a.name, url: a.url, affiliationIndices: a.affiliations || [], affiliationNames: (a.affiliations || []) .map((idx) => affiliations[idx - 1]?.name) .filter(Boolean) as string[], })), affiliations: affiliations.map((a) => ({ name: a.name, url: a.url })), date: options.published || new Date().toISOString(), doi: options.doi || undefined, licence: options.licence || undefined, }; } const EMPTY_CSS = { variables: ":root { --text-color: #000; }", reset: "", base: "", layout: "", print: "", editorTokens: "", article: "", components: "", publisher: "", }; // ── 1.1 Y.Doc Extraction ────────────────────────────────────────── describe("1.1 Y.Doc extraction", () => { it("1.1.1 extracts frontmatter from Y.Doc", async () => { const ydoc = createTestYDoc(FULL_DOC_OPTIONS); const metaMap = ydoc.getMap("frontmatter"); expect(metaMap.get("title")).toBe("Test Article Title"); expect(metaMap.get("subtitle")).toBe("A subtitle for testing"); expect(metaMap.get("description")).toBe("This is a test article description."); expect(metaMap.get("doi")).toBe("10.1234/test.2025"); const authors = ydoc.getArray("frontmatter.authors").toArray(); expect(authors).toHaveLength(2); expect((authors[0] as any).name).toBe("Alice Smith"); const affiliations = ydoc.getArray("frontmatter.affiliations").toArray(); expect(affiliations).toHaveLength(2); expect((affiliations[0] as any).name).toBe("University of Testing"); }); it("1.1.2 empty doc does not throw", async () => { const ydoc = createTestYDoc({ title: "Empty" }); const fragment = ydoc.getXmlFragment("default"); expect(fragment.length).toBe(0); expect(ydoc.getMap("frontmatter").get("title")).toBe("Empty"); }); it("1.1.3 extracts citations from Y.Map", async () => { const ydoc = createTestYDoc(FULL_DOC_OPTIONS); const citationsMap = ydoc.getMap("citations"); const entries: Record = {}; citationsMap.forEach((value, key) => { entries[key] = value; }); expect(entries).toHaveProperty("smith2024"); expect((entries.smith2024 as any).title).toBe("On Testing"); }); }); // ── 1.2 HTML Generation ─────────────────────────────────────────── describe("1.2 HTML generation", () => { it("1.2.1 produces valid self-contained HTML", async () => { const meta = buildMeta(FULL_DOC_OPTIONS); const html = await renderArticleHTML(MINIMAL_DOC_JSON, meta, EMPTY_CSS); expect(html).toContain(""); expect(html).toContain(""); expect(html).toContain("