test: share chat model select state fixtures

This commit is contained in:
Peter Steinberger
2026-04-20 17:23:29 +01:00
parent 3ed4b69b38
commit cb2fc70741

View File

@@ -10,101 +10,113 @@ import {
DEFAULT_CHAT_MODEL_CATALOG,
} from "./chat-model.test-helpers.ts";
type ChatModelStateInput = Parameters<typeof resolveChatModelSelectState>[0];
type ResolvedChatModelState = ReturnType<typeof resolveChatModelSelectState>;
function createChatModelState(
params: Partial<Omit<ChatModelStateInput, "sessionKey">> = {},
): ChatModelStateInput {
return {
sessionKey: "main",
chatModelOverrides: {},
chatModelCatalog: [],
sessionsResult: createSessionsListResult({ model: null, modelProvider: null }),
...params,
};
}
function expectOptionValues(
resolved: ResolvedChatModelState,
params: { include?: string[]; exclude?: string[] },
) {
const values = resolved.options.map((option) => option.value);
for (const value of params.include ?? []) {
expect(values).toContain(value);
}
for (const value of params.exclude ?? []) {
expect(values).not.toContain(value);
}
}
describe("chat-model-select-state", () => {
it("uses the server-qualified value when the active session provider is present", () => {
const state = {
sessionKey: "main",
chatModelOverrides: {},
const state = createChatModelState({
chatModelCatalog: createModelCatalog(DEEPSEEK_CHAT_MODEL),
sessionsResult: createSessionsListResult({
model: "deepseek-chat",
modelProvider: "deepseek",
}),
};
});
expect(resolveChatModelOverrideValue(state)).toBe("deepseek/deepseek-chat");
});
it("falls back to the server-qualified value when catalog lookup fails", () => {
const state = {
sessionKey: "main",
chatModelOverrides: {},
chatModelCatalog: [],
const state = createChatModelState({
sessionsResult: createSessionsListResult({
model: "gpt-5-mini",
modelProvider: "openai",
}),
};
});
expect(resolveChatModelOverrideValue(state)).toBe("openai/gpt-5-mini");
});
it("normalizes cached bare overrides to the matching catalog option", () => {
const state = {
sessionKey: "main",
const state = createChatModelState({
chatModelOverrides: { main: { kind: "raw", value: "gpt-5-mini" } },
chatModelCatalog: createModelCatalog(...DEFAULT_CHAT_MODEL_CATALOG),
sessionsResult: createSessionsListResult({ model: null, modelProvider: null }),
} as const;
});
const resolved = resolveChatModelSelectState(state);
expect(resolved.currentOverride).toBe("openai/gpt-5-mini");
expect(resolved.options.map((option) => option.value)).toContain("openai/gpt-5-mini");
expect(resolved.options.map((option) => option.value)).not.toContain("gpt-5-mini");
expectOptionValues(resolved, { include: ["openai/gpt-5-mini"], exclude: ["gpt-5-mini"] });
});
it("prefers catalog provider matches over stale session providers", () => {
const state = {
sessionKey: "main",
chatModelOverrides: {},
const state = createChatModelState({
chatModelCatalog: createModelCatalog(DEEPSEEK_CHAT_MODEL),
sessionsResult: createSessionsListResult({
model: "deepseek-chat",
modelProvider: "zai",
}),
};
});
expect(resolveChatModelSelectState(state).currentOverride).toBe("deepseek/deepseek-chat");
});
it("preserves already-qualified active-session models when the provider is stale and the catalog is empty", () => {
const state = {
sessionKey: "main",
chatModelOverrides: {},
chatModelCatalog: [],
const state = createChatModelState({
sessionsResult: createSessionsListResult({
model: "openai/gpt-5-mini",
modelProvider: "zai",
}),
};
});
const resolved = resolveChatModelSelectState(state);
expect(resolved.currentOverride).toBe("openai/gpt-5-mini");
expect(resolved.options.map((option) => option.value)).toContain("openai/gpt-5-mini");
expect(resolved.options.map((option) => option.value)).not.toContain("zai/openai/gpt-5-mini");
expectOptionValues(resolved, {
include: ["openai/gpt-5-mini"],
exclude: ["zai/openai/gpt-5-mini"],
});
});
it("builds picker options without introducing a bare duplicate", () => {
const state = {
sessionKey: "main",
chatModelOverrides: {},
const state = createChatModelState({
chatModelCatalog: createModelCatalog(...DEFAULT_CHAT_MODEL_CATALOG),
sessionsResult: createSessionsListResult({
model: "gpt-5-mini",
modelProvider: "openai",
}),
};
});
const resolved = resolveChatModelSelectState(state);
expect(resolved.currentOverride).toBe("openai/gpt-5-mini");
expect(resolved.options.map((option) => option.value)).toContain("openai/gpt-5-mini");
expect(resolved.options.map((option) => option.value)).not.toContain("gpt-5-mini");
expectOptionValues(resolved, { include: ["openai/gpt-5-mini"], exclude: ["gpt-5-mini"] });
});
it("uses catalog names for the default label and matching picker options", () => {
const state = {
sessionKey: "main",
chatModelOverrides: {},
const state = createChatModelState({
chatModelCatalog: createModelCatalog({
id: "moonshotai/kimi-k2.5",
alias: "Kimi K2.5 (NVIDIA)",
@@ -117,7 +129,7 @@ describe("chat-model-select-state", () => {
defaultsModel: "moonshotai/kimi-k2.5",
defaultsProvider: "nvidia",
}),
};
});
const resolved = resolveChatModelSelectState(state);
expect(resolved.currentOverride).toBe("nvidia/moonshotai/kimi-k2.5");
@@ -129,9 +141,7 @@ describe("chat-model-select-state", () => {
});
it("disambiguates duplicate friendly names in picker options and default labels", () => {
const state = {
sessionKey: "main",
chatModelOverrides: {},
const state = createChatModelState({
chatModelCatalog: createModelCatalog(
{
id: "claude-3-7-sonnet",
@@ -150,7 +160,7 @@ describe("chat-model-select-state", () => {
defaultsModel: "claude-3-7-sonnet",
defaultsProvider: "openrouter",
}),
};
});
const resolved = resolveChatModelSelectState(state);
expect(resolved.currentOverride).toBe("anthropic/claude-3-7-sonnet");
@@ -166,9 +176,7 @@ describe("chat-model-select-state", () => {
});
it("falls back to id and provider when duplicate names share the same provider", () => {
const state = {
sessionKey: "main",
chatModelOverrides: {},
const state = createChatModelState({
chatModelCatalog: createModelCatalog(
{
id: "claude-3-7-sonnet",
@@ -187,7 +195,7 @@ describe("chat-model-select-state", () => {
defaultsModel: "claude-3-7-sonnet-thinking",
defaultsProvider: "anthropic",
}),
};
});
const resolved = resolveChatModelSelectState(state);
expect(resolved.currentOverride).toBe("anthropic/claude-3-7-sonnet");