mirror of
https://github.com/apache/superset.git
synced 2026-07-20 05:36:00 +00:00
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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -62,18 +62,20 @@ export const CardSortSelect = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<CompactFilterTrigger
|
||||
label={t('Sort')}
|
||||
hasValue={false}
|
||||
onClear={() => {}}
|
||||
tooltipTitle={String(currentValue.label)}
|
||||
>
|
||||
<CompactSelectPanel
|
||||
ref={panelRef}
|
||||
selects={selectOptions}
|
||||
value={currentValue}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
</CompactFilterTrigger>
|
||||
<span data-test="card-sort-select">
|
||||
<CompactFilterTrigger
|
||||
label={t('Sort')}
|
||||
hasValue={false}
|
||||
onClear={() => {}}
|
||||
tooltipTitle={String(currentValue.label)}
|
||||
>
|
||||
<CompactSelectPanel
|
||||
ref={panelRef}
|
||||
selects={selectOptions}
|
||||
value={currentValue}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
</CompactFilterTrigger>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -126,47 +126,48 @@ function UIFilters(
|
||||
? tooltipLabels[index]
|
||||
: undefined;
|
||||
return (
|
||||
<CompactFilterTrigger
|
||||
key={key}
|
||||
label={Header}
|
||||
hasValue={!!selectValue}
|
||||
tooltipTitle={tooltipTitle}
|
||||
onClear={() => {
|
||||
filterRefs[index]?.current?.clearFilter?.();
|
||||
updateFilterValue(index, undefined);
|
||||
setTooltipLabels(prev => {
|
||||
const next = { ...prev };
|
||||
delete next[index];
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<CompactSelectPanel
|
||||
ref={filterRefs[index]}
|
||||
selects={selects}
|
||||
fetchSelects={fetchSelects}
|
||||
value={initialValue as SelectOption | undefined}
|
||||
loading={loading ?? false}
|
||||
onSelect={(
|
||||
option: SelectOption | undefined,
|
||||
isClear?: boolean,
|
||||
) => {
|
||||
if (option && !isClear) {
|
||||
setTooltipLabels(prev => ({
|
||||
...prev,
|
||||
[index]:
|
||||
typeof option.label === 'string'
|
||||
? option.label
|
||||
: String(option.value ?? ''),
|
||||
}));
|
||||
}
|
||||
if (onFilterUpdate && !isClear) {
|
||||
onFilterUpdate(option);
|
||||
}
|
||||
updateFilterValue(index, option);
|
||||
<span key={key} data-test="select-filter-container">
|
||||
<CompactFilterTrigger
|
||||
label={Header}
|
||||
hasValue={!!selectValue}
|
||||
tooltipTitle={tooltipTitle}
|
||||
onClear={() => {
|
||||
filterRefs[index]?.current?.clearFilter?.();
|
||||
updateFilterValue(index, undefined);
|
||||
setTooltipLabels(prev => {
|
||||
const next = { ...prev };
|
||||
delete next[index];
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</CompactFilterTrigger>
|
||||
>
|
||||
<CompactSelectPanel
|
||||
ref={filterRefs[index]}
|
||||
selects={selects}
|
||||
fetchSelects={fetchSelects}
|
||||
value={initialValue as SelectOption | undefined}
|
||||
loading={loading ?? false}
|
||||
onSelect={(
|
||||
option: SelectOption | undefined,
|
||||
isClear?: boolean,
|
||||
) => {
|
||||
if (option && !isClear) {
|
||||
setTooltipLabels(prev => ({
|
||||
...prev,
|
||||
[index]:
|
||||
typeof option.label === 'string'
|
||||
? option.label
|
||||
: String(option.value ?? ''),
|
||||
}));
|
||||
}
|
||||
if (onFilterUpdate && !isClear) {
|
||||
onFilterUpdate(option);
|
||||
}
|
||||
updateFilterValue(index, option);
|
||||
}}
|
||||
/>
|
||||
</CompactFilterTrigger>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (input === 'search' && typeof Header === 'string') {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user