OpenTUI is here (#2685)

This commit is contained in:
Dax
2025-10-31 15:07:36 -04:00
committed by GitHub
parent 81c617770d
commit 96bdeb3c7b
104 changed files with 8459 additions and 716 deletions

View File

@@ -11,7 +11,10 @@ type TmpDirOptions<T> = {
export async function tmpdir<T>(options?: TmpDirOptions<T>) {
const dirpath = path.join(os.tmpdir(), "opencode-test-" + Math.random().toString(36).slice(2))
await $`mkdir -p ${dirpath}`.quiet()
if (options?.git) await $`git init`.cwd(dirpath).quiet()
if (options?.git) {
await $`git init`.cwd(dirpath).quiet()
await $`git commit --allow-empty -m "root commit ${dirpath}"`.cwd(dirpath).quiet()
}
const extra = await options?.init?.(dirpath)
const result = {
[Symbol.asyncDispose]: async () => {

View File

@@ -0,0 +1,305 @@
import { describe, test, expect } from "bun:test"
import { Keybind } from "../src/util/keybind"
describe("Keybind.toString", () => {
test("should convert simple key to string", () => {
const info: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: false, name: "f" }
expect(Keybind.toString(info)).toBe("f")
})
test("should convert ctrl modifier to string", () => {
const info: Keybind.Info = { ctrl: true, meta: false, shift: false, leader: false, name: "x" }
expect(Keybind.toString(info)).toBe("ctrl+x")
})
test("should convert leader key to string", () => {
const info: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: true, name: "f" }
expect(Keybind.toString(info)).toBe("<leader> f")
})
test("should convert multiple modifiers to string", () => {
const info: Keybind.Info = { ctrl: true, meta: true, shift: false, leader: false, name: "g" }
expect(Keybind.toString(info)).toBe("ctrl+alt+g")
})
test("should convert all modifiers to string", () => {
const info: Keybind.Info = { ctrl: true, meta: true, shift: true, leader: true, name: "h" }
expect(Keybind.toString(info)).toBe("<leader> ctrl+alt+shift+h")
})
test("should convert shift modifier to string", () => {
const info: Keybind.Info = { ctrl: false, meta: false, shift: true, leader: false, name: "enter" }
expect(Keybind.toString(info)).toBe("shift+enter")
})
test("should convert function key to string", () => {
const info: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: false, name: "f2" }
expect(Keybind.toString(info)).toBe("f2")
})
test("should convert special key to string", () => {
const info: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: false, name: "pgup" }
expect(Keybind.toString(info)).toBe("pgup")
})
test("should handle empty name", () => {
const info: Keybind.Info = { ctrl: true, meta: false, shift: false, leader: false, name: "" }
expect(Keybind.toString(info)).toBe("ctrl")
})
test("should handle only modifiers", () => {
const info: Keybind.Info = { ctrl: true, meta: true, shift: true, leader: true, name: "" }
expect(Keybind.toString(info)).toBe("<leader> ctrl+alt+shift")
})
test("should handle only leader with no other parts", () => {
const info: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: true, name: "" }
expect(Keybind.toString(info)).toBe("<leader>")
})
})
describe("Keybind.match", () => {
test("should match identical keybinds", () => {
const a: Keybind.Info = { ctrl: true, meta: false, shift: false, leader: false, name: "x" }
const b: Keybind.Info = { ctrl: true, meta: false, shift: false, leader: false, name: "x" }
expect(Keybind.match(a, b)).toBe(true)
})
test("should not match different key names", () => {
const a: Keybind.Info = { ctrl: true, meta: false, shift: false, leader: false, name: "x" }
const b: Keybind.Info = { ctrl: true, meta: false, shift: false, leader: false, name: "y" }
expect(Keybind.match(a, b)).toBe(false)
})
test("should not match different modifiers", () => {
const a: Keybind.Info = { ctrl: true, meta: false, shift: false, leader: false, name: "x" }
const b: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: false, name: "x" }
expect(Keybind.match(a, b)).toBe(false)
})
test("should match leader keybinds", () => {
const a: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: true, name: "f" }
const b: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: true, name: "f" }
expect(Keybind.match(a, b)).toBe(true)
})
test("should not match leader vs non-leader", () => {
const a: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: true, name: "f" }
const b: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: false, name: "f" }
expect(Keybind.match(a, b)).toBe(false)
})
test("should match complex keybinds", () => {
const a: Keybind.Info = { ctrl: true, meta: true, shift: false, leader: false, name: "g" }
const b: Keybind.Info = { ctrl: true, meta: true, shift: false, leader: false, name: "g" }
expect(Keybind.match(a, b)).toBe(true)
})
test("should not match with one modifier different", () => {
const a: Keybind.Info = { ctrl: true, meta: true, shift: false, leader: false, name: "g" }
const b: Keybind.Info = { ctrl: true, meta: true, shift: true, leader: false, name: "g" }
expect(Keybind.match(a, b)).toBe(false)
})
test("should match simple key without modifiers", () => {
const a: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: false, name: "a" }
const b: Keybind.Info = { ctrl: false, meta: false, shift: false, leader: false, name: "a" }
expect(Keybind.match(a, b)).toBe(true)
})
})
describe("Keybind.parse", () => {
test("should parse simple key", () => {
const result = Keybind.parse("f")
expect(result).toEqual([
{
ctrl: false,
meta: false,
shift: false,
leader: false,
name: "f",
},
])
})
test("should parse leader key syntax", () => {
const result = Keybind.parse("<leader>f")
expect(result).toEqual([
{
ctrl: false,
meta: false,
shift: false,
leader: true,
name: "f",
},
])
})
test("should parse ctrl modifier", () => {
const result = Keybind.parse("ctrl+x")
expect(result).toEqual([
{
ctrl: true,
meta: false,
shift: false,
leader: false,
name: "x",
},
])
})
test("should parse multiple modifiers", () => {
const result = Keybind.parse("ctrl+alt+u")
expect(result).toEqual([
{
ctrl: true,
meta: true,
shift: false,
leader: false,
name: "u",
},
])
})
test("should parse shift modifier", () => {
const result = Keybind.parse("shift+f2")
expect(result).toEqual([
{
ctrl: false,
meta: false,
shift: true,
leader: false,
name: "f2",
},
])
})
test("should parse meta/alt modifier", () => {
const result = Keybind.parse("meta+g")
expect(result).toEqual([
{
ctrl: false,
meta: true,
shift: false,
leader: false,
name: "g",
},
])
})
test("should parse leader with modifier", () => {
const result = Keybind.parse("<leader>h")
expect(result).toEqual([
{
ctrl: false,
meta: false,
shift: false,
leader: true,
name: "h",
},
])
})
test("should parse multiple keybinds separated by comma", () => {
const result = Keybind.parse("ctrl+c,<leader>q")
expect(result).toEqual([
{
ctrl: true,
meta: false,
shift: false,
leader: false,
name: "c",
},
{
ctrl: false,
meta: false,
shift: false,
leader: true,
name: "q",
},
])
})
test("should parse shift+enter combination", () => {
const result = Keybind.parse("shift+enter")
expect(result).toEqual([
{
ctrl: false,
meta: false,
shift: true,
leader: false,
name: "enter",
},
])
})
test("should parse ctrl+j combination", () => {
const result = Keybind.parse("ctrl+j")
expect(result).toEqual([
{
ctrl: true,
meta: false,
shift: false,
leader: false,
name: "j",
},
])
})
test("should handle 'none' value", () => {
const result = Keybind.parse("none")
expect(result).toEqual([])
})
test("should handle special keys", () => {
const result = Keybind.parse("pgup")
expect(result).toEqual([
{
ctrl: false,
meta: false,
shift: false,
leader: false,
name: "pgup",
},
])
})
test("should handle function keys", () => {
const result = Keybind.parse("f2")
expect(result).toEqual([
{
ctrl: false,
meta: false,
shift: false,
leader: false,
name: "f2",
},
])
})
test("should handle complex multi-modifier combination", () => {
const result = Keybind.parse("ctrl+alt+g")
expect(result).toEqual([
{
ctrl: true,
meta: true,
shift: false,
leader: false,
name: "g",
},
])
})
test("should be case insensitive", () => {
const result = Keybind.parse("CTRL+X")
expect(result).toEqual([
{
ctrl: true,
meta: false,
shift: false,
leader: false,
name: "x",
},
])
})
})

View File

@@ -21,7 +21,9 @@ describe("tool.patch", () => {
await Instance.provide({
directory: "/tmp",
fn: async () => {
await expect(patchTool.execute({ patchText: "" }, ctx)).rejects.toThrow("patchText is required")
await expect(patchTool.execute({ patchText: "" }, ctx)).rejects.toThrow(
"patchText is required",
)
},
})
})
@@ -30,7 +32,9 @@ describe("tool.patch", () => {
await Instance.provide({
directory: "/tmp",
fn: async () => {
await expect(patchTool.execute({ patchText: "invalid patch" }, ctx)).rejects.toThrow("Failed to parse patch")
await expect(patchTool.execute({ patchText: "invalid patch" }, ctx)).rejects.toThrow(
"Failed to parse patch",
)
},
})
})
@@ -113,7 +117,9 @@ describe("tool.patch", () => {
// Verify file was created with correct content
const filePath = path.join(fixture.path, "config.js")
const content = await fs.readFile(filePath, "utf-8")
expect(content).toBe('const API_KEY = "test-key"\nconst DEBUG = false\nconst VERSION = "1.0"')
expect(content).toBe(
'const API_KEY = "test-key"\nconst DEBUG = false\nconst VERSION = "1.0"',
)
},
})
})