// @vitest-environment jsdom import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' import { afterEach, describe, expect, it, vi } from 'vitest' import type { ModelTarget } from '@deepseek-ai/dsh-client-connection/client' import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client' import type { ComponentProps } from 'react' import type { ModelDirectoryState } from '../src/client/directory.ts' import { ModelSelect } from '../src/client/ModelSelect.tsx' import { zh } from '../src/client/locales.ts' import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts' // The seat's key domain is model ∪ common; the stub mirrors the real lookup // chain: package dictionary, then common vocabulary, then the key. const t: ComponentProps['t'] = (key, params) => { const template = (zh as Record)[key] ?? (commonZh as Record)[key] ?? key return params === undefined ? template : template.replace(/\{(\w+)\}/g, (match, name: string) => name in params ? String(params[name]) : match) } const reasoning = { efforts: [ { id: 'off', name: 'Off' }, { id: 'high', name: 'High' }, { id: 'max', name: 'Max', description: 'Largest budget' }, ], defaultEffort: 'high', } function state(overrides: Partial = {}): ModelDirectoryState { return { current: { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, groups: [{ id: 'deepseek-official', name: 'DeepSeek', models: [{ id: 'deepseek-v4-flash', name: 'DeepSeek-V4-Flash', reasoning }], }], failures: [], status: 'ready', error: null, ...overrides, } } afterEach(cleanup) describe('ModelSelect reasoning effort', () => { it('renders adapter metadata and submits the effort as part of the session target', async () => { const directory = createSnapshotStore(state()) const select = vi.fn(async (target: ModelTarget) => { directory.set(state({ current: target })) return true }) render() const trigger = screen.getByRole('button', { name: '选择模型,当前 DeepSeek-V4-Flash,推理等级 High', }) fireEvent.click(trigger) fireEvent.click(screen.getByRole('menuitem', { name: /推理等级/ })) expect(screen.getAllByRole('menuitemradio').map(item => item.textContent)) .toEqual(['Off', 'High', 'MaxLargest budget']) fireEvent.click(screen.getByRole('menuitemradio', { name: /Max/ })) await waitFor(() => { expect(select).toHaveBeenCalledWith({ provider: 'deepseek-official', model: 'deepseek-v4-flash', reasoningEffort: 'max', }) expect(trigger.getAttribute('aria-label')).toBe('选择模型,当前 DeepSeek-V4-Flash,推理等级 Max') }) }) it('offers provider default only when the adapter does not configure a model default', () => { const directory = createSnapshotStore(state({ groups: [{ id: 'provider', name: 'Provider', models: [{ id: 'model', name: 'Model', reasoning: { efforts: [{ id: 'standard', name: 'Standard' }] }, }], }], current: { provider: 'provider', model: 'model' }, })) render() fireEvent.click(screen.getByRole('button', { name: '选择模型,当前 Model,推理等级 Default', })) fireEvent.click(screen.getByRole('menuitem', { name: /推理等级/ })) expect(screen.getAllByRole('menuitemradio').map(item => item.textContent)) .toEqual(['Default', 'Standard']) }) it('prompts for a new selection when the current target is no longer advertised', () => { const directory = createSnapshotStore(state({ current: { provider: 'deepseek-official', model: 'removed-model' }, })) const select = vi.fn().mockResolvedValue(true) render() const trigger = screen.getByRole('button', { name: '选择模型' }) expect(trigger.textContent).toContain('选择模型') fireEvent.click(trigger) expect(screen.queryByRole('menuitem', { name: /推理等级/ })).toBeNull() fireEvent.click(screen.getByRole('menuitem', { name: /模型/ })) expect(screen.queryByText('removed-model')).toBeNull() expect(screen.getByRole('menuitemradio', { name: 'DeepSeek-V4-Flash' })).toBeTruthy() }) it('renders no Agent-bound control for an addressed subagent session', () => { const load = vi.fn() render() expect(screen.queryByRole('button')).toBeNull() expect(load).not.toHaveBeenCalled() }) })