diff --git a/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts b/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts new file mode 100644 index 000000000..4f7889402 --- /dev/null +++ b/packages/pi-tui/src/components/__tests__/cancellable-loader.test.ts @@ -0,0 +1,45 @@ +// pi-tui CancellableLoader component regression tests +// Copyright (c) 2026 Jeremy McSpadden + +import { describe, it, mock, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { CancellableLoader } from "../cancellable-loader.js"; + +function makeMockTUI() { + return { requestRender: mock.fn() } as any; +} + +describe("CancellableLoader", () => { + let loader: CancellableLoader; + let tui: ReturnType; + + beforeEach(() => { + tui = makeMockTUI(); + }); + + afterEach(() => { + loader?.dispose(); + }); + + it("dispose() aborts the AbortController signal", () => { + loader = new CancellableLoader(tui, (s) => s, (s) => s, "test"); + assert.equal(loader.aborted, false); + loader.dispose(); + assert.equal(loader.aborted, true); + }); + + it("dispose() clears the onAbort callback", () => { + loader = new CancellableLoader(tui, (s) => s, (s) => s, "test"); + loader.onAbort = () => {}; + loader.dispose(); + assert.equal(loader.onAbort, undefined); + }); + + it("signal is aborted after dispose()", () => { + loader = new CancellableLoader(tui, (s) => s, (s) => s, "test"); + const signal = loader.signal; + assert.equal(signal.aborted, false); + loader.dispose(); + assert.equal(signal.aborted, true); + }); +}); diff --git a/packages/pi-tui/src/components/__tests__/input.test.ts b/packages/pi-tui/src/components/__tests__/input.test.ts new file mode 100644 index 000000000..c47100492 --- /dev/null +++ b/packages/pi-tui/src/components/__tests__/input.test.ts @@ -0,0 +1,35 @@ +// pi-tui Input component regression tests +// Copyright (c) 2026 Jeremy McSpadden + +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { Input } from "../input.js"; + +describe("Input", () => { + it("paste buffer is cleared when focus is lost", () => { + const input = new Input(); + input.focused = true; + + // Simulate starting a paste (bracket paste start marker) + input.handleInput("\x1b[200~partial"); + + // Now lose focus mid-paste + input.focused = false; + + // Regain focus — should not have stale paste state + input.focused = true; + + // Typing normal text should work without paste buffer corruption + input.handleInput("hello"); + assert.equal(input.getValue(), "hello"); + }); + + it("focused getter/setter works correctly", () => { + const input = new Input(); + assert.equal(input.focused, false); + input.focused = true; + assert.equal(input.focused, true); + input.focused = false; + assert.equal(input.focused, false); + }); +}); diff --git a/packages/pi-tui/src/components/__tests__/loader.test.ts b/packages/pi-tui/src/components/__tests__/loader.test.ts new file mode 100644 index 000000000..9c22056fa --- /dev/null +++ b/packages/pi-tui/src/components/__tests__/loader.test.ts @@ -0,0 +1,45 @@ +// pi-tui Loader component regression tests +// Copyright (c) 2026 Jeremy McSpadden + +import { describe, it, mock, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { Loader } from "../loader.js"; + +function makeMockTUI() { + return { requestRender: mock.fn() } as any; +} + +describe("Loader", () => { + let loader: Loader; + let tui: ReturnType; + + beforeEach(() => { + tui = makeMockTUI(); + }); + + afterEach(() => { + loader?.stop(); + }); + + it("start() is idempotent — calling twice does not leak intervals", () => { + loader = new Loader(tui, (s) => s, (s) => s, "test"); + // Constructor calls start() once, call it again + loader.start(); + // stop() should clear the interval cleanly without orphaned timers + loader.stop(); + }); + + it("dispose() stops the interval and nulls the TUI reference", () => { + loader = new Loader(tui, (s) => s, (s) => s, "test"); + loader.dispose(); + // After dispose, calling stop() again should be safe (no-op) + loader.stop(); + }); + + it("stop() is safe to call multiple times", () => { + loader = new Loader(tui, (s) => s, (s) => s, "test"); + loader.stop(); + loader.stop(); + loader.stop(); + }); +});