You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/06/09 23:26:12 UTC
(superset) branch master updated: test(dashboard): RTL coverage for native filter modal and sidebar (#40778)
This is an automated email from the ASF dual-hosted git repository.
sadpandajoe pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 1bfdb19e889 test(dashboard): RTL coverage for native filter modal and sidebar (#40778)
1bfdb19e889 is described below
commit 1bfdb19e8896125ea6778e5995aea93faeebd05c
Author: Joe Li <jo...@preset.io>
AuthorDate: Tue Jun 9 16:26:04 2026 -0700
test(dashboard): RTL coverage for native filter modal and sidebar (#40778)
Co-authored-by: Claude Opus 4.7 (1M context) <no...@anthropic.com>
---
.../nativeFilters/FilterBar/FilterBar.test.tsx | 58 ++++++++
.../FiltersConfigModal/FiltersConfigModal.test.tsx | 149 ++++++++++++++++++++-
.../FiltersConfigModal/NativeFiltersModal.test.tsx | 28 +++-
3 files changed, 223 insertions(+), 12 deletions(-)
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx
index 60717143daa..7fcafa6a1d2 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx
@@ -902,3 +902,61 @@ test('Clear All on a required filter disables Apply via validateStatus', async (
expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled();
updateDataMaskSpy.mockRestore();
});
+
+test('FilterBar renders the configured filter name in the bar', async () => {
+ const filterId = 'NATIVE_FILTER-name-render';
+ const filter = createFilter({
+ id: filterId,
+ name: 'Region',
+ filterType: 'filter_select',
+ targets: [{ datasetId: 7, column: { name: 'region' } }],
+ chartsInScope: [18],
+ });
+ const state = {
+ ...stateWithoutNativeFilters,
+ dashboardInfo: {
+ id: 1,
+ dash_edit_perm: true,
+ filterBarOrientation: FilterBarOrientation.Vertical,
+ metadata: {
+ native_filter_configuration: [filter],
+ chart_configuration: {},
+ },
+ },
+ dashboardState: {
+ ...stateWithoutNativeFilters.dashboardState,
+ activeTabs: ['ROOT_ID'],
+ },
+ dataMask: { [filterId]: createDataMask(filterId, undefined, {}) },
+ nativeFilters: {
+ filters: { [filterId]: filter },
+ filtersState: {},
+ },
+ };
+
+ const props = createOpenedBarProps();
+ renderFilterBar(props, state);
+ await act(async () => {
+ jest.advanceTimersByTime(300);
+ });
+
+ expect(await screen.findByText('Region')).toBeInTheDocument();
+});
+
+test('Clicking the gear "Add or edit filters and controls" item opens the FiltersConfigModal', async () => {
+ const props = createOpenedBarProps();
+ renderFilterBar(props, stateWithoutNativeFilters);
+ await act(async () => {
+ jest.advanceTimersByTime(100);
+ });
+
+ const gear = await screen.findByTestId('filterbar-orientation-icon');
+ userEvent.click(gear);
+
+ const addEditItem = await screen.findByText(
+ 'Add or edit filters and controls',
+ );
+ userEvent.click(addEditItem);
+
+ expect(await screen.findByTestId('filter-modal')).toBeInTheDocument();
+});
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
index ebdd0038077..61941dad70c 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
@@ -844,9 +844,7 @@ test('enables save button and includes updated title when editing an existing di
jest.useRealTimers();
await waitFor(() =>
- expect(
- screen.getByRole('button', { name: SAVE_REGEX }),
- ).not.toBeDisabled(),
+ expect(screen.getByRole('button', { name: SAVE_REGEX })).not.toBeDisabled(),
);
await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
@@ -910,9 +908,7 @@ test('enables save button and includes updated title when editing an existing ch
jest.useRealTimers();
await waitFor(() =>
- expect(
- screen.getByRole('button', { name: SAVE_REGEX }),
- ).not.toBeDisabled(),
+ expect(screen.getByRole('button', { name: SAVE_REGEX })).not.toBeDisabled(),
);
await userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
@@ -960,3 +956,144 @@ test('empty state disappears when a filter is added via dropdown', async () => {
});
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
});
+
+test('restores a deleted filter via the "Restore filter" button', async () => {
+ const nativeFilterConfig = [
+ buildNativeFilter('NATIVE_FILTER-1', 'state', []),
+ buildNativeFilter('NATIVE_FILTER-2', 'country', []),
+ ];
+ const state = {
+ ...defaultState(),
+ dashboardInfo: {
+ metadata: { native_filter_configuration: nativeFilterConfig },
+ },
+ dashboardLayout,
+ };
+
+ defaultRender(state, { ...props, createNewOnOpen: false });
+
+ const filterContainer = screen.getByTestId('filter-title-container');
+ const firstTab = within(filterContainer).getAllByRole('tab')[0];
+ fireEvent.click(within(firstTab).getByRole('button', { name: /delete/i }));
+
+ expect(
+ await screen.findByText(/you have removed this filter/i),
+ ).toBeInTheDocument();
+ const restoreButton = screen.getByTestId('restore-filter-button');
+ await userEvent.click(restoreButton);
+
+ await waitFor(() => {
+ expect(
+ screen.queryByText(/you have removed this filter/i),
+ ).not.toBeInTheDocument();
+ });
+ expect(screen.getByRole('textbox', { name: FILTER_NAME_REGEX })).toHaveValue(
+ 'state',
+ );
+}, 30000);
+
+test('undoes a filter deletion via the sidebar "Undo?" link', async () => {
+ const nativeFilterConfig = [
+ buildNativeFilter('NATIVE_FILTER-1', 'state', []),
+ buildNativeFilter('NATIVE_FILTER-2', 'country', []),
+ ];
+ const state = {
+ ...defaultState(),
+ dashboardInfo: {
+ metadata: { native_filter_configuration: nativeFilterConfig },
+ },
+ dashboardLayout,
+ };
+
+ defaultRender(state, { ...props, createNewOnOpen: false });
+
+ const filterContainer = screen.getByTestId('filter-title-container');
+ const firstTab = within(filterContainer).getAllByRole('tab')[0];
+ fireEvent.click(within(firstTab).getByRole('button', { name: /delete/i }));
+
+ const undoButton = await screen.findByTestId('undo-button');
+ expect(undoButton).toHaveTextContent(/undo\?/i);
+ await userEvent.click(undoButton);
+
+ await waitFor(() => {
+ expect(
+ screen.queryByText(/you have removed this filter/i),
+ ).not.toBeInTheDocument();
+ });
+ expect(screen.getByRole('textbox', { name: FILTER_NAME_REGEX })).toHaveValue(
+ 'state',
+ );
+}, 30000);
+
+test('shows info tooltips beside value-filter options and reveals tooltip text on hover', async () => {
+ defaultRender();
+
+ // Upstream Cypress checked six tooltips on the value filter (nativeFilterTooltips
+ // 0..5); asserting the count keeps this test honest if tooltips get added or
+ // removed alongside a regression to the option list.
+ const tooltipIcons = screen.getAllByLabelText(/show info tooltip/i);
+ expect(tooltipIcons.length).toBeGreaterThanOrEqual(6);
+
+ await userEvent.hover(tooltipIcons[0]);
+
+ // role='tooltip' trips an nwsapi bug on antd's internal :only-child selectors;
+ // query the portal node by class and require non-empty text content so an empty
+ // tooltip render does not pass.
+ await waitFor(() => {
+ const tooltip = document.querySelector('.ant-tooltip-inner');
+ expect(tooltip).toBeInTheDocument();
+ expect(tooltip?.textContent?.trim()).toBeTruthy();
+ });
+}, 30000);
+
+test('numerical range filter — Range Type selector lets the user pick a display mode', async () => {
+ defaultRender();
+
+ await userEvent.click(screen.getByText(VALUE_REGEX));
+ await userEvent.click(await screen.findByText(NUMERICAL_RANGE_REGEX));
+
+ const rangeTypeCombobox = await screen.findByRole('combobox', {
+ name: /range type/i,
+ });
+
+ // Default render is "Slider and range input"; asserting Slider is absent first
+ // ensures the post-click assertion proves a state change rather than passing on
+ // the default selection.
+ expect(
+ document.querySelector('.ant-select-selection-item[title="Slider"]'),
+ ).not.toBeInTheDocument();
+
+ await userEvent.click(rangeTypeCombobox);
+ const sliderOption = await screen.findByRole('option', {
+ name: /^slider$/i,
+ });
+ await userEvent.click(sliderOption);
+
+ // antd Select renders the active selection as a span whose title attribute is
+ // the picked option's label.
+ await waitFor(() => {
+ expect(
+ document.querySelector('.ant-select-selection-item[title="Slider"]'),
+ ).toBeInTheDocument();
+ });
+}, 30000);
+
+test('toggles "Filter has default value" to show and hide the Default Value control', async () => {
+ defaultRender();
+
+ const defaultValueCheckbox = getCheckbox(DEFAULT_VALUE_REGEX);
+ expect(defaultValueCheckbox).not.toBeChecked();
+ expect(screen.queryByText(/^default value$/i)).not.toBeInTheDocument();
+
+ await userEvent.click(defaultValueCheckbox);
+
+ expect(defaultValueCheckbox).toBeChecked();
+ expect(await screen.findByText(/^default value$/i)).toBeInTheDocument();
+
+ await userEvent.click(defaultValueCheckbox);
+
+ expect(defaultValueCheckbox).not.toBeChecked();
+ await waitFor(() => {
+ expect(screen.queryByText(/^default value$/i)).not.toBeInTheDocument();
+ });
+});
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/NativeFiltersModal.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/NativeFiltersModal.test.tsx
index 9ee2a9130ab..723dc35c760 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/NativeFiltersModal.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/NativeFiltersModal.test.tsx
@@ -84,6 +84,14 @@ test('the form validates required fields', async () => {
expect(onSave).toHaveBeenCalledTimes(0);
});
+async function openDropdownAndAddFilter(
+ getByTestId: (id: string) => HTMLElement,
+ findByRole: (role: string, opts: { name: RegExp }) => Promise<HTMLElement>,
+) {
+ fireEvent.mouseEnter(getByTestId('new-item-dropdown-button'));
+ fireEvent.click(await findByRole('menuitem', { name: /add filter/i }));
+}
+
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('createNewOnOpen', () => {
test('does not show alert when there is no unsaved filters', async () => {
@@ -99,15 +107,23 @@ describe('createNewOnOpen', () => {
onCancel,
createNewOnOpen: false,
});
- const dropdownButton = getByTestId('new-item-dropdown-button');
- fireEvent.mouseEnter(dropdownButton);
- const addFilterMenuItem = await findByRole('menuitem', {
- name: /add filter/i,
- });
- fireEvent.click(addFilterMenuItem);
+ await openDropdownAndAddFilter(getByTestId, findByRole);
fireEvent.click(getByRole('button', { name: 'Cancel' }));
expect(onCancel).toHaveBeenCalledTimes(0);
expect(getByRole('alert')).toBeInTheDocument();
expect(getByRole('alert')).toHaveTextContent('There are unsaved changes.');
});
+
+ test('confirm-cancel button proceeds with cancel after the unsaved alert', async () => {
+ const onCancel = jest.fn();
+ const { getByRole, getByTestId, findByRole } = setup({
+ onCancel,
+ createNewOnOpen: false,
+ });
+ await openDropdownAndAddFilter(getByTestId, findByRole);
+ fireEvent.click(getByRole('button', { name: 'Cancel' }));
+ expect(getByRole('alert')).toBeInTheDocument();
+ fireEvent.click(getByTestId('native-filter-modal-confirm-cancel-button'));
+ expect(onCancel).toHaveBeenCalledTimes(1);
+ });
});