| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | |
| 3 | import type { ServerProviderSkill } from "@t3tools/contracts"; |
| 4 | |
| 5 | import { searchProviderSkills } from "./providerSkillSearch"; |
| 6 | |
| 7 | function 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 | |
| 15 | describe("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 | }); |