1import {
2 ANTIGRAVITY_DEFAULT_MODEL,
3 ProviderDriverKind,
4 ProviderInstanceId,
5 type ServerProvider,
6} from "@t3tools/contracts";
7import { describe, expect, it } from "vite-plus/test";
8
9import { deriveProviderInstanceEntries } from "../../providerInstances";
10import {
11 adjacentModelPickerProvider,
12 resolveModelPickerSelectedModel,
13 shouldIncludeModelPickerOption,
14 shouldOfferModelPickerSetup,
15} from "./ModelPickerContent";
16
17function entry(status: ServerProvider["status"], driver = "opencode") {
18 return deriveProviderInstanceEntries([
19 {
20 instanceId: ProviderInstanceId.make(`${driver}_work`),
21 driver: ProviderDriverKind.make(driver),
22 enabled: true,
23 installed: true,
24 version: null,
25 status,
26 auth: { status: "authenticated" },
27 checkedAt: "2026-08-28T00:00:00.000Z",
28 models: [],
29 slashCommands: [],
30 skills: [],
31 },
32 ])[0]!;
33}
34
35describe("shouldIncludeModelPickerOption", () => {
36 it.each(["ready", "error"] as const)(
37 "never offers the internal Antigravity default marker as a model when %s",
38 (status) => {
39 const providerEntry = entry(status, "antigravity");
40 expect(
41 shouldIncludeModelPickerOption({
42 entry: providerEntry,
43 option: {
44 slug: ANTIGRAVITY_DEFAULT_MODEL,
45 name: ANTIGRAVITY_DEFAULT_MODEL,
46 isUnavailable: true,
47 },
48 activeInstanceId: providerEntry.instanceId,
49 activeModel: ANTIGRAVITY_DEFAULT_MODEL,
50 }),
51 ).toBe(false);
52 },
53 );
54
55 it.each([
56 ["opencode", "error"],
57 ["opencode", "warning"],
58 ["antigravity", "error"],
59 ["antigravity", "warning"],
60 ] as const)(
61 "keeps only the active synthetic %s row when the provider status is %s",
62 (driver, status) => {
63 const providerEntry = entry(status, driver);
64 const activeInstanceId = providerEntry.instanceId;
65 const activeModel = "missing-model";
66
67 expect(
68 shouldIncludeModelPickerOption({
69 entry: providerEntry,
70 option: {
71 slug: activeModel,
72 name: activeModel,
73 isUnavailable: true,
74 },
75 activeInstanceId,
76 activeModel,
77 }),
78 ).toBe(true);
79 expect(
80 shouldIncludeModelPickerOption({
81 entry: providerEntry,
82 option: { slug: "stale/model", name: "Stale model" },
83 activeInstanceId,
84 activeModel,
85 }),
86 ).toBe(false);
87 expect(
88 shouldIncludeModelPickerOption({
89 entry: providerEntry,
90 option: {
91 slug: "other/missing",
92 name: "Other missing",
93 isUnavailable: true,
94 },
95 activeInstanceId,
96 activeModel,
97 }),
98 ).toBe(false);
99 expect(
100 shouldIncludeModelPickerOption({
101 entry: providerEntry,
102 option: { slug: activeModel, name: activeModel, isUnavailable: true },
103 activeInstanceId: ProviderInstanceId.make(`${driver}_personal`),
104 activeModel,
105 }),
106 ).toBe(false);
107 },
108 );
109});
110
111describe("resolveModelPickerSelectedModel", () => {
112 it("follows the catalog default for the marker but keeps an explicit native model", () => {
113 const driverKind = ProviderDriverKind.make("antigravity");
114 const previousOptions = [
115 { slug: "gemini-fast", name: "Gemini Fast", aliases: [ANTIGRAVITY_DEFAULT_MODEL] },
116 { slug: "gemini-pro", name: "Gemini Pro" },
117 ];
118 const nextOptions = [
119 { slug: "gemini-fast", name: "Gemini Fast" },
120 { slug: "gemini-pro", name: "Gemini Pro", aliases: [ANTIGRAVITY_DEFAULT_MODEL] },
121 ];
122
123 expect(
124 resolveModelPickerSelectedModel({
125 driverKind,
126 model: ANTIGRAVITY_DEFAULT_MODEL,
127 options: previousOptions,
128 })?.slug,
129 ).toBe("gemini-fast");
130 expect(
131 resolveModelPickerSelectedModel({
132 driverKind,
133 model: ANTIGRAVITY_DEFAULT_MODEL,
134 options: nextOptions,
135 })?.slug,
136 ).toBe("gemini-pro");
137 expect(
138 resolveModelPickerSelectedModel({
139 driverKind,
140 model: "gemini-fast",
141 options: nextOptions,
142 })?.slug,
143 ).toBe("gemini-fast");
144 });
145
146 it("does not guess the default from the first model in a catalog", () => {
147 expect(
148 resolveModelPickerSelectedModel({
149 driverKind: ProviderDriverKind.make("antigravity"),
150 model: ANTIGRAVITY_DEFAULT_MODEL,
151 options: [{ slug: "gemini-fast", name: "Gemini Fast" }],
152 }),
153 ).toBeUndefined();
154 });
155});
156
157describe("shouldOfferModelPickerSetup", () => {
158 const availableModel = { slug: "gemini-3.1-pro", name: "Gemini 3.1 Pro" };
159
160 it("offers setup before an Antigravity account has models", () => {
161 expect(shouldOfferModelPickerSetup(entry("error", "antigravity"), [])).toBe(true);
162 });
163
164 it("offers setup after sign-out even if a model remains cached", () => {
165 const providerEntry = entry("ready", "antigravity");
166 expect(
167 shouldOfferModelPickerSetup(
168 {
169 ...providerEntry,
170 snapshot: { ...providerEntry.snapshot, auth: { status: "unauthenticated" } },
171 },
172 [availableModel],
173 ),
174 ).toBe(true);
175 });
176
177 it("offers setup when the only model is an unavailable saved selection", () => {
178 expect(
179 shouldOfferModelPickerSetup(entry("ready", "antigravity"), [
180 { ...availableModel, isUnavailable: true },
181 ]),
182 ).toBe(true);
183 });
184
185 it("does not offer setup for a ready account with available models", () => {
186 expect(shouldOfferModelPickerSetup(entry("ready", "antigravity"), [availableModel])).toBe(
187 false,
188 );
189 });
190
191 it("does not restore a disabled provider while its status snapshot is stale", () => {
192 expect(
193 shouldOfferModelPickerSetup({ ...entry("error", "antigravity"), enabled: false }, []),
194 ).toBe(false);
195 });
196
197 it("keeps providers without integrated setup on their existing path", () => {
198 expect(shouldOfferModelPickerSetup(entry("error", "codex"), [])).toBe(false);
199 });
200
201 it("uses the environment's setup capability for other drivers", () => {
202 const providerEntry = entry("error", "custom_driver");
203 expect(
204 shouldOfferModelPickerSetup(
205 {
206 ...providerEntry,
207 snapshot: {
208 ...providerEntry.snapshot,
209 setup: { canAuthenticate: true, canInstall: false },
210 },
211 },
212 [],
213 ),
214 ).toBe(true);
215 });
216});
217
218describe("adjacentModelPickerProvider", () => {
219 const codex = entry("ready", "codex");
220 const claude = entry("ready", "claudeAgent");
221 const unavailable = entry("error");
222 const input = {
223 entries: [codex, unavailable, claude],
224 disabledInstanceIds: undefined,
225 selectableUnavailableInstanceIds: undefined,
226 };
227
228 it("wraps through favorites and ready instances, skipping unavailable providers", () => {
229 expect(
230 adjacentModelPickerProvider({ ...input, selectedInstanceId: codex.instanceId, direction: 1 }),
231 ).toBe(claude.instanceId);
232 expect(
233 adjacentModelPickerProvider({ ...input, selectedInstanceId: "favorites", direction: -1 }),
234 ).toBe(claude.instanceId);
235 expect(
236 adjacentModelPickerProvider({
237 ...input,
238 selectedInstanceId: claude.instanceId,
239 direction: 1,
240 }),
241 ).toBe("favorites");
242 });
243
244 it("keeps thread locks and the selected unavailable catalog", () => {
245 expect(
246 adjacentModelPickerProvider({
247 ...input,
248 disabledInstanceIds: new Set([claude.instanceId]),
249 selectedInstanceId: codex.instanceId,
250 direction: 1,
251 }),
252 ).toBe("favorites");
253 expect(
254 adjacentModelPickerProvider({
255 ...input,
256 selectableUnavailableInstanceIds: new Set([unavailable.instanceId]),
257 selectedInstanceId: codex.instanceId,
258 direction: 1,
259 }),
260 ).toBe(unavailable.instanceId);
261 });
262
263 it("handles an empty catalog and a removed selection in either direction", () => {
264 expect(
265 adjacentModelPickerProvider({
266 ...input,
267 entries: [],
268 selectedInstanceId: codex.instanceId,
269 direction: -1,
270 }),
271 ).toBe("favorites");
272 expect(
273 adjacentModelPickerProvider({
274 ...input,
275 selectedInstanceId: unavailable.instanceId,
276 direction: 1,
277 }),
278 ).toBe("favorites");
279 expect(
280 adjacentModelPickerProvider({
281 ...input,
282 selectedInstanceId: unavailable.instanceId,
283 direction: -1,
284 }),
285 ).toBe(claude.instanceId);
286 });
287});