| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- // @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<typeof ModelSelect>['t'] = (key, params) => {
- const template = (zh as Record<string, string>)[key]
- ?? (commonZh as Record<string, string>)[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> = {}): 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<ModelDirectoryState>(state())
- const select = vi.fn(async (target: ModelTarget) => {
- directory.set(state({ current: target }))
- return true
- })
- render(<ModelSelect
- locked={false}
- available
- directory={directory}
- load={vi.fn()}
- select={select}
- t={t}
- />)
- 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(<ModelSelect
- locked={false}
- available
- directory={directory}
- load={vi.fn()}
- select={vi.fn().mockResolvedValue(true)}
- t={t}
- />)
- 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(<ModelSelect
- locked={false}
- available
- directory={directory}
- load={vi.fn()}
- select={select}
- t={t}
- />)
- 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(<ModelSelect
- locked={false}
- available={false}
- directory={createSnapshotStore(state())}
- load={load}
- select={vi.fn().mockResolvedValue(false)}
- t={t}
- />)
- expect(screen.queryByRole('button')).toBeNull()
- expect(load).not.toHaveBeenCalled()
- })
- })
|