carbon-tokenization / backend /tests /html-renderer-snapshot.test.ts
tfrere's picture
tfrere HF Staff
fix(publisher): render HF user card via dedicated transformer
f621e1d
Raw
History Blame Contribute Delete
10.1 kB
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("<!DOCTYPE html>");
expect(html).toContain("<title>Test Article</title>");
expect(html).toContain("Hello world");
});
it("escapes HTML in meta fields", async () => {
const meta: PublishMeta = { ...BASIC_META, title: 'Title with "quotes" & <tags>' };
const json = minimalDoc([{ type: "paragraph" }]);
const html = await renderArticleHTML(json, meta, EMPTY_CSS);
expect(html).toContain("&amp;");
expect(html).toContain("&lt;tags&gt;");
expect(html).not.toContain('<tags>');
});
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("<details");
expect(html).toContain("<summary>");
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("<iframe");
});
it("injects embed HTML from Y.Map into iframe srcdoc", async () => {
const json = minimalDoc([{
type: "htmlEmbed",
attrs: { src: "my-chart", title: "Chart", desc: "" },
}]);
const embeds = { "my-chart": '<div id="chart"><script>console.log("hello")<\/script></div>' };
const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS, undefined, undefined, embeds);
expect(html).toContain("html-embed-container");
expect(html).toContain('data-embed-src="my-chart"');
expect(html).toContain("srcdoc=");
expect(html).toContain("ColorPalettes");
expect(html).toContain("chart");
});
it("leaves srcdoc empty when embed key is missing", async () => {
const json = minimalDoc([{
type: "htmlEmbed",
attrs: { src: "missing-chart", title: "Chart", desc: "" },
}]);
const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS, undefined, undefined, {});
expect(html).toContain('data-embed-src="missing-chart"');
expect(html).toContain('srcdoc=""');
});
});
describe("postProcess - hfUser", () => {
it("transforms hfUser div into the HF profile card", async () => {
const json = minimalDoc([{
type: "hfUser",
attrs: { username: "tfrere", name: "Thibaud Frere", url: "https://huggingface.co/tfrere" },
}]);
const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS);
expect(html).toContain('class="hf-user"');
expect(html).toContain('class="hf-user__avatar"');
expect(html).toContain("https://huggingface.co/api/users/tfrere/avatar");
expect(html).toContain("Thibaud Frere");
expect(html).toContain("@tfrere");
expect(html).toContain('href="https://huggingface.co/tfrere"');
expect(html).not.toContain('data-component="hfUser"');
});
it("falls back to username when name and url are missing", async () => {
const json = minimalDoc([{
type: "hfUser",
attrs: { username: "alice", name: "", url: "" },
}]);
const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS);
expect(html).toContain('class="hf-user"');
expect(html).toContain("@alice");
expect(html).toContain('href="https://huggingface.co/alice"');
});
it("removes the placeholder div when username is missing", async () => {
const json = minimalDoc([{
type: "hfUser",
attrs: { username: "", name: "Anon", url: "" },
}]);
const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS);
expect(html).not.toContain('class="hf-user"');
expect(html).not.toContain('data-component="hfUser"');
});
});
describe("postProcess - bibliography", () => {
it("injects bibliography HTML into the placeholder", async () => {
const json = minimalDoc([
{ type: "paragraph", content: [{ type: "citation", attrs: { key: "test2024", label: "[1]" } }] },
{ type: "bibliography", attrs: { renderedHtml: "" } },
]);
const citationData: CitationData = {
entries: [{ id: "test2024" }],
orderedKeys: ["test2024"],
style: "apa",
};
const biblioHtml = '<div class="csl-entry">Test entry</div>';
const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS, citationData, biblioHtml);
expect(html).toContain("bibliography-content");
expect(html).toContain('id="ref-test2024"');
expect(html).toContain("Test entry");
});
});
describe("snapshot - full render", () => {
it("matches snapshot for a typical article", async () => {
const json = minimalDoc([
{ type: "heading", attrs: { level: 2 }, content: [{ type: "text", text: "Introduction" }] },
{ type: "paragraph", content: [
{ type: "text", text: "This is a test article with a " },
{ type: "citation", attrs: { key: "ref1", label: "Ref (2024)" } },
{ type: "text", text: " citation." },
] },
{ type: "paragraph", content: [
{ type: "text", text: "A footnote here" },
{ type: "footnote", attrs: { content: "Important detail" } },
] },
{ type: "bibliography", attrs: { renderedHtml: "" } },
]);
const citationData: CitationData = {
entries: [{ id: "ref1", title: "Reference One" }],
orderedKeys: ["ref1"],
style: "apa",
};
const biblioHtml = '<div class="csl-entry">Reference One. 2024.</div>';
const html = await renderArticleHTML(json, BASIC_META, EMPTY_CSS, citationData, biblioHtml);
expect(html).toMatchSnapshot();
});
});