import { describe, test, expect, beforeAll } from "bun:test"; import { detectLang, hasBinaryContent, highlightStartup } from "../src/services/highlight.ts"; import { parseDiff, highlightFile } from "../src/services/diffHighlight.ts"; beforeAll(async () => { await highlightStartup(); }); describe("detectLang", () => { // Extensions test("detects TypeScript", () => expect(detectLang("foo.ts")).toBe("typescript")); test("detects TSX", () => expect(detectLang("foo.tsx")).toBe("tsx")); test("detects JavaScript", () => expect(detectLang("foo.js")).toBe("javascript")); test("detects Python", () => expect(detectLang("foo.py")).toBe("python")); test("detects Rust", () => expect(detectLang("foo.rs")).toBe("rust")); test("detects Go", () => expect(detectLang("foo.go")).toBe("go")); test("detects C", () => expect(detectLang("foo.c")).toBe("c")); test("detects C++", () => expect(detectLang("foo.cpp")).toBe("cpp")); test("detects C header", () => expect(detectLang("foo.h")).toBe("c")); test("detects YAML", () => expect(detectLang("foo.yml")).toBe("yaml")); test("detects TOML", () => expect(detectLang("foo.toml")).toBe("toml")); test("detects JSON", () => expect(detectLang("foo.json")).toBe("json")); test("detects Markdown", () => expect(detectLang("foo.md")).toBe("markdown")); test("detects Shell", () => expect(detectLang("foo.sh")).toBe("shellscript")); test("detects diff/patch", () => expect(detectLang("foo.patch")).toBe("diff")); // Specific filenames test("detects Dockerfile by filename", () => expect(detectLang("Dockerfile")).toBe("docker")); test("detects Makefile by filename", () => expect(detectLang("Makefile")).toBe("make")); // Path stripping test("strips path before detecting", () => expect(detectLang("src/foo/bar.ts")).toBe("typescript")); test("strips path for Dockerfile", () => expect(detectLang("infra/Dockerfile")).toBe("docker")); // Fallback test("falls back to text for unknown extension", () => expect(detectLang("foo.xyz")).toBe("text")); test("falls back to text for no extension", () => expect(detectLang("LICENSE")).toBe("text")); }); describe("hasBinaryContent", () => { test("detects NUL byte as binary", () => { expect(hasBinaryContent(Buffer.from([0x68, 0x65, 0x00, 0x6c, 0x6f]))).toBe(true); }); test("detects NUL-only buffer as binary", () => { expect(hasBinaryContent(Buffer.alloc(10, 0))).toBe(true); }); test("plain text is not binary", () => { expect(hasBinaryContent(Buffer.from("hello world\n"))).toBe(false); }); test("CR LF text is not binary", () => { expect(hasBinaryContent(Buffer.from("line1\r\nline2\r\n"))).toBe(false); }); test("other control chars without NUL are not binary", () => { // \x01, \x07 (BEL), \x1b (ESC) — git treats these as text expect(hasBinaryContent(Buffer.from([0x01, 0x07, 0x1b, 0x41]))).toBe(false); }); test("only checks first 8000 bytes", () => { // NUL appears after the 8000-byte sample window — should not be detected const buf = Buffer.alloc(8001, 0x41); buf[8000] = 0x00; expect(hasBinaryContent(buf)).toBe(false); }); test("detects NUL within the 8000-byte sample", () => { const buf = Buffer.alloc(8001, 0x41); buf[7999] = 0x00; expect(hasBinaryContent(buf)).toBe(true); }); }); // Build a minimal unified diff string with the given added line(s). function makeDiff(...lines: string[]): string { return [ "diff --git a/test.txt b/test.txt", "index 0000000..1111111 100644", "--- a/test.txt", "+++ b/test.txt", `@@ -0,0 +${lines.length} @@`, ...lines.map((l) => `+${l}`), ].join("\n"); } describe("diff control character rendering", () => { test("CR renders as ^M with diff-ctrl span", async () => { const files = await parseDiff(makeDiff("hello\rworld")); const rendered = await highlightFile(files[0]!); const html = rendered.hunks[0]!.rows[0]!.html; expect(html).toContain('^M'); }); test("trailing CR renders as ^M on every line, not just the last", async () => { // Simulates a CRLF line-ending change where every line ends with \r. // Shiki normalises \r\n → \n and would silently drop the \r from all // but the last line without the pre-strip fix. const files = await parseDiff(makeDiff("alpha\r", "beta\r", "gamma\r")); const rendered = await highlightFile(files[0]!); const rows = rendered.hunks[0]!.rows; for (const row of rows) { expect(row.html).toContain('^M'); } }); test("BEL (\\x07) renders as ^G", async () => { const files = await parseDiff(makeDiff("ring\x07bell")); const rendered = await highlightFile(files[0]!); const html = rendered.hunks[0]!.rows[0]!.html; expect(html).toContain('^G'); }); test("ESC (\\x1b) renders as ^[", async () => { const files = await parseDiff(makeDiff("\x1b[31mred\x1b[0m")); const rendered = await highlightFile(files[0]!); const html = rendered.hunks[0]!.rows[0]!.html; expect(html).toContain('^['); }); test("TAB does not render as a control char span", async () => { const files = await parseDiff(makeDiff("\thello")); const rendered = await highlightFile(files[0]!); const html = rendered.hunks[0]!.rows[0]!.html; expect(html).not.toContain("diff-ctrl"); }); test("normal text has no control char spans", async () => { const files = await parseDiff(makeDiff("hello world")); const rendered = await highlightFile(files[0]!); const html = rendered.hunks[0]!.rows[0]!.html; expect(html).not.toContain("diff-ctrl"); }); });