mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 08:03:28 +00:00
feat(new-workspace): set selected agent as default
This commit is contained in:
@@ -1,11 +1,93 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { TuiAgent } from '../../../../shared/types'
|
||||
import { AGENT_CATALOG, AgentIcon } from '@/lib/agent-catalog'
|
||||
import { AGENT_FAVICON_ASSETS } from '@/lib/agent-favicon-assets'
|
||||
import AgentCombobox from './AgentCombobox'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
describe('AgentCombobox', () => {
|
||||
it('sets the closed trigger selection as the default agent', () => {
|
||||
const onSetDefault = vi.fn()
|
||||
render(
|
||||
<AgentCombobox
|
||||
agents={AGENT_CATALOG}
|
||||
value="codex"
|
||||
onValueChange={vi.fn()}
|
||||
defaultAgent="claude"
|
||||
onSetDefault={onSetDefault}
|
||||
/>
|
||||
)
|
||||
|
||||
const trigger = screen.getByRole('combobox')
|
||||
fireEvent.contextMenu(trigger)
|
||||
|
||||
expect(trigger.getAttribute('aria-expanded')).toBe('false')
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Set as default' }))
|
||||
expect(onSetDefault).toHaveBeenCalledWith('codex')
|
||||
})
|
||||
|
||||
it('maps the closed Blank Terminal selection to the blank default preference', () => {
|
||||
const onSetDefault = vi.fn()
|
||||
render(
|
||||
<AgentCombobox
|
||||
agents={AGENT_CATALOG}
|
||||
value={null}
|
||||
onValueChange={vi.fn()}
|
||||
defaultAgent="codex"
|
||||
onSetDefault={onSetDefault}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.contextMenu(screen.getByRole('combobox'))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Set as default' }))
|
||||
|
||||
expect(onSetDefault).toHaveBeenCalledWith('blank')
|
||||
})
|
||||
|
||||
it('marks the closed trigger when its selection is already the default', () => {
|
||||
const onSetDefault = vi.fn()
|
||||
render(
|
||||
<AgentCombobox
|
||||
agents={AGENT_CATALOG}
|
||||
value="codex"
|
||||
onValueChange={vi.fn()}
|
||||
defaultAgent="codex"
|
||||
onSetDefault={onSetDefault}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.contextMenu(screen.getByRole('combobox'))
|
||||
const menuItem = screen.getByRole('menuitem', { name: 'Current default' })
|
||||
|
||||
expect(menuItem.hasAttribute('data-disabled')).toBe(true)
|
||||
fireEvent.click(menuItem)
|
||||
expect(onSetDefault).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps the expanded agent-row default action available', () => {
|
||||
const onSetDefault = vi.fn()
|
||||
render(
|
||||
<AgentCombobox
|
||||
agents={AGENT_CATALOG}
|
||||
value="codex"
|
||||
onValueChange={vi.fn()}
|
||||
defaultAgent="codex"
|
||||
onSetDefault={onSetDefault}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('combobox'))
|
||||
fireEvent.contextMenu(screen.getByRole('option', { name: 'Claude' }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: 'Set as default' }))
|
||||
|
||||
expect(onSetDefault).toHaveBeenCalledWith('claude')
|
||||
})
|
||||
|
||||
it('keeps enough trigger width for GitHub Copilot when callers pass min-w-0', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<AgentCombobox
|
||||
|
||||
@@ -43,7 +43,7 @@ type AgentComboboxProps = {
|
||||
* currently-applied choice. */
|
||||
defaultAgent?: DefaultAgentPreference
|
||||
/** Optional handler for right-click "Set as default" action. When provided,
|
||||
* each list item (including Blank Terminal) gets a context menu. */
|
||||
* the selected trigger and each list item get a context menu. */
|
||||
onSetDefault?: (agent: DefaultAgentPreference) => void
|
||||
triggerClassName?: string
|
||||
/** When set, pressing Enter on the closed combobox trigger invokes this
|
||||
@@ -69,6 +69,12 @@ type ItemRenderArgs = {
|
||||
label: string
|
||||
}
|
||||
|
||||
type AgentDefaultContextMenuProps = {
|
||||
children: React.ReactNode
|
||||
isDefault: boolean
|
||||
onSetDefault?: () => void
|
||||
}
|
||||
|
||||
function AgentIconLabel({
|
||||
icon,
|
||||
label
|
||||
@@ -86,6 +92,29 @@ function AgentIconLabel({
|
||||
)
|
||||
}
|
||||
|
||||
function AgentDefaultContextMenu({
|
||||
children,
|
||||
isDefault,
|
||||
onSetDefault
|
||||
}: AgentDefaultContextMenuProps): React.ReactNode {
|
||||
if (!onSetDefault) {
|
||||
return children
|
||||
}
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger asChild>{children}</ContextMenuTrigger>
|
||||
<ContextMenuContent className="z-[70]">
|
||||
<ContextMenuItem onSelect={onSetDefault} disabled={isDefault}>
|
||||
<Star className="size-3.5" />
|
||||
{isDefault
|
||||
? translate('auto.components.agent.AgentCombobox.1b0d6965fa', 'Current default')
|
||||
: translate('auto.components.agent.AgentCombobox.9c6b59fe58', 'Set as default')}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
)
|
||||
}
|
||||
|
||||
function renderItem({
|
||||
key,
|
||||
itemValue,
|
||||
@@ -109,23 +138,12 @@ function renderItem({
|
||||
<AgentIconLabel icon={icon} label={label} />
|
||||
</CommandItem>
|
||||
)
|
||||
if (!onSetDefault) {
|
||||
return row
|
||||
}
|
||||
return (
|
||||
// Why: z-[70] sits above PopoverContent's z-[60] so the right-click menu
|
||||
// renders in front of the still-open combobox popover instead of behind it.
|
||||
<ContextMenu key={key}>
|
||||
<ContextMenuTrigger asChild>{row}</ContextMenuTrigger>
|
||||
<ContextMenuContent className="z-[70]">
|
||||
<ContextMenuItem onSelect={onSetDefault} disabled={isDefault}>
|
||||
<Star className="size-3.5" />
|
||||
{isDefault
|
||||
? translate('auto.components.agent.AgentCombobox.1b0d6965fa', 'Current default')
|
||||
: translate('auto.components.agent.AgentCombobox.9c6b59fe58', 'Set as default')}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
<AgentDefaultContextMenu key={key} isDefault={isDefault} onSetDefault={onSetDefault}>
|
||||
{row}
|
||||
</AgentDefaultContextMenu>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -288,41 +306,46 @@ export default function AgentCombobox({
|
||||
// trigger free to overflow its dialog column and look misaligned with Project/Name.
|
||||
<div className="min-w-0 w-full">
|
||||
<Popover open={open} onOpenChange={handleOpenChange}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
onKeyDown={handleTriggerKeyDown}
|
||||
className={cn(
|
||||
// Why: callers sometimes pass `min-w-0` for grid layouts, but
|
||||
// the compact trigger still needs room for "GitHub Copilot".
|
||||
// py-0 clears the default size's py-2 so icon+label center in h-8/h-9.
|
||||
'h-8 justify-between px-3 py-0 text-xs font-normal',
|
||||
triggerClassName,
|
||||
!allowNarrowTrigger && TRIGGER_MIN_WIDTH_CLASS
|
||||
)}
|
||||
data-agent-combobox-root="true"
|
||||
>
|
||||
{selectedAgent ? (
|
||||
<AgentIconLabel
|
||||
icon={<AgentIcon agent={selectedAgent.id} size={14} />}
|
||||
label={selectedAgent.label}
|
||||
/>
|
||||
) : (
|
||||
<AgentIconLabel
|
||||
icon={<Terminal className="size-3.5" />}
|
||||
label={
|
||||
emptyLabel ??
|
||||
translate('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<ChevronsUpDown className="size-3.5 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<AgentDefaultContextMenu
|
||||
isDefault={defaultAgent === (value ?? 'blank')}
|
||||
onSetDefault={onSetDefault ? () => onSetDefault(value ?? 'blank') : undefined}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
onKeyDown={handleTriggerKeyDown}
|
||||
className={cn(
|
||||
// Why: callers sometimes pass `min-w-0` for grid layouts, but
|
||||
// the compact trigger still needs room for "GitHub Copilot".
|
||||
// py-0 clears the default size's py-2 so icon+label center in h-8/h-9.
|
||||
'h-8 justify-between px-3 py-0 text-xs font-normal',
|
||||
triggerClassName,
|
||||
!allowNarrowTrigger && TRIGGER_MIN_WIDTH_CLASS
|
||||
)}
|
||||
data-agent-combobox-root="true"
|
||||
>
|
||||
{selectedAgent ? (
|
||||
<AgentIconLabel
|
||||
icon={<AgentIcon agent={selectedAgent.id} size={14} />}
|
||||
label={selectedAgent.label}
|
||||
/>
|
||||
) : (
|
||||
<AgentIconLabel
|
||||
icon={<Terminal className="size-3.5" />}
|
||||
label={
|
||||
emptyLabel ??
|
||||
translate('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<ChevronsUpDown className="size-3.5 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</AgentDefaultContextMenu>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
className={cn(
|
||||
|
||||
Reference in New Issue
Block a user