test(ui): pin who may record an explicit Default effort

Nothing covered ModelControls, which is where the effort restore decides
between "the user chose Default" and "nothing was found". Three cases: a
concrete effort in the session history is restored, history without an effort
records no choice, and a preserved manual override keeps a recorded Default.
The middle one fails on the previous commit's parent — it recorded `null` and
then `undefined` for the same restore.

Also drops the `fetchAll` binding GitView stopped using when the post-bootstrap
refresh moved to a forced `fetchStatus`; it was failing `bun run lint:ui`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZuVVgziiLjD81W5vaxdH2
This commit is contained in:
Iuliia Ivashko
2026-09-04 17:41:35 +03:00
co-authored by Claude Opus 5
parent 025cd3d46c
commit 4dfbb5bd1c
2 changed files with 373 additions and 2 deletions
@@ -0,0 +1,373 @@
import React, { act } from 'react';
import { beforeEach, describe, expect, mock, test } from 'bun:test';
import { createRoot } from 'react-dom/client';
import { Window } from 'happy-dom';
import { create } from 'zustand';
/**
* Restoring a session must not invent an effort choice.
*
* The selection store keeps three states for a session's effort: an effort
* name, `null` for an explicit "Default", and `undefined` for no choice at all.
* Only the picker may write `null`. When a restore path writes it instead, the
* session latches onto "Default" — `null` outranks the agent and settings
* defaults by design — and the concrete effort the session's own history
* carries can never come back. These tests pin who writes what.
*/
type VariantChoice = string | null | undefined;
type UserModelChoice = {
id: string;
agent?: string;
providerID: string;
modelID: string;
variant?: string;
};
const PROVIDER_ID = 'openai';
const MODEL_ID = 'gpt-5.5';
const AGENT = 'build';
const SESSION_ID = 'ses_restore';
const model = {
id: MODEL_ID,
name: MODEL_ID,
providerID: PROVIDER_ID,
variants: { low: {}, high: {} },
};
const provider = { id: PROVIDER_ID, name: PROVIDER_ID, models: [model] };
const agent = { name: AGENT, mode: 'primary' as const };
let latestUserChoice: UserModelChoice | null = null;
let preserveManualOverride = false;
/** Every effort written for the session, in order, including `undefined`. */
const variantWrites: VariantChoice[] = [];
/** Every `(override, inherited)` pair pushed into the config store. */
const overrideWrites: Array<{ override: VariantChoice; inherited: string | undefined }> = [];
type ConfigState = {
providers: typeof provider[];
agents: typeof agent[];
modelsMetadata: Record<string, never>;
currentProviderId: string;
currentModelId: string;
currentVariant: string | undefined;
currentVariantSelection: { override: VariantChoice; inherited: string | undefined };
currentAgentName: string | undefined;
settingsDefaultVariant: string | undefined;
settingsDefaultAgent: string | undefined;
selectionSource: 'auto' | 'manual';
setProvider: (providerId: string) => void;
setSelectedProvider: (providerId: string) => void;
setModel: (modelId: string) => void;
setAgent: (agentName: string) => void;
setCurrentVariant: (variant: string | undefined) => void;
setCurrentVariantOverride: (override: VariantChoice, inherited: string | undefined) => void;
getCurrentProvider: () => typeof provider;
getCurrentAgent: () => typeof agent;
getVisibleAgents: () => typeof agent[];
getCurrentModelVariants: () => string[];
getModelMetadata: () => undefined;
};
const useConfigStore = create<ConfigState>((set) => ({
providers: [provider],
agents: [agent],
modelsMetadata: {},
currentProviderId: PROVIDER_ID,
currentModelId: MODEL_ID,
currentVariant: undefined,
currentVariantSelection: { override: undefined, inherited: undefined },
currentAgentName: AGENT,
settingsDefaultVariant: undefined,
settingsDefaultAgent: undefined,
selectionSource: 'auto',
setProvider: (providerId) => set({ currentProviderId: providerId }),
setSelectedProvider: () => undefined,
setModel: (modelId) => set({ currentModelId: modelId }),
setAgent: (agentName) => set({ currentAgentName: agentName }),
// Mirrors the real store, including its no-op guard: without that guard an
// unchanged write returns a fresh state object every render and the
// component's variant effects never settle.
setCurrentVariant: (variant) => {
useConfigStore.getState().setCurrentVariantOverride(undefined, variant);
},
setCurrentVariantOverride: (override, inherited) => {
set((state) => {
const currentVariant = override === null ? undefined : override ?? inherited;
if (
state.currentVariant === currentVariant
&& state.currentVariantSelection.override === override
&& state.currentVariantSelection.inherited === inherited
) {
return state;
}
overrideWrites.push({ override, inherited });
return { currentVariant, currentVariantSelection: { override, inherited } };
});
},
getCurrentProvider: () => provider,
getCurrentAgent: () => agent,
getVisibleAgents: () => [agent],
getCurrentModelVariants: () => Object.keys(model.variants),
getModelMetadata: () => undefined,
}));
type SelectionState = {
savedVariant: VariantChoice;
sessionAgentSelections: Map<string, string>;
getSessionModelSelection: () => { providerId: string; modelId: string } | null;
getSessionAgentSelection: () => string | null;
getAgentModelForSession: () => { providerId: string; modelId: string } | null;
getAgentModelVariantForSession: () => VariantChoice;
saveSessionModelSelection: () => void;
saveSessionAgentSelection: () => void;
saveAgentModelForSession: () => void;
saveAgentModelVariantForSession: (
sessionId: string,
agentName: string,
providerId: string,
modelId: string,
variant: VariantChoice,
) => void;
};
const useSelectionStore = create<SelectionState>((set, get) => ({
savedVariant: undefined,
sessionAgentSelections: new Map([[SESSION_ID, AGENT]]),
getSessionModelSelection: () => ({ providerId: PROVIDER_ID, modelId: MODEL_ID }),
getSessionAgentSelection: () => AGENT,
getAgentModelForSession: () => ({ providerId: PROVIDER_ID, modelId: MODEL_ID }),
getAgentModelVariantForSession: () => get().savedVariant,
saveSessionModelSelection: () => undefined,
saveSessionAgentSelection: () => undefined,
saveAgentModelForSession: () => undefined,
saveAgentModelVariantForSession: (_sessionId, _agentName, _providerId, _modelId, variant) => {
variantWrites.push(variant);
set({ savedVariant: variant });
},
}));
const useSessionUIStore = create(() => ({
currentSessionId: SESSION_ID,
getDirectoryForSession: () => '/workspace/project',
}));
const useUIStore = create(() => ({
isMobile: false,
isModelSelectorOpen: false,
hiddenModels: [],
providerOrder: [],
shortcutOverrides: {},
isFavoriteModel: () => false,
toggleFavoriteModel: () => undefined,
reorderFavoriteModel: () => undefined,
setProviderOrder: () => undefined,
setModelSelectorOpen: () => undefined,
setSettingsDialogOpen: () => undefined,
setSettingsPage: () => undefined,
addRecentAgent: () => undefined,
addRecentModel: () => undefined,
addRecentEffort: () => undefined,
}));
const passthrough = ({ children }: React.PropsWithChildren) => <div>{children}</div>;
mock.module('@/lib/messages/userModelChoice', () => ({
findLatestUserModelChoice: () => latestUserChoice,
shouldPreserveManualModelOverride: () => preserveManualOverride,
}));
mock.module('@/stores/useConfigStore', () => ({ useConfigStore }));
mock.module('@/sync/selection-store', () => ({ useSelectionStore }));
mock.module('@/sync/session-ui-store', () => ({ useSessionUIStore }));
mock.module('@/stores/useUIStore', () => ({ useUIStore }));
mock.module('@/stores/contextStore', () => ({
useContextStore: <T,>(selector: (state: { hasHydrated: boolean }) => T): T => selector({ hasHydrated: true }),
}));
mock.module('@/sync/sync-context', () => ({
useSessionMessages: () => [],
useSessionRenderable: () => true,
}));
mock.module('@/sync/use-sync', () => ({ useSync: () => ({ sessions: [] }) }));
mock.module('@/sync/sync-refs', () => ({ getSyncParts: () => [] }));
mock.module('@/components/ui/dropdown-menu', () => ({
DropdownMenu: passthrough,
DropdownMenuContent: passthrough,
DropdownMenuItem: passthrough,
DropdownMenuLabel: passthrough,
DropdownMenuSeparator: () => null,
DropdownMenuTrigger: passthrough,
}));
mock.module('@/components/ui/input', () => ({
Input: (props: React.InputHTMLAttributes<HTMLInputElement>) => <input {...props} />,
}));
mock.module('@/components/ui/MobileOverlayPanel', () => ({ MobileOverlayPanel: passthrough }));
mock.module('@/components/ui/ProviderLogo', () => ({ ProviderLogo: () => null }));
mock.module('@/components/ui/ScrollableOverlay', () => ({ ScrollableOverlay: passthrough }));
mock.module('@/components/ui/tooltip', () => ({
Tooltip: passthrough,
TooltipContent: passthrough,
TooltipTrigger: passthrough,
}));
mock.module('@/components/icon/Icon', () => ({ Icon: () => null }));
mock.module('@/components/model-picker/ModelPickerList', () => ({ ModelPickerList: () => null }));
mock.module('@/hooks/useRuntimeAPIs', () => ({ useIsVSCodeRuntime: () => false }));
mock.module('@/hooks/useModelLists', () => ({ useModelLists: () => ({ favoriteModels: [], recentModels: [] }) }));
mock.module('@/hooks/useIsTextTruncated', () => ({ useIsTextTruncated: () => false }));
mock.module('@/hooks/useOpenCodeReadiness', () => ({
useOpenCodeReadiness: () => ({ isReady: true, isUnavailable: false }),
}));
mock.module('@/lib/device', () => ({ useDeviceInfo: () => ({ isTouch: false }) }));
mock.module('@/lib/desktop', () => ({ isDesktopShell: () => false }));
mock.module('@/lib/startupTrace', () => ({ markStartupTrace: () => undefined }));
const { ModelControls } = await import('./ModelControls');
const { I18nProvider } = await import('@/lib/i18n');
const DOM_GLOBAL_NAMES = [
'window',
'document',
'navigator',
'Node',
'Element',
'HTMLElement',
'HTMLIFrameElement',
'localStorage',
'requestAnimationFrame',
'cancelAnimationFrame',
'IS_REACT_ACT_ENVIRONMENT',
] as const;
const frameTimers = new Map<number, ReturnType<Window['setTimeout']>>();
let nextFrameHandle = 1;
const installDom = () => {
const happyWindow = new Window({ url: 'http://localhost' });
const previous = DOM_GLOBAL_NAMES.map(
(name) => [name, Object.getOwnPropertyDescriptor(globalThis, name)] as const,
);
const values = {
window: happyWindow,
document: happyWindow.document,
navigator: happyWindow.navigator,
Node: happyWindow.Node,
Element: happyWindow.Element,
HTMLElement: happyWindow.HTMLElement,
HTMLIFrameElement: happyWindow.HTMLIFrameElement,
localStorage: happyWindow.localStorage,
// The component focuses the composer through rAF on several paths.
requestAnimationFrame: (callback: FrameRequestCallback) => {
const handle = nextFrameHandle++;
frameTimers.set(handle, happyWindow.setTimeout(() => {
frameTimers.delete(handle);
callback(0);
}, 0));
return handle;
},
cancelAnimationFrame: (handle: number) => {
const timer = frameTimers.get(handle);
if (timer === undefined) return;
frameTimers.delete(handle);
happyWindow.clearTimeout(timer);
},
IS_REACT_ACT_ENVIRONMENT: true,
};
for (const name of DOM_GLOBAL_NAMES) {
Object.defineProperty(globalThis, name, { value: values[name], configurable: true, writable: true });
}
const container = document.createElement('div');
document.body.appendChild(container);
return {
container,
restore: () => {
for (const [name, descriptor] of previous) {
if (descriptor) Object.defineProperty(globalThis, name, descriptor);
else Reflect.deleteProperty(globalThis, name);
}
},
};
};
const renderModelControls = async () => {
const dom = installDom();
const root = createRoot(dom.container);
await act(async () => root.render(
<I18nProvider>
<ModelControls />
</I18nProvider>,
));
return {
dom,
cleanup: async () => {
await act(async () => root.unmount());
dom.restore();
},
};
};
describe('ModelControls effort restore', () => {
beforeEach(() => {
variantWrites.length = 0;
overrideWrites.length = 0;
latestUserChoice = null;
preserveManualOverride = false;
useSelectionStore.setState({ savedVariant: undefined });
useConfigStore.setState({
currentProviderId: PROVIDER_ID,
currentModelId: MODEL_ID,
currentAgentName: AGENT,
currentVariant: undefined,
currentVariantSelection: { override: undefined, inherited: undefined },
settingsDefaultVariant: undefined,
selectionSource: 'auto',
});
});
test('restores the concrete effort the session history carries', async () => {
latestUserChoice = { id: 'msg-1', agent: AGENT, providerID: PROVIDER_ID, modelID: MODEL_ID, variant: 'low' };
const { cleanup } = await renderModelControls();
try {
expect(variantWrites).toContain('low');
expect(variantWrites).not.toContain(null);
expect(useSelectionStore.getState().savedVariant).toBe('low');
expect(useConfigStore.getState().currentVariantSelection.override).toBe('low');
} finally {
await cleanup();
}
});
test('history without an effort records no choice instead of an explicit Default', async () => {
latestUserChoice = { id: 'msg-2', agent: AGENT, providerID: PROVIDER_ID, modelID: MODEL_ID };
const { cleanup } = await renderModelControls();
try {
expect(variantWrites).not.toContain(null);
expect(useSelectionStore.getState().savedVariant).toBe(undefined);
expect(useConfigStore.getState().currentVariantSelection.override).toBe(undefined);
} finally {
await cleanup();
}
});
test('a preserved manual override keeps a recorded explicit Default', async () => {
latestUserChoice = { id: 'msg-3', agent: AGENT, providerID: PROVIDER_ID, modelID: MODEL_ID, variant: 'high' };
preserveManualOverride = true;
useSelectionStore.setState({ savedVariant: null });
useConfigStore.setState({ selectionSource: 'manual' });
const { cleanup } = await renderModelControls();
try {
expect(useSelectionStore.getState().savedVariant).toBeNull();
expect(useConfigStore.getState().currentVariantSelection.override).toBeNull();
} finally {
await cleanup();
}
});
});
@@ -292,7 +292,6 @@ export const GitView: React.FC<GitViewProps> = ({ isActive }) => {
const isLogLoading = useGitLoadingLog(gitDirectory ?? null);
const {
setActiveDirectory,
fetchAll,
ensureAll,
fetchStatus,
fetchBranches,
@@ -308,7 +307,6 @@ export const GitView: React.FC<GitViewProps> = ({ isActive }) => {
selectNestedRepo,
} = useGitStore(useShallow((state) => ({
setActiveDirectory: state.setActiveDirectory,
fetchAll: state.fetchAll,
ensureAll: state.ensureAll,
fetchStatus: state.fetchStatus,
fetchBranches: state.fetchBranches,