// @vitest-environment jsdom import { describe, expect, it } from "vitest"; import { highlightCode, resolveHighlightLanguage, MAX_HIGHLIGHT_LENGTH, } from "./code-highlight"; /** Reconstruct the source from a highlighted fragment (invariant helper). */ const textOf = (frag: DocumentFragment): string => { const holder = document.createElement("div"); holder.appendChild(frag.cloneNode(true)); return holder.textContent ?? ""; }; /** Collect token classes present in a highlighted fragment. */ const tokenClasses = (frag: DocumentFragment): string[] => { const holder = document.createElement("div"); holder.appendChild(frag.cloneNode(true)); return Array.from(holder.querySelectorAll("span[class^='persona-code-token-']")) .map((el) => el.className); }; const classFor = (source: string, lang: string, snippet: string): string | null => { const holder = document.createElement("div"); holder.appendChild(highlightCode(source, lang)); const spans = Array.from( holder.querySelectorAll("span[class^='persona-code-token-']") ); const hit = spans.find((el) => el.textContent === snippet); return hit ? hit.className : null; }; describe("resolveHighlightLanguage", () => { it("prefers explicit language and maps aliases", () => { expect(resolveHighlightLanguage("html")).toBe("html"); expect(resolveHighlightLanguage("htm")).toBe("html"); expect(resolveHighlightLanguage("mjs")).toBe("js"); expect(resolveHighlightLanguage("tsx")).toBe("js"); expect(resolveHighlightLanguage("typescript")).toBe("js"); expect(resolveHighlightLanguage("jsonc")).toBe("json"); }); it("falls back to the file extension", () => { expect(resolveHighlightLanguage(undefined, "src/app.css")).toBe("css"); expect(resolveHighlightLanguage(undefined, "a/b/main.ts")).toBe("js"); expect(resolveHighlightLanguage(undefined, "index.html")).toBe("html"); }); it("returns null for unknown languages and extensionless paths", () => { expect(resolveHighlightLanguage("rust")).toBeNull(); expect(resolveHighlightLanguage(undefined, "Makefile")).toBeNull(); expect(resolveHighlightLanguage(undefined)).toBeNull(); }); }); describe("highlightCode token classification", () => { it("classifies js keywords, strings, numbers, and comments", () => { expect(classFor("const x = 1;", "js", "const")).toBe( "persona-code-token-keyword" ); expect(classFor('const s = "hi";', "js", '"hi"')).toBe( "persona-code-token-string" ); expect(classFor("const n = 42;", "js", "42")).toBe( "persona-code-token-number" ); expect(classFor("// note\nx", "js", "// note")).toBe( "persona-code-token-comment" ); expect(classFor("const b = true;", "js", "true")).toBe( "persona-code-token-keyword" ); }); it("classifies json keys as property and values as string", () => { const src = '{"name": "cat", "n": 3, "ok": true}'; expect(classFor(src, "json", '"name"')).toBe("persona-code-token-property"); expect(classFor(src, "json", '"cat"')).toBe("persona-code-token-string"); expect(classFor(src, "json", "3")).toBe("persona-code-token-number"); expect(classFor(src, "json", "true")).toBe("persona-code-token-keyword"); }); it("classifies css properties, values, at-rules, and comments", () => { const src = "@media all { .a { color: #fff; margin: 4px; } } /* c */"; expect(classFor(src, "css", "color")).toBe("persona-code-token-property"); expect(classFor(src, "css", "#fff")).toBe("persona-code-token-number"); expect(classFor(src, "css", "@media")).toBe("persona-code-token-keyword"); expect(classFor(src, "css", "/* c */")).toBe("persona-code-token-comment"); }); it("classifies html tags, attributes, attribute values, and comments", () => { const src = 'hi'; expect(classFor(src, "html", "href")).toBe("persona-code-token-attr"); expect(classFor(src, "html", '"x"')).toBe("persona-code-token-string"); expect(classFor(src, "html", "")).toBe( "persona-code-token-comment" ); // Tag bracket + name. expect(classFor(src, "html", " bodies with the js tokenizer", () => { const src = ""; expect(classFor(src, "html", "const")).toBe("persona-code-token-keyword"); }); }); describe("highlightCode line + newline invariants", () => { it("splits a multi-line block comment across lines without newlines in spans", () => { const src = "/* line one\nline two */\nvar x;"; const frag = highlightCode(src, "js"); const holder = document.createElement("div"); holder.appendChild(frag.cloneNode(true)); const commentSpans = holder.querySelectorAll(".persona-code-token-comment"); // The block comment spans two lines → two comment spans, neither with "\n". expect(commentSpans.length).toBe(2); commentSpans.forEach((el) => expect(el.textContent).not.toContain("\n")); // No token span anywhere contains a newline. holder .querySelectorAll("span[class^='persona-code-token-']") .forEach((el) => expect(el.textContent).not.toContain("\n")); }); it("reconstructs the source verbatim (textContent === source)", () => { for (const [src, lang] of [ ["const x = 1;\nconst y = 2;\n", "js"], ["line1\nline2\nline3", "js"], ["a\n\nb\n", "js"], ['{"k": "v"}\n', "json"], ["
\n hi\n
\n", "html"], ["", "js"], ] as const) { expect(textOf(highlightCode(src, lang))).toBe(src); } }); it("does not emit a phantom numbered line for a trailing newline", () => { const holder = document.createElement("div"); holder.appendChild(highlightCode("a\nb\n", "js")); // "a\nb\n" → 2 numbered lines, trailing newline preserved in textContent. expect(holder.querySelectorAll(".persona-code-line").length).toBe(2); expect(holder.textContent).toBe("a\nb\n"); }); it("keeps one line for wholly empty source", () => { const holder = document.createElement("div"); holder.appendChild(highlightCode("", "js")); expect(holder.querySelectorAll(".persona-code-line").length).toBe(1); expect(holder.textContent).toBe(""); }); }); describe("highlightCode fallbacks and safety", () => { it("returns plain line-numbered text for an unknown language", () => { const frag = highlightCode("const x = 1;", "rust"); expect(tokenClasses(frag)).toEqual([]); }); it("skips tokenization for oversized sources", () => { const big = "const x = 1;\n".repeat( Math.ceil((MAX_HIGHLIGHT_LENGTH + 100) / 13) ); expect(big.length).toBeGreaterThan(MAX_HIGHLIGHT_LENGTH); const frag = highlightCode(big, "js"); expect(tokenClasses(frag)).toEqual([]); // Still line-numbered (line spans present) and verbatim. const holder = document.createElement("div"); holder.appendChild(frag); expect(holder.querySelector(".persona-code-line")).toBeTruthy(); }); it("never emits any element other than spans for hostile input", () => { // XSS-shaped content embedded as a JS string literal. const src = 'const html = "";'; const holder = document.createElement("div"); holder.appendChild(highlightCode(src, "js")); // Only elements exist; no (or anything else) was created. const all = Array.from(holder.querySelectorAll("*")); expect(all.every((el) => el.tagName === "SPAN")).toBe(true); expect(holder.querySelector("img")).toBeNull(); // The dangerous text survives as inert text content. expect(holder.textContent).toContain(""); }); });