1import { describe, expect, it } from "vite-plus/test";
2
3import type { ServerProviderSkill } from "@t3tools/contracts";
4
5import { searchProviderSkills } from "./providerSkillSearch";
6
7function makeSkill(input: Partial<ServerProviderSkill> & Pick<ServerProviderSkill, "name">) {
8 return {
9 path: `/tmp/${input.name}/SKILL.md`,
10 enabled: true,
11 ...input,
12 } satisfies ServerProviderSkill;
13}
14
15describe("searchProviderSkills", () => {
16 it.each(["$", "€", "£", "¥", "₹", "₩", "₿", "𑿝"])(
17 "searches skills with a %s prefix",
18 (prefix) => {
19 const skill = makeSkill({ name: "review" });
20 expect(searchProviderSkills([skill], `${prefix}review`)).toEqual([skill]);
21 },
22 );
23
24 it("moves exact ui matches ahead of broader ui matches", () => {
25 const skills = [
26 makeSkill({
27 name: "agent-browser",
28 displayName: "Agent Browser",
29 shortDescription: "Browser automation CLI for AI agents",
30 }),
31 makeSkill({
32 name: "building-native-ui",
33 displayName: "Building Native Ui",
34 shortDescription: "Complete guide for building beautiful apps with Expo Router",
35 }),
36 makeSkill({
37 name: "ui",
38 displayName: "Ui",
39 shortDescription: "Explore, build, and refine UI.",
40 }),
41 ];
42
43 expect(searchProviderSkills(skills, "ui").map((skill) => skill.name)).toEqual([
44 "ui",
45 "building-native-ui",
46 ]);
47 });
48
49 it("uses fuzzy ranking for abbreviated queries", () => {
50 const skills = [
51 makeSkill({ name: "gh-fix-ci", displayName: "Gh Fix Ci" }),
52 makeSkill({ name: "github", displayName: "Github" }),
53 makeSkill({ name: "agent-browser", displayName: "Agent Browser" }),
54 ];
55
56 expect(searchProviderSkills(skills, "gfc").map((skill) => skill.name)).toEqual(["gh-fix-ci"]);
57 });
58
59 it("keeps user-only skills and omits agent-only ones", () => {
60 const skills = [
61 makeSkill({ name: "re-release-version", userInvocationOnly: true }),
62 makeSkill({ name: "release-context", userInvocable: false }),
63 makeSkill({ name: "release-version" }),
64 ];
65
66 expect(searchProviderSkills(skills, "release").map((skill) => skill.name)).toEqual([
67 "release-version",
68 "re-release-version",
69 ]);
70 });
71
72 it("omits disabled skills from results", () => {
73 const skills = [
74 makeSkill({ name: "ui", displayName: "Ui", enabled: false }),
75 makeSkill({ name: "frontend-design", displayName: "Frontend Design" }),
76 ];
77
78 expect(searchProviderSkills(skills, "ui").map((skill) => skill.name)).toEqual([]);
79 });
80
81 it("returns every enabled skill for an empty query", () => {
82 const skills = [
83 makeSkill({ name: "unslop" }),
84 makeSkill({ name: "browser" }),
85 makeSkill({ name: "disabled", enabled: false }),
86 ];
87
88 expect(searchProviderSkills(skills, "").map((skill) => skill.name)).toEqual([
89 "unslop",
90 "browser",
91 ]);
92 });
93
94 it("returns the first enabled definition for each skill name", () => {
95 const skills = [
96 makeSkill({ name: "branch-audit", path: "/Users/matt/.codex/skills/branch-audit/SKILL.md" }),
97 makeSkill({ name: "browser" }),
98 makeSkill({ name: "branch-audit", path: "/Users/matt/.agents/skills/branch-audit/SKILL.md" }),
99 ];
100
101 expect(searchProviderSkills(skills, "").map((skill) => skill.path)).toEqual([
102 "/Users/matt/.codex/skills/branch-audit/SKILL.md",
103 "/tmp/browser/SKILL.md",
104 ]);
105 });
106});