import { afterEach, describe, expect, test } from "bun:test" import { For } from "solid-js" import { testRender, type JSX } from "@opentui/solid" import { formatCompletedSubagentDetail, formatSubagentRetry, formatSubagentTitle, formatSubagentToolcalls, InlineToolRow, } from "../../../src/cli/cmd/tui/routes/session/index" let testSetup: Awaited> | undefined afterEach(() => { testSetup?.renderer.destroy() testSetup = undefined }) type ToolFixture = { icon: string; label: string; error?: string } const tools: readonly ToolFixture[] = [ { icon: "✱", label: 'Grep "OPENCODE.*DB|database|sqlite|drizzle|dev.*db|data.*dir|xdg|APPDATA" in packages/opencode/src (151 matches)', }, { icon: "✱", label: 'Glob "**/*db*" in packages/opencode (6 matches)', }, { icon: "→", label: "Read packages/opencode/src/storage/db.ts [offset=1, limit=130]", }, { icon: "→", label: "Read packages/opencode/src/index.ts [offset=1, limit=100]", error: "No LSP server available for this file type.", }, { icon: "✱", label: 'Grep "export const OPENCODE_DB|OPENCODE_DB|OPENCODE_DEV|Global\\.Path\\.data|data =" in packages/opencode/src (115 matches)', }, ] as const function ShellOutput() { return ( # List files $ ls file.ts ) } function UserMessage() { return ( Check whether the next tool remains separated. ) } function Fixture(props: { errorExpanded?: boolean; before?: "shell" | "user" }) { return ( {props.before === "shell" && } {props.before === "user" && } {(item) => ( id === "message-user"} > {item.label} )} ) } function SubagentGroupFixture() { return ( Grep "Task" (2 matches) Explore Task — Inspect active task spacing {"General Task — Confirm completed task spacing\n↳ 1 toolcall · 501ms"} Read src/cli/cmd/tui/routes/session/index.tsx ) } function LoadedReadBeforeSubagentFixture() { return ( Read src/cli/cmd/tui/routes/session/index.tsx ↳ Loaded src/cli/cmd/tui/routes/session/tools.tsx {"Explore Task — Inspect active task spacing\n↳ 1 toolcall · 501ms"} ) } async function renderFrame(component: () => JSX.Element, options: { width: number; height: number }) { testSetup = await testRender(component, options) await testSetup.renderOnce() await Bun.sleep(25) await testSetup.renderOnce() return testSetup .captureCharFrame() .split("\n") .map((line) => line.trimEnd()) .join("\n") .trimEnd() } describe("TUI inline tool wrapping", () => { test("formats completed subagent toolcall details", () => { expect(formatCompletedSubagentDetail(0, "501ms")).toBe("501ms") expect(formatCompletedSubagentDetail(1, "501ms")).toBe("1 toolcall · 501ms") expect(formatCompletedSubagentDetail(2, "501ms")).toBe("2 toolcalls · 501ms") expect(formatSubagentToolcalls(0)).toBe("0 toolcalls") }) test("keeps background state attached to the subagent identity", () => { expect(formatSubagentTitle("Explore", "Inspect renderer", false)).toBe("Explore Task — Inspect renderer") expect(formatSubagentTitle("Explore", "Inspect renderer", true)).toBe( "Explore Task (background) — Inspect renderer", ) }) test("keeps retry status ahead of wrapping messages", () => { expect(formatSubagentRetry(2, "Rate limited by provider")).toBe("Retrying (attempt 2) · Rate limited by provider") }) test("snapshots consecutive grep, glob, and read rows at a narrow width", async () => { expect(await renderFrame(() => , { width: 72, height: 12 })).toMatchSnapshot() }) test("snapshots expanded tool errors under the tool text", async () => { expect(await renderFrame(() => , { width: 72, height: 12 })).toMatchSnapshot() }) test("keeps separation after a shell output block", async () => { expect(await renderFrame(() => , { width: 72, height: 16 })).toMatchSnapshot() }) test("keeps separation after a padded user message", async () => { expect(await renderFrame(() => , { width: 72, height: 14 })).toMatchSnapshot() }) test("separates a contiguous subagent group from inline tools", async () => { expect(await renderFrame(() => , { width: 72, height: 10 })).toMatchSnapshot() }) test("separates a subagent group after an expanded read", async () => { expect(await renderFrame(() => , { width: 72, height: 8 })).toMatchSnapshot() }) })