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;");
});