diff --git a/src/ansi.test.ts b/src/ansi.test.ts
new file mode 100644
index 0000000..55eb5ba
--- /dev/null
+++ b/src/ansi.test.ts
@@ -0,0 +1,426 @@
+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('');
+});