From fca825625dcfedf32ef75df84506547c8013bfb6 Mon Sep 17 00:00:00 2001 From: kasiazjc Date: Thu, 21 May 2026 14:01:56 +0000 Subject: [PATCH] fix(tests): update tests for compact filter pills UI changes Update test selectors and expectations across ListView, ChartList, DashboardList, DatasetList, UsersList, GroupsList, RolesList, and RowLevelSecurityList tests to match the new compact pill filter UI. Key changes: - select-filter-container wrapper added back for backward compat - card-sort-select data-test added back to CardSortSelect wrapper - Multiple search filter test updated: only first search renders - Tests using role=combobox updated to use new selectors - Tests for hidden search labels updated - Added selectPillOption helper to testing-library for compact pill UI --- .../spec/helpers/testing-library.tsx | 30 +++++++ .../components/ListView/CardSortSelect.tsx | 28 ++++--- .../ListView/Filters/index.test.tsx | 6 +- .../src/components/ListView/Filters/index.tsx | 81 ++++++++++--------- .../src/components/ListView/ListView.test.tsx | 5 +- .../src/pages/ChartList/ChartList.test.tsx | 9 ++- .../DashboardList.cardview.test.tsx | 16 ++-- .../DatasetList.integration.test.tsx | 6 +- .../DatasetList/DatasetList.listview.test.tsx | 23 ++---- .../pages/DatasetList/DatasetList.test.tsx | 7 +- .../src/pages/GroupsList/GroupsList.test.tsx | 16 ++-- .../src/pages/RolesList/RolesList.test.tsx | 7 +- .../RowLevelSecurityList.test.tsx | 9 ++- .../src/pages/UsersList/UsersList.test.tsx | 20 ++--- 14 files changed, 151 insertions(+), 112 deletions(-) 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 () => {