mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-19 16:55:02 +02:00
feat(editor): Add search to the Agent Builder switcher
Co-authored-by: linear-code[bot] <222613912+linear-code[bot]@users.noreply.github.com>
This commit is contained in:
parent
67c1981f2f
commit
fe524fd520
|
|
@ -8985,6 +8985,8 @@
|
|||
"agents.builder.header.tab.raw": "Raw",
|
||||
"agents.builder.header.workspaceCrumb": "Workspace",
|
||||
"agents.builder.header.projectFallback": "Project",
|
||||
"agents.builder.header.switcher.searchPlaceholder": "Search agents",
|
||||
"agents.builder.header.switcher.noMatch": "No matching agents",
|
||||
"agents.builder.header.switcher.empty": "No other agents",
|
||||
"agents.builder.header.switcher.newAgent": "New agent",
|
||||
"agents.builder.header.switcher.ariaLabel": "Switch agent",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ vi.mock('vue-router', () => ({
|
|||
RouterLink: { template: '<a><slot/></a>' },
|
||||
}));
|
||||
|
||||
vi.mock('@n8n/design-system', () => ({
|
||||
vi.mock('@n8n/design-system', async (importOriginal) => ({
|
||||
useDropdownSearch: (await importOriginal<typeof import('@n8n/design-system')>())
|
||||
.useDropdownSearch,
|
||||
N8nAssistantIcon: {
|
||||
name: 'N8nAssistantIcon',
|
||||
template: '<i data-testid="stub-assistant-icon" />',
|
||||
|
|
@ -80,13 +82,27 @@ vi.mock('@n8n/design-system', () => ({
|
|||
N8nDropdownMenu: {
|
||||
name: 'N8nDropdownMenu',
|
||||
template: '<div v-bind="$attrs"><slot name="trigger" /><slot name="footer" /></div>',
|
||||
props: ['items', 'placement', 'extraPopperClass'],
|
||||
emits: ['select'],
|
||||
props: {
|
||||
items: Array,
|
||||
placement: String,
|
||||
extraPopperClass: String,
|
||||
searchable: Boolean,
|
||||
searchPlaceholder: String,
|
||||
emptyText: String,
|
||||
},
|
||||
emits: ['select', 'search'],
|
||||
},
|
||||
'n8n-dropdown-menu': {
|
||||
name: 'N8nDropdownMenu',
|
||||
template: '<div v-bind="$attrs"><slot name="trigger" /><slot name="footer" /></div>',
|
||||
props: ['items', 'placement', 'extraPopperClass'],
|
||||
props: {
|
||||
items: Array,
|
||||
placement: String,
|
||||
extraPopperClass: String,
|
||||
searchable: Boolean,
|
||||
searchPlaceholder: String,
|
||||
emptyText: String,
|
||||
},
|
||||
emits: ['select'],
|
||||
},
|
||||
N8nActionDropdown: {
|
||||
|
|
@ -97,13 +113,17 @@ vi.mock('@n8n/design-system', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
vi.mock('../components/AgentPublishButton.vue', () => ({
|
||||
default: { name: 'AgentPublishButton', template: '<div />' },
|
||||
}));
|
||||
|
||||
import AgentBuilderHeader from '../components/AgentBuilderHeader.vue';
|
||||
|
||||
type DropdownStubWrapper = VueWrapper<{
|
||||
items: Array<{ id: string; label?: string; disabled?: boolean }>;
|
||||
extraPopperClass?: string;
|
||||
$options: unknown;
|
||||
$emit: (event: 'select', value: string) => void;
|
||||
$emit: (event: 'select' | 'search', value: string) => void;
|
||||
}>;
|
||||
|
||||
function getDropdown(wrapper: ReturnType<typeof mountHeader>, testId: string) {
|
||||
|
|
@ -293,6 +313,47 @@ describe('AgentBuilderHeader', () => {
|
|||
expect(options.map((option) => option.id)).toEqual(['a2']);
|
||||
});
|
||||
|
||||
it('filters the switcher by agent name and restores the list when search clears', async () => {
|
||||
agentsListRef.value = [
|
||||
baseAgent,
|
||||
{ ...baseAgent, id: 'a2', name: 'Researcher' },
|
||||
{ ...baseAgent, id: 'a3', name: 'News brief' },
|
||||
];
|
||||
const wrapper = mountHeader();
|
||||
const switcher = getDropdown(wrapper, 'agent-header-switcher');
|
||||
|
||||
expect(switcher.props()).toMatchObject({
|
||||
searchable: true,
|
||||
searchPlaceholder: 'agents.builder.header.switcher.searchPlaceholder',
|
||||
emptyText: 'agents.builder.header.switcher.noMatch',
|
||||
});
|
||||
switcher.vm.$emit('search', ' RESEARCH ');
|
||||
await flushPromises();
|
||||
expect(getSwitcherOptions(wrapper).map((item) => item.id)).toEqual(['a2']);
|
||||
|
||||
switcher.vm.$emit('select', 'a2');
|
||||
expect(wrapper.emitted('switch-agent')).toEqual([['a2']]);
|
||||
|
||||
switcher.vm.$emit('search', 'Darwin');
|
||||
await flushPromises();
|
||||
expect(getSwitcherOptions(wrapper)).toEqual([]);
|
||||
expect(wrapper.find('[data-testid="agent-header-new-agent"]').exists()).toBe(true);
|
||||
|
||||
switcher.vm.$emit('search', '');
|
||||
await flushPromises();
|
||||
expect(getSwitcherOptions(wrapper).map((item) => item.id)).toEqual(['a2', 'a3']);
|
||||
});
|
||||
|
||||
it('excludes the empty placeholder from search results', async () => {
|
||||
agentsListRef.value = [baseAgent];
|
||||
const wrapper = mountHeader();
|
||||
|
||||
getDropdown(wrapper, 'agent-header-switcher').vm.$emit('search', 'switcher.empty');
|
||||
await flushPromises();
|
||||
|
||||
expect(getSwitcherOptions(wrapper)).toEqual([]);
|
||||
});
|
||||
|
||||
it('shows a disabled "No other agents" entry when the project has only this agent', async () => {
|
||||
agentsListRef.value = [baseAgent];
|
||||
ensureLoadedMock.mockResolvedValue(agentsListRef.value);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
N8nDropdownMenu,
|
||||
N8nDropdownMenuItem,
|
||||
N8nIcon,
|
||||
useDropdownSearch,
|
||||
} from '@n8n/design-system';
|
||||
import type { IconOrEmoji, PathItem } from '@n8n/design-system';
|
||||
import type { DropdownMenuItemProps } from '@n8n/design-system';
|
||||
|
|
@ -115,6 +116,9 @@ const switcherOptions = computed<Array<DropdownMenuItemProps<string>>>(() => {
|
|||
}));
|
||||
});
|
||||
|
||||
const { filteredItems: filteredSwitcherOptions, handleSearch: handleSwitcherSearch } =
|
||||
useDropdownSearch(switcherOptions, { isSearchable: (item) => !item.disabled });
|
||||
|
||||
function onSwitcherSelect(id: string) {
|
||||
if (id === '__empty__') return;
|
||||
emit('switch-agent', id);
|
||||
|
|
@ -173,9 +177,13 @@ function onMenuSelect(id: string) {
|
|||
<template #append>
|
||||
<span :class="$style.crumbSeparator" aria-hidden="true">/</span>
|
||||
<N8nDropdownMenu
|
||||
:items="switcherOptions"
|
||||
:items="filteredSwitcherOptions"
|
||||
searchable
|
||||
:search-placeholder="i18n.baseText('agents.builder.header.switcher.searchPlaceholder')"
|
||||
:empty-text="i18n.baseText('agents.builder.header.switcher.noMatch')"
|
||||
placement="bottom-start"
|
||||
data-testid="agent-header-switcher"
|
||||
@search="handleSwitcherSearch"
|
||||
@select="onSwitcherSelect"
|
||||
>
|
||||
<template #trigger>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user