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:
5 files changed
+62
-4
No files matched your search
@@ -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) }}
|
||||
>
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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 })
|
||||
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
Reference in New Issue
Block a user