You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by va...@apache.org on 2025/08/11 14:45:46 UTC
(superset) 01/01: fix: Slack channels and Color Palettes search
This is an automated email from the ASF dual-hosted git repository.
vavila pushed a commit to branch fix/slack-search
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 6d1772fe23ce146369d5f4d88207ac2318ce0807
Author: Vitor Avila <vi...@preset.io>
AuthorDate: Mon Aug 11 11:44:53 2025 -0300
fix: Slack channels and Color Palettes search
---
.../src/components/Select/Select.test.tsx | 119 +++++++++++++++++++--
.../src/components/Select/Select.tsx | 24 ++++-
2 files changed, 133 insertions(+), 10 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-core/src/components/Select/Select.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/Select/Select.test.tsx
index b6c2af4a81..7e64a59d6e 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/Select/Select.test.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/Select/Select.test.tsx
@@ -1047,10 +1047,115 @@ test('typing and deleting the last character for a new option displays correctly
jest.useRealTimers();
});
-/*
- TODO: Add tests that require scroll interaction. Needs further investigation.
- - Fetches more data when scrolling and more data is available
- - Doesn't fetch more data when no more data is available
- - Requests the correct page and page size
- - Sets the page to zero when a new search is made
- */
+describe('grouped options search', () => {
+ const GROUPED_OPTIONS = [
+ {
+ label: 'Male',
+ options: OPTIONS.filter(option => option.gender === 'Male'),
+ },
+ {
+ label: 'Female',
+ options: OPTIONS.filter(option => option.gender === 'Female'),
+ },
+ ];
+
+ it('searches within grouped options and shows matching groups', async () => {
+ render(<Select {...defaultProps} options={GROUPED_OPTIONS} />);
+ await open();
+
+ await type('John');
+
+ expect(await findSelectOption('John')).toBeInTheDocument();
+ expect(await findSelectOption('Johnny')).toBeInTheDocument();
+ expect(screen.queryByText('Female')).not.toBeInTheDocument();
+ expect(screen.queryByText('Olivia')).not.toBeInTheDocument();
+ expect(screen.getByText('Male')).toBeInTheDocument();
+ expect(screen.queryByText('Female')).not.toBeInTheDocument();
+ });
+
+ it('shows multiple groups when search matches both', async () => {
+ render(<Select {...defaultProps} options={GROUPED_OPTIONS} />);
+ await open();
+
+ await type('er');
+
+ expect(screen.getByText('Male')).toBeInTheDocument();
+ expect(screen.getByText('Female')).toBeInTheDocument();
+ expect(await findSelectOption('Oliver')).toBeInTheDocument();
+ expect(await findSelectOption('Cher')).toBeInTheDocument();
+ expect(await findSelectOption('Her')).toBeInTheDocument();
+ });
+
+ it('handles case-insensitive search in grouped options', async () => {
+ render(<Select {...defaultProps} options={GROUPED_OPTIONS} />);
+ await open();
+
+ await type('EMMA');
+
+ expect(await findSelectOption('Emma')).toBeInTheDocument();
+ expect(screen.getByText('Female')).toBeInTheDocument();
+ expect(screen.queryByText('Male')).not.toBeInTheDocument();
+ });
+
+ it('shows no options when search matches nothing in any group', async () => {
+ render(<Select {...defaultProps} options={GROUPED_OPTIONS} />);
+ await open();
+
+ await type('xyz123');
+
+ expect(screen.queryByText('Male')).not.toBeInTheDocument();
+ expect(screen.queryByText('Female')).not.toBeInTheDocument();
+ expect(
+ screen.getByText(NO_DATA, { selector: '.ant-empty-description' }),
+ ).toBeInTheDocument();
+ });
+
+ it('works in multiple selection mode with grouped options', async () => {
+ render(
+ <Select {...defaultProps} options={GROUPED_OPTIONS} mode="multiple" />,
+ );
+ await open();
+
+ await type('John');
+
+ await userEvent.click(await findSelectOption('John'));
+
+ // Clear search and search for female name
+ await clearTypedText();
+ await type('Emma');
+ await userEvent.click(await findSelectOption('Emma'));
+
+ // Both should be selected
+ const values = await findAllSelectValues();
+ expect(values).toHaveLength(2);
+ expect(values[0]).toHaveTextContent('John');
+ expect(values[1]).toHaveTextContent('Emma');
+ });
+
+ it('preserves group structure when not searching', async () => {
+ render(<Select {...defaultProps} options={GROUPED_OPTIONS} />);
+ await open();
+
+ expect(screen.getByText('Male')).toBeInTheDocument();
+ expect(screen.getByText('Female')).toBeInTheDocument();
+ expect(await findSelectOption('John')).toBeInTheDocument();
+ expect(await findSelectOption('Emma')).toBeInTheDocument();
+ });
+
+ it('handles empty groups gracefully', async () => {
+ const optionsWithEmptyGroup = [
+ ...GROUPED_OPTIONS,
+ {
+ label: 'Empty Group',
+ options: [],
+ },
+ ];
+
+ render(<Select {...defaultProps} options={optionsWithEmptyGroup} />);
+ await open();
+
+ await type('John');
+ expect(await findSelectOption('John')).toBeInTheDocument();
+ expect(screen.queryByText('Empty Group')).not.toBeInTheDocument();
+ });
+});
diff --git a/superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx b/superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx
index 48cb2cfe17..adfa1f7bcb 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/Select/Select.tsx
@@ -373,9 +373,27 @@ const Select = forwardRef(
setSelectOptions(updatedOptions);
}
- const filteredOptions = updatedOptions.filter(
- (option: AntdLabeledValue) => handleFilterOption(search, option),
- );
+ const filteredOptions = updatedOptions
+ .map((option: any) => {
+ /*
+ If it's a group, filter its nested options and only return it
+ if it has matching options
+ */
+ if ('options' in option && Array.isArray(option.options)) {
+ const filteredGroupOptions = option.options.filter(
+ (subOption: AntdLabeledValue) =>
+ handleFilterOption(search, subOption),
+ );
+ return filteredGroupOptions.length > 0
+ ? { ...option, options: filteredGroupOptions }
+ : null;
+ }
+
+ return handleFilterOption(search, option as AntdLabeledValue)
+ ? option
+ : null;
+ })
+ .filter((option): option is AntdLabeledValue => option !== null);
setVisibleOptions(filteredOptions);
setInputValue(searchValue);