|
|
@@ -1,146 +0,0 @@
|
|
|
-/** @jsxImportSource @opentui/solid */
|
|
|
-import { TextareaRenderable } from "@opentui/core"
|
|
|
-import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
|
|
|
-import { testRender, useRenderer } from "@opentui/solid"
|
|
|
-import { expect, test } from "bun:test"
|
|
|
-import { mkdir } from "node:fs/promises"
|
|
|
-import path from "node:path"
|
|
|
-import { onCleanup } from "solid-js"
|
|
|
-import { tmpdir } from "../../fixture/fixture"
|
|
|
-import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
|
|
-import type { TuiKeybind } from "../../../src/cli/cmd/tui/config/keybind"
|
|
|
-
|
|
|
-async function wait(fn: () => boolean, timeout = 2000) {
|
|
|
- const start = Date.now()
|
|
|
- while (!fn()) {
|
|
|
- if (Date.now() - start > timeout) throw new Error("timed out waiting for condition")
|
|
|
- await Bun.sleep(10)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-async function mountPrompt(input: {
|
|
|
- root: string
|
|
|
- keybinds: Partial<TuiKeybind.Keybinds>
|
|
|
- onConfirm: (value: string) => void
|
|
|
-}) {
|
|
|
- const { Global } = await import("@opencode-ai/core/global")
|
|
|
- const previous = {
|
|
|
- config: Global.Path.config,
|
|
|
- state: Global.Path.state,
|
|
|
- }
|
|
|
- Global.Path.config = path.join(input.root, "config")
|
|
|
- Global.Path.state = path.join(input.root, "state")
|
|
|
- await mkdir(Global.Path.config, { recursive: true })
|
|
|
- await mkdir(Global.Path.state, { recursive: true })
|
|
|
- await Bun.write(path.join(Global.Path.state, "kv.json"), "{}")
|
|
|
-
|
|
|
- const [
|
|
|
- { DialogProvider },
|
|
|
- { DialogPrompt },
|
|
|
- { KVProvider },
|
|
|
- { ThemeProvider },
|
|
|
- { TuiConfigProvider },
|
|
|
- { ToastProvider },
|
|
|
- { OpencodeKeymapProvider, registerOpencodeKeymap },
|
|
|
- ] = await Promise.all([
|
|
|
- import("../../../src/cli/cmd/tui/ui/dialog"),
|
|
|
- import("../../../src/cli/cmd/tui/ui/dialog-prompt"),
|
|
|
- import("../../../src/cli/cmd/tui/context/kv"),
|
|
|
- import("../../../src/cli/cmd/tui/context/theme"),
|
|
|
- import("../../../src/cli/cmd/tui/context/tui-config"),
|
|
|
- import("../../../src/cli/cmd/tui/ui/toast"),
|
|
|
- import("../../../src/cli/cmd/tui/keymap"),
|
|
|
- ])
|
|
|
-
|
|
|
- function Harness() {
|
|
|
- const renderer = useRenderer()
|
|
|
- const keymap = createDefaultOpenTuiKeymap(renderer)
|
|
|
- const resolvedConfig = createTuiResolvedConfig({
|
|
|
- keybinds: input.keybinds,
|
|
|
- leader_timeout: 1000,
|
|
|
- })
|
|
|
- const off = registerOpencodeKeymap(keymap, renderer, resolvedConfig)
|
|
|
- onCleanup(off)
|
|
|
-
|
|
|
- return (
|
|
|
- <OpencodeKeymapProvider keymap={keymap}>
|
|
|
- <TuiConfigProvider config={resolvedConfig}>
|
|
|
- <KVProvider>
|
|
|
- <ThemeProvider mode="dark">
|
|
|
- <ToastProvider>
|
|
|
- <DialogProvider>
|
|
|
- <DialogPrompt title="Rename Session" value="draft" onConfirm={input.onConfirm} />
|
|
|
- </DialogProvider>
|
|
|
- </ToastProvider>
|
|
|
- </ThemeProvider>
|
|
|
- </KVProvider>
|
|
|
- </TuiConfigProvider>
|
|
|
- </OpencodeKeymapProvider>
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- const app = await testRender(() => <Harness />, { kittyKeyboard: true })
|
|
|
- return {
|
|
|
- app,
|
|
|
- async cleanup() {
|
|
|
- app.renderer.destroy()
|
|
|
- Global.Path.config = previous.config
|
|
|
- Global.Path.state = previous.state
|
|
|
- },
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-test("dialog prompt submit wins when return is also input newline", async () => {
|
|
|
- await using tmp = await tmpdir()
|
|
|
- const confirmed: string[] = []
|
|
|
- const prompt = await mountPrompt({
|
|
|
- root: tmp.path,
|
|
|
- keybinds: {
|
|
|
- input_submit: "super+return",
|
|
|
- input_newline: "return,shift+return,alt+return,ctrl+j",
|
|
|
- },
|
|
|
- onConfirm: (value) => confirmed.push(value),
|
|
|
- })
|
|
|
-
|
|
|
- try {
|
|
|
- await wait(() => prompt.app.renderer.currentFocusedEditor instanceof TextareaRenderable)
|
|
|
- const textarea = prompt.app.renderer.currentFocusedEditor
|
|
|
- if (!(textarea instanceof TextareaRenderable)) throw new Error("expected focused dialog textarea")
|
|
|
-
|
|
|
- prompt.app.mockInput.pressEnter()
|
|
|
-
|
|
|
- expect(confirmed).toEqual(["draft"])
|
|
|
- expect(textarea.plainText).toBe("draft")
|
|
|
- } finally {
|
|
|
- await prompt.cleanup()
|
|
|
- }
|
|
|
-})
|
|
|
-
|
|
|
-test("dialog prompt submit can be rebound separately from input submit", async () => {
|
|
|
- await using tmp = await tmpdir()
|
|
|
- const confirmed: string[] = []
|
|
|
- const prompt = await mountPrompt({
|
|
|
- root: tmp.path,
|
|
|
- keybinds: {
|
|
|
- input_submit: "return",
|
|
|
- "dialog.prompt.submit": "ctrl+y",
|
|
|
- },
|
|
|
- onConfirm: (value) => confirmed.push(value),
|
|
|
- })
|
|
|
-
|
|
|
- try {
|
|
|
- await wait(() => prompt.app.renderer.currentFocusedEditor instanceof TextareaRenderable)
|
|
|
- const textarea = prompt.app.renderer.currentFocusedEditor
|
|
|
- if (!(textarea instanceof TextareaRenderable)) throw new Error("expected focused dialog textarea")
|
|
|
-
|
|
|
- prompt.app.mockInput.pressEnter()
|
|
|
- expect(confirmed).toEqual([])
|
|
|
- expect(textarea.plainText).toBe("draft")
|
|
|
-
|
|
|
- prompt.app.mockInput.pressKey("y", { ctrl: true })
|
|
|
-
|
|
|
- expect(confirmed).toEqual(["draft"])
|
|
|
- } finally {
|
|
|
- await prompt.cleanup()
|
|
|
- }
|
|
|
-})
|