1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 filterCommandPaletteItems,
5 nextPaletteIndex,
6 type CommandPaletteItem,
7} from "./commandPaletteItems";
8
9function item(
10 key: string,
11 title: string,
12 kind: CommandPaletteItem["kind"],
13 searchTerms: string[] = [],
14): CommandPaletteItem {
15 return { key, title, kind, searchTerms, run: () => {} };
16}
17
18const items = [
19 item("new", "New thread in…", "action", ["project", "create"]),
20 item("settings", "Open settings", "action", ["preferences"]),
21 item("project", "Mobile app", "project", ["/workspaces/mobile", "new thread"]),
22 item("siva:one", "Keyboard shortcuts", "thread", ["Mobile app", "Siva"]),
23 item("mac:one", "Mobile app", "thread", ["Mac"]),
24];
25const emptyMatches = new Set<string>();
26
27describe("filterCommandPaletteItems", () => {
28 it("shows actions and recent threads in their original order when the query is empty", () => {
29 expect(filterCommandPaletteItems(items, "", emptyMatches).map((item) => item.key)).toEqual([
30 "new",
31 "settings",
32 "siva:one",
33 "mac:one",
34 ]);
35 });
36
37 it("matches query tokens across titles and metadata and ranks exact titles first", () => {
38 expect(
39 filterCommandPaletteItems(items, " MOBILE app ", emptyMatches).map((item) => item.key),
40 ).toEqual(["project", "mac:one", "siva:one"]);
41 expect(
42 filterCommandPaletteItems(items, "siva keyboard", emptyMatches).map((item) => item.key),
43 ).toEqual(["siva:one"]);
44 });
45
46 it("supports the desktop actions-only prefix and action aliases", () => {
47 expect(filterCommandPaletteItems(items, ">", emptyMatches).map((item) => item.key)).toEqual([
48 "new",
49 "settings",
50 ]);
51 expect(
52 filterCommandPaletteItems(items, "> preferences", emptyMatches).map((item) => item.key),
53 ).toEqual(["settings"]);
54 expect(
55 filterCommandPaletteItems(items, "> new thread", emptyMatches).map((item) => item.key),
56 ).toEqual(["new"]);
57 });
58
59 it("includes server content matches scoped to the correct environment, except in actions-only mode", () => {
60 const matches = new Set(["siva:one", "project"]);
61 expect(
62 filterCommandPaletteItems(items, "message content", matches).map((item) => item.key),
63 ).toEqual(["siva:one"]);
64 expect(filterCommandPaletteItems(items, "> message content", matches)).toEqual([]);
65 });
66});
67
68describe("nextPaletteIndex", () => {
69 it("wraps arrow navigation in both directions and handles empty results", () => {
70 expect(nextPaletteIndex(0, -1, 3)).toBe(2);
71 expect(nextPaletteIndex(2, 1, 3)).toBe(0);
72 expect(nextPaletteIndex(0, 1, 3)).toBe(1);
73 expect(nextPaletteIndex(0, -1, 0)).toBe(0);
74 expect(nextPaletteIndex(0, 1, 0)).toBe(0);
75 });
76});