import { expect, test } from "bun:test"; import { ansiToHtml, trimLineEndPreserveAnsi } from "./ansi"; // 1. XSS/HTML escaping tests test("escapes < to <", () => { const result = ansiToHtml("
"); expect(result).toBe("<div>"); }); test("escapes > to >", () => { const result = ansiToHtml("a > b"); expect(result).toBe("a > b"); }); test("escapes & to &", () => { const result = ansiToHtml("foo & bar"); expect(result).toBe("foo & bar"); }); test("escapes "); expect(result).toBe("<script>alert('xss')</script>"); }); test("escapes HTML in styled text", () => { const result = ansiToHtml("\x1b[31m\x1b[0m"); expect(result).toContain("<script>"); expect(result).toContain("</script>"); expect(result).not.toContain("\x1b[0m"); expect(result).toContain("<script>"); expect(result).toContain("&alert"); expect(result).toContain("color:#f85149"); }); test("preserves internal spaces", () => { const result = ansiToHtml("hello world"); expect(result).toBe("hello world"); }); test("handles only ANSI codes with no text", () => { const result = ansiToHtml("\x1b[31m\x1b[0m"); // Opens and closes span even with no text expect(result).toBe(''); }); // OSC sequences (Operating System Command) test("strips OSC terminal title with BEL", () => { // ESC ] 0 ; title BEL const result = ansiToHtml("\x1b]0;My Terminal Title\x07text after"); expect(result).toBe("text after"); }); test("strips OSC terminal title with ST", () => { // ESC ] 0 ; title ESC \ const result = ansiToHtml("\x1b]0;My Terminal Title\x1b\\text after"); expect(result).toBe("text after"); }); test("strips OSC with emoji in title", () => { const result = ansiToHtml("\x1b]0;★ Claude Code\x07hello"); expect(result).toBe("hello"); }); test("strips multiple OSC sequences", () => { const result = ansiToHtml("\x1b]0;title1\x07text\x1b]0;title2\x07more"); expect(result).toBe("textmore"); }); // Private mode sequences (DEC) test("strips bracketed paste mode enable", () => { // ESC [ ? 2004 h const result = ansiToHtml("\x1b[?2004htext"); expect(result).toBe("text"); }); test("strips bracketed paste mode disable", () => { // ESC [ ? 2004 l const result = ansiToHtml("\x1b[?2004ltext"); expect(result).toBe("text"); }); test("strips mixed private mode and SGR", () => { const result = ansiToHtml("\x1b[?2004h\x1b[31mred\x1b[0m\x1b[?2004l"); expect(result).toBe('red'); }); test("strips cursor visibility sequences", () => { // ESC [ ? 25 h (show cursor) and ESC [ ? 25 l (hide cursor) const result = ansiToHtml("\x1b[?25lhidden cursor\x1b[?25h"); expect(result).toBe("hidden cursor"); }); test("handles Claude Code typical output", () => { // Simulated Claude Code startup with title, bracketed paste, and colors const input = "\x1b]0;★ Claude Code\x07\x1b[?2004h\x1b[1;33mClaude Code\x1b[0m v2.1.15\x1b[?2004l"; const result = ansiToHtml(input); expect(result).toContain("Claude Code"); expect(result).toContain("v2.1.15"); expect(result).not.toContain("2004"); expect(result).not.toContain("]0;"); });