1import { describe, expect, it } from "vite-plus/test";
2import { ProviderDriverKind } from "@t3tools/contracts";
3
4import type { ComposerCommandItem } from "./ComposerCommandMenu";
5import {
6 searchSlashCommandItems,
7 slashCommandItemsForPromptPosition,
8} from "./composerSlashCommandSearch";
9
10describe("searchSlashCommandItems", () => {
11 const claudeDriver = ProviderDriverKind.make("claudeAgent");
12
13 it("moves exact provider command matches ahead of broader description matches", () => {
14 const items = [
15 {
16 id: "slash:default",
17 type: "slash-command",
18 command: "default",
19 label: "/default",
20 description: "Switch this thread back to normal build mode",
21 },
22 {
23 id: "provider-slash-command:claudeAgent:ui",
24 type: "provider-slash-command",
25 provider: claudeDriver,
26 command: { name: "ui" },
27 label: "/ui",
28 description: "Explore, build, and refine UI.",
29 },
30 {
31 id: "provider-slash-command:claudeAgent:frontend-design",
32 type: "provider-slash-command",
33 provider: claudeDriver,
34 command: { name: "frontend-design" },
35 label: "/frontend-design",
36 description: "Create distinctive, production-grade frontend interfaces",
37 },
38 ] satisfies Array<
39 Extract<ComposerCommandItem, { type: "slash-command" | "provider-slash-command" | "skill" }>
40 >;
41
42 expect(searchSlashCommandItems(items, "ui").map((item) => item.id)).toEqual([
43 "provider-slash-command:claudeAgent:ui",
44 "slash:default",
45 ]);
46 });
47
48 it("supports fuzzy provider command matches", () => {
49 const items = [
50 {
51 id: "provider-slash-command:claudeAgent:gh-fix-ci",
52 type: "provider-slash-command",
53 provider: claudeDriver,
54 command: { name: "gh-fix-ci" },
55 label: "/gh-fix-ci",
56 description: "Fix failing GitHub Actions",
57 },
58 {
59 id: "provider-slash-command:claudeAgent:github",
60 type: "provider-slash-command",
61 provider: claudeDriver,
62 command: { name: "github" },
63 label: "/github",
64 description: "General GitHub help",
65 },
66 ] satisfies Array<
67 Extract<ComposerCommandItem, { type: "slash-command" | "provider-slash-command" | "skill" }>
68 >;
69
70 expect(searchSlashCommandItems(items, "gfc").map((item) => item.id)).toEqual([
71 "provider-slash-command:claudeAgent:gh-fix-ci",
72 ]);
73 });
74
75 it("includes skills by name and description", () => {
76 const items = [
77 {
78 id: "skill:claudeAgent:browser",
79 type: "skill",
80 provider: claudeDriver,
81 skill: {
82 name: "browser",
83 path: "/skills/browser/SKILL.md",
84 enabled: true,
85 shortDescription: "Open and control the in-app browser",
86 },
87 label: "/skill:browser",
88 description: "Open and control the in-app browser",
89 },
90 ] satisfies Array<Extract<ComposerCommandItem, { type: "skill" }>>;
91
92 expect(searchSlashCommandItems(items, "browser").map((item) => item.id)).toEqual([
93 "skill:claudeAgent:browser",
94 ]);
95 expect(searchSlashCommandItems(items, "control").map((item) => item.id)).toEqual([
96 "skill:claudeAgent:browser",
97 ]);
98 });
99
100 it("matches skills by display name", () => {
101 const items = [
102 {
103 id: "skill:claudeAgent:ask-matt",
104 type: "skill",
105 provider: claudeDriver,
106 skill: {
107 name: "ask-matt",
108 displayName: "Ask Matt",
109 path: "/skills/ask-matt/SKILL.md",
110 enabled: true,
111 shortDescription: "Find the right skill or workflow",
112 },
113 label: "/skill:ask-matt",
114 description: "Find the right skill or workflow",
115 },
116 ] satisfies Array<Extract<ComposerCommandItem, { type: "skill" }>>;
117
118 expect(searchSlashCommandItems(items, "ask matt").map((item) => item.id)).toEqual([
119 "skill:claudeAgent:ask-matt",
120 ]);
121 expect(searchSlashCommandItems(items, "/skill:ask-matt").map((item) => item.id)).toEqual([
122 "skill:claudeAgent:ask-matt",
123 ]);
124 });
125
126 it("matches skills by their rendered prefix", () => {
127 const items = [
128 {
129 id: "skill:claudeAgent:browser",
130 type: "skill",
131 provider: claudeDriver,
132 skill: {
133 name: "browser",
134 path: "/skills/browser/SKILL.md",
135 enabled: true,
136 },
137 label: "/skill:browser",
138 description: "Open and control the in-app browser",
139 },
140 ] satisfies Array<Extract<ComposerCommandItem, { type: "skill" }>>;
141
142 expect(searchSlashCommandItems(items, "/skill:brow").map((item) => item.id)).toEqual([
143 "skill:claudeAgent:browser",
144 ]);
145 expect(searchSlashCommandItems(items, "/sk").map((item) => item.id)).toEqual([
146 "skill:claudeAgent:browser",
147 ]);
148 expect(searchSlashCommandItems(items, "/ill")).toEqual([]);
149 });
150
151 it("keeps skills alongside commands for an empty slash query", () => {
152 const items = [
153 {
154 id: "slash:model",
155 type: "slash-command",
156 command: "model",
157 label: "/model",
158 description: "Switch model",
159 },
160 {
161 id: "skill:claudeAgent:unslop",
162 type: "skill",
163 provider: claudeDriver,
164 skill: {
165 name: "unslop",
166 path: "/skills/unslop/SKILL.md",
167 enabled: true,
168 },
169 label: "/skill:unslop",
170 description: "Cut AI tells from writing",
171 },
172 ] satisfies Array<Extract<ComposerCommandItem, { type: "slash-command" | "skill" }>>;
173
174 expect(searchSlashCommandItems(items, "").map((item) => item.id)).toEqual([
175 "slash:model",
176 "skill:claudeAgent:unslop",
177 ]);
178 });
179
180 it("hides provider commands from slash completion after the first message line", () => {
181 const items = [
182 {
183 id: "slash:model",
184 type: "slash-command",
185 command: "model",
186 label: "/model",
187 description: "Switch model",
188 },
189 {
190 id: "provider-slash-command:claudeAgent:compact",
191 type: "provider-slash-command",
192 provider: claudeDriver,
193 command: { name: "compact" },
194 label: "/compact",
195 description: "Compact the conversation",
196 },
197 {
198 id: "skill:claudeAgent:unslop",
199 type: "skill",
200 provider: claudeDriver,
201 skill: {
202 name: "unslop",
203 path: "/skills/unslop/SKILL.md",
204 enabled: true,
205 },
206 label: "/skill:unslop",
207 description: "Cut AI tells from writing",
208 },
209 ] satisfies Array<
210 Extract<ComposerCommandItem, { type: "slash-command" | "provider-slash-command" | "skill" }>
211 >;
212
213 expect(slashCommandItemsForPromptPosition(items, false).map((item) => item.id)).toEqual([
214 "slash:model",
215 "skill:claudeAgent:unslop",
216 ]);
217 expect(slashCommandItemsForPromptPosition(items, true).map((item) => item.id)).toEqual([
218 "slash:model",
219 "provider-slash-command:claudeAgent:compact",
220 "skill:claudeAgent:unslop",
221 ]);
222 });
223});