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);
+  });
 });