Files
deepseek-harness/packages/client/ui-primitives/tests/settings-fields.client.spec.tsx
T
Yichen Jiang 93e67201dd refactor(web): move the official settings pages into companion packages
Each official settings page now lives in a client package of its own:
`ui-settings-shell`, `ui-settings-agent-loop`, `ui-settings-subagent`
and `ui-settings-web-search` register into `plugins.item` while the Host
serves their namespaces, through the new `settingsScope.whileServed`
watch, each with its own dictionary and styles. The form model, the
fields and the form move to `ui-primitives` as the shared settings kit,
so a community package's browser half builds a page the same way.
`ui-settings-plugins` keeps only the Built-in plugins section shell
around the inventory tab.

The companions hold Loader rows in `web-app` behind the section; the
client catalog, config catalog and module graph regenerate; the
settings-card cookbook walks through a companion package.
2026-09-17 17:59:12 +08:00

153 lines
4.8 KiB
TypeScript

// @vitest-environment jsdom
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { SettingsSecretField, SettingsValueField } from '../src/settings-form/fields.tsx'
afterEach(cleanup)
const frame = {
id: 'field',
label: 'Command timeout',
hint: 'How long one command may run.',
overriddenLabel: 'Overridden',
resetLabel: 'Reset to default',
invalidLabel: 'Enter a number.',
disabled: false,
overridden: false,
invalid: false,
}
describe('SettingsValueField', () => {
it('stages every keystroke without writing', () => {
const onEdit = vi.fn()
render(<SettingsValueField {...frame} text="60000" onEdit={onEdit} onReset={vi.fn()} />)
fireEvent.change(screen.getByLabelText('Command timeout'), { target: { value: '9000' } })
expect(onEdit).toHaveBeenCalledWith('9000')
})
it('renders the staged text it is given rather than a draft of its own', () => {
const { rerender } = render(<SettingsValueField {...frame} text="60000" onEdit={vi.fn()} onReset={vi.fn()} />)
expect(screen.getByLabelText('Command timeout')).toHaveProperty('value', '60000')
rerender(<SettingsValueField {...frame} text="9000" onEdit={vi.fn()} onReset={vi.fn()} />)
expect(screen.getByLabelText('Command timeout')).toHaveProperty('value', '9000')
})
it('offers the reset only while an override would stand', () => {
const onReset = vi.fn()
const { rerender } = render(<SettingsValueField {...frame} text="9000" onEdit={vi.fn()} onReset={onReset} />)
expect(screen.queryByRole('button', { name: 'Reset to default' })).toBeNull()
rerender(<SettingsValueField {...frame} overridden text="9000" onEdit={vi.fn()} onReset={onReset} />)
fireEvent.click(screen.getByRole('button', { name: 'Reset to default' }))
expect(screen.getByText('Overridden')).toBeTruthy()
expect(onReset).toHaveBeenCalledOnce()
})
it('replaces the hint with the reason an invalid draft cannot be saved', () => {
render(<SettingsValueField {...frame} invalid text="soon" onEdit={vi.fn()} onReset={vi.fn()} />)
expect(screen.getByText('Enter a number.')).toBeTruthy()
expect(screen.queryByText('How long one command may run.')).toBeNull()
expect(screen.getByLabelText('Command timeout').getAttribute('aria-invalid')).toBe('true')
})
it('hints a numeric keypad and renders a placeholder when asked', () => {
render(
<SettingsValueField
{...frame}
numeric
placeholder="https://api.deepseek.com"
text=""
onEdit={vi.fn()}
onReset={vi.fn()}
/>,
)
const input = screen.getByLabelText('Command timeout')
expect(input.getAttribute('inputmode')).toBe('numeric')
expect(input).toHaveProperty('placeholder', 'https://api.deepseek.com')
})
it('disables the control and its reset while the document is read-only', () => {
render(<SettingsValueField {...frame} disabled overridden text="9000" onEdit={vi.fn()} onReset={vi.fn()} />)
expect(screen.getByLabelText('Command timeout')).toHaveProperty('disabled', true)
expect(screen.getByRole('button', { name: 'Reset to default' })).toHaveProperty('disabled', true)
})
})
describe('SettingsSecretField', () => {
const secret = {
id: 'key',
label: 'API key',
hint: 'Stored outside the settings file.',
disabled: false,
}
it('stages the draft and never renders it', () => {
const onEdit = vi.fn()
render(
<SettingsSecretField
{...secret}
text=""
configured={false}
stateLabel="No key is configured."
onEdit={onEdit}
/>,
)
const input = screen.getByLabelText('API key')
fireEvent.change(input, { target: { value: 'ds-secret' } })
expect(onEdit).toHaveBeenCalledWith('ds-secret')
expect(input).toHaveProperty('type', 'password')
})
it('reports the configured state the Host holds', () => {
const { rerender } = render(
<SettingsSecretField
{...secret}
text=""
configured={false}
stateLabel="No key is configured."
onEdit={vi.fn()}
/>,
)
expect(screen.getByText('No key is configured.')).toBeTruthy()
rerender(
<SettingsSecretField
{...secret}
text="ds-secret"
configured
stateLabel="A key is configured."
onEdit={vi.fn()}
/>,
)
expect(screen.getByText('A key is configured.')).toBeTruthy()
expect(screen.getByLabelText('API key')).toHaveProperty('value', 'ds-secret')
})
it('disables the control when it is told to', () => {
render(
<SettingsSecretField
{...secret}
disabled
text=""
configured
stateLabel="A key is configured."
onEdit={vi.fn()}
/>,
)
expect(screen.getByLabelText('API key')).toHaveProperty('disabled', true)
})
})