|
|
@@ -13,8 +13,10 @@ import { AgentLoopCard } from '../src/client/AgentLoopCard.tsx'
|
|
|
import type { AgentLoopCardProps } from '../src/client/AgentLoopCard.tsx'
|
|
|
import { BashCard } from '../src/client/BashCard.tsx'
|
|
|
import type { BashCardProps } from '../src/client/BashCard.tsx'
|
|
|
+import { ConfigurablePluginsTab } from '../src/client/ConfigurablePluginsTab.tsx'
|
|
|
+import type { ConfigurablePluginsTabProps } from '../src/client/ConfigurablePluginsTab.tsx'
|
|
|
import { PluginConfigSection } from '../src/client/PluginConfigSection.tsx'
|
|
|
-import type { PluginConfigSectionProps } from '../src/client/PluginConfigSection.tsx'
|
|
|
+import type { PluginConfigSectionProps, PluginSettingsTabRow } from '../src/client/PluginConfigSection.tsx'
|
|
|
import { WebSearchCard } from '../src/client/WebSearchCard.tsx'
|
|
|
import type { WebSearchCardProps } from '../src/client/WebSearchCard.tsx'
|
|
|
import type { AgentLoopCardState } from '../src/client/agent-loop-store.ts'
|
|
|
@@ -46,15 +48,26 @@ function cardActions() {
|
|
|
return { edit: vi.fn(), resetField: vi.fn(), save: vi.fn(), discard: vi.fn() }
|
|
|
}
|
|
|
|
|
|
-function renderSection(cardCount: number, cards = 'cards') {
|
|
|
+function renderSection(rows: readonly PluginSettingsTabRow[]) {
|
|
|
const props = {
|
|
|
t,
|
|
|
- cardCount,
|
|
|
- renderSlot: () => <li>{cards}</li>,
|
|
|
+ useTabs: (selector: (value: readonly PluginSettingsTabRow[]) => unknown) => selector(rows),
|
|
|
+ renderSlot: (_name: string, _owner: unknown, options: { only?: string }) => (
|
|
|
+ <span>{options.only}</span>
|
|
|
+ ),
|
|
|
} as unknown as PluginConfigSectionProps
|
|
|
render(<PluginConfigSection {...props} />)
|
|
|
}
|
|
|
|
|
|
+function renderConfigurable(cardCount: number, cards = 'cards') {
|
|
|
+ const props = {
|
|
|
+ t,
|
|
|
+ cardCount,
|
|
|
+ renderSlot: () => <li>{cards}</li>,
|
|
|
+ } as unknown as ConfigurablePluginsTabProps
|
|
|
+ render(<ConfigurablePluginsTab {...props} />)
|
|
|
+}
|
|
|
+
|
|
|
function renderBash(state: Partial<BashCardState> = {}) {
|
|
|
const store = createSnapshotStore<BashCardState>({
|
|
|
...settled,
|
|
|
@@ -69,26 +82,89 @@ function renderBash(state: Partial<BashCardState> = {}) {
|
|
|
}
|
|
|
|
|
|
describe('PluginConfigSection', () => {
|
|
|
- it('says so when no plugin contributed a card', () => {
|
|
|
- renderSection(0)
|
|
|
+ it('says so when no plugin contributed a tab', () => {
|
|
|
+ renderSection([])
|
|
|
|
|
|
expect(screen.getByText(en.empty)).toBeTruthy()
|
|
|
- expect(screen.queryByText('cards')).toBeNull()
|
|
|
+ expect(screen.queryByRole('tab')).toBeNull()
|
|
|
})
|
|
|
|
|
|
- it('renders the card list once a plugin contributed one', () => {
|
|
|
- renderSection(1)
|
|
|
+ it('defaults to the first ordered tab and mounts another only after selection', () => {
|
|
|
+ renderSection([
|
|
|
+ { id: 'configurable', order: 0, label: en.configurableTab },
|
|
|
+ { id: 'all', order: 10, label: 'Plugin list' },
|
|
|
+ ])
|
|
|
|
|
|
- expect(screen.getByText('cards')).toBeTruthy()
|
|
|
- expect(screen.queryByText(en.empty)).toBeNull()
|
|
|
+ const configurable = screen.getByRole('tab', { name: en.configurableTab })
|
|
|
+ const all = screen.getByRole('tab', { name: 'Plugin list' })
|
|
|
+ expect(configurable.getAttribute('aria-selected')).toBe('true')
|
|
|
+ expect(screen.getByText('configurable')).toBeTruthy()
|
|
|
+ expect(screen.queryByText('all')).toBeNull()
|
|
|
+
|
|
|
+ fireEvent.click(all)
|
|
|
+ expect(all.getAttribute('aria-selected')).toBe('true')
|
|
|
+ expect(screen.getByText('all')).toBeTruthy()
|
|
|
+ expect(screen.getByText('configurable').closest('[role="tabpanel"]')).toHaveProperty('hidden', true)
|
|
|
+
|
|
|
+ fireEvent.click(configurable)
|
|
|
+ expect(configurable.getAttribute('aria-selected')).toBe('true')
|
|
|
+ expect(screen.getByText('all').closest('[role="tabpanel"]')).toHaveProperty('hidden', true)
|
|
|
})
|
|
|
|
|
|
it('leads with its own heading and intro', () => {
|
|
|
- renderSection(1)
|
|
|
+ renderSection([{ id: 'configurable', order: 0, label: en.configurableTab }])
|
|
|
|
|
|
expect(screen.getByRole('heading', { name: en.title })).toBeTruthy()
|
|
|
expect(screen.getByText(en.intro)).toBeTruthy()
|
|
|
})
|
|
|
+
|
|
|
+ it('moves focus and selection with standard horizontal tab keys', () => {
|
|
|
+ renderSection([
|
|
|
+ { id: 'configurable', order: 0, label: en.configurableTab },
|
|
|
+ { id: 'all', order: 10, label: 'Plugin list' },
|
|
|
+ { id: 'diagnostics', order: 20, label: 'Diagnostics' },
|
|
|
+ ])
|
|
|
+
|
|
|
+ const configurable = screen.getByRole('tab', { name: en.configurableTab })
|
|
|
+ const all = screen.getByRole('tab', { name: 'Plugin list' })
|
|
|
+ const diagnostics = screen.getByRole('tab', { name: 'Diagnostics' })
|
|
|
+ expect(configurable.getAttribute('tabindex')).toBe('0')
|
|
|
+ expect(all.getAttribute('tabindex')).toBe('-1')
|
|
|
+
|
|
|
+ configurable.focus()
|
|
|
+ fireEvent.keyDown(configurable, { key: 'ArrowRight' })
|
|
|
+ expect(document.activeElement).toBe(all)
|
|
|
+ expect(all.getAttribute('aria-selected')).toBe('true')
|
|
|
+
|
|
|
+ fireEvent.keyDown(all, { key: 'End' })
|
|
|
+ expect(document.activeElement).toBe(diagnostics)
|
|
|
+ fireEvent.keyDown(diagnostics, { key: 'ArrowRight' })
|
|
|
+ expect(document.activeElement).toBe(configurable)
|
|
|
+ fireEvent.keyDown(configurable, { key: 'ArrowLeft' })
|
|
|
+ expect(document.activeElement).toBe(diagnostics)
|
|
|
+ fireEvent.keyDown(diagnostics, { key: 'Home' })
|
|
|
+ expect(document.activeElement).toBe(configurable)
|
|
|
+
|
|
|
+ fireEvent.keyDown(configurable, { key: 'Escape' })
|
|
|
+ expect(document.activeElement).toBe(configurable)
|
|
|
+ expect(configurable.getAttribute('aria-selected')).toBe('true')
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+describe('ConfigurablePluginsTab', () => {
|
|
|
+ it('says so when no plugin contributed a card', () => {
|
|
|
+ renderConfigurable(0)
|
|
|
+
|
|
|
+ expect(screen.getByText(en.empty)).toBeTruthy()
|
|
|
+ expect(screen.queryByText('cards')).toBeNull()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('renders the card list once a plugin contributed one', () => {
|
|
|
+ renderConfigurable(1)
|
|
|
+
|
|
|
+ expect(screen.getByText('cards')).toBeTruthy()
|
|
|
+ expect(screen.queryByText(en.empty)).toBeNull()
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
describe('BashCard', () => {
|