mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-22 18:25:32 -04:00
238 lines
7.7 KiB
TypeScript
238 lines
7.7 KiB
TypeScript
import { expect, test } from "bun:test"
|
|
import { BoxRenderable, TextRenderable } from "@opentui/core"
|
|
import { Effect } from "effect"
|
|
import { createHarness, execute, type Harness, matches, snapshot, state } from "../src/frontend/actions"
|
|
import { SimulationRenderer } from "../src/frontend/renderer"
|
|
import { SimulationSemantics } from "../src/frontend/semantics"
|
|
|
|
test("matches literal screen text", () => {
|
|
const harness = { screen: () => "OpenCode [ready].*" }
|
|
|
|
expect(matches(harness, "OpenCode")).toBe(true)
|
|
expect(matches(harness, "[ready].*")).toBe(true)
|
|
expect(matches(harness, "OpenCode.*ready")).toBe(false)
|
|
expect(matches(harness, "opencode")).toBe(false)
|
|
})
|
|
|
|
test("normalizes named keys for OpenTUI", async () => {
|
|
const pressed: Array<readonly [string, object | undefined]> = []
|
|
const harness = {
|
|
renderer: {
|
|
root: { getChildren: () => [] },
|
|
currentFocusedRenderable: undefined,
|
|
currentFocusedEditor: undefined,
|
|
},
|
|
mockInput: {
|
|
pressKey: (key: string, modifiers?: object) => pressed.push([key, modifiers]),
|
|
},
|
|
renderOnce: async () => {},
|
|
} as unknown as Harness
|
|
|
|
await Effect.runPromise(
|
|
execute(harness, {
|
|
type: "ui.press",
|
|
key: "escape",
|
|
modifiers: { ctrl: true },
|
|
}),
|
|
)
|
|
await Effect.runPromise(execute(harness, { type: "ui.press", key: "x" }))
|
|
|
|
expect(pressed).toEqual([
|
|
["ESCAPE", { ctrl: true }],
|
|
["x", undefined],
|
|
])
|
|
})
|
|
|
|
test("clicks a target at relative coordinates through descendant text", async () => {
|
|
await Effect.runPromise(
|
|
Effect.scoped(
|
|
Effect.gen(function* () {
|
|
const renderer = yield* SimulationRenderer.create({})
|
|
let clicks = 0
|
|
const button = new BoxRenderable(renderer, {
|
|
id: "permission.action.once",
|
|
width: 12,
|
|
height: 1,
|
|
onMouseUp: () => clicks++,
|
|
})
|
|
button.add(new TextRenderable(renderer, { content: "Allow once" }))
|
|
renderer.root.add(button)
|
|
const harness = createHarness(renderer)
|
|
yield* Effect.promise(() => harness.renderOnce())
|
|
|
|
expect(state(harness).elements).toContainEqual(expect.objectContaining({ id: button.id, clickable: true }))
|
|
yield* execute(harness, { type: "ui.click", target: button.num, x: 1, y: 0 })
|
|
expect(clicks).toBe(1)
|
|
|
|
renderer.root.remove(button)
|
|
const error = yield* execute(harness, { type: "ui.click", target: button.num, x: 1, y: 0 }).pipe(Effect.flip)
|
|
expect(error.message).toContain("click target is stale or unavailable")
|
|
}),
|
|
),
|
|
)
|
|
})
|
|
|
|
test("rejects a semantic click when the live identity does not match", async () => {
|
|
await Effect.runPromise(
|
|
Effect.scoped(
|
|
Effect.gen(function* () {
|
|
const renderer = yield* SimulationRenderer.create({})
|
|
let clicks = 0
|
|
const button = new BoxRenderable(renderer, {
|
|
id: "session.permission.action.once",
|
|
width: 12,
|
|
height: 1,
|
|
onMouseUp: () => clicks++,
|
|
})
|
|
SimulationSemantics.bind(() => ({
|
|
instance: "permission-2",
|
|
role: "option",
|
|
label: "Allow once",
|
|
}))(button)
|
|
renderer.root.add(button)
|
|
const harness = createHarness(renderer)
|
|
yield* Effect.promise(() => harness.renderOnce())
|
|
|
|
const error = yield* execute(harness, {
|
|
type: "ui.click",
|
|
target: button.num,
|
|
x: 1,
|
|
y: 0,
|
|
semantic: {
|
|
id: "session.permission.action.once",
|
|
instance: "permission-1",
|
|
element: button.num,
|
|
},
|
|
}).pipe(Effect.flip)
|
|
expect(error.message).toContain("semantic click target is stale or unavailable")
|
|
expect(clicks).toBe(0)
|
|
|
|
yield* execute(harness, {
|
|
type: "ui.click",
|
|
target: button.num,
|
|
x: 1,
|
|
y: 0,
|
|
semantic: {
|
|
id: "session.permission.action.once",
|
|
instance: "permission-2",
|
|
element: button.num,
|
|
},
|
|
})
|
|
expect(clicks).toBe(1)
|
|
}),
|
|
),
|
|
)
|
|
})
|
|
|
|
test("snapshots lazy semantic hierarchy and interaction state", async () => {
|
|
await Effect.runPromise(
|
|
Effect.scoped(
|
|
Effect.gen(function* () {
|
|
const renderer = yield* SimulationRenderer.create({})
|
|
let selected = "once"
|
|
const dialog = new BoxRenderable(renderer, { id: "session.permission" })
|
|
const actions = new BoxRenderable(renderer, { id: "session.permission.actions" })
|
|
const once = new BoxRenderable(renderer, { id: "session.permission.action.once" })
|
|
SimulationSemantics.bind(() => ({
|
|
instance: "permission-1",
|
|
role: "dialog",
|
|
label: "Permission required",
|
|
expanded: false,
|
|
}))(dialog)
|
|
SimulationSemantics.bind(() => ({
|
|
instance: "permission-1",
|
|
role: "listbox",
|
|
label: "Permission choices",
|
|
}))(actions)
|
|
SimulationSemantics.bind(() => ({
|
|
instance: "permission-1",
|
|
role: "option",
|
|
label: "Allow once",
|
|
focused: selected === "once",
|
|
selected: selected === "once",
|
|
disabled: false,
|
|
}))(once)
|
|
renderer.root.add(dialog)
|
|
dialog.add(actions)
|
|
actions.add(once)
|
|
|
|
expect(snapshot(createHarness(renderer))).toEqual({
|
|
format: "opencode-ui-snapshot-v1",
|
|
nodes: [
|
|
{
|
|
id: "session.permission",
|
|
instance: "permission-1",
|
|
role: "dialog",
|
|
label: "Permission required",
|
|
element: dialog.num,
|
|
expanded: false,
|
|
},
|
|
{
|
|
id: "session.permission.actions",
|
|
instance: "permission-1",
|
|
parent: "session.permission",
|
|
role: "listbox",
|
|
label: "Permission choices",
|
|
element: actions.num,
|
|
},
|
|
{
|
|
id: "session.permission.action.once",
|
|
instance: "permission-1",
|
|
parent: "session.permission.actions",
|
|
role: "option",
|
|
label: "Allow once",
|
|
element: once.num,
|
|
focused: true,
|
|
selected: true,
|
|
disabled: false,
|
|
},
|
|
],
|
|
})
|
|
|
|
selected = "reject"
|
|
expect(snapshot(createHarness(renderer)).nodes.at(-1)).toMatchObject({
|
|
id: "session.permission.action.once",
|
|
focused: false,
|
|
selected: false,
|
|
})
|
|
dialog.visible = false
|
|
expect(snapshot(createHarness(renderer)).nodes).toEqual([])
|
|
}),
|
|
),
|
|
)
|
|
})
|
|
|
|
test("rejects duplicate semantic identities", async () => {
|
|
await Effect.runPromise(
|
|
Effect.scoped(
|
|
Effect.gen(function* () {
|
|
const renderer = yield* SimulationRenderer.create({})
|
|
const first = new BoxRenderable(renderer, { id: "duplicate" })
|
|
const second = new BoxRenderable(renderer, { id: "duplicate" })
|
|
const definition = () => ({ role: "option" })
|
|
SimulationSemantics.bind(definition)(first)
|
|
SimulationSemantics.bind(definition)(second)
|
|
renderer.root.add(first)
|
|
renderer.root.add(second)
|
|
|
|
expect(() => snapshot(createHarness(renderer))).toThrow("duplicate semantic UI id: duplicate")
|
|
}),
|
|
),
|
|
)
|
|
})
|
|
|
|
test("validates lazy semantic definitions before returning them", async () => {
|
|
await Effect.runPromise(
|
|
Effect.scoped(
|
|
Effect.gen(function* () {
|
|
const renderer = yield* SimulationRenderer.create({})
|
|
const invalid = new BoxRenderable(renderer, { id: "invalid" })
|
|
SimulationSemantics.bind(() => ({ role: "" }))(invalid)
|
|
renderer.root.add(invalid)
|
|
|
|
expect(() => snapshot(createHarness(renderer))).toThrow()
|
|
}),
|
|
),
|
|
)
|
|
})
|