import { describe, it, expect } from "vitest"; import { renderArticleHTML, type PublishMeta, type CitationData } from "../src/publisher/html-renderer.js"; import type { PublishCSS } from "../src/publisher/index.js"; const EMPTY_CSS: PublishCSS = { variables: "", reset: "", base: "", layout: "", print: "", editorTokens: "", article: "", components: "", publisher: "", }; const BASIC_META: PublishMeta = { title: "Test Article", description: "A test article", authors: [{ name: "Alice", affiliationIndices: [1], affiliationNames: ["MIT"] }], affiliations: [{ name: "MIT" }], date: "2025-01-01", }; function minimalDoc(content: any[]) { return { type: "doc", content }; } describe("renderArticleHTML", () => { it("produces a complete HTML document", async () => { const json = minimalDoc([{ type: "paragraph", content: [{ type: "text", text: "Hello world" }] }]); const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS); expect(html).toContain(""); expect(html).toContain("Test Article"); expect(html).toContain("Hello world"); }); it("escapes HTML in meta fields", async () => { const meta: PublishMeta = { ...BASIC_META, title: 'Title with "quotes" & ' }; const json = minimalDoc([{ type: "paragraph" }]); const html = await renderArticleHTML(json, meta, EMPTY_CSS); expect(html).toContain("&"); expect(html).toContain("<tags>"); expect(html).not.toContain(''); }); it("renders PDF download link when pdfUrl is set", async () => { const meta: PublishMeta = { ...BASIC_META, pdfUrl: "/published/test/article.pdf" }; const json = minimalDoc([{ type: "paragraph" }]); const html = await renderArticleHTML(json, meta, EMPTY_CSS); expect(html).toContain("Download PDF"); expect(html).toContain("/published/test/article.pdf"); }); }); describe("postProcess - accordion", () => { it("transforms accordion div into details/summary", async () => { const json = minimalDoc([{ type: "accordion", attrs: { title: "My Section", open: false }, content: [{ type: "paragraph", content: [{ type: "text", text: "Inner content" }] }], }]); const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS); expect(html).toContain(""); expect(html).toContain("Inner content"); }); }); describe("postProcess - citations", () => { it("replaces citation spans with anchor links", async () => { const json = minimalDoc([{ type: "paragraph", content: [ { type: "text", text: "See " }, { type: "citation", attrs: { key: "smith2024", label: "Smith (2024)" } }, ], }]); const citationData: CitationData = { entries: [{ id: "smith2024", title: "Test Paper" }], orderedKeys: ["smith2024"], style: "apa", }; const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS, citationData); expect(html).toContain('href="#ref-smith2024"'); expect(html).toContain("citation-inline"); }); it("uses numeric labels for IEEE style", async () => { const json = minimalDoc([{ type: "paragraph", content: [ { type: "citation", attrs: { key: "doe2023", label: "Doe (2023)" } }, ], }]); const citationData: CitationData = { entries: [{ id: "doe2023" }], orderedKeys: ["doe2023"], style: "ieee", }; const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS, citationData); expect(html).toContain("[1]"); }); }); describe("postProcess - footnotes", () => { it("collects footnotes and appends a footnotes section", async () => { const json = minimalDoc([{ type: "paragraph", content: [ { type: "text", text: "Text" }, { type: "footnote", attrs: { content: "This is a footnote" } }, ], }]); const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS); expect(html).toContain('class="footnote-ref"'); expect(html).toContain('id="fn-1"'); expect(html).toContain("This is a footnote"); expect(html).toContain('class="footnotes"'); }); it("numbers multiple footnotes sequentially", async () => { const json = minimalDoc([{ type: "paragraph", content: [ { type: "footnote", attrs: { content: "First note" } }, { type: "text", text: " and " }, { type: "footnote", attrs: { content: "Second note" } }, ], }]); const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS); expect(html).toContain('id="fn-1"'); expect(html).toContain('id="fn-2"'); expect(html).toContain("First note"); expect(html).toContain("Second note"); }); }); describe("postProcess - mermaid", () => { it("transforms mermaid div into pre.mermaid", async () => { const json = minimalDoc([{ type: "mermaid", attrs: { code: "graph TD\n A --> B" }, }]); const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS); expect(html).toContain('class="mermaid"'); expect(html).toContain("graph TD"); }); }); describe("postProcess - htmlEmbed", () => { it("transforms htmlEmbed div into iframe", async () => { const json = minimalDoc([{ type: "htmlEmbed", attrs: { src: "d3-chart.html", title: "Chart", desc: "" }, }]); const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS); expect(html).toContain("html-embed-container"); expect(html).toContain('data-embed-src="d3-chart.html"'); expect(html).toContain(" { const json = minimalDoc([{ type: "htmlEmbed", attrs: { src: "my-chart", title: "Chart", desc: "" }, }]); const embeds = { "my-chart": '