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%
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