import { Schema } from "effect"; import { describe, expect, it } from "vitest"; import { AppSettingsSchema, DEFAULT_TIMESTAMP_FORMAT, getAppModelOptions, normalizeCustomModelSlugs, resolveAppModelSelection, } from "./appSettings"; describe("normalizeCustomModelSlugs", () => { it("normalizes aliases, removes built-ins, and deduplicates values", () => { expect( normalizeCustomModelSlugs([ " custom/internal-model ", "gpt-5.3-codex", "5.3", "custom/internal-model", "", null, ]), ).toEqual(["custom/internal-model"]); }); it("normalizes provider-specific aliases for claude", () => { expect(normalizeCustomModelSlugs(["sonnet"], "claudeAgent")).toEqual([]); expect(normalizeCustomModelSlugs(["claude/custom-sonnet"], "claudeAgent")).toEqual([ "claude/custom-sonnet", ]); }); }); describe("getAppModelOptions", () => { it("appends saved custom models after the built-in options", () => { const options = getAppModelOptions("codex", ["custom/internal-model"]); expect(options.map((option) => option.slug)).toEqual([ "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.3-codex-spark", "gpt-5.2-codex", "gpt-5.2", "custom/internal-model", ]); }); it("keeps the currently selected custom model available even if it is no longer saved", () => { const options = getAppModelOptions("codex", [], "custom/selected-model"); expect(options.at(-1)).toEqual({ slug: "custom/selected-model", name: "custom/selected-model", isCustom: true, }); }); it("keeps a saved custom provider model available as an exact slug option", () => { const options = getAppModelOptions("claudeAgent", ["claude/custom-opus"], "claude/custom-opus"); expect(options.some((option) => option.slug === "claude/custom-opus" && option.isCustom)).toBe( true, ); }); }); describe("resolveAppModelSelection", () => { it("preserves saved custom model slugs instead of falling back to the default", () => { expect(resolveAppModelSelection("codex", ["galapagos-alpha"], "galapagos-alpha")).toBe( "galapagos-alpha", ); }); it("falls back to the provider default when no model is selected", () => { expect(resolveAppModelSelection("codex", [], "")).toBe("gpt-5.4"); }); }); describe("timestamp format defaults", () => { it("defaults timestamp format to locale", () => { expect(DEFAULT_TIMESTAMP_FORMAT).toBe("locale"); }); }); describe("provider-specific custom models", () => { it("includes provider-specific custom slugs in non-codex model lists", () => { const claudeOptions = getAppModelOptions("claudeAgent", ["claude/custom-opus"]); expect(claudeOptions.some((option) => option.slug === "claude/custom-opus")).toBe(true); }); }); describe("AppSettingsSchema", () => { it("fills decoding defaults for persisted settings that predate newer keys", () => { const decode = Schema.decodeSync(Schema.fromJsonString(AppSettingsSchema)); expect( decode( JSON.stringify({ codexBinaryPath: "/usr/local/bin/codex", confirmThreadDelete: false, }), ), ).toMatchObject({ codexBinaryPath: "/usr/local/bin/codex", codexHomePath: "", defaultThreadEnvMode: "local", confirmThreadDelete: false, enableAssistantStreaming: false, timestampFormat: DEFAULT_TIMESTAMP_FORMAT, customCodexModels: [], customClaudeModels: [], }); }); });