mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-28 04:29:42 +00:00
Preserve the existing hover highlight without letting mouse movement change what Enter confirms. Add a TUI regression test so hovered actions still click correctly while keyboard selection stays stable.
116 lines
3.5 KiB
TypeScript
116 lines
3.5 KiB
TypeScript
/** @jsxImportSource @opentui/solid */
|
|
import { describe, expect, test } from "bun:test"
|
|
import { testRender } from "@opentui/solid"
|
|
import path from "path"
|
|
import { DialogProvider } from "../../../src/cli/cmd/tui/ui/dialog"
|
|
import { ToastProvider } from "../../../src/cli/cmd/tui/ui/toast"
|
|
import { KeybindProvider } from "../../../src/cli/cmd/tui/context/keybind"
|
|
import { KVProvider } from "../../../src/cli/cmd/tui/context/kv"
|
|
import { ThemeProvider } from "../../../src/cli/cmd/tui/context/theme"
|
|
import { TuiConfigProvider } from "../../../src/cli/cmd/tui/context/tui-config"
|
|
import { Prompt } from "../../../src/cli/cmd/tui/routes/session/permission"
|
|
import { Global } from "../../../src/global"
|
|
|
|
type Setup = Awaited<ReturnType<typeof testRender>>
|
|
|
|
function App(props: { onSelect: (option: "once" | "always" | "reject") => void }) {
|
|
return (
|
|
<TuiConfigProvider config={{}}>
|
|
<KVProvider>
|
|
<ThemeProvider mode="dark">
|
|
<ToastProvider>
|
|
<DialogProvider>
|
|
<KeybindProvider>
|
|
<Prompt
|
|
title="Permission required"
|
|
body={
|
|
<box>
|
|
<text>Prompt body</text>
|
|
</box>
|
|
}
|
|
options={{ once: "Allow once", always: "Allow always", reject: "Reject" }}
|
|
onSelect={props.onSelect}
|
|
/>
|
|
</KeybindProvider>
|
|
</DialogProvider>
|
|
</ToastProvider>
|
|
</ThemeProvider>
|
|
</KVProvider>
|
|
</TuiConfigProvider>
|
|
)
|
|
}
|
|
|
|
function locate(frame: string, text: string) {
|
|
const lines = frame.split("\n")
|
|
const y = lines.findIndex((line) => line.includes(text))
|
|
if (y === -1) throw new Error(`Could not locate ${text}`)
|
|
return { x: lines[y]!.indexOf(text), y }
|
|
}
|
|
|
|
async function waitForPrompt(setup: Setup) {
|
|
for (let i = 0; i < 50; i++) {
|
|
await setup.renderOnce()
|
|
const frame = setup.captureCharFrame()
|
|
if (frame.includes("Allow once") && frame.includes("Allow always") && frame.includes("Reject")) {
|
|
return frame
|
|
}
|
|
await Bun.sleep(10)
|
|
}
|
|
|
|
throw new Error("Timed out waiting for permission prompt")
|
|
}
|
|
|
|
async function prepareKv() {
|
|
await Bun.write(path.join(Global.Path.state, "kv.json"), JSON.stringify({}))
|
|
}
|
|
|
|
describe("permission prompt", () => {
|
|
test("hover does not change the option Enter confirms", async () => {
|
|
const calls: string[] = []
|
|
await prepareKv()
|
|
const setup = await testRender(() => <App onSelect={(option) => calls.push(option)} />, {
|
|
width: 80,
|
|
height: 20,
|
|
})
|
|
|
|
try {
|
|
const frame = await waitForPrompt(setup)
|
|
const reject = locate(frame, "Reject")
|
|
|
|
setup.mockInput.pressArrow("right")
|
|
await setup.renderOnce()
|
|
|
|
await setup.mockMouse.moveTo(reject.x, reject.y)
|
|
await setup.renderOnce()
|
|
|
|
setup.mockInput.pressEnter()
|
|
|
|
expect(calls).toEqual(["always"])
|
|
} finally {
|
|
setup.renderer.destroy()
|
|
}
|
|
})
|
|
|
|
test("click still selects the hovered option", async () => {
|
|
const calls: string[] = []
|
|
await prepareKv()
|
|
const setup = await testRender(() => <App onSelect={(option) => calls.push(option)} />, {
|
|
width: 80,
|
|
height: 20,
|
|
})
|
|
|
|
try {
|
|
const frame = await waitForPrompt(setup)
|
|
const reject = locate(frame, "Reject")
|
|
|
|
setup.mockInput.pressArrow("right")
|
|
await setup.renderOnce()
|
|
|
|
await setup.mockMouse.click(reject.x, reject.y)
|
|
|
|
expect(calls).toEqual(["reject"])
|
|
} finally {
|
|
setup.renderer.destroy()
|
|
}
|
|
})
|
|
})
|