The native adapter's route was named deepseek, colliding with pi-ai's catalog provider of the same name, so the two DeepSeek paths could never be mounted side by side. The web settings page needs both configurable at once. Compositions, fixtures, goldens, scaffolding defaults, and docs all move together (pre-release, no shim); TUI/session-query-spill/ missing-credential goldens re-recorded through their keyless refresh modes because provider-name length shifts box padding and spill truncation points.
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
// @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 { ModelDirectoryState } from '../src/client/directory.ts'
|
|
import { ModelSelect } from '../src/client/ModelSelect.tsx'
|
|
|
|
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(state())
|
|
const select = vi.fn(async (target: ModelTarget) => {
|
|
directory.update((snapshot) => { snapshot.current = target })
|
|
return true
|
|
})
|
|
render(<ModelSelect
|
|
locked={false}
|
|
directory={directory}
|
|
load={vi.fn()}
|
|
select={select}
|
|
/>)
|
|
|
|
const trigger = screen.getByRole('button', {
|
|
name: '选择模型,当前 DeepSeek-V4-Flash,推理等级 High',
|
|
})
|
|
fireEvent.click(trigger)
|
|
fireEvent.click(screen.getByRole('menuitem', { name: /Effort/ }))
|
|
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}
|
|
directory={directory}
|
|
load={vi.fn()}
|
|
select={vi.fn().mockResolvedValue(true)}
|
|
/>)
|
|
|
|
fireEvent.click(screen.getByRole('button', {
|
|
name: '选择模型,当前 Model,推理等级 Provider default',
|
|
}))
|
|
fireEvent.click(screen.getByRole('menuitem', { name: /Effort/ }))
|
|
expect(screen.getAllByRole('menuitemradio').map(item => item.textContent))
|
|
.toEqual(['Provider default', 'Standard'])
|
|
})
|
|
})
|