Skip to content
Source
Sign inSign up

modelSearch.test.ts

168 lines4.6 KB

1import { describe, expect, it } from "vite-plus/test";
2​
3import { rankModelPickerSearch, scoreModelPickerSearch } from "./modelSearch.ts";
4​
5describe("scoreModelPickerSearch", () => {
6 it("ranks exact token matches ahead of fuzzier matches", () => {
7 const exactScore = scoreModelPickerSearch(
8 {
9 driverKind: "opencode",
10 providerDisplayName: "opencode",
11 name: "Claude Opus 4.7",
12 subProvider: "GitHub Copilot",
13 },
14 "copilot opus",
15 );
16 const fuzzyScore = scoreModelPickerSearch(
17 {
18 driverKind: "opencode",
19 providerDisplayName: "opencode",
20 name: "Claude Opus 4.7",
21 subProvider: "GitHub Copilot",
22 },
23 "coplt op",
24 );
25​
26 expect(exactScore).not.toBeNull();
27 expect(fuzzyScore).not.toBeNull();
28 expect(exactScore!).toBeLessThan(fuzzyScore!);
29 });
30​
31 it("does not let the favorite boost outrank clearly better textual matches", () => {
32 const favoriteScore = scoreModelPickerSearch(
33 {
34 driverKind: "claudeAgent",
35 providerDisplayName: "Claude",
36 name: "Claude Opus 4.7",
37 isFavorite: true,
38 },
39 "opus 4.7",
40 );
41 const nonFavoriteExactScore = scoreModelPickerSearch(
42 {
43 driverKind: "cursor",
44 providerDisplayName: "Cursor",
45 name: "Opus 4.7",
46 },
47 "opus 4.7",
48 );
49​
50 expect(favoriteScore).not.toBeNull();
51 expect(nonFavoriteExactScore).not.toBeNull();
52 expect(nonFavoriteExactScore!).toBeLessThan(favoriteScore!);
53 });
54});
55​
56type CatalogModel = {
57 driverKind: string;
58 providerDisplayName: string;
59 name: string;
60 subProvider?: string;
61 isFavorite?: boolean;
62};
63​
64function instanceModels(
65 driverKind: string,
66 providerDisplayName: string,
67 names: ReadonlyArray<string>,
68): CatalogModel[] {
69 return names.map((name) => ({ driverKind, providerDisplayName, name }));
71​
72const catalog: CatalogModel[] = [
73 ...instanceModels("claudeAgent", "Claude", [
74 "Claude Opus 5.5",
75 "Claude Sonnet 5.5",
76 "Claude Fable 5.1",
77 "Claude Fable 5",
78 "Claude Opus 5",
79 "Claude Opus 4.8",
80 "Claude Opus 4.7",
81 "Claude Opus 4.6",
82 "Claude Opus 4.5",
83 "Claude Sonnet 5",
84 "Claude Sonnet 4.6",
85 "Claude Haiku 5.5",
86 "Claude Haiku 4.5",
87 ]),
88 ...instanceModels("codex", "Codex", [
89 "GPT-6.1 Sol",
90 "GPT-6 Astra",
91 "GPT-6 Luna",
92 "GPT-6 Sol",
93 "GPT-5.6 Sol",
94 "GPT-5.6 Terra",
95 "GPT-5.6 Luna",
96 "GPT-5.5",
97 ]),
98 ...instanceModels("grok", "Grok", ["Grok 4.7", "Grok 4.6", "Grok 4.5"]),
99 ...instanceModels("antigravity", "Antigravity", [
100 "Gemini 3.8 Flash (High)",
101 "Gemini 3.8 Flash (Medium)",
102 "Gemini 3.8 Flash (Low)",
103 "Gemini 3.7 Flash (High)",
104 "Gemini 3.7 Flash (Medium)",
105 "Gemini 3.7 Flash (Low)",
106 "Gemini 3.6 Flash (High)",
107 "Gemini 3.6 Flash (Medium)",
108 "Gemini 3.6 Flash (Low)",
109 "Gemini 3.1 Pro (High)",
110 "Gemini 3.1 Pro (Low)",
111 ]),
112 {
113 driverKind: "opencode",
114 providerDisplayName: "OpenCode",
115 name: "Kimi K3",
116 subProvider: "Moonshot",
117 },
118 {
119 driverKind: "opencode",
120 providerDisplayName: "OpenCode",
121 name: "Kimi K2.5",
122 subProvider: "Moonshot",
123 },
124 {
125 driverKind: "opencode",
126 providerDisplayName: "OpenCode",
127 name: "Kimi K2",
128 subProvider: "Moonshot",
129 },
130];
131​
132function search(query: string, models: ReadonlyArray<CatalogModel> = catalog): string[] {
133 return rankModelPickerSearch(models, query, (model) => model).map((model) => model.name);
135​
136describe("rankModelPickerSearch", () => {
137 it("keeps newest first when a version token narrows the family", () => {
138 expect(search("opus 4")).toEqual([
139 "Claude Opus 4.8",
140 "Claude Opus 4.7",
141 "Claude Opus 4.6",
142 "Claude Opus 4.5",
143 ]);
144 expect(search("opus 5")).toEqual(["Claude Opus 5.5", "Claude Opus 5", "Claude Opus 4.5"]);
145 expect(search("opus 5.")).toEqual(["Claude Opus 5.5"]);
146 });
147​
148 it("still ranks an exact name above newer models that extend it", () => {
149 expect(search("gpt-6 sol").slice(0, 2)).toEqual(["GPT-6 Sol", "GPT-6.1 Sol"]);
150 expect(search("claude opus 5").slice(0, 2)).toEqual(["Claude Opus 5", "Claude Opus 5.5"]);
151 expect(search("Grok 4.6")[0]).toBe("Grok 4.6");
152 });
153​
154 it("matches the provider when the name does not", () => {
155 expect(search("moonshot")).toEqual(["Kimi K3", "Kimi K2.5", "Kimi K2"]);
156 });
157​
158 it("ranks a favorite above newer models in the same family", () => {
159 const models = catalog.map((model) =>
160 model.name === "Claude Opus 4.5" ? { ...model, isFavorite: true } : model,
161 );
162 expect(search("opus", models).slice(0, 3)).toEqual([
163 "Claude Opus 4.5",
164 "Claude Opus 5.5",
165 "Claude Opus 5",
166 ]);
167 });
168});