SPB Git

spb/khaelor Public

KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.

TypeScript 82.9% HTML 14.9% CSS 1.1% JavaScript 0.7%
6.5 KB · 175 lines typescript
Raw Blame History
1/**2 * KHAELOR3 * File: tests/tui/v2-widgets.test.ts4 * Description: TUI v2 widget tests — gradients, sparkline, verify strip, design panel, splash, motion, doctor (TUI v2).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { describe, expect, it } from "vitest";11import { renderDesignPanel } from "../../src/tui/components/design-panel.js";12import { brailleSparkline } from "../../src/tui/components/sparkline.js";13import { renderVerifyStrip } from "../../src/tui/components/verify-strip.js";14import { doctorReport } from "../../src/tui/doctor.js";15import { MotionController } from "../../src/tui/motion.js";16import { renderSplashFrame, SPLASH_FRAMES } from "../../src/tui/splash.js";17import { gradientText, mixOklch } from "../../src/tui/theme/gradient.js";18import { resolveTheme } from "../../src/tui/theme.js";1920const mono = resolveTheme({ colorDepth: "mono" });21const truecolor = resolveTheme({ colorDepth: "truecolor" });2223describe("OKLCH gradient", () => {24  it("interpolates endpoints exactly and midpoints smoothly", () => {25    expect(mixOklch([255, 107, 53], [255, 184, 107], 0)).toEqual([255, 107, 53]);26    expect(mixOklch([255, 107, 53], [255, 184, 107], 1)).toEqual([255, 184, 107]);27    const mid = mixOklch([255, 107, 53], [255, 184, 107], 0.5);28    expect(mid[0]).toBeGreaterThan(200); // stays warm — no muddy midpoint29  });3031  it("emits one truecolor SGR per character and closes the color", () => {32    const painted = gradientText("ABC", [255, 107, 53], [255, 184, 107]);33    expect(painted.match(/\x1b\[38;2;/g)).toHaveLength(3);34    expect(painted.endsWith("\x1b[39m")).toBe(true);35  });3637  it("theme.paintGradient('ember', …) is identity in mono", () => {38    expect(mono.paintGradient("ember", "KHAELOR")).toBe("KHAELOR");39    expect(truecolor.paintGradient("ember", "KHAELOR")).toContain("\x1b[38;2;");40  });41});4243describe("brailleSparkline", () => {44  it("renders two values per braille cell", () => {45    expect(brailleSparkline([])).toBe("");46    expect(brailleSparkline([1, 2, 3, 4])).toHaveLength(2);47    const flat = brailleSparkline([5, 5, 5, 5]);48    expect(flat).toHaveLength(2);49    expect(flat[0]).toBe(flat[1]);50  });5152  it("caps to maxCells keeping the most recent samples", () => {53    const line = brailleSparkline([1, 2, 3, 4, 5, 6, 7, 8], 2);54    expect(line).toHaveLength(2);55  });56});5758describe("verify strip", () => {59  it("shows live check states with a spinner on running checks", () => {60    const lines = renderVerifyStrip(61      [62        { check: "typecheck", state: "ok", durationMs: 1200 },63        { check: "tests", state: "running" },64      ],65      100,66      mono,67    );68    expect(lines[0]).toContain("verify");69    expect(lines[0]).toContain("typecheck ✓");70    expect(lines[0]).toContain("tests ⠧");71  });7273  it("contracts to a single ✓ verified line when everything passes (TUI v2 §5.4)", () => {74    const lines = renderVerifyStrip(75      [76        { check: "typecheck", state: "ok", durationMs: 1200 },77        { check: "tests", state: "ok", durationMs: 4200 },78      ],79      100,80      mono,81    );82    expect(lines).toHaveLength(1);83    expect(lines[0]).toContain("✓ verified 4.2s");84  });8586  it("pins failures with their first error lines", () => {87    const lines = renderVerifyStrip(88      [{ check: "tests", state: "failed", errorHead: ["FAIL src/a.test.ts", "expected 2 to be 3"] }],89      100,90      mono,91    );92    expect(lines[0]).toContain("tests ✗");93    expect(lines[1]).toContain("FAIL src/a.test.ts");94  });95});9697describe("design panel", () => {98  const artifact = {99    goal: "Fix the renderer memory leak",100    filesTouched: ["src/tui/render.ts", "src/tui/buffer.ts"],101    approach: "Replace the growing buffer with a ring buffer.",102    risks: ["scrollback regression (medium)"],103    verification: "vitest run tests/tui + manual demo",104    outOfScope: ["theming"],105  };106107  it("renders the full pending panel with approve/reject hints", () => {108    const lines = renderDesignPanel({ ...artifact, decision: "pending" }, 100, mono);109    const text = lines.join("\n");110    expect(text).toContain("DESIGN — approval required");111    expect(text).toContain("Fix the renderer memory leak");112    expect(text).toContain("[a]");113    expect(text).toContain("[r]");114    expect(lines[0]).toContain("╭");115    expect(lines[lines.length - 1]).toContain("╰");116  });117118  it("collapses to one line once decided (TUI v2 §5.2)", () => {119    const approved = renderDesignPanel({ ...artifact, decision: "auto-approved" }, 100, mono);120    expect(approved).toHaveLength(1);121    expect(approved[0]).toContain("✓ design auto-approved · 2 files");122    const rejected = renderDesignPanel({ ...artifact, decision: "rejected" }, 100, mono);123    expect(rejected[0]).toContain("✗ design rejected");124  });125});126127describe("splash", () => {128  it("renders the wordmark and settles on the final gradient frame", () => {129    const first = renderSplashFrame(0, true);130    const last = renderSplashFrame(SPLASH_FRAMES - 1, true);131    expect(first.length).toBe(last.length);132    // The tagline is gradient-painted per character — strip SGR before matching.133    const plain = last.join("\n").replace(/\x1b\[[0-9;]*m/g, "");134    expect(plain).toContain("understand · design · implement");135    expect(last.join("\n")).toContain("\x1b[38;2;");136    const monoFrame = renderSplashFrame(0, false);137    expect(monoFrame.join("\n")).not.toContain("\x1b[");138  });139});140141describe("motion controller", () => {142  it("advances effects only on tick and drops finished transitions", () => {143    const motion = new MotionController({ enabled: true });144    motion.start("phase", "phase-flash");145    expect(motion.frame("phase")).toBe(0);146    motion.tick();147    expect(motion.frame("phase")).toBe(1);148    motion.tick();149    motion.tick();150    expect(motion.frame("phase")).toBeNull(); // 3 frames max151  });152153  it("--motion off reduces everything to static instantly", () => {154    const motion = new MotionController({ enabled: false });155    motion.start("panel", "panel-breath");156    expect(motion.frame("panel")).toBeNull();157    expect(motion.breathIntensity("panel")).toBe(0);158  });159});160161describe("doctor-tui", () => {162  it("reports detected capabilities with reasons", () => {163    const report = doctorReport(164      { TERM: "xterm-256color", COLORTERM: "truecolor", TERM_PROGRAM: "iTerm.app", LANG: "en_US.UTF-8" },165      true,166      120,167    );168    const text = report.join("\n");169    expect(text).toContain("truecolor");170    expect(text).toContain("utf-8");171    expect(text).toContain("osc8 links");172    expect(text).toContain("khaelis");173  });174});175