120 lines
3.5 KiB
TypeScript
120 lines
3.5 KiB
TypeScript
import { afterEach, describe, expect, test } from "bun:test"
|
|
import { For } from "solid-js"
|
|
import { testRender, type JSX } from "@opentui/solid"
|
|
import { InlineToolRow } from "../../../src/cli/cmd/tui/routes/session/index"
|
|
|
|
let testSetup: Awaited<ReturnType<typeof testRender>> | 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 (
|
|
<box id="tool-block-shell" marginTop={1} paddingTop={1} paddingBottom={1} paddingLeft={2} gap={1}>
|
|
<text paddingLeft={3}># List files</text>
|
|
<box gap={1}>
|
|
<text>$ ls</text>
|
|
<text>file.ts</text>
|
|
</box>
|
|
</box>
|
|
)
|
|
}
|
|
|
|
function UserMessage() {
|
|
return (
|
|
<box id="message-user">
|
|
<box paddingTop={1} paddingBottom={1} paddingLeft={2}>
|
|
<text>Check whether the next tool remains separated.</text>
|
|
</box>
|
|
</box>
|
|
)
|
|
}
|
|
|
|
function Fixture(props: { errorExpanded?: boolean; before?: "shell" | "user" }) {
|
|
return (
|
|
<box flexDirection="column" width={72}>
|
|
<box flexDirection="column">
|
|
{props.before === "shell" && <ShellOutput />}
|
|
{props.before === "user" && <UserMessage />}
|
|
<For each={tools}>
|
|
{(item) => (
|
|
<InlineToolRow
|
|
icon={item.icon}
|
|
complete={true}
|
|
pending=""
|
|
failed={Boolean(item.error)}
|
|
error={item.error}
|
|
errorExpanded={props.errorExpanded}
|
|
separateAfter={(id) => id === "message-user"}
|
|
>
|
|
{item.label}
|
|
</InlineToolRow>
|
|
)}
|
|
</For>
|
|
</box>
|
|
</box>
|
|
)
|
|
}
|
|
|
|
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("snapshots consecutive grep, glob, and read rows at a narrow width", async () => {
|
|
expect(await renderFrame(() => <Fixture />, { width: 72, height: 12 })).toMatchSnapshot()
|
|
})
|
|
|
|
test("snapshots expanded tool errors under the tool text", async () => {
|
|
expect(await renderFrame(() => <Fixture errorExpanded />, { width: 72, height: 12 })).toMatchSnapshot()
|
|
})
|
|
|
|
test("keeps separation after a shell output block", async () => {
|
|
expect(await renderFrame(() => <Fixture before="shell" />, { width: 72, height: 16 })).toMatchSnapshot()
|
|
})
|
|
|
|
test("keeps separation after a padded user message", async () => {
|
|
expect(await renderFrame(() => <Fixture before="user" />, { width: 72, height: 14 })).toMatchSnapshot()
|
|
})
|
|
})
|