diff --git a/src/terminal.test.ts b/src/terminal.test.ts new file mode 100644 index 0000000..1bc5a7c --- /dev/null +++ b/src/terminal.test.ts @@ -0,0 +1,100 @@ +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(/