import { describe, expect, test } from "bun:test"; import { createTerminal, disposeTerminal, serializeAsHTML } from "./terminal"; // Helper to write to terminal and wait for completion function writeSync( term: ReturnType, data: string, ): Promise { return new Promise((resolve) => { term.terminal.write(data, resolve); }); } describe("terminal", () => { test("creates terminal with correct dimensions", () => { const term = createTerminal(80, 24); expect(term.terminal.cols).toBe(80); expect(term.terminal.rows).toBe(24); disposeTerminal(term); }); test("writes data to terminal buffer", async () => { const term = createTerminal(80, 24); await writeSync(term, "Hello, world!"); const html = serializeAsHTML(term); expect(html).toContain("Hello, world!"); disposeTerminal(term); }); test("handles ANSI cursor movement", async () => { const term = createTerminal(80, 24); await writeSync(term, "AAA\x1b[3D"); // Write AAA, move cursor back 3 await writeSync(term, "BBB"); // Overwrite with BBB const html = serializeAsHTML(term); expect(html).toContain("BBB"); expect(html).not.toContain("AAA"); disposeTerminal(term); }); test("handles carriage return correctly", async () => { const term = createTerminal(80, 24); await writeSync(term, "Old text\rNew"); // CR should move to col 0 const html = serializeAsHTML(term); expect(html).toContain("New"); expect(html).not.toContain("Old"); disposeTerminal(term); }); test("handles incomplete ANSI sequences across writes", async () => { const term = createTerminal(80, 24); await writeSync(term, "Hello\x1b["); // Incomplete CSI await writeSync(term, "31mRed\x1b[0m"); // Complete it const html = serializeAsHTML(term); expect(html).toContain("Red"); // Check for red styling (color may vary in format) expect(html).toMatch(/color|red|#[a-f0-9]{6}/i); disposeTerminal(term); }); test("handles screen clearing", async () => { const term = createTerminal(80, 24); await writeSync(term, "First line\n"); await writeSync(term, "Second line"); await writeSync(term, "\x1b[2J"); // Clear screen await writeSync(term, "\x1b[H"); // Cursor home await writeSync(term, "After clear"); const html = serializeAsHTML(term); expect(html).toContain("After clear"); // "First line" should be in scrollback but not current screen disposeTerminal(term); }); test("handles cursor position command", async () => { const term = createTerminal(80, 24); await writeSync(term, "\x1b[5;10H"); // Move cursor to row 5, col 10 await writeSync(term, "Positioned"); const html = serializeAsHTML(term); expect(html).toContain("Positioned"); // CSI H should not leak through as literal characters expect(html).not.toMatch(/^[HJ]$/m); disposeTerminal(term); }); test("handles resize", () => { const term = createTerminal(80, 24); term.terminal.resize(120, 40); expect(term.terminal.cols).toBe(120); expect(term.terminal.rows).toBe(40); disposeTerminal(term); }); test("serialize returns valid HTML structure", async () => { const term = createTerminal(80, 24); await writeSync(term, "Test content"); const html = serializeAsHTML(term); // Should be valid HTML (has span structure from xterm serialize addon) expect(html).toMatch(/