mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 11:39:41 +03:00
fix(tui): honor falsy dialog current values (#47782)
Co-authored-by: nexxeln <95541290+nexxeln@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
f094a10645
commit
2bf6a9813a
@@ -135,7 +135,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||
() => props.current,
|
||||
(current) => {
|
||||
if (props.focusCurrent === false) return
|
||||
if (current) {
|
||||
if (current !== undefined) {
|
||||
const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current))
|
||||
if (currentIndex >= 0) {
|
||||
setStore("selected", currentIndex)
|
||||
@@ -309,7 +309,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||
scrollAfterLayout(true, option.value)
|
||||
return
|
||||
}
|
||||
if (!current || props.focusCurrent === false) return
|
||||
if (current === undefined || props.focusCurrent === false) return
|
||||
const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current))
|
||||
if (currentIndex < 0) return
|
||||
moveTo(currentIndex, true)
|
||||
|
||||
@@ -82,10 +82,10 @@ async function renderSelect(
|
||||
return app
|
||||
}
|
||||
|
||||
async function mountSelect(
|
||||
async function mountSelect<T>(
|
||||
root: string,
|
||||
initial: DialogSelectOption<string>[],
|
||||
current?: string,
|
||||
initial: DialogSelectOption<T>[],
|
||||
current?: T,
|
||||
focusCurrent?: boolean,
|
||||
select?: { flat?: boolean },
|
||||
) {
|
||||
@@ -108,13 +108,16 @@ async function mountSelect(
|
||||
import("../../../src/ui/toast"),
|
||||
])
|
||||
|
||||
const selected: string[] = []
|
||||
const moved: string[] = []
|
||||
let replaceOptions!: (options: DialogSelectOption<string>[]) => void
|
||||
const selected: T[] = []
|
||||
const moved: T[] = []
|
||||
let replaceOptions!: (options: DialogSelectOption<T>[]) => void
|
||||
let replaceCurrent!: (value: T | undefined) => void
|
||||
|
||||
function Harness() {
|
||||
const [options, setOptions] = createSignal(initial)
|
||||
replaceOptions = setOptions
|
||||
const [value, setCurrent] = createSignal(current)
|
||||
replaceCurrent = (value) => setCurrent(() => value)
|
||||
|
||||
function Fixture() {
|
||||
const dialog = useDialog()
|
||||
@@ -123,7 +126,7 @@ async function mountSelect(
|
||||
<DialogSelect
|
||||
title="Mutable options"
|
||||
options={options()}
|
||||
current={current}
|
||||
current={value()}
|
||||
focusCurrent={focusCurrent}
|
||||
flat={select?.flat}
|
||||
onMove={(option) => moved.push(option.value)}
|
||||
@@ -155,7 +158,7 @@ async function mountSelect(
|
||||
app.renderer.start()
|
||||
await app.waitForFrame((frame) => frame.includes("Mutable options"))
|
||||
await app.waitFor(() => app.renderer.currentFocusedEditor instanceof InputRenderable)
|
||||
return { app, moved, replaceOptions, selected }
|
||||
return { app, moved, replaceOptions, replaceCurrent, selected }
|
||||
}
|
||||
|
||||
test("budgets option content for constrained and full-width large dialogs", () => {
|
||||
@@ -442,6 +445,58 @@ test("keeps the current option selected when options reorder", async () => {
|
||||
}
|
||||
})
|
||||
|
||||
test.each([false, 0, "", null, "current", undefined])("focuses current %p when it changes", async (current) => {
|
||||
await using tmp = await tmpdir()
|
||||
const select = await mountSelect<string | typeof current>(
|
||||
tmp.path,
|
||||
[
|
||||
{ title: "First", value: "first" },
|
||||
{ title: "Current", value: current === undefined ? "current" : current },
|
||||
],
|
||||
"first",
|
||||
)
|
||||
|
||||
try {
|
||||
select.replaceCurrent(current)
|
||||
await select.app.waitForVisualIdle()
|
||||
select.app.mockInput.pressEnter()
|
||||
await select.app.waitFor(() => select.selected.length === 1)
|
||||
|
||||
expect(select.selected).toEqual([current === undefined ? "first" : current])
|
||||
} finally {
|
||||
select.app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test.each([false, 0, "", null, "current", undefined])(
|
||||
"restores current %p after clearing a filter",
|
||||
async (current) => {
|
||||
await using tmp = await tmpdir()
|
||||
const select = await mountSelect<string | typeof current>(
|
||||
tmp.path,
|
||||
[
|
||||
...Array.from({ length: 6 }, (_, index) => ({ title: `Item-${index}`, value: `item-${index}` })),
|
||||
{ title: "Current", value: current === undefined ? "current" : current },
|
||||
],
|
||||
current,
|
||||
)
|
||||
|
||||
try {
|
||||
await select.app.mockInput.typeText("Item-0")
|
||||
await select.app.waitForFrame((frame) => frame.includes("Item-0") && !frame.includes("Item-1"))
|
||||
select.app.mockInput.pressKey("c", { ctrl: true })
|
||||
await select.app.waitForVisualIdle()
|
||||
select.app.mockInput.pressEnter()
|
||||
await select.app.waitFor(() => select.selected.length === 1)
|
||||
|
||||
expect(select.selected).toEqual([current === undefined ? "item-0" : current])
|
||||
expect(select.app.captureCharFrame().includes("Current")).toBe(current !== undefined)
|
||||
} finally {
|
||||
select.app.renderer.destroy()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
test("shows no-match and still closes after a flat filter goes empty", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const select = await mountSelect(
|
||||
|
||||
Reference in New Issue
Block a user