import { toHtml } from "hast-util-to-html";
import { getSharedHighlighter } from "@pierre/diffs";
import { describe, expect, it } from "vite-plus/test";
import { createIncrementalHighlightedDocument } from "./incrementalHighlighting";
const samples = {
typescript: "/* multi\nline comment */\nconst x = `template\n${1 + 2}`;\nconst re = /abc/;\n",
python: '#!/usr/bin/python\nx = """multi\nline"""\nprint(x)\n',
bash: "#!/bin/bash\ncat <\nconst x = 1;\n\n\n",
markdown: "# heading\n\n```ts\nconst a = 1;\n```\n\ntext\n",
rust: 'fn main() {\n let x = r#"multi\nline"#;\n}\n',
tsx: 'const element = \n{value}\n
;\n',
json: '{\n "value": [1,\n 2, 3]\n}\n',
yaml: "key: |\n multiline\n value\nnext: true\n",
css: '/* comment\n continued */\np::before {\n content: "text";\n}\n',
sql: "SELECT 'multi\nline'\nFROM table_name;\n",
} as const;
const highlighterPromise = getSharedHighlighter({
langs: Object.keys(samples) as Array,
themes: ["pierre-dark", "pierre-light"],
preferredHighlighter: "shiki-wasm",
});
describe("incremental code highlighting", () => {
it.each(Object.entries(samples))(
"matches full HTML at every streaming prefix in %s",
async (language, code) => {
const highlighter = await highlighterPromise;
for (const theme of ["pierre-dark", "pierre-light"] as const) {
const highlight = createIncrementalHighlightedDocument(highlighter, language, theme);
for (let end = 0; end <= code.length; end++) {
const text = code.slice(0, end);
expect(toHtml(highlight(text)), `${theme}, prefix ${end}`).toBe(
highlighter.codeToHtml(text, { lang: language, theme }),
);
}
}
},
);
it("resets after edits and truncation, including edits to a completed line", async () => {
const highlighter = await highlighterPromise;
const highlight = createIncrementalHighlightedDocument(
highlighter,
"typescript",
"pierre-dark",
);
const inputs = [
"/* open\ncomment\n",
"/* open\ncomment\n*/\nconst x = 1;",
"const edited = 2;\nconst x = 1;",
"const edited = 2;\nconst x = 10;",
"const edited = 2;\n",
"",
"\n\n\nconst fresh = true;\n",
];
for (const text of inputs) {
expect(toHtml(highlight(text))).toBe(
highlighter.codeToHtml(text, { lang: "typescript", theme: "pierre-dark" }),
);
}
});
it.each(["text", "plaintext", "plain", "txt", "ansi"])(
"preserves %s without requesting grammar state",
async (language) => {
const highlighter = await highlighterPromise;
const highlight = createIncrementalHighlightedDocument(highlighter, language, "pierre-dark");
for (const text of ["plain\ntext", "\u001b[31mred\ncontinued", "\n"]) {
expect(toHtml(highlight(text))).toBe(
highlighter.codeToHtml(text, { lang: language, theme: "pierre-dark" }),
);
}
},
);
it("preserves partial CRLF and CR line endings", async () => {
const highlighter = await highlighterPromise;
const highlight = createIncrementalHighlightedDocument(
highlighter,
"typescript",
"pierre-dark",
);
const code = "/* multi\r\nline */\r\nconst x = 1;\r\n";
for (let end = 0; end <= code.length; end++) {
const text = code.slice(0, end);
expect(toHtml(highlight(text))).toBe(
highlighter.codeToHtml(text, { lang: "typescript", theme: "pierre-dark" }),
);
}
});
});