test(ui-agent-preset): cover the paths the extraction left uncovered

The shared picker's id fallback on both trust levels, the chip refusing an
unrelated settings namespace, a refused `settings.describe`, and a blank
draft that names no source. Drops the picker's `title`, which this layer's
one caller never passes.
This commit is contained in:
Yichen Jiang
2026-08-07 16:06:01 +08:00
parent 93ad74cab5
commit a4f3aa073f
5 changed files with 62 additions and 4 deletions

View File

@@ -28,8 +28,6 @@ export interface PresetMenuProps {
chevronClassName: string | undefined
/** Whether the trigger refuses interaction. */
disabled: boolean
/** Native tooltip, absent where the surface offers none. */
title?: string
/** Whether the menu is open — the surface owns this so it can force it shut. */
open: boolean
/** Report the menu's next open state. */
@@ -45,7 +43,7 @@ export interface PresetMenuProps {
*/
export function PresetMenu({
options, selectedId, label, userTrustLabel, buttonClassName, chevronClassName,
disabled, title, open, onOpenChange, onSelect,
disabled, open, onOpenChange, onSelect,
}: PresetMenuProps) {
return (
<Menu
@@ -73,7 +71,6 @@ export function PresetMenu({
className={buttonClassName}
aria-haspopup="menu"
aria-expanded={open}
{...title === undefined ? {} : { title }}
disabled={disabled}
onClick={() => { onOpenChange(!open) }}
>

View File

@@ -300,7 +300,13 @@ describe('ui-agent-preset apply', () => {
// lives on another screen: without this the next session — the very one
// the setting governs — would be composed from the previous default until
// a reload.
// An unrelated namespace moves nothing: the chip re-reads on its own
// setting, not on every settings write in the process.
moveDefault()
ctx.emit('settings/changed', 'llm-deepseek')
await Promise.resolve()
expect(seat.hooks.agentPresetSeat.getSnapshot().current).toBe('standard')
ctx.emit('settings/changed', 'agent-presets')
await vi.waitFor(() => {
expect(seat.hooks.agentPresetSeat.getSnapshot().current).toBe('minimal')

View File

@@ -105,6 +105,29 @@ describe('the General-settings row', () => {
expect(screen.getAllByText('标准模式')).toHaveLength(2)
})
it('falls back to the id for a preset that published no name', () => {
renderRow({
currentValue: 'mine',
options: [
{ id: 'standard', trust: 'system', name: '标准模式' },
{ id: 'bare', trust: 'system' },
{ id: 'mine', trust: 'user' },
{ id: 'ours', trust: 'user', name: '团队模式' },
],
})
// The trigger names the preset; with no metadata the id is all there is.
expect(screen.getByRole('button').textContent).toContain('mine')
fireEvent.click(screen.getByRole('button'))
// A locally authored preset is marked whether or not it named itself.
expect(screen.getByText(`团队模式 · ${en.userTrust}`)).toBeTruthy()
expect(screen.getByText(`mine · ${en.userTrust}`)).toBeTruthy()
// A shipped preset with no metadata is listed by id and carries no mark.
expect(screen.getByText('bare')).toBeTruthy()
})
it('writes the picked preset and closes the menu', () => {
const actions = renderRow()
fireEvent.click(screen.getByRole('button'))

View File

@@ -200,6 +200,16 @@ describe('the composition editor', () => {
writable: true, name: '我的预设', description: '', saving: false, error: null,
}
it('opens a blank draft without naming a preset it came from', () => {
const { source: _copied, ...blank } = draft
renderSection({ draft: { ...blank, id: '', creating: true } })
// "New preset" starts empty — copying is what the per-row Duplicate does,
// so a blank draft has no source to name and shows no copied-from hint.
expect(screen.getByText(en.newPreset)).toBeTruthy()
expect(screen.queryByText(new RegExp(en.copyOf))).toBeNull()
})
it('replaces the list while editing, and returns to it', () => {
const actions = renderSection({ draft })

View File

@@ -419,4 +419,26 @@ describe('the new-session chip controller', () => {
expect(controller.store.getSnapshot().error).toBe('socket closed')
})
it('reports a refused describe as a failure rather than a half-read row', async () => {
const api = {
agentPresets: {
list: () => Promise.resolve({
rpcId: 'r',
result: { ok: true as const, value: { presets: [{ id: 'standard', trust: 'system', isDefault: true }], authorable: true } },
}),
},
// The roster answered; `settings.describe` is what rejected, and the row
// cannot claim a writable default it never confirmed.
settings: { describe: () => Promise.reject(new Error('socket closed')) },
} as unknown as IApiClient
const controller = new AgentPresetSettingsController(api)
await controller.load()
expect(controller.store.getSnapshot().status).toBe('error')
expect(controller.store.getSnapshot().error).toBe('socket closed')
})
})