diff --git a/superset-frontend/spec/helpers/testing-library.tsx b/superset-frontend/spec/helpers/testing-library.tsx
index e97a5ce85a7..7e8ab6151a7 100644
--- a/superset-frontend/spec/helpers/testing-library.tsx
+++ b/superset-frontend/spec/helpers/testing-library.tsx
@@ -152,3 +152,33 @@ export async function selectOption(option: string, selectName?: string) {
);
await userEvent.click(item);
}
+
+/**
+ * Select an option from a compact pill filter (new UI that replaced comboboxes).
+ * Clicks the pill button matching the label, then clicks the option in the panel.
+ */
+export async function selectPillOption(option: string, pillLabel?: string) {
+ let pill: HTMLElement;
+ if (pillLabel) {
+ // Find the pill whose text content includes the label
+ pill = await waitFor(() => {
+ const pills = screen.getAllByTestId('compact-filter-pill');
+ const match = pills.find(p => p.textContent?.includes(pillLabel));
+ if (!match)
+ throw new Error(`Could not find pill with label "${pillLabel}"`);
+ return match;
+ });
+ } else {
+ pill = await screen.findByTestId('compact-filter-pill');
+ }
+ await userEvent.click(pill);
+ // Wait for the option list to appear and click the item
+ const item = await waitFor(() => {
+ const listbox = document.querySelector('[role="listbox"]');
+ if (!listbox) throw new Error('No listbox found');
+ const opt = within(listbox as HTMLElement).getByText(option);
+ if (!opt) throw new Error(`Option "${option}" not found`);
+ return opt;
+ });
+ await userEvent.click(item);
+}
diff --git a/superset-frontend/src/components/ListView/CardSortSelect.tsx b/superset-frontend/src/components/ListView/CardSortSelect.tsx
index 0307a73c2bb..a1e3f4be2d0 100644
--- a/superset-frontend/src/components/ListView/CardSortSelect.tsx
+++ b/superset-frontend/src/components/ListView/CardSortSelect.tsx
@@ -62,18 +62,20 @@ export const CardSortSelect = ({
};
return (
- {}}
- tooltipTitle={String(currentValue.label)}
- >
-
-
+
+ {}}
+ tooltipTitle={String(currentValue.label)}
+ >
+
+
+
);
};
diff --git a/superset-frontend/src/components/ListView/Filters/index.test.tsx b/superset-frontend/src/components/ListView/Filters/index.test.tsx
index 613645786f3..74b2cc36ba0 100644
--- a/superset-frontend/src/components/ListView/Filters/index.test.tsx
+++ b/superset-frontend/src/components/ListView/Filters/index.test.tsx
@@ -260,7 +260,7 @@ test('numerical_range pill shows active state when value is set', () => {
expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument();
});
-test('renders multiple search filters with different inputName values', () => {
+test('renders only the first search filter when multiple search filters are configured', () => {
const filters = [
{
Header: 'Name',
@@ -288,8 +288,8 @@ test('renders multiple search filters with different inputName values', () => {
/>,
);
+ // Only the first search filter renders — one search box per page
const inputs = screen.getAllByTestId('filters-search') as HTMLInputElement[];
- expect(inputs).toHaveLength(2);
+ expect(inputs).toHaveLength(1);
expect(inputs[0].name).toBe('filter_name_search');
- expect(inputs[1].name).toBe('description');
});
diff --git a/superset-frontend/src/components/ListView/Filters/index.tsx b/superset-frontend/src/components/ListView/Filters/index.tsx
index 7849519e36f..1086928abc4 100644
--- a/superset-frontend/src/components/ListView/Filters/index.tsx
+++ b/superset-frontend/src/components/ListView/Filters/index.tsx
@@ -126,47 +126,48 @@ function UIFilters(
? tooltipLabels[index]
: undefined;
return (
- {
- filterRefs[index]?.current?.clearFilter?.();
- updateFilterValue(index, undefined);
- setTooltipLabels(prev => {
- const next = { ...prev };
- delete next[index];
- return next;
- });
- }}
- >
- {
- if (option && !isClear) {
- setTooltipLabels(prev => ({
- ...prev,
- [index]:
- typeof option.label === 'string'
- ? option.label
- : String(option.value ?? ''),
- }));
- }
- if (onFilterUpdate && !isClear) {
- onFilterUpdate(option);
- }
- updateFilterValue(index, option);
+
+ {
+ filterRefs[index]?.current?.clearFilter?.();
+ updateFilterValue(index, undefined);
+ setTooltipLabels(prev => {
+ const next = { ...prev };
+ delete next[index];
+ return next;
+ });
}}
- />
-
+ >
+ {
+ if (option && !isClear) {
+ setTooltipLabels(prev => ({
+ ...prev,
+ [index]:
+ typeof option.label === 'string'
+ ? option.label
+ : String(option.value ?? ''),
+ }));
+ }
+ if (onFilterUpdate && !isClear) {
+ onFilterUpdate(option);
+ }
+ updateFilterValue(index, option);
+ }}
+ />
+
+
);
}
if (input === 'search' && typeof Header === 'string') {
diff --git a/superset-frontend/src/components/ListView/ListView.test.tsx b/superset-frontend/src/components/ListView/ListView.test.tsx
index 797b02afa22..0d08bcf9fd9 100644
--- a/superset-frontend/src/components/ListView/ListView.test.tsx
+++ b/superset-frontend/src/components/ListView/ListView.test.tsx
@@ -345,7 +345,10 @@ describe('ListView', () => {
initialSort: [{ id: 'something' }],
});
- const sortSelect = screen.getByTestId('card-sort-select');
+ const sortSelectContainer = screen.getByTestId('card-sort-select');
+ const sortSelect = sortSelectContainer.querySelector(
+ '[data-test="compact-filter-pill"]',
+ ) as HTMLElement;
await userEvent.click(sortSelect);
const sortOption = screen.getByText('Alphabetical');
diff --git a/superset-frontend/src/pages/ChartList/ChartList.test.tsx b/superset-frontend/src/pages/ChartList/ChartList.test.tsx
index 6f9dedc5a09..1b16c7674b7 100644
--- a/superset-frontend/src/pages/ChartList/ChartList.test.tsx
+++ b/superset-frontend/src/pages/ChartList/ChartList.test.tsx
@@ -57,9 +57,12 @@ const mockUser = {
const findFilterByLabel = (labelText: string) => {
const containers = screen.getAllByTestId('select-filter-container');
for (const container of containers) {
- const label = container.querySelector('label');
- if (label?.textContent === labelText) {
- return container.querySelector('[role="combobox"], .ant-select');
+ // Compact pill filters show the label as button text
+ const pill = container.querySelector(
+ '[data-test="compact-filter-pill"]',
+ ) as HTMLElement | null;
+ if (pill && pill.textContent?.includes(labelText)) {
+ return pill;
}
}
return null;
diff --git a/superset-frontend/src/pages/DashboardList/DashboardList.cardview.test.tsx b/superset-frontend/src/pages/DashboardList/DashboardList.cardview.test.tsx
index c03299cfc43..7733251176d 100644
--- a/superset-frontend/src/pages/DashboardList/DashboardList.cardview.test.tsx
+++ b/superset-frontend/src/pages/DashboardList/DashboardList.cardview.test.tsx
@@ -156,18 +156,16 @@ describe('DashboardList Card View Tests', () => {
).toBeInTheDocument();
});
- // Find the sort select by its testId, then the combobox within it
+ // Find the sort select by its testId, then the pill button within it
const sortContainer = screen.getByTestId('card-sort-select');
- const sortCombobox = within(sortContainer).getByRole('combobox');
- await userEvent.click(sortCombobox);
+ // eslint-disable-next-line testing-library/no-node-access
+ const sortPill = sortContainer.querySelector(
+ '[data-test="compact-filter-pill"]',
+ ) as HTMLElement;
+ await userEvent.click(sortPill);
// Select "Alphabetical" from the dropdown
- const alphabeticalOption = await waitFor(() =>
- within(
- // eslint-disable-next-line testing-library/no-node-access
- document.querySelector('.rc-virtual-list')!,
- ).getByText('Alphabetical'),
- );
+ const alphabeticalOption = await screen.findByText('Alphabetical');
await userEvent.click(alphabeticalOption);
await waitFor(() => {
diff --git a/superset-frontend/src/pages/DatasetList/DatasetList.integration.test.tsx b/superset-frontend/src/pages/DatasetList/DatasetList.integration.test.tsx
index 2a28b7bb11c..ee654a77458 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.integration.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.integration.test.tsx
@@ -20,7 +20,7 @@ import { act, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import rison from 'rison';
-import { selectOption } from 'spec/helpers/testing-library';
+import { selectPillOption } from 'spec/helpers/testing-library';
import {
setupMocks,
renderDatasetList,
@@ -102,11 +102,11 @@ test('ListView provider correctly merges filter + sort + pagination state on ref
).toBeGreaterThan(callsBeforeSort);
});
- // 2. Apply a filter using selectOption helper
+ // 2. Apply a filter using selectPillOption helper (compact pill UI)
const beforeFilterCallCount = fetchMock.callHistory.calls(
API_ENDPOINTS.DATASOURCE_COMBINED,
).length;
- await selectOption('Virtual', 'Type');
+ await selectPillOption('Virtual', 'Type');
// Wait for filter API call to complete
await waitFor(() => {
diff --git a/superset-frontend/src/pages/DatasetList/DatasetList.listview.test.tsx b/superset-frontend/src/pages/DatasetList/DatasetList.listview.test.tsx
index 883658156c9..6ad588a8128 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.listview.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.listview.test.tsx
@@ -27,7 +27,7 @@ import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import rison from 'rison';
import { SupersetClient } from '@superset-ui/core';
-import { selectOption } from 'spec/helpers/testing-library';
+import { selectPillOption } from 'spec/helpers/testing-library';
import {
setupMocks,
renderDatasetList,
@@ -1510,11 +1510,8 @@ test('bulk selection clears when filter changes', async () => {
API_ENDPOINTS.DATASOURCE_COMBINED,
).length;
- // Wait for filter combobox to be ready before applying filter
- await screen.findByRole('combobox', { name: 'Type' });
-
- // Apply a filter using selectOption helper
- await selectOption('Virtual', 'Type');
+ // Apply a filter using selectPillOption helper (compact pill UI)
+ await selectPillOption('Virtual', 'Type');
// Wait for filter API call to complete
await waitFor(() => {
@@ -1556,16 +1553,13 @@ test('type filter API call includes correct filter parameter', async () => {
expect(screen.getByTestId('listview-table')).toBeInTheDocument();
});
- // Wait for Type filter combobox
- await screen.findByRole('combobox', { name: 'Type' });
-
// Snapshot call count before filter
const callsBeforeFilter = fetchMock.callHistory.calls(
API_ENDPOINTS.DATASOURCE_COMBINED,
).length;
- // Apply Type filter
- await selectOption('Virtual', 'Type');
+ // Apply Type filter using compact pill UI
+ await selectPillOption('Virtual', 'Type');
// Wait for filter API call to complete
await waitFor(() => {
@@ -1606,16 +1600,13 @@ test('type filter persists after duplicating a dataset', async () => {
expect(screen.getByTestId('listview-table')).toBeInTheDocument();
});
- // Wait for Type filter combobox
- await screen.findByRole('combobox', { name: 'Type' });
-
// Snapshot call count before filter
const callsBeforeFilter = fetchMock.callHistory.calls(
API_ENDPOINTS.DATASOURCE_COMBINED,
).length;
- // Apply Type filter
- await selectOption('Virtual', 'Type');
+ // Apply Type filter using compact pill UI
+ await selectPillOption('Virtual', 'Type');
// Wait for filter API call to complete
await waitFor(() => {
diff --git a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
index 2d1c4752e9b..ee4c1257aae 100644
--- a/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
+++ b/superset-frontend/src/pages/DatasetList/DatasetList.test.tsx
@@ -200,8 +200,8 @@ test('renders Name search filter', async () => {
test('renders Type filter (Virtual/Physical dropdown)', async () => {
renderDatasetList(mockAdminUser);
- // Filter dropdowns should be present
- const filters = await screen.findAllByRole('combobox');
+ // Filter pills should be present (compact pill UI)
+ const filters = await screen.findAllByTestId('compact-filter-pill');
expect(filters.length).toBeGreaterThan(0);
});
@@ -445,7 +445,8 @@ test('selecting Database filter triggers API call with database relation filter'
await waitForDatasetsPageReady();
- const filtersContainers = screen.getAllByRole('combobox');
+ // Filter pills should be present (compact pill UI replaces comboboxes)
+ const filtersContainers = screen.getAllByTestId('compact-filter-pill');
expect(filtersContainers.length).toBeGreaterThan(0);
});
diff --git a/superset-frontend/src/pages/GroupsList/GroupsList.test.tsx b/superset-frontend/src/pages/GroupsList/GroupsList.test.tsx
index 785066048e8..81bd86f8078 100644
--- a/superset-frontend/src/pages/GroupsList/GroupsList.test.tsx
+++ b/superset-frontend/src/pages/GroupsList/GroupsList.test.tsx
@@ -121,13 +121,17 @@ describe('GroupsList', () => {
test('renders the filters correctly', async () => {
await renderComponent();
- const filtersSelect = screen.getAllByTestId('filters-select')[0];
- expect(within(filtersSelect).getByText(/name/i)).toBeInTheDocument();
- expect(within(filtersSelect).getByText(/label/i)).toBeInTheDocument();
- expect(within(filtersSelect).getByText(/description/i)).toBeInTheDocument();
- expect(within(filtersSelect).getByText(/roles/i)).toBeInTheDocument();
- expect(within(filtersSelect).getByText(/users/i)).toBeInTheDocument();
+ // The compact filter UI renders the first search filter as an input,
+ // and select filters as pill buttons. Only "Name" search renders inline;
+ // "Label" and "Description" searches are hidden (one search box per page).
+ expect(screen.getByTestId('filters-search')).toBeInTheDocument();
+
+ // Select filters render as compact pill buttons
+ const pills = screen.getAllByTestId('compact-filter-pill');
+ const pillLabels = pills.map(p => p.textContent ?? '');
+ expect(pillLabels.some(l => /roles/i.test(l))).toBe(true);
+ expect(pillLabels.some(l => /users/i.test(l))).toBe(true);
});
test('renders correct columns in the table', async () => {
diff --git a/superset-frontend/src/pages/RolesList/RolesList.test.tsx b/superset-frontend/src/pages/RolesList/RolesList.test.tsx
index 39d8069429c..6a179238d4e 100644
--- a/superset-frontend/src/pages/RolesList/RolesList.test.tsx
+++ b/superset-frontend/src/pages/RolesList/RolesList.test.tsx
@@ -151,8 +151,11 @@ describe('RolesList', () => {
test('renders filters options', async () => {
await renderAndWait();
- const typeFilter = screen.queryAllByTestId('filters-select');
- expect(typeFilter).toHaveLength(4);
+ // Compact filter UI: one search input for "Name" and 3 select pills
+ // (Users, Permissions, Groups).
+ expect(screen.getByTestId('filters-search')).toBeInTheDocument();
+ const selectContainers = screen.getAllByTestId('select-filter-container');
+ expect(selectContainers).toHaveLength(3);
});
test('renders correct list columns', async () => {
diff --git a/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx b/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx
index b23b2421461..538e7ae41b1 100644
--- a/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx
+++ b/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx
@@ -166,11 +166,14 @@ describe('RuleList RTL', () => {
test('renders filter options', async () => {
await renderAndWait();
+ // Compact filter UI: only the first search filter renders (Name),
+ // subsequent search filters (Group Key) are hidden — one search box per page.
const searchFilters = screen.queryAllByTestId('filters-search');
- expect(searchFilters).toHaveLength(2);
+ expect(searchFilters).toHaveLength(1);
- const typeFilter = screen.queryAllByTestId('filters-select');
- expect(typeFilter).toHaveLength(3); // Update to expect 3 select filters
+ // Select filters render as compact pill buttons (Filter Type, Modified by)
+ const selectContainers = screen.queryAllByTestId('select-filter-container');
+ expect(selectContainers).toHaveLength(2);
});
test('renders correct list columns', async () => {
diff --git a/superset-frontend/src/pages/UsersList/UsersList.test.tsx b/superset-frontend/src/pages/UsersList/UsersList.test.tsx
index e994bd485cc..63539113eed 100644
--- a/superset-frontend/src/pages/UsersList/UsersList.test.tsx
+++ b/superset-frontend/src/pages/UsersList/UsersList.test.tsx
@@ -138,16 +138,16 @@ describe('UsersList', () => {
test('renders filters options', async () => {
await renderAndWait();
- const submenu = screen.queryAllByTestId('filters-select')[0];
- expect(within(submenu).getByText(/first name/i)).toBeInTheDocument();
- expect(within(submenu).getByText(/last name/i)).toBeInTheDocument();
- expect(within(submenu).getByText(/email/i)).toBeInTheDocument();
- expect(within(submenu).getByText(/username/i)).toBeInTheDocument();
- expect(within(submenu).getByText(/roles/i)).toBeInTheDocument();
- expect(within(submenu).getByText(/is active?/i)).toBeInTheDocument();
- expect(within(submenu).getByText(/created on/i)).toBeInTheDocument();
- expect(within(submenu).getByText(/changed on/i)).toBeInTheDocument();
- expect(within(submenu).getByText(/last login/i)).toBeInTheDocument();
+ // The compact filter UI shows: only the first search filter as an input,
+ // and select/datetime filters as pill buttons. Only "First name" search
+ // renders (subsequent search filters are hidden — one search box per page).
+ expect(screen.getByTestId('filters-search')).toBeInTheDocument();
+
+ // Select and datetime filters render as compact pill buttons
+ const pills = screen.getAllByTestId('compact-filter-pill');
+ const pillLabels = pills.map(p => p.textContent ?? '');
+ expect(pillLabels.some(l => /roles/i.test(l))).toBe(true);
+ expect(pillLabels.some(l => /is active\?/i.test(l))).toBe(true);
});
test('renders correct list columns', async () => {