Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
684 lines
21 KiB
TypeScript
684 lines
21 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { AgentGUIComposerSettingsVM } from "./agentGuiNodeTypes";
|
|
import {
|
|
buildComposerModelMenuModel,
|
|
formatModelDisplayLabel,
|
|
type AgentComposerSettingsMenuLabels
|
|
} from "./composerSettingsMenuModel";
|
|
|
|
const labels: AgentComposerSettingsMenuLabels = {
|
|
modelLabel: "Model",
|
|
modelSelectionLabel: "Model selection",
|
|
modelContextWindowSuffix: "context window",
|
|
modelTooltipVersionLabel: "Version",
|
|
defaultModel: "Default model",
|
|
loadingOptions: "Loading…",
|
|
inheritedUnavailable: "Unavailable",
|
|
reasoningLabel: "Reasoning",
|
|
reasoningDegreeLabel: "Reasoning degree",
|
|
reasoningOptionDefault: "Default",
|
|
reasoningOptionMinimal: "Minimal",
|
|
reasoningOptionLow: "Low",
|
|
reasoningOptionMedium: "Medium",
|
|
reasoningOptionHigh: "High",
|
|
reasoningOptionXHigh: "X-High",
|
|
reasoningOptionMax: "Max",
|
|
reasoningOptionUltra: "Ultra",
|
|
speedLabel: "Speed",
|
|
speedSelectionLabel: "Speed",
|
|
speedOptionStandard: "Standard",
|
|
speedOptionStandardDescription: "Standard localized",
|
|
speedOptionFast: "Fast",
|
|
speedOptionFastDescription: "Fast localized",
|
|
permissionLabel: "Permissions",
|
|
planModeLabel: "Plan",
|
|
modelDescriptions: {
|
|
frontierComplexCoding: "Frontier",
|
|
everydayCoding: "Everyday",
|
|
smallFastCostEfficient: "Small",
|
|
codingOptimized: "Coding",
|
|
ultraFastCoding: "Ultra",
|
|
professionalLongRunning: "Pro"
|
|
}
|
|
};
|
|
|
|
function vm(
|
|
overrides: Partial<AgentGUIComposerSettingsVM> = {}
|
|
): AgentGUIComposerSettingsVM {
|
|
return {
|
|
sessionSettings: null,
|
|
draftSettings: {
|
|
model: "gpt-5.5",
|
|
reasoningEffort: "high",
|
|
speed: "standard",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
},
|
|
supportsModel: true,
|
|
supportsReasoningEffort: true,
|
|
supportsSpeed: true,
|
|
speedUnavailable: false,
|
|
availableSpeeds: [
|
|
{ value: "standard", label: "standard", description: "Standard speed" },
|
|
{ value: "fast", label: "fast", description: "1.5x speed" }
|
|
],
|
|
supportsPlanMode: false,
|
|
isSettingsLoading: false,
|
|
modelUnavailable: false,
|
|
reasoningUnavailable: false,
|
|
availableModels: [
|
|
{ value: "gpt-5.5", label: "gpt-5.5" },
|
|
{ value: "gpt-5.4", label: "gpt-5.4" }
|
|
],
|
|
availableReasoningEfforts: [
|
|
{ value: "default", label: "Default" },
|
|
{ value: "low", label: "Low" },
|
|
{ value: "high", label: "High" },
|
|
{ value: "max", label: "Max" },
|
|
{ value: "ultra", label: "ultra" },
|
|
{ value: "none", label: "Off" }
|
|
],
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
describe("buildComposerModelMenuModel", () => {
|
|
it("models the trigger, sections and resolved option labels", () => {
|
|
const menu = buildComposerModelMenuModel(vm(), labels);
|
|
|
|
expect(menu.disabled).toBe(false);
|
|
expect(menu.trigger).toMatchObject({
|
|
isFast: false,
|
|
modelLabel: "GPT-5.5",
|
|
reasoningLabel: "High",
|
|
combinedLabel: "GPT-5.5 High",
|
|
showCombined: false
|
|
});
|
|
|
|
expect(menu.model.show).toBe(true);
|
|
expect(menu.model.selectedValue).toBe("gpt-5.5");
|
|
expect(menu.model.options).toEqual([
|
|
{ value: "gpt-5.5", label: "GPT-5.5", description: undefined },
|
|
{ value: "gpt-5.4", label: "GPT-5.4", description: undefined }
|
|
]);
|
|
|
|
expect(menu.reasoning.show).toBe(true);
|
|
expect(menu.reasoning.selectedLabel).toBe("High");
|
|
expect(menu.reasoning.options).toEqual([
|
|
{ value: "default", label: "Default" },
|
|
{ value: "low", label: "Low" },
|
|
{ value: "high", label: "High" },
|
|
{ value: "max", label: "Max" },
|
|
{ value: "ultra", label: "Ultra" },
|
|
{ value: "none", label: "Off" }
|
|
]);
|
|
|
|
expect(menu.speed.show).toBe(true);
|
|
expect(menu.speed.selectedLabel).toBe("Standard");
|
|
expect(menu.speed.options).toEqual([
|
|
{
|
|
value: "standard",
|
|
label: "Standard",
|
|
description: "Standard localized"
|
|
},
|
|
{ value: "fast", label: "Fast", description: "Fast localized" }
|
|
]);
|
|
});
|
|
|
|
it("localizes ultra in the final menu presentation", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
draftSettings: {
|
|
model: "gpt-5.5",
|
|
reasoningEffort: "ultra",
|
|
speed: "standard",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
},
|
|
selectedReasoningEffortValue: "ultra",
|
|
availableReasoningEfforts: [{ value: "ultra", label: "ultra" }]
|
|
}),
|
|
{ ...labels, reasoningOptionUltra: "极致" }
|
|
);
|
|
|
|
expect(menu.reasoning.selectedLabel).toBe("极致");
|
|
expect(menu.reasoning.options).toEqual([{ value: "ultra", label: "极致" }]);
|
|
});
|
|
|
|
it("uses the provider-localized label for an extensible reasoning value", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
draftSettings: {
|
|
model: "gpt-5.6-sol",
|
|
reasoningEffort: "none",
|
|
speed: "standard",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
},
|
|
selectedReasoningEffortValue: "none",
|
|
availableReasoningEfforts: [{ value: "none", label: "关闭" }]
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.reasoning.selectedLabel).toBe("关闭");
|
|
expect(menu.reasoning.options).toEqual([{ value: "none", label: "关闭" }]);
|
|
});
|
|
|
|
it("localizes ultra effort in model summaries", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
availableModels: [
|
|
{
|
|
value: "gpt-5.6-sol",
|
|
label: "GPT-5.6-Sol",
|
|
description: "GPT-5.6 Sol · ultra effort"
|
|
}
|
|
],
|
|
selectedModelValue: "gpt-5.6-sol"
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.model.options[0]).toMatchObject({
|
|
summary: ["Ultra"],
|
|
tooltip: { version: "Version: ultra effort" }
|
|
});
|
|
});
|
|
|
|
it("presents the typed model consumption multiplier", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
availableModels: [
|
|
{
|
|
value: "hy3",
|
|
label: "Hy3",
|
|
consumptionMultiplier: "0.71"
|
|
}
|
|
],
|
|
selectedModelValue: "hy3"
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.model.options[0]).toMatchObject({
|
|
label: "Hy3",
|
|
tooltip: {
|
|
consumptionMultiplier: "0.71x",
|
|
title: "Hy3"
|
|
}
|
|
});
|
|
expect(menu.model.options[0]?.tooltip?.description).toBeUndefined();
|
|
});
|
|
|
|
it("marks the trigger as fast and localizes model descriptions", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
draftSettings: {
|
|
model: "gpt-5.5",
|
|
reasoningEffort: "high",
|
|
speed: "fast",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
},
|
|
availableModels: [
|
|
{
|
|
value: "gpt-5.5",
|
|
label: "GPT-5.5",
|
|
description:
|
|
"Frontier model for complex coding, research, and real-world work."
|
|
}
|
|
]
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.trigger.isFast).toBe(true);
|
|
expect(menu.speed.selectedLabel).toBe("Fast");
|
|
expect(menu.model.options[0]).toMatchObject({
|
|
description: "Frontier",
|
|
tooltip: {
|
|
description: "Frontier",
|
|
title: "GPT-5.5"
|
|
}
|
|
});
|
|
});
|
|
|
|
it("presents Default with the provider-resolved Claude model", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
draftSettings: {
|
|
model: "default",
|
|
reasoningEffort: "high",
|
|
speed: "standard",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
},
|
|
selectedModelValue: "default",
|
|
effectiveModelValue: "claude-sonnet-4-6",
|
|
availableModels: [
|
|
{
|
|
value: "default",
|
|
label: "Default (recommended)",
|
|
description:
|
|
"Opus 4.8 with 1M context · Most capable for complex work"
|
|
},
|
|
{
|
|
value: "sonnet",
|
|
label: "Sonnet",
|
|
description: "Sonnet 4.6 · Best for everyday tasks · medium effort"
|
|
},
|
|
{
|
|
value: "opus",
|
|
label: "Opus 4.8",
|
|
description:
|
|
"Opus 4.8 with 1M context · Most capable for complex work"
|
|
}
|
|
]
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.trigger.modelLabel).toBe("Default (Sonnet)");
|
|
expect(menu.model.options).toEqual([
|
|
{
|
|
value: "default",
|
|
label: "Default (Sonnet)",
|
|
description: "Sonnet 4.6 · Best for everyday tasks · medium effort",
|
|
summary: ["Medium"],
|
|
tooltip: {
|
|
description: "Best for everyday tasks",
|
|
title: "Sonnet 4.6",
|
|
version: "Version: medium effort"
|
|
}
|
|
},
|
|
{
|
|
value: "sonnet",
|
|
label: "Sonnet",
|
|
description: "Sonnet 4.6 · Best for everyday tasks · medium effort",
|
|
summary: ["Medium"],
|
|
tooltip: {
|
|
description: "Best for everyday tasks",
|
|
title: "Sonnet 4.6",
|
|
version: "Version: medium effort"
|
|
}
|
|
},
|
|
{
|
|
value: "opus",
|
|
label: "Opus",
|
|
description: "Opus 4.8 with 1M context · Most capable for complex work",
|
|
summary: ["1M"],
|
|
tooltip: {
|
|
contextWindow: "1M context window",
|
|
description: "Most capable for complex work",
|
|
title: "Opus 4.8"
|
|
}
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("does not present the catalog's static Default description as runtime truth", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
draftSettings: {
|
|
model: "default",
|
|
reasoningEffort: "high",
|
|
speed: "standard",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
},
|
|
selectedModelValue: "default",
|
|
availableModels: [
|
|
{
|
|
value: "default",
|
|
label: "Sonnet 5",
|
|
description: "Efficient for routine tasks"
|
|
},
|
|
{
|
|
value: "opus",
|
|
label: "Opus 4.8",
|
|
description: "Most capable"
|
|
}
|
|
]
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.trigger.modelLabel).toBe("Default");
|
|
expect(menu.model.options[0]).toEqual({
|
|
value: "default",
|
|
label: "Default",
|
|
description: undefined
|
|
});
|
|
});
|
|
|
|
it("injects the selected value when missing from the advertised options", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
selectedModelValue: "custom-model",
|
|
draftSettings: {
|
|
model: "custom-model",
|
|
reasoningEffort: "high",
|
|
speed: "standard",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
}
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.model.selectedValue).toBe("custom-model");
|
|
expect(menu.model.options[0]).toEqual({
|
|
value: "custom-model",
|
|
label: "Custom-Model",
|
|
description: undefined
|
|
});
|
|
});
|
|
|
|
it("collapses the model list to the latest version per family when enabled", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
collapseModelOptionsToLatest: true,
|
|
selectedModelValue: "claude-sonnet-5[thinking=true]",
|
|
draftSettings: {
|
|
model: "claude-sonnet-5[thinking=true]",
|
|
reasoningEffort: "high",
|
|
speed: "standard",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
},
|
|
availableModels: [
|
|
{ value: "default[]", label: "Auto" },
|
|
{
|
|
value: "claude-sonnet-4.6[thinking=true]",
|
|
label: "claude-sonnet-4.6"
|
|
},
|
|
{
|
|
value: "claude-sonnet-4.7[thinking=true]",
|
|
label: "claude-sonnet-4.7"
|
|
},
|
|
{ value: "claude-sonnet-5[thinking=true]", label: "claude-sonnet-5" },
|
|
{ value: "gpt-5.2[fast=false]", label: "gpt-5.2" },
|
|
{ value: "gpt-5.3-codex[fast=false]", label: "gpt-5.3-codex" }
|
|
]
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.model.options.map((option) => option.value)).toEqual([
|
|
"default[]",
|
|
"claude-sonnet-5[thinking=true]",
|
|
"gpt-5.3-codex[fast=false]"
|
|
]);
|
|
});
|
|
|
|
it("keeps a selected older version visible after the family collapses", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
collapseModelOptionsToLatest: true,
|
|
selectedModelValue: "claude-sonnet-4.6[thinking=true]",
|
|
draftSettings: {
|
|
model: "claude-sonnet-4.6[thinking=true]",
|
|
reasoningEffort: "high",
|
|
speed: "standard",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
},
|
|
availableModels: [
|
|
{
|
|
value: "claude-sonnet-4.6[thinking=true]",
|
|
label: "claude-sonnet-4.6"
|
|
},
|
|
{ value: "claude-sonnet-5[thinking=true]", label: "claude-sonnet-5" }
|
|
]
|
|
}),
|
|
labels
|
|
);
|
|
|
|
const values = menu.model.options.map((option) => option.value);
|
|
expect(values).toContain("claude-sonnet-5[thinking=true]");
|
|
expect(values).toContain("claude-sonnet-4.6[thinking=true]");
|
|
expect(menu.model.selectedValue).toBe("claude-sonnet-4.6[thinking=true]");
|
|
});
|
|
|
|
it("normalizes GPT casing while capitalizing each label segment", () => {
|
|
expect(formatModelDisplayLabel("gpt-5.5")).toBe("GPT-5.5");
|
|
expect(formatModelDisplayLabel("gpt-5.3-codex")).toBe("GPT-5.3-Codex");
|
|
expect(formatModelDisplayLabel("Gpt-5.5-codex")).toBe("GPT-5.5-Codex");
|
|
expect(formatModelDisplayLabel("vendor/gpt-5.5")).toBe("Vendor/GPT-5.5");
|
|
expect(formatModelDisplayLabel("codex")).toBe("Codex");
|
|
expect(formatModelDisplayLabel("custom-model")).toBe("Custom-Model");
|
|
});
|
|
|
|
it("hides dimensions that are unsupported or unavailable", () => {
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
supportsSpeed: false,
|
|
availableSpeeds: [],
|
|
reasoningUnavailable: true
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.model.show).toBe(true);
|
|
expect(menu.reasoning.show).toBe(false);
|
|
expect(menu.speed.show).toBe(false);
|
|
expect(menu.trigger.reasoningLabel).toBe("");
|
|
expect(menu.trigger.showCombined).toBe(true);
|
|
});
|
|
|
|
it("does not show a stale default effort when reasoning is not configurable", () => {
|
|
// Cursor-like: draft still carries the GUI default "high", but the
|
|
// provider does not expose a reasoning selector.
|
|
const menu = buildComposerModelMenuModel(
|
|
vm({
|
|
supportsReasoningEffort: false,
|
|
availableReasoningEfforts: [],
|
|
draftSettings: {
|
|
model: "gpt-5.2[reasoning=medium,fast=false]",
|
|
reasoningEffort: "high",
|
|
speed: null,
|
|
planMode: false,
|
|
permissionModeId: "agent"
|
|
},
|
|
availableModels: [
|
|
{
|
|
value: "gpt-5.2[reasoning=medium,fast=false]",
|
|
label: "gpt-5.2"
|
|
}
|
|
],
|
|
supportsSpeed: false,
|
|
availableSpeeds: []
|
|
}),
|
|
labels
|
|
);
|
|
|
|
expect(menu.reasoning.show).toBe(false);
|
|
expect(menu.trigger).toMatchObject({
|
|
modelLabel: "GPT-5.2",
|
|
reasoningLabel: "",
|
|
combinedLabel: "GPT-5.2",
|
|
showCombined: true
|
|
});
|
|
});
|
|
|
|
it("shows the loading copy on the trigger while options load", () => {
|
|
// No model resolved yet — the placeholder must read as loading, not the
|
|
// "Default" fallback (which looks like a real choice).
|
|
const loadingVm = (overrides: Partial<AgentGUIComposerSettingsVM>) =>
|
|
vm({
|
|
availableModels: [],
|
|
draftSettings: {
|
|
model: null,
|
|
reasoningEffort: null,
|
|
speed: null,
|
|
planMode: false,
|
|
permissionModeId: null
|
|
},
|
|
selectedModelValue: null,
|
|
...overrides
|
|
});
|
|
expect(
|
|
buildComposerModelMenuModel(
|
|
loadingVm({ isSettingsLoading: true }),
|
|
labels
|
|
).trigger.modelLabel
|
|
).toBe("Loading…");
|
|
expect(
|
|
buildComposerModelMenuModel(
|
|
loadingVm({ isModelOptionsLoading: true }),
|
|
labels
|
|
).trigger.modelLabel
|
|
).toBe("Loading…");
|
|
});
|
|
|
|
it("disables the menu while settings load or nothing is configurable", () => {
|
|
expect(
|
|
buildComposerModelMenuModel(vm({ isSettingsLoading: true }), labels)
|
|
.disabled
|
|
).toBe(true);
|
|
expect(
|
|
buildComposerModelMenuModel(
|
|
vm({
|
|
supportsModel: false,
|
|
supportsReasoningEffort: false,
|
|
supportsSpeed: false,
|
|
availableModels: [],
|
|
availableReasoningEfforts: [],
|
|
availableSpeeds: []
|
|
}),
|
|
labels
|
|
).disabled
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("buildComposerModelMenuModel plan, search and history", () => {
|
|
function planVm(
|
|
overrides: Partial<AgentGUIComposerSettingsVM> = {}
|
|
): AgentGUIComposerSettingsVM {
|
|
return vm({
|
|
modelPlan: { id: "plan-1", name: "Volc Coding Plan", protocol: "openai" },
|
|
availableModels: [
|
|
{ value: "gpt-5.5", label: "gpt-5.5", description: "Volc Coding Plan" },
|
|
{ value: "gpt-5.4", label: "gpt-5.4", description: "Volc Coding Plan" }
|
|
],
|
|
...overrides
|
|
});
|
|
}
|
|
|
|
function manyModels(count: number): AgentGUIComposerSettingsVM {
|
|
return vm({
|
|
availableModels: Array.from({ length: count }, (_, index) => ({
|
|
value: `model-${index + 1}`,
|
|
label: `model-${index + 1}`
|
|
})),
|
|
draftSettings: {
|
|
model: "model-1",
|
|
reasoningEffort: "high",
|
|
speed: "standard",
|
|
planMode: false,
|
|
permissionModeId: "preset"
|
|
}
|
|
});
|
|
}
|
|
|
|
it("surfaces the bound plan and inline plan-name descriptions", () => {
|
|
const menu = buildComposerModelMenuModel(planVm(), labels);
|
|
expect(menu.model.plan).toEqual({ id: "plan-1", name: "Volc Coding Plan" });
|
|
expect(menu.model.optionDescriptionInline).toBe(true);
|
|
expect(menu.model.options[0]?.description).toBe("Volc Coding Plan");
|
|
|
|
const withoutPlan = buildComposerModelMenuModel(vm(), labels);
|
|
expect(withoutPlan.model.plan).toBeNull();
|
|
expect(withoutPlan.model.optionDescriptionInline).toBe(false);
|
|
});
|
|
|
|
it("enables the filter input above the threshold and filters by label/id", () => {
|
|
const small = buildComposerModelMenuModel(vm(), labels);
|
|
expect(small.model.searchEnabled).toBe(false);
|
|
// Below the threshold the query is ignored entirely.
|
|
expect(
|
|
buildComposerModelMenuModel(vm(), labels, { searchQuery: "5.4" }).model
|
|
.options
|
|
).toHaveLength(2);
|
|
|
|
const large = buildComposerModelMenuModel(manyModels(10), labels);
|
|
expect(large.model.searchEnabled).toBe(true);
|
|
expect(large.model.options).toHaveLength(10);
|
|
|
|
const filtered = buildComposerModelMenuModel(manyModels(10), labels, {
|
|
searchQuery: "MODEL-1"
|
|
});
|
|
expect(filtered.model.searchQuery).toBe("MODEL-1");
|
|
expect(filtered.model.options.map((option) => option.value)).toEqual([
|
|
"model-1",
|
|
"model-10"
|
|
]);
|
|
expect(
|
|
buildComposerModelMenuModel(manyModels(10), labels, {
|
|
searchQuery: "no-match"
|
|
}).model.options
|
|
).toHaveLength(0);
|
|
});
|
|
|
|
it("splits favorites and recents into pinned groups without duplicates", () => {
|
|
const menu = buildComposerModelMenuModel(manyModels(10), labels, {
|
|
favoriteModelIds: ["model-3", "model-9", "model-unknown"],
|
|
recentModelIds: ["model-9", "model-2", "model-5"]
|
|
});
|
|
expect(menu.model.favoriteOptions.map((option) => option.value)).toEqual([
|
|
"model-3",
|
|
"model-9"
|
|
]);
|
|
// model-9 is favorited, so recents keep only the rest, in recency order.
|
|
expect(menu.model.recentOptions.map((option) => option.value)).toEqual([
|
|
"model-2",
|
|
"model-5"
|
|
]);
|
|
expect(menu.model.favoriteValues).toEqual(["model-3", "model-9"]);
|
|
const remaining = menu.model.options.map((option) => option.value);
|
|
expect(remaining).not.toContain("model-3");
|
|
expect(remaining).not.toContain("model-9");
|
|
expect(remaining).not.toContain("model-2");
|
|
expect(remaining).not.toContain("model-5");
|
|
expect(remaining).toHaveLength(6);
|
|
});
|
|
|
|
it("applies the search filter to the pinned groups too", () => {
|
|
const menu = buildComposerModelMenuModel(manyModels(10), labels, {
|
|
favoriteModelIds: ["model-3"],
|
|
recentModelIds: ["model-2"],
|
|
searchQuery: "model-2"
|
|
});
|
|
expect(menu.model.favoriteOptions).toHaveLength(0);
|
|
expect(menu.model.recentOptions.map((option) => option.value)).toEqual([
|
|
"model-2"
|
|
]);
|
|
expect(menu.model.options).toHaveLength(0);
|
|
});
|
|
|
|
it("shows the switch-effect hint only for active modelSwitch sessions", () => {
|
|
expect(
|
|
buildComposerModelMenuModel(
|
|
vm({ modelSwitchTakesEffectNextTurn: true }),
|
|
labels
|
|
).switchEffectHint
|
|
).toBe(true);
|
|
expect(buildComposerModelMenuModel(vm(), labels).switchEffectHint).toBe(
|
|
false
|
|
);
|
|
// The hint is a model-menu affordance: no model section, no hint.
|
|
expect(
|
|
buildComposerModelMenuModel(
|
|
vm({
|
|
modelSwitchTakesEffectNextTurn: true,
|
|
supportsModel: false,
|
|
availableModels: []
|
|
}),
|
|
labels
|
|
).switchEffectHint
|
|
).toBe(false);
|
|
});
|
|
});
|