mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-09 22:25:02 +03:00
df39a70a1d
Co-authored-by: jlongster <17031+jlongster@users.noreply.github.com>
158 lines
5.0 KiB
TypeScript
158 lines
5.0 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import { createComputed, createRoot, createSignal } from "solid-js"
|
|
import type { CommandPaletteEntry } from "@/shell/commands/palette"
|
|
import { createCommandPaletteSearch } from "@/shell/commands/search"
|
|
|
|
const copy: CommandPaletteEntry = {
|
|
id: "command:session.copyID",
|
|
type: "command",
|
|
title: "Copy Session ID",
|
|
category: "Commands",
|
|
}
|
|
|
|
const file: CommandPaletteEntry = { id: "file:copy.txt", type: "file", title: "copy.txt", category: "Files" }
|
|
|
|
const setting: CommandPaletteEntry = {
|
|
id: "setting:copy",
|
|
type: "setting",
|
|
title: "Copy on select",
|
|
category: "Settings",
|
|
}
|
|
|
|
const session: CommandPaletteEntry = {
|
|
id: "session:copy",
|
|
type: "session",
|
|
title: "Copy session",
|
|
category: "Sessions",
|
|
updated: 2,
|
|
}
|
|
|
|
const tab: CommandPaletteEntry = { ...session, id: "session:tab", title: "Copy tab", updated: 1 }
|
|
|
|
describe("command palette search", () => {
|
|
test("matches commands synchronously and cancels obsolete requests", () => {
|
|
const signals: AbortSignal[] = []
|
|
|
|
const root = createRoot((dispose) => {
|
|
const [query, setQuery] = createSignal("")
|
|
|
|
const search = createCommandPaletteSearch({
|
|
query,
|
|
items: (text) => (text === "copy session" ? [copy] : []),
|
|
sources: [
|
|
(_text, signal) => {
|
|
signals.push(signal)
|
|
|
|
return new Promise<CommandPaletteEntry[]>(() => {})
|
|
},
|
|
],
|
|
})
|
|
|
|
return { search, setQuery, dispose }
|
|
})
|
|
|
|
root.setQuery(" copy session ")
|
|
expect(root.search.items()).toEqual([copy])
|
|
expect(root.search.loading()).toBe(true)
|
|
expect(signals[0].aborted).toBe(true)
|
|
root.setQuery("no match")
|
|
expect(root.search.items()).toEqual([])
|
|
expect(signals[1].aborted).toBe(true)
|
|
root.dispose()
|
|
expect(signals[2].aborted).toBe(true)
|
|
})
|
|
|
|
test("lists commands first, then sessions with open tabs leading, settings and files, publishes each source independently, and drops stale results on a new query", async () => {
|
|
const files = Promise.withResolvers<CommandPaletteEntry[]>()
|
|
const sessions = Promise.withResolvers<CommandPaletteEntry[]>()
|
|
const fileVisible = Promise.withResolvers<void>()
|
|
const sessionVisible = Promise.withResolvers<void>()
|
|
|
|
const root = createRoot((dispose) => {
|
|
const [query, setQuery] = createSignal("copy")
|
|
|
|
const search = createCommandPaletteSearch({
|
|
query,
|
|
items: () => [copy, setting],
|
|
sources: [() => sessions.promise, () => files.promise],
|
|
open: (entry) => entry.id === tab.id,
|
|
})
|
|
|
|
createComputed(() => {
|
|
if (search.items().some((entry) => entry.id === file.id)) fileVisible.resolve()
|
|
|
|
if (search.items().some((entry) => entry.id === session.id)) sessionVisible.resolve()
|
|
})
|
|
|
|
return { search, setQuery, dispose }
|
|
})
|
|
|
|
expect(root.search.items()).toEqual([copy, setting])
|
|
files.resolve([file])
|
|
await fileVisible.promise
|
|
expect(root.search.items()).toEqual([copy, setting, file])
|
|
expect(root.search.loading()).toBe(true)
|
|
sessions.resolve([session, tab])
|
|
await sessionVisible.promise
|
|
expect(root.search.items()).toEqual([copy, tab, session, setting, file])
|
|
expect(root.search.loading()).toBe(false)
|
|
root.setQuery("new query")
|
|
expect(root.search.items()).toEqual([copy, setting])
|
|
root.dispose()
|
|
})
|
|
|
|
test("failed searches do not hide commands or successful sources", async () => {
|
|
const settled = Promise.withResolvers<void>()
|
|
|
|
const root = createRoot((dispose) => {
|
|
const search = createCommandPaletteSearch({
|
|
query: () => "copy",
|
|
items: () => [copy],
|
|
sources: [() => Promise.reject(new Error("offline")), () => Promise.resolve([file])],
|
|
})
|
|
|
|
createComputed(() => {
|
|
if (!search.loading()) settled.resolve()
|
|
})
|
|
|
|
return { search, dispose }
|
|
})
|
|
|
|
await settled.promise
|
|
expect(root.search.items()).toEqual([copy, file])
|
|
root.dispose()
|
|
})
|
|
|
|
test("filters previous remote results synchronously while a refined query is pending", async () => {
|
|
const first = Promise.withResolvers<CommandPaletteEntry[]>()
|
|
const second = Promise.withResolvers<CommandPaletteEntry[]>()
|
|
const otherFile: CommandPaletteEntry = { id: "file: readme.md", type: "file", title: "readme.md", category: "Files" }
|
|
const ready = Promise.withResolvers<void>()
|
|
|
|
const root = createRoot((dispose) => {
|
|
const [query, setQuery] = createSignal("c")
|
|
|
|
const search = createCommandPaletteSearch({
|
|
query,
|
|
items: (q) => (q === "c" ? [copy] : []),
|
|
sources: [(q) => (q === "c" ? first.promise : second.promise)],
|
|
})
|
|
|
|
createComputed(() => {
|
|
if (search.items().some((entry) => entry.id === file.id)) ready.resolve()
|
|
})
|
|
|
|
return { search, setQuery, dispose }
|
|
})
|
|
|
|
first.resolve([file, otherFile])
|
|
await ready.promise
|
|
expect(root.search.items()).toEqual([copy, file, otherFile])
|
|
|
|
root.setQuery("copy")
|
|
expect(root.search.loading()).toBe(true)
|
|
expect(root.search.items()).toEqual([file])
|
|
root.dispose()
|
|
})
|
|
})
|