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%
4.8 KB · 136 lines typescript
Raw Blame History
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