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/status.test.ts4 * Description: Status bar width-degradation tests and agent status line tests (real timers, no fabrication).5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { describe, expect, it } from "vitest";11import { renderStatusBar } from "../../src/tui/components/status-bar.js";12import type { StatusBarData } from "../../src/tui/components/status-bar.js";13import { renderStatusLine } from "../../src/tui/components/status-line.js";14import { stripAnsi } from "../../src/tui/renderer/ansi.js";15import { resolveTheme } from "../../src/tui/theme.js";1617const mono = resolveTheme({ colorDepth: "mono" });1819const FULL: StatusBarData = {20 branch: "main",21 dirty: { added: 4, removed: 1 },22 model: "claude-sonnet-4-5",23 contextPct: 31,24 costUsd: 0.42,25 processCount: 2,26};2728describe("renderStatusBar degradation", () => {29 it("width ≥ 110: all segments, full model id, context gauge (TUI v2 §3)", () => {30 expect(renderStatusBar(FULL, 120, mono)).toBe(31 " main +4 −1 │ claude-sonnet-4-5 │ ▰▰▱▱▱▱▱▱ 31% │ $0.42 │ ● 2",32 );33 });3435 it("width 100–109: context falls back to the worded percent", () => {36 expect(renderStatusBar(FULL, 105, mono)).toBe(37 " main +4 −1 │ claude-sonnet-4-5 │ context 31% │ $0.42 │ ● 2",38 );39 });4041 it("phase ribbon leads the cockpit when a phase is active (v2 §1)", () => {42 const bar = renderStatusBar({ ...FULL, phase: "design" }, 130, mono);43 expect(bar).toContain("◑ DESIGN");44 expect(bar.indexOf("◑")).toBeLessThan(bar.indexOf("main"));45 });4647 it("width 80–99: model shortens to its alias", () => {48 expect(renderStatusBar(FULL, 90, mono)).toBe(49 " main +4 −1 │ sonnet │ context 31% │ $0.42 │ ● 2",50 );51 });5253 it("width 60–79: cost and processes drop whole", () => {54 expect(renderStatusBar(FULL, 70, mono)).toBe(" main +4 −1 │ sonnet │ 31%");55 });5657 it("width < 60: branch and context percent only", () => {58 expect(renderStatusBar(FULL, 50, mono)).toBe(" main │ 31%");59 });6061 it("segments with no data are absent, not zeroed", () => {62 expect(renderStatusBar({ model: "claude-sonnet-4-5" }, 120, mono)).toBe(63 " claude-sonnet-4-5",64 );65 expect(renderStatusBar({}, 120, mono)).toBe(" ");66 });6768 it("processes drop when none are running", () => {69 const bar = renderStatusBar({ ...FULL, processCount: 0 }, 120, mono);70 expect(bar).not.toContain("●");71 });7273 it("context pressure appends the /compact nudge at ≥ 80%", () => {74 const gauge = renderStatusBar({ ...FULL, contextPct: 85 }, 120, mono);75 expect(gauge).toContain("85% · /compact");76 const worded = renderStatusBar({ ...FULL, contextPct: 85 }, 105, mono);77 expect(worded).toContain("context 85% · /compact");78 });7980 it("queued steering shows its count", () => {81 const bar = renderStatusBar({ ...FULL, queuedCount: 1 }, 120, mono);82 expect(bar).toContain("⋯ 1 queued");83 });8485 it("never exceeds the terminal width", () => {86 for (const width of [40, 60, 80, 100, 120]) {87 expect(stripAnsi(renderStatusBar(FULL, width, mono)).length).toBeLessThanOrEqual(width);88 }89 });90});9192describe("renderStatusLine", () => {93 it("collapses to nothing when idle", () => {94 expect(renderStatusLine({ kind: "idle", startedAt: 0 }, 5000, mono, 80)).toBeNull();95 });9697 it("shows the state, detail, and a REAL elapsed timer", () => {98 const line = renderStatusLine(99 { kind: "running", detail: "npm test", startedAt: 1000 },100 3300,101 mono,102 80,103 );104 expect(line).toBe(" ● Running · npm test · 2.3s");105 });106107 it("shows extracted progress instead of the timer when present", () => {108 const line = renderStatusLine(109 { kind: "running", detail: "npm test", startedAt: 1000, progress: "41/148" },110 9000,111 mono,112 80,113 );114 expect(line).toBe(" ● Running · npm test · 41/148");115 });116117 it("waits-for-permission state is spelled out", () => {118 const line = renderStatusLine({ kind: "waiting", startedAt: 1000 }, 1400, mono, 80);119 expect(line).toContain("Waiting for permission");120 });121122 it("stopping acknowledgment uses the ◌ glyph (shape carries state)", () => {123 expect(renderStatusLine({ kind: "stopping", startedAt: 0 }, 100, mono, 80)).toBe(124 " ◌ Stopping…",125 );126 });127128 it("pulses the dot between two shades in color, stable glyph in mono", () => {129 const dark = resolveTheme({ colorDepth: "truecolor" });130 const a = renderStatusLine({ kind: "thinking", startedAt: 0 }, 0, dark, 80) as string;131 const b = renderStatusLine({ kind: "thinking", startedAt: 0 }, 250, dark, 80) as string;132 expect(a).not.toBe(b); // different pulse shade133 expect(stripAnsi(a).slice(0, 3)).toBe(stripAnsi(b).slice(0, 3)); // same glyph134 });135});136