/** * Renderer unit tests — pure-function assertions for render.ts, smartypants.ts, * and print-css.ts. No Playwright, no PDF generation. */ import { describe, expect, test } from "bun:test"; import { render, sanitizeUntrustedHtml } from "../src/render"; import { smartypants } from "../src/smartypants"; import { printCss } from "../src/print-css"; // ─── smartypants ────────────────────────────────────────────── describe("smartypants", () => { test("converts straight double quotes to curly", () => { const out = smartypants(`
She said "hello" to him.
`); expect(out).toContain("\u201chello\u201d"); }); test("converts em dash (--)", () => { const out = smartypants(`This is it -- the answer.
`); expect(out).toContain("\u2014"); }); test("converts ellipsis (...)", () => { const out = smartypants(`Wait...
`); expect(out).toContain("\u2026"); }); test("converts apostrophes in contractions", () => { const out = smartypants(`don't you know?
`); expect(out).toContain("don\u2019t"); }); test("does NOT touch content inside blocks", () => {
const input = `const x = "hello"; // it's fine
`;
const out = smartypants(input);
expect(out).toBe(input); // unchanged
});
test("does NOT touch content inside blocks", () => {
const input = `"quoted" -- don't
`;
const out = smartypants(input);
expect(out).toBe(input);
});
test("does NOT touch inline code", () => {
const out = smartypants(`Use it's like this: "hello".
`);
expect(out).toContain("it's");
expect(out).toContain("\u201chello\u201d");
});
test("does NOT touch URLs", () => {
const out = smartypants(`Visit https://example.com/it's-page for "details".
`);
expect(out).toContain("https://example.com/it's-page");
expect(out).toContain("\u201cdetails\u201d");
});
test("does NOT touch HTML attribute values", () => {
const out = smartypants(`link`);
expect(out).toContain(`href="it's-a-test.html"`);
});
test("does NOT convert -- in CLI flags", () => {
// Prose like "try --verbose mode" should not turn -- into em dash
const out = smartypants(`Try --verbose mode.
`);
// Since "--" is followed by a word char but not preceded by word/space,
// it should remain intact. We're lenient here — acceptable either way.
expect(out).toMatch(/--verbose|—verbose/);
});
});
// ─── sanitizer ──────────────────────────────────────────────
describe("sanitizeUntrustedHtml", () => {
test("strips world
`;
const out = sanitizeUntrustedHtml(input);
expect(out).not.toContain(" `;
const out = sanitizeUntrustedHtml(input);
expect(out).not.toContain("">hi`;
expect(sanitizeUntrustedHtml(input)).not.toContain("srcdoc");
});
});
// ─── end-to-end render ──────────────────────────────────────────────
describe("render (end-to-end)", () => {
test("produces a full HTML document with title, body, and CSS", () => {
const result = render({
markdown: `# Hello\n\nA paragraph with "quotes" and -- dashes.\n`,
});
expect(result.html).toContain("");
expect(result.html).toContain("Hello ");
expect(result.html).toContain("...
expect(result.html).toMatch(/