diff --git a/packages/ui-patterns/src/FilterBar/FilterBar.test.tsx b/packages/ui-patterns/src/FilterBar/FilterBar.test.tsx index eb6dcf8fea2..94732014676 100644 --- a/packages/ui-patterns/src/FilterBar/FilterBar.test.tsx +++ b/packages/ui-patterns/src/FilterBar/FilterBar.test.tsx @@ -521,4 +521,69 @@ describe('FilterBar', () => { expect(screen.getByDisplayValue('active')).toBeInTheDocument() expect(screen.queryByText('AND')).not.toBeInTheDocument() }) + + it('selects a column named "group" without creating a nested group', async () => { + const user = userEvent.setup() + const propertiesWithGroup: FilterProperty[] = [ + { + label: 'group', + name: 'group', + type: 'string', + operators: ['=', '!=', 'CONTAINS'], + }, + { + label: 'Name', + name: 'name', + type: 'string', + operators: ['=', '!='], + }, + ] + + let currentFilters: FilterGroup = initialFilters + const handleFilterChange = vi.fn((filters) => { + currentFilters = filters + }) + + const { rerender } = render( + + ) + + const freeform = screen.getByPlaceholderText('Filter by group, Name') + await user.click(freeform) + + expect(await screen.findByText('group')).toBeInTheDocument() + + await user.click(screen.getByText('group')) + + await waitFor(() => { + expect(handleFilterChange).toHaveBeenCalled() + }) + + rerender( + + ) + + // Should create a filter condition for the "group" column, NOT a nested filter group + expect(currentFilters.conditions).toHaveLength(1) + const condition = currentFilters.conditions[0] + expect('logicalOperator' in condition).toBe(false) + expect((condition as { propertyName: string }).propertyName).toBe('group') + + // Should show the operator input for the "group" column + await waitFor(() => { + expect(screen.getByLabelText('Operator for group')).toBeInTheDocument() + }) + }) }) diff --git a/packages/ui-patterns/src/FilterBar/menuItems.test.ts b/packages/ui-patterns/src/FilterBar/menuItems.test.ts index 73e73972b64..60be56a664c 100644 --- a/packages/ui-patterns/src/FilterBar/menuItems.test.ts +++ b/packages/ui-patterns/src/FilterBar/menuItems.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { buildOperatorItems, buildValueItems } from './menuItems' +import { buildOperatorItems, buildPropertyItems, buildValueItems } from './menuItems' import { FilterGroup, FilterProperty } from './types' const stringProperty: FilterProperty = { @@ -146,3 +146,63 @@ describe('buildValueItems', () => { ]) }) }) + +describe('buildPropertyItems', () => { + const properties: FilterProperty[] = [ + { label: 'Name', name: 'name', type: 'string', operators: ['='] }, + { label: 'Status', name: 'status', type: 'string', operators: ['='] }, + ] + + it('returns property items matching the input', () => { + const items = buildPropertyItems({ + filterProperties: properties, + inputValue: 'nam', + }) + + expect(items).toEqual([{ value: 'name', label: 'Name' }]) + }) + + it('uses a sentinel value for the "New Group" action when supportsOperators is true', () => { + const items = buildPropertyItems({ + filterProperties: properties, + inputValue: '', + supportsOperators: true, + }) + + const groupItem = items.find((item) => item.label === 'New Group') + expect(groupItem).toBeDefined() + expect(groupItem?.value).toBe('__new_group__') + }) + + it('does not include "New Group" when supportsOperators is false', () => { + const items = buildPropertyItems({ + filterProperties: properties, + inputValue: '', + supportsOperators: false, + }) + + expect(items.find((item) => item.label === 'New Group')).toBeUndefined() + }) + + it('does not collide with a property named "group"', () => { + const propertiesWithGroup: FilterProperty[] = [ + { label: 'group', name: 'group', type: 'string', operators: ['='] }, + { label: 'Name', name: 'name', type: 'string', operators: ['='] }, + ] + + const items = buildPropertyItems({ + filterProperties: propertiesWithGroup, + inputValue: '', + supportsOperators: true, + }) + + const groupPropertyItem = items.find((item) => item.label === 'group') + const newGroupItem = items.find((item) => item.label === 'New Group') + + expect(groupPropertyItem).toBeDefined() + expect(groupPropertyItem?.value).toBe('group') + expect(newGroupItem).toBeDefined() + expect(newGroupItem?.value).toBe('__new_group__') + expect(groupPropertyItem?.value).not.toBe(newGroupItem?.value) + }) +}) diff --git a/packages/ui-patterns/src/FilterBar/menuItems.ts b/packages/ui-patterns/src/FilterBar/menuItems.ts index bb58c84f5fe..31edfd651b4 100644 --- a/packages/ui-patterns/src/FilterBar/menuItems.ts +++ b/packages/ui-patterns/src/FilterBar/menuItems.ts @@ -94,7 +94,7 @@ export function buildPropertyItems(params: { ) if (supportsOperators) { - items.push({ value: 'group', label: 'New Group' }) + items.push({ value: '__new_group__', label: 'New Group' }) } if (actions && trimmedInput.length > 0) { diff --git a/packages/ui-patterns/src/FilterBar/useCommandHandling.ts b/packages/ui-patterns/src/FilterBar/useCommandHandling.ts index 61119c48a9a..2adbce682ac 100644 --- a/packages/ui-patterns/src/FilterBar/useCommandHandling.ts +++ b/packages/ui-patterns/src/FilterBar/useCommandHandling.ts @@ -175,7 +175,7 @@ export function useCommandHandling({ return } - if (item.value === 'group') { + if (item.value === '__new_group__') { handleGroupCommand() return } diff --git a/packages/ui-patterns/src/FilterBar/useCommandMenu.ts b/packages/ui-patterns/src/FilterBar/useCommandMenu.ts index 7330c86905b..81b285f7040 100644 --- a/packages/ui-patterns/src/FilterBar/useCommandMenu.ts +++ b/packages/ui-patterns/src/FilterBar/useCommandMenu.ts @@ -52,7 +52,7 @@ export function useCommandMenu({ if (supportsOperators) { items.push({ - value: 'group', + value: '__new_group__', label: 'New Group', }) }