| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | |
| 3 | import { |
| 4 | filterCommandPaletteItems, |
| 5 | nextPaletteIndex, |
| 6 | type CommandPaletteItem, |
| 7 | } from "./commandPaletteItems"; |
| 8 | |
| 9 | function 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 | |
| 18 | const 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 | ]; |
| 25 | const emptyMatches = new Set<string>(); |
| 26 | |
| 27 | describe("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 | |
| 68 | describe("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 | }); |