import "../../index.css";
import {
type ModelSelection,
ClaudeModelOptions,
CodexModelOptions,
DEFAULT_MODEL_BY_PROVIDER,
DEFAULT_SERVER_SETTINGS,
ProjectId,
ThreadId,
} from "@t3tools/contracts";
import { page } from "vitest/browser";
import { useCallback } from "react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { render } from "vitest-browser-react";
import { TraitsPicker } from "./TraitsPicker";
import {
COMPOSER_DRAFT_STORAGE_KEY,
ComposerThreadDraftState,
useComposerDraftStore,
useComposerThreadDraft,
useEffectiveComposerModelState,
} from "../../composerDraftStore";
import { DEFAULT_CLIENT_SETTINGS } from "~/clientSettings";
// ── Claude TraitsPicker tests ─────────────────────────────────────────
const CLAUDE_THREAD_ID = ThreadId.makeUnsafe("thread-claude-traits");
function ClaudeTraitsPickerHarness(props: {
model: string;
fallbackModelSelection: ModelSelection | null;
triggerVariant?: "ghost" | "outline";
}) {
const prompt = useComposerThreadDraft(CLAUDE_THREAD_ID).prompt;
const setPrompt = useComposerDraftStore((store) => store.setPrompt);
const { modelOptions, selectedModel } = useEffectiveComposerModelState({
threadId: CLAUDE_THREAD_ID,
selectedProvider: "claudeAgent",
threadModelSelection: props.fallbackModelSelection,
projectModelSelection: null,
settings: {
...DEFAULT_SERVER_SETTINGS,
...DEFAULT_CLIENT_SETTINGS,
},
});
const handlePromptChange = useCallback(
(nextPrompt: string) => {
setPrompt(CLAUDE_THREAD_ID, nextPrompt);
},
[setPrompt],
);
return (
);
}
async function mountClaudePicker(props?: {
model?: string;
prompt?: string;
options?: ClaudeModelOptions;
fallbackModelOptions?: {
effort?: "low" | "medium" | "high" | "max" | "ultrathink";
thinking?: boolean;
fastMode?: boolean;
} | null;
skipDraftModelOptions?: boolean;
triggerVariant?: "ghost" | "outline";
}) {
const model = props?.model ?? "claude-opus-4-6";
const claudeOptions = !props?.skipDraftModelOptions ? props?.options : undefined;
const draftsByThreadId: Record = {
[CLAUDE_THREAD_ID]: {
prompt: props?.prompt ?? "",
images: [],
nonPersistedImageIds: [],
persistedAttachments: [],
terminalContexts: [],
modelSelectionByProvider: props?.skipDraftModelOptions
? {}
: {
claudeAgent: {
provider: "claudeAgent",
model,
...(claudeOptions && Object.keys(claudeOptions).length > 0
? { options: claudeOptions }
: {}),
},
},
activeProvider: "claudeAgent",
runtimeMode: null,
interactionMode: null,
},
};
useComposerDraftStore.setState({
draftsByThreadId,
draftThreadsByThreadId: {},
projectDraftThreadIdByProjectId: {},
});
const host = document.createElement("div");
document.body.append(host);
const fallbackModelSelection =
props?.fallbackModelOptions !== undefined
? ({
provider: "claudeAgent",
model,
options: props.fallbackModelOptions ?? undefined,
} satisfies ModelSelection)
: null;
const screen = await render(
,
{ container: host },
);
const cleanup = async () => {
await screen.unmount();
host.remove();
};
return {
[Symbol.asyncDispose]: cleanup,
cleanup,
};
}
describe("TraitsPicker (Claude)", () => {
afterEach(() => {
document.body.innerHTML = "";
useComposerDraftStore.setState({
draftsByThreadId: {},
draftThreadsByThreadId: {},
projectDraftThreadIdByProjectId: {},
stickyModelSelectionByProvider: {},
});
});
it("shows fast mode controls for Opus", async () => {
await using _ = await mountClaudePicker();
await page.getByRole("button").click();
await vi.waitFor(() => {
const text = document.body.textContent ?? "";
expect(text).toContain("Fast Mode");
expect(text).toContain("off");
expect(text).toContain("on");
});
});
it("hides fast mode controls for non-Opus models", async () => {
await using _ = await mountClaudePicker({ model: "claude-sonnet-4-6" });
await page.getByRole("button").click();
await vi.waitFor(() => {
expect(document.body.textContent ?? "").not.toContain("Fast Mode");
});
});
it("shows only the provided effort options", async () => {
await using _ = await mountClaudePicker({
model: "claude-sonnet-4-6",
});
await page.getByRole("button").click();
await vi.waitFor(() => {
const text = document.body.textContent ?? "";
expect(text).toContain("Low");
expect(text).toContain("Medium");
expect(text).toContain("High");
expect(text).not.toContain("Max");
expect(text).toContain("Ultrathink");
});
});
it("shows a th inking on/off dropdown for Haiku", async () => {
await using _ = await mountClaudePicker({
model: "claude-haiku-4-5",
options: { thinking: true },
});
await vi.waitFor(() => {
expect(document.body.textContent ?? "").toContain("Thinking On");
});
await page.getByRole("button").click();
await vi.waitFor(() => {
const text = document.body.textContent ?? "";
expect(text).toContain("Thinking");
expect(text).toContain("On (default)");
expect(text).toContain("Off");
});
});
it("shows prompt-controlled Ultrathink state with disabled effort controls", async () => {
await using _ = await mountClaudePicker({
model: "claude-opus-4-6",
options: { effort: "high" },
prompt: "Ultrathink:\nInvestigate this",
});
await vi.waitFor(() => {
expect(document.body.textContent ?? "").toContain("Ultrathink");
expect(document.body.textContent ?? "").not.toContain("Ultrathink · Prompt");
});
await page.getByRole("button").click();
await vi.waitFor(() => {
const text = document.body.textContent ?? "";
expect(text).toContain("Effort");
expect(text).toContain("Remove Ultrathink from the prompt to change effort.");
expect(text).not.toContain("Fallback Effort");
});
});
it("persists sticky claude model options when traits change", async () => {
await using _ = await mountClaudePicker({
model: "claude-opus-4-6",
options: { effort: "medium", fastMode: false },
});
await page.getByRole("button").click();
await page.getByRole("menuitemradio", { name: "Max" }).click();
expect(
useComposerDraftStore.getState().stickyModelSelectionByProvider.claudeAgent,
).toMatchObject({
provider: "claudeAgent",
options: {
effort: "max",
},
});
});
it("accepts outline trigger styling", async () => {
await using _ = await mountClaudePicker({
triggerVariant: "outline",
});
const button = document.querySelector("button");
if (!(button instanceof HTMLButtonElement)) {
throw new Error("Expected traits trigger button to be rendered.");
}
expect(button.className).toContain("border-input");
expect(button.className).toContain("bg-popover");
});
});
// ── Codex TraitsPicker tests ──────────────────────────────────────────
async function mountCodexPicker(props: { model?: string; options?: CodexModelOptions }) {
const threadId = ThreadId.makeUnsafe("thread-codex-traits");
const model = props.model ?? DEFAULT_MODEL_BY_PROVIDER.codex;
const draftsByThreadId: Record = {
[threadId]: {
prompt: "",
images: [],
nonPersistedImageIds: [],
persistedAttachments: [],
terminalContexts: [],
modelSelectionByProvider: {
codex: {
provider: "codex",
model,
...(props.options ? { options: props.options } : {}),
},
},
activeProvider: "codex",
runtimeMode: null,
interactionMode: null,
},
};
useComposerDraftStore.setState({
draftsByThreadId,
draftThreadsByThreadId: {},
projectDraftThreadIdByProjectId: {
[ProjectId.makeUnsafe("project-codex-traits")]: threadId,
},
});
const host = document.createElement("div");
document.body.append(host);
const screen = await render(
{}}
/>,
{ container: host },
);
const cleanup = async () => {
await screen.unmount();
host.remove();
};
return {
[Symbol.asyncDispose]: cleanup,
cleanup,
};
}
describe("TraitsPicker (Codex)", () => {
afterEach(() => {
document.body.innerHTML = "";
localStorage.removeItem(COMPOSER_DRAFT_STORAGE_KEY);
useComposerDraftStore.setState({
draftsByThreadId: {},
draftThreadsByThreadId: {},
projectDraftThreadIdByProjectId: {},
stickyModelSelectionByProvider: {},
});
});
it("shows fast mode controls", async () => {
await using _ = await mountCodexPicker({
options: { fastMode: false },
});
await page.getByRole("button").click();
await vi.waitFor(() => {
const text = document.body.textContent ?? "";
expect(text).toContain("Fast Mode");
expect(text).toContain("off");
expect(text).toContain("on");
});
});
it("shows Fast in the trigger label when fast mode is active", async () => {
await using _ = await mountCodexPicker({
options: { fastMode: true },
});
await vi.waitFor(() => {
expect(document.body.textContent ?? "").toContain("High · Fast");
});
});
it("shows only the provided effort options", async () => {
await using _ = await mountCodexPicker({
options: { fastMode: false },
});
await page.getByRole("button").click();
await vi.waitFor(() => {
const text = document.body.textContent ?? "";
expect(text).toContain("Low");
expect(text).toContain("Medium");
expect(text).toContain("High");
expect(text).toContain("Extra High");
});
});
it("persists sticky codex model options when traits change", async () => {
await using _ = await mountCodexPicker({
options: { fastMode: false },
});
await page.getByRole("button").click();
await page.getByRole("menuitemradio", { name: "on" }).click();
expect(useComposerDraftStore.getState().stickyModelSelectionByProvider.codex).toMatchObject({
provider: "codex",
options: { fastMode: true },
});
});
});