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:
@@ -28,8 +28,6 @@ export interface PresetMenuProps {
|
|||||||
chevronClassName: string | undefined
|
chevronClassName: string | undefined
|
||||||
/** Whether the trigger refuses interaction. */
|
/** Whether the trigger refuses interaction. */
|
||||||
disabled: boolean
|
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. */
|
/** Whether the menu is open — the surface owns this so it can force it shut. */
|
||||||
open: boolean
|
open: boolean
|
||||||
/** Report the menu's next open state. */
|
/** Report the menu's next open state. */
|
||||||
@@ -45,7 +43,7 @@ export interface PresetMenuProps {
|
|||||||
*/
|
*/
|
||||||
export function PresetMenu({
|
export function PresetMenu({
|
||||||
options, selectedId, label, userTrustLabel, buttonClassName, chevronClassName,
|
options, selectedId, label, userTrustLabel, buttonClassName, chevronClassName,
|
||||||
disabled, title, open, onOpenChange, onSelect,
|
disabled, open, onOpenChange, onSelect,
|
||||||
}: PresetMenuProps) {
|
}: PresetMenuProps) {
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
@@ -73,7 +71,6 @@ export function PresetMenu({
|
|||||||
className={buttonClassName}
|
className={buttonClassName}
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
{...title === undefined ? {} : { title }}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => { onOpenChange(!open) }}
|
onClick={() => { onOpenChange(!open) }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -300,7 +300,13 @@ describe('ui-agent-preset apply', () => {
|
|||||||
// lives on another screen: without this the next session — the very one
|
// lives on another screen: without this the next session — the very one
|
||||||
// the setting governs — would be composed from the previous default until
|
// the setting governs — would be composed from the previous default until
|
||||||
// a reload.
|
// a reload.
|
||||||
|
// An unrelated namespace moves nothing: the chip re-reads on its own
|
||||||
|
// setting, not on every settings write in the process.
|
||||||
moveDefault()
|
moveDefault()
|
||||||
|
ctx.emit('settings/changed', 'llm-deepseek')
|
||||||
|
await Promise.resolve()
|
||||||
|
expect(seat.hooks.agentPresetSeat.getSnapshot().current).toBe('standard')
|
||||||
|
|
||||||
ctx.emit('settings/changed', 'agent-presets')
|
ctx.emit('settings/changed', 'agent-presets')
|
||||||
await vi.waitFor(() => {
|
await vi.waitFor(() => {
|
||||||
expect(seat.hooks.agentPresetSeat.getSnapshot().current).toBe('minimal')
|
expect(seat.hooks.agentPresetSeat.getSnapshot().current).toBe('minimal')
|
||||||
|
|||||||
@@ -105,6 +105,29 @@ describe('the General-settings row', () => {
|
|||||||
expect(screen.getAllByText('标准模式')).toHaveLength(2)
|
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', () => {
|
it('writes the picked preset and closes the menu', () => {
|
||||||
const actions = renderRow()
|
const actions = renderRow()
|
||||||
fireEvent.click(screen.getByRole('button'))
|
fireEvent.click(screen.getByRole('button'))
|
||||||
|
|||||||
@@ -200,6 +200,16 @@ describe('the composition editor', () => {
|
|||||||
writable: true, name: '我的预设', description: '', saving: false, error: null,
|
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', () => {
|
it('replaces the list while editing, and returns to it', () => {
|
||||||
const actions = renderSection({ draft })
|
const actions = renderSection({ draft })
|
||||||
|
|
||||||
|
|||||||
@@ -419,4 +419,26 @@ describe('the new-session chip controller', () => {
|
|||||||
|
|
||||||
expect(controller.store.getSnapshot().error).toBe('socket closed')
|
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