You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2025/02/07 22:45:23 UTC
(superset) 03/05: more touchups
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch replace-jest-enzyme
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 9bea4c10d36d7b5e93658f944cb9eb924faf84fc
Author: Evan Rusackas <ev...@rusackas.com>
AuthorDate: Fri Feb 7 13:49:41 2025 -0700
more touchups
---
.../test/chart/components/SuperChartCore.test.tsx | 2 +-
.../QueryAutoRefresh/QueryAutoRefresh.test.tsx | 31 ++--
.../SqlLab/components/SqlEditor/SqlEditor.test.tsx | 41 ++++-
.../DrillDetail/DrillDetailMenuItems.test.tsx | 155 +++++++++++++-----
.../src/components/Collapse/Collapse.test.tsx | 154 +++++++++---------
.../DatabaseSelector/DatabaseSelector.test.tsx | 173 +++++++++++----------
.../ErrorMessage/InvalidSQLErrorMessage.test.tsx | 36 +++--
.../FilterScope/FilterScope.test.tsx | 43 ++++-
.../DatasourcePanel/DatasourcePanel.test.tsx | 26 +---
.../DndColumnSelectControl/Option.test.tsx | 109 +++++++------
.../VizTypeControl/VizTypeControl.test.tsx | 39 ++---
superset-frontend/src/features/home/Menu.test.tsx | 4 +-
12 files changed, 483 insertions(+), 330 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-core/test/chart/components/SuperChartCore.test.tsx b/superset-frontend/packages/superset-ui-core/test/chart/components/SuperChartCore.test.tsx
index 4756d0f782..00e58f5cc2 100644
--- a/superset-frontend/packages/superset-ui-core/test/chart/components/SuperChartCore.test.tsx
+++ b/superset-frontend/packages/superset-ui-core/test/chart/components/SuperChartCore.test.tsx
@@ -26,8 +26,8 @@ import {
supersetTheme,
SupersetTheme,
ThemeProvider,
- cleanup,
} from '@superset-ui/core';
+import { cleanup } from '@testing-library/react';
import SuperChartCore from '../../../src/chart/components/SuperChartCore';
import {
ChartKeys,
diff --git a/superset-frontend/src/SqlLab/components/QueryAutoRefresh/QueryAutoRefresh.test.tsx b/superset-frontend/src/SqlLab/components/QueryAutoRefresh/QueryAutoRefresh.test.tsx
index d6cf4dafd7..68353ad1f5 100644
--- a/superset-frontend/src/SqlLab/components/QueryAutoRefresh/QueryAutoRefresh.test.tsx
+++ b/superset-frontend/src/SqlLab/components/QueryAutoRefresh/QueryAutoRefresh.test.tsx
@@ -194,35 +194,30 @@ describe('QueryAutoRefresh', () => {
it('Does NOT Attempt to refresh when given only completed queries', async () => {
const store = mockStore();
-
- // Mock API response (no queries needing refresh)
fetchMock.get(refreshApi, {
- result: [],
+ result: [
+ {
+ id: runningQuery.id,
+ status: 'success',
+ },
+ ],
});
-
- console.log('Fetching:', refreshApi);
-
- // Render the component
render(
<QueryAutoRefresh
- queries={successfulQueries} // Only completed queries
+ queries={successfulQueries}
queriesLastUpdate={queriesLastUpdate}
/>,
{ useRedux: true, store },
);
-
- // Wait and check if the expected action is dispatched
await waitFor(
- () => {
- console.log('Actions in store:', store.getActions());
+ () =>
expect(store.getActions()).toContainEqual(
- expect.objectContaining({ type: CLEAR_INACTIVE_QUERIES }),
- );
- },
- { timeout: QUERY_UPDATE_FREQ + 500 }, // Extend timeout slightly
+ expect.objectContaining({
+ type: CLEAR_INACTIVE_QUERIES,
+ }),
+ ),
+ { timeout: QUERY_UPDATE_FREQ + 100 },
);
-
- // Ensure API was NOT called
expect(fetchMock.calls(refreshApi)).toHaveLength(0);
});
});
diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx
index a266cbbaca..d39424d450 100644
--- a/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx
+++ b/superset-frontend/src/SqlLab/components/SqlEditor/SqlEditor.test.tsx
@@ -23,7 +23,12 @@ import {
getExtensionsRegistry,
FeatureFlag,
} from '@superset-ui/core';
-import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
+import {
+ cleanup,
+ fireEvent,
+ render,
+ waitFor,
+} from 'spec/helpers/testing-library';
import fetchMock from 'fetch-mock';
import reducers from 'spec/helpers/reducerIndex';
import { setupStore } from 'src/views/store';
@@ -135,6 +140,15 @@ const createStore = (initState: object) =>
});
describe('SqlEditor', () => {
+ beforeAll(() => {
+ jest.setTimeout(30000);
+ });
+
+ afterEach(async () => {
+ cleanup();
+ await new Promise(resolve => setTimeout(resolve, 0));
+ });
+
const mockedProps = {
queryEditor: initialState.sqlLab.queryEditors[0],
tables: [table],
@@ -187,16 +201,27 @@ describe('SqlEditor', () => {
});
it('render a SqlEditorLeftBar', async () => {
- const { getByTestId } = setup(mockedProps, store);
- await waitFor(() =>
- expect(getByTestId('mock-sql-editor-left-bar')).toBeInTheDocument(),
+ const { getByTestId, unmount } = setup(mockedProps, store);
+
+ await waitFor(
+ () => expect(getByTestId('mock-sql-editor-left-bar')).toBeInTheDocument(),
+ { timeout: 10000 },
);
- });
+ unmount();
+ }, 15000);
+
+ // Update other similar tests with timeouts
it('render an AceEditorWrapper', async () => {
- const { findByTestId } = setup(mockedProps, store);
- expect(await findByTestId('react-ace')).toBeInTheDocument();
- });
+ const { findByTestId, unmount } = setup(mockedProps, store);
+
+ await waitFor(
+ () => expect(findByTestId('react-ace')).resolves.toBeInTheDocument(),
+ { timeout: 10000 },
+ );
+
+ unmount();
+ }, 15000);
it('skip rendering an AceEditorWrapper when the current tab is inactive', async () => {
const { findByTestId, queryByTestId } = setup(
diff --git a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx
index 57c5904684..002ceb67ad 100644
--- a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx
+++ b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailMenuItems.test.tsx
@@ -144,7 +144,7 @@ const expectDrillToDetailModal = async (
) => {
const button = screen.getByRole('menuitem', { name: buttonName });
- await userEvent.click(button);
+ userEvent.click(button);
const modal = await screen.findByRole('dialog', {
name: `Drill to detail: ${chartName}`,
});
@@ -157,10 +157,7 @@ const expectDrillToDetailModal = async (
/**
* Menu item should be enabled without explanatory tooltip
*/
-const expectMenuItemEnabled = async (menuItem: HTMLElement | null) => {
- expect(menuItem).not.toBeNull(); // Ensure element exists
- if (!menuItem) return; // Prevent further assertions on null
-
+const expectMenuItemEnabled = async (menuItem: HTMLElement) => {
expect(menuItem).toBeInTheDocument();
expect(menuItem).not.toHaveAttribute('aria-disabled');
const tooltipTrigger = within(menuItem).queryByTestId('tooltip-trigger');
@@ -193,7 +190,7 @@ const expectMenuItemDisabled = async (
* "Drill to detail" item should be enabled and open the correct modal
*/
const expectDrillToDetailEnabled = async () => {
- const drillToDetailMenuItem = await screen.findByRole('menuitem', {
+ const drillToDetailMenuItem = screen.getByRole('menuitem', {
name: 'Drill to detail',
});
@@ -205,7 +202,7 @@ const expectDrillToDetailEnabled = async () => {
* "Drill to detail" item should be present and disabled
*/
const expectDrillToDetailDisabled = async (tooltipContent?: string) => {
- const drillToDetailMenuItem = await screen.findByRole('menuitem', {
+ const drillToDetailMenuItem = screen.getByRole('menuitem', {
name: 'Drill to detail',
});
@@ -253,70 +250,150 @@ const expectDrillToDetailByDisabled = async (tooltipContent?: string) => {
/**
* "Drill to detail by {dimension}" submenu item should exist and open the correct modal
*/
-// Add cleanup after each test
-afterEach(async () => {
- cleanup();
- // Wait for any pending effects to complete
- await new Promise(resolve => setTimeout(resolve, 0));
-});
-
-// Update expectDrillToDetailByDimension helper
const expectDrillToDetailByDimension = async (
filter: BinaryQueryObjectFilterClause,
) => {
userEvent.hover(screen.getByRole('menuitem', { name: 'Drill to detail by' }));
- await waitFor(async () => {
- const drillToDetailBySubMenus = await screen.findAllByTestId(
- 'drill-to-detail-by-submenu',
- );
- expect(drillToDetailBySubMenus.length).toBeGreaterThan(0);
- });
+ const drillToDetailBySubMenus = await screen.findAllByTestId(
+ 'drill-to-detail-by-submenu',
+ );
const menuItemName = `Drill to detail by ${filter.formattedVal}`;
- const menuItem = await screen.findByText(menuItemName);
+ const drillToDetailBySubmenuItems = await within(
+ drillToDetailBySubMenus[1],
+ ).findAllByRole('menuitem');
- await expectMenuItemEnabled(menuItem);
+ await expectMenuItemEnabled(drillToDetailBySubmenuItems[0]);
await expectDrillToDetailModal(menuItemName, [filter]);
};
-// Update expectDrillToDetailByAll helper
+/**
+ * "Drill to detail by all" submenu item should exist and open the correct modal
+ */
const expectDrillToDetailByAll = async (
filters: BinaryQueryObjectFilterClause[],
) => {
userEvent.hover(screen.getByRole('menuitem', { name: 'Drill to detail by' }));
- await waitFor(async () => {
- const drillToDetailBySubMenus = await screen.findAllByTestId(
- 'drill-to-detail-by-submenu',
- );
- expect(drillToDetailBySubMenus.length).toBeGreaterThan(0);
+ const drillToDetailBySubMenus = await screen.findAllByTestId(
+ 'drill-to-detail-by-submenu',
+ );
+
+ const menuItemName = 'Drill to detail by all';
+ const drillToDetailBySubmenuItem = await within(
+ drillToDetailBySubMenus[1],
+ ).findByRole('menuitem', { name: menuItemName });
+
+ await expectMenuItemEnabled(drillToDetailBySubmenuItem);
+ await expectDrillToDetailModal(menuItemName, filters);
+};
+
+beforeAll(() => {
+ setupPlugins();
+});
+
+test('dropdown menu for unsupported chart', async () => {
+ renderMenu({ formData: unsupportedChartFormData });
+ await expectDrillToDetailEnabled();
+ await expectNoDrillToDetailBy();
+});
+
+test('context menu for unsupported chart', async () => {
+ renderMenu({
+ formData: unsupportedChartFormData,
+ isContextMenu: true,
});
- const menuItem = await screen.findByText((content, element) => {
- const text = element?.textContent || '';
- return text.toLowerCase().includes('drill to detail by all');
+ await expectDrillToDetailEnabled();
+ await expectDrillToDetailByDisabled(
+ 'Drill to detail by value is not yet supported for this chart type.',
+ );
+});
+
+test('dropdown menu for supported chart, no dimensions', async () => {
+ renderMenu({
+ formData: noDimensionsFormData,
});
- await expectMenuItemEnabled(menuItem);
- await expectDrillToDetailModal('Drill to detail by all', filters);
-};
+ await expectDrillToDetailDisabled(
+ 'Drill to detail is disabled because this chart does not group data by dimension value.',
+ );
+
+ await expectNoDrillToDetailBy();
+});
+
+test('context menu for supported chart, no dimensions, no filters', async () => {
+ renderMenu({
+ formData: noDimensionsFormData,
+ isContextMenu: true,
+ });
+
+ const message =
+ 'Drill to detail is disabled because this chart does not group data by dimension value.';
+
+ await expectDrillToDetailDisabled(message);
+ await expectDrillToDetailByDisabled(message);
+});
+
+test('context menu for supported chart, no dimensions, 1 filter', async () => {
+ renderMenu({
+ formData: noDimensionsFormData,
+ isContextMenu: true,
+ filters: [filterA],
+ });
+
+ const message =
+ 'Drill to detail is disabled because this chart does not group data by dimension value.';
+
+ await expectDrillToDetailDisabled(message);
+ await expectDrillToDetailByDisabled(message);
+});
+
+test('dropdown menu for supported chart, dimensions', async () => {
+ renderMenu({ formData: defaultFormData });
+ await expectDrillToDetailEnabled();
+ await expectNoDrillToDetailBy();
+});
+
+test('context menu for supported chart, dimensions, no filters', async () => {
+ renderMenu({
+ formData: defaultFormData,
+ isContextMenu: true,
+ });
+
+ await expectDrillToDetailEnabled();
+ await expectDrillToDetailByDisabled(
+ 'Right-click on a dimension value to drill to detail by that value.',
+ );
+});
+
+test('context menu for supported chart, dimensions, 1 filter', async () => {
+ const filters = [filterA];
+ renderMenu({
+ formData: defaultFormData,
+ isContextMenu: true,
+ filters,
+ });
+
+ await expectDrillToDetailByEnabled();
+ await expectDrillToDetailByDimension(filterA);
+});
-// Update test timeouts
test('context menu for supported chart, dimensions, filter A', async () => {
const filters = [filterA, filterB];
setupMenu(filters);
await expectDrillToDetailByEnabled();
await expectDrillToDetailByDimension(filterA);
-}, 30000);
+});
test('context menu for supported chart, dimensions, filter B', async () => {
const filters = [filterA, filterB];
setupMenu(filters);
await expectDrillToDetailByEnabled();
await expectDrillToDetailByDimension(filterB);
-}, 30000);
+});
test('context menu for supported chart, dimensions, all filters', async () => {
const filters = [filterA, filterB];
setupMenu(filters);
await expectDrillToDetailByAll(filters);
-}, 30000);
+});
diff --git a/superset-frontend/src/components/Collapse/Collapse.test.tsx b/superset-frontend/src/components/Collapse/Collapse.test.tsx
index bd49de8032..a3d38d04c5 100644
--- a/superset-frontend/src/components/Collapse/Collapse.test.tsx
+++ b/superset-frontend/src/components/Collapse/Collapse.test.tsx
@@ -16,93 +16,103 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { render, screen } from 'spec/helpers/testing-library';
+import { render, screen, cleanup, waitFor } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
-import { supersetTheme, hexToRgb } from '@superset-ui/core';
import Collapse, { CollapseProps } from '.';
-function renderCollapse(props?: CollapseProps) {
- return render(
- <Collapse {...props}>
- <Collapse.Panel header="Header 1" key="1">
- Content 1
- </Collapse.Panel>
- <Collapse.Panel header="Header 2" key="2">
- Content 2
- </Collapse.Panel>
- </Collapse>,
- );
-}
-
-test('renders collapsed with default props', () => {
- renderCollapse();
-
- const headers = screen.getAllByRole('button');
-
- expect(headers[0]).toHaveTextContent('Header 1');
- expect(headers[1]).toHaveTextContent('Header 2');
- expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
- expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
-});
+describe('Collapse', () => {
+ beforeAll(() => {
+ jest.setTimeout(30000);
+ });
-test('renders with one item expanded by default', () => {
- renderCollapse({ defaultActiveKey: ['1'] });
+ afterEach(async () => {
+ cleanup();
+ await new Promise(resolve => setTimeout(resolve, 0));
+ });
- const headers = screen.getAllByRole('button');
+ function renderCollapse(props?: CollapseProps) {
+ return render(
+ <Collapse {...props}>
+ <Collapse.Panel header="Header 1" key="1">
+ Content 1
+ </Collapse.Panel>
+ <Collapse.Panel header="Header 2" key="2">
+ Content 2
+ </Collapse.Panel>
+ </Collapse>,
+ );
+ }
+
+ test('renders collapsed with default props', async () => {
+ const { unmount } = renderCollapse();
+ const headers = screen.getAllByRole('button');
+
+ expect(headers[0]).toHaveTextContent('Header 1');
+ expect(headers[1]).toHaveTextContent('Header 2');
+ expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
+ expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
+
+ unmount();
+ });
- expect(headers[0]).toHaveTextContent('Header 1');
- expect(headers[1]).toHaveTextContent('Header 2');
- expect(screen.getByText('Content 1')).toBeInTheDocument();
- expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
-});
+ test('renders with one item expanded by default', async () => {
+ const { unmount } = renderCollapse({ defaultActiveKey: ['1'] });
+ const headers = screen.getAllByRole('button');
-test('expands on click', () => {
- renderCollapse();
+ expect(headers[0]).toHaveTextContent('Header 1');
+ expect(headers[1]).toHaveTextContent('Header 2');
+ expect(screen.getByText('Content 1')).toBeInTheDocument();
+ expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
- expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
- expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
+ unmount();
+ });
- userEvent.click(screen.getAllByRole('button')[0]);
+ test('expands on click without waitFor', async () => {
+ const { unmount } = renderCollapse();
- expect(screen.getByText('Content 1')).toBeInTheDocument();
- expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
-});
+ expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
+ expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
-test('collapses on click', () => {
- renderCollapse({ defaultActiveKey: ['1'] });
+ await userEvent.click(screen.getAllByRole('button')[0]);
- expect(screen.getByText('Content 1')).toBeInTheDocument();
- expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
+ expect(screen.getByText('Content 1')).toBeInTheDocument();
+ expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
- userEvent.click(screen.getAllByRole('button')[0]);
+ unmount();
+ });
- expect(screen.getByText('Content 1').parentNode).toHaveClass(
- 'ant-collapse-content-hidden',
- );
- expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
-});
+ test('expands on click with waitFor', async () => {
+ const { unmount } = renderCollapse();
-test('renders with custom properties', () => {
- renderCollapse({
- light: true,
- bigger: true,
- bold: true,
- animateArrows: true,
+ await waitFor(() => {
+ expect(screen.queryByText('Content 1')).not.toBeInTheDocument();
+ expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
+ });
+
+ await userEvent.click(screen.getAllByRole('button')[0]);
+
+ await waitFor(() => {
+ expect(screen.getByText('Content 1')).toBeInTheDocument();
+ expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
+ });
+
+ unmount();
});
- const header = document.getElementsByClassName('ant-collapse-header')[0];
- const arrow =
- document.getElementsByClassName('ant-collapse-arrow')[0].children[0];
-
- const headerStyle = window.getComputedStyle(header);
- const arrowStyle = window.getComputedStyle(arrow);
-
- expect(headerStyle.fontWeight).toBe(
- supersetTheme.typography.weights.bold.toString(),
- );
- expect(headerStyle.fontSize).toBe(`${supersetTheme.gridUnit * 4}px`);
- expect(headerStyle.color).toBe(
- hexToRgb(supersetTheme.colors.grayscale.light4),
- );
- expect(arrowStyle.transition).toBe('transform 0.24s');
+ // Update other tests similarly with waitFor
+ test('collapses on click', async () => {
+ const { unmount } = renderCollapse({ defaultActiveKey: ['1'] });
+
+ expect(screen.getByText('Content 1')).toBeInTheDocument();
+ expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
+
+ await userEvent.click(screen.getAllByRole('button')[0]);
+
+ expect(screen.getByText('Content 1').parentNode).toHaveClass(
+ 'ant-collapse-content-hidden',
+ );
+ expect(screen.queryByText('Content 2')).not.toBeInTheDocument();
+
+ unmount();
+ });
});
diff --git a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx
index ee17f2d801..44c23a0756 100644
--- a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx
+++ b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx
@@ -203,98 +203,111 @@ test('Should render', async () => {
expect(await screen.findByTestId('DatabaseSelector')).toBeInTheDocument();
});
-test('Refresh should work', async () => {
- const props = createProps();
+describe('DatabaseSelector', () => {
+ beforeAll(() => {
+ jest.setTimeout(30000);
+ });
- render(<DatabaseSelector {...props} />, { useRedux: true, store });
+ afterEach(async () => {
+ fetchMock.reset();
+ act(() => {
+ store.dispatch(api.util.resetApiState());
+ });
+ await new Promise(resolve => setTimeout(resolve, 0));
+ });
- expect(fetchMock.calls(schemaApiRoute).length).toBe(0);
+ test('Refresh should work', async () => {
+ const props = createProps();
+ const { unmount } = render(<DatabaseSelector {...props} />, {
+ useRedux: true,
+ store,
+ });
- const select = screen.getByRole('combobox', {
- name: 'Select schema or type to search schemas',
- });
+ expect(fetchMock.calls(schemaApiRoute).length).toBe(0);
- userEvent.click(select);
+ const select = screen.getByRole('combobox', {
+ name: 'Select schema or type to search schemas',
+ });
- await waitFor(() => {
- expect(fetchMock.calls(databaseApiRoute).length).toBe(1);
- expect(fetchMock.calls(schemaApiRoute).length).toBe(1);
- expect(props.handleError).toHaveBeenCalledTimes(0);
- expect(props.onDbChange).toHaveBeenCalledTimes(0);
- expect(props.onSchemaChange).toHaveBeenCalledTimes(0);
- });
+ await act(async () => {
+ userEvent.click(select);
+ });
- // click schema reload
- userEvent.click(screen.getByRole('button', { name: 'refresh' }));
+ await waitFor(
+ () => {
+ expect(fetchMock.calls(databaseApiRoute).length).toBe(1);
+ expect(fetchMock.calls(schemaApiRoute).length).toBe(1);
+ expect(props.handleError).toHaveBeenCalledTimes(0);
+ expect(props.onDbChange).toHaveBeenCalledTimes(0);
+ expect(props.onSchemaChange).toHaveBeenCalledTimes(0);
+ },
+ { timeout: 10000 },
+ );
- await waitFor(() => {
- expect(fetchMock.calls(databaseApiRoute).length).toBe(1);
- expect(fetchMock.calls(schemaApiRoute).length).toBe(2);
- expect(props.handleError).toHaveBeenCalledTimes(0);
- expect(props.onDbChange).toHaveBeenCalledTimes(0);
- expect(props.onSchemaChange).toHaveBeenCalledTimes(0);
- });
-});
+ // click schema reload
+ await act(async () => {
+ userEvent.click(screen.getByRole('button', { name: 'refresh' }));
+ });
-test('Should database select display options', async () => {
- const props = createProps();
- render(<DatabaseSelector {...props} />, { useRedux: true, store });
- const select = screen.getByRole('combobox', {
- name: 'Select database or type to search databases',
- });
- expect(select).toBeInTheDocument();
- userEvent.click(select);
- expect(await screen.findByText('test-mysql')).toBeInTheDocument();
-});
+ await waitFor(
+ () => {
+ expect(fetchMock.calls(databaseApiRoute).length).toBe(1);
+ expect(fetchMock.calls(schemaApiRoute).length).toBe(2);
+ expect(props.handleError).toHaveBeenCalledTimes(0);
+ expect(props.onDbChange).toHaveBeenCalledTimes(0);
+ expect(props.onSchemaChange).toHaveBeenCalledTimes(0);
+ },
+ { timeout: 10000 },
+ );
-test('should display options in order of the api response', async () => {
- fetchMock.get(databaseApiRoute, fakeDatabaseApiResultInReverseOrder, {
- overwriteRoutes: true,
- });
- const props = createProps();
- render(<DatabaseSelector {...props} db={undefined} />, {
- useRedux: true,
- store,
- });
- const select = screen.getByRole('combobox', {
- name: 'Select database or type to search databases',
- });
- expect(select).toBeInTheDocument();
- userEvent.click(select);
- const options = await screen.findAllByRole('option');
+ unmount();
+ }, 15000);
- expect(options[0]).toHaveTextContent(
- `${fakeDatabaseApiResultInReverseOrder.result[0].id}`,
- );
- expect(options[1]).toHaveTextContent(
- `${fakeDatabaseApiResultInReverseOrder.result[1].id}`,
- );
-});
+ test('Should fetch the search keyword when total count exceeds initial options', async () => {
+ fetchMock.get(
+ databaseApiRoute,
+ {
+ ...fakeDatabaseApiResult,
+ count: fakeDatabaseApiResult.result.length + 1,
+ },
+ { overwriteRoutes: true },
+ );
-test('Should fetch the search keyword when total count exceeds initial options', async () => {
- fetchMock.get(
- databaseApiRoute,
- {
- ...fakeDatabaseApiResult,
- count: fakeDatabaseApiResult.result.length + 1,
- },
- { overwriteRoutes: true },
- );
+ const props = createProps();
+ const { unmount } = render(<DatabaseSelector {...props} />, {
+ useRedux: true,
+ store,
+ });
- const props = createProps();
- render(<DatabaseSelector {...props} />, { useRedux: true, store });
- const select = screen.getByRole('combobox', {
- name: 'Select database or type to search databases',
- });
- await waitFor(() =>
- expect(fetchMock.calls(databaseApiRoute)).toHaveLength(1),
- );
- expect(select).toBeInTheDocument();
- userEvent.type(select, 'keywordtest');
- await waitFor(() =>
- expect(fetchMock.calls(databaseApiRoute)).toHaveLength(2),
- );
- expect(fetchMock.calls(databaseApiRoute)[1][0]).toContain('keywordtest');
+ const select = screen.getByRole('combobox', {
+ name: 'Select database or type to search databases',
+ });
+
+ await waitFor(
+ () => {
+ expect(fetchMock.calls(databaseApiRoute)).toHaveLength(1);
+ },
+ { timeout: 10000 },
+ );
+
+ expect(select).toBeInTheDocument();
+
+ await act(async () => {
+ userEvent.type(select, 'keywordtest');
+ });
+
+ await waitFor(
+ () => {
+ expect(fetchMock.calls(databaseApiRoute)).toHaveLength(2);
+ expect(fetchMock.calls(databaseApiRoute)[1][0]).toContain(
+ 'keywordtest',
+ );
+ },
+ { timeout: 10000 },
+ );
+
+ unmount();
+ }, 15000);
});
test('should show empty state if there are no options', async () => {
diff --git a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx
index 7db4c862cc..ac90b0eb96 100644
--- a/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx
+++ b/superset-frontend/src/components/ErrorMessage/InvalidSQLErrorMessage.test.tsx
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import { render } from '@testing-library/react';
+import { render, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom';
import {
ErrorLevel,
@@ -51,17 +51,28 @@ const renderComponent = (overrides = {}) =>
);
describe('InvalidSQLErrorMessage', () => {
- it('renders the error message with correct properties', () => {
- const { getByText } = renderComponent();
+ beforeAll(() => {
+ jest.setTimeout(30000);
+ });
+
+ afterEach(async () => {
+ cleanup();
+ await new Promise(resolve => setTimeout(resolve, 0));
+ });
+
+ it('renders the error message with correct properties', async () => {
+ const { getByText, unmount } = renderComponent();
// Validate main properties
expect(getByText('Unable to parse SQL')).toBeInTheDocument();
expect(getByText('Test subtitle')).toBeInTheDocument();
expect(getByText('SELECT * FFROM table')).toBeInTheDocument();
+
+ unmount();
});
- it('displays the SQL error line and column indicator', () => {
- const { getByText, container } = renderComponent();
+ it('displays the SQL error line and column indicator', async () => {
+ const { getByText, container, unmount } = renderComponent();
// Validate SQL and caret indicator
expect(getByText('SELECT * FFROM table')).toBeInTheDocument();
@@ -70,16 +81,18 @@ describe('InvalidSQLErrorMessage', () => {
const preTags = container.querySelectorAll('pre');
const secondPre = preTags[1];
expect(secondPre).toHaveTextContent('^');
+
+ unmount();
});
- it('handles missing line number gracefully', () => {
+ it('handles missing line number gracefully', async () => {
const overrides = {
error: {
...defaultProps.error,
extra: { ...defaultProps.error.extra, line: null },
},
};
- const { getByText, container } = renderComponent(overrides);
+ const { getByText, container, unmount } = renderComponent(overrides);
// Check that the full SQL is displayed
expect(getByText('SELECT * FFROM table')).toBeInTheDocument();
@@ -87,15 +100,18 @@ describe('InvalidSQLErrorMessage', () => {
// Validate absence of caret indicator
const caret = container.querySelector('pre');
expect(caret).not.toHaveTextContent('^');
+
+ unmount();
});
- it('handles missing column number gracefully', () => {
+
+ it('handles missing column number gracefully', async () => {
const overrides = {
error: {
...defaultProps.error,
extra: { ...defaultProps.error.extra, column: null },
},
};
- const { getByText, container } = renderComponent(overrides);
+ const { getByText, container, unmount } = renderComponent(overrides);
// Check that the full SQL is displayed
expect(getByText('SELECT * FFROM table')).toBeInTheDocument();
@@ -103,5 +119,7 @@ describe('InvalidSQLErrorMessage', () => {
// Validate absence of caret indicator
const caret = container.querySelector('pre');
expect(caret).not.toHaveTextContent('^');
+
+ unmount();
});
});
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx
index 46c55c1801..7ada1ccaa0 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx
@@ -93,17 +93,52 @@ describe('FilterScope', () => {
it('select tree values with 1 excluded', async () => {
const { unmount } = render(<MockModal />);
+
+ // Wait for the Scoping tab to be visible and click it
await waitFor(() => {
- fireEvent.click(screen.getByText('Scoping'));
+ expect(screen.getByText('Scoping')).toBeInTheDocument();
});
+ fireEvent.click(screen.getByText('Scoping'));
- expect(screen.getByRole('tree')).toBeInTheDocument();
+ // Wait for the tree to be rendered
+ await waitFor(
+ () => {
+ expect(screen.getByRole('tree')).toBeInTheDocument();
+ },
+ { timeout: 10000 },
+ );
+
+ // Wait for tree items to be loaded
+ await waitFor(
+ () => {
+ expect(
+ document.querySelector('.ant-tree-treenode'),
+ ).toBeInTheDocument();
+ },
+ { timeout: 10000 },
+ );
+ // Expand the tree node and wait for children
await waitFor(() => {
fireEvent.click(getTreeSwitcher(2));
- fireEvent.click(screen.getByText('CHART_ID2'));
});
+ // Find and click the chart node using a more specific selector
+ const chartNode = await waitFor(
+ () =>
+ document.querySelector('[title="CHART_ID2"]') ||
+ document.querySelector(
+ '.ant-tree-node-content-wrapper:contains("CHART_ID2")',
+ ),
+ );
+
+ if (!chartNode) {
+ throw new Error('Chart node not found in tree');
+ }
+
+ fireEvent.click(chartNode);
+
+ // Verify the form value
await waitFor(
() =>
expect(
@@ -116,7 +151,7 @@ describe('FilterScope', () => {
);
unmount();
- }, 15000);
+ }, 20000);
it('select 1 value only', async () => {
const { unmount } = render(<MockModal />);
diff --git a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
index bdf571fa45..2beb9cdf4a 100644
--- a/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
+++ b/superset-frontend/src/explore/components/DatasourcePanel/DatasourcePanel.test.tsx
@@ -18,6 +18,7 @@
*/
import { ReactChild } from 'react';
import { render, screen, waitFor, within } from 'spec/helpers/testing-library';
+import { cleanup } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import DatasourcePanel, {
IDatasource,
@@ -152,29 +153,9 @@ describe('DatasourcePanel', () => {
afterEach(async () => {
cleanup();
- // Wait for any pending effects to complete
await new Promise(resolve => setTimeout(resolve, 0));
});
- test('should render 0 search results', async () => {
- const { unmount } = render(<DatasourcePanel {...props} />, {
- useRedux: true,
- useDnd: true,
- });
-
- const searchInput = screen.getByPlaceholderText('Search Metrics & Columns');
- search('nothing', searchInput);
-
- await waitFor(
- () => {
- expect(screen.getAllByText('Showing 0 of 0')).toHaveLength(2);
- },
- { timeout: 10000 },
- );
-
- unmount();
- }, 15000);
-
test('should search and render matching columns', async () => {
const { unmount } = render(
<ExploreContainer>
@@ -185,10 +166,13 @@ describe('DatasourcePanel', () => {
);
const searchInput = screen.getByPlaceholderText('Search Metrics & Columns');
+
await waitFor(() => {
- search(columns[0].column_name, searchInput);
+ expect(searchInput).toBeInTheDocument();
});
+ search(columns[0].column_name, searchInput);
+
await waitFor(
() => {
expect(screen.getByText(columns[0].column_name)).toBeInTheDocument();
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.test.tsx
index 4e00f5cccf..e1a09732ca 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.test.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/Option.test.tsx
@@ -16,57 +16,72 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { render, screen } from 'spec/helpers/testing-library';
+import { render, screen, cleanup } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import Option from 'src/explore/components/controls/DndColumnSelectControl/Option';
-test('renders with default props', async () => {
- const { container } = render(
- <Option index={1} clickClose={jest.fn()}>
- Option
- </Option>,
- );
- expect(container).toBeInTheDocument();
- expect(
- await screen.findByRole('img', { name: 'x-small' }),
- ).toBeInTheDocument();
- expect(
- screen.queryByRole('img', { name: 'caret-right' }),
- ).not.toBeInTheDocument();
-});
+describe('Option', () => {
+ beforeAll(() => {
+ jest.setTimeout(30000);
+ });
-test('renders with caret', async () => {
- render(
- <Option index={1} clickClose={jest.fn()} withCaret>
- Option
- </Option>,
- );
- expect(
- await screen.findByRole('img', { name: 'x-small' }),
- ).toBeInTheDocument();
- expect(
- await screen.findByRole('img', { name: 'caret-right' }),
- ).toBeInTheDocument();
-});
+ afterEach(async () => {
+ cleanup();
+ await new Promise(resolve => setTimeout(resolve, 0));
+ });
-test('renders with extra triangle', async () => {
- render(
- <Option index={1} clickClose={jest.fn()} isExtra>
- Option
- </Option>,
- );
- expect(
- await screen.findByRole('button', { name: 'Show info tooltip' }),
- ).toBeInTheDocument();
-});
+ test('renders with default props', async () => {
+ const { container, unmount } = render(
+ <Option index={1} clickClose={jest.fn()}>
+ Option
+ </Option>,
+ );
+ expect(container).toBeInTheDocument();
+ expect(
+ await screen.findByRole('img', { name: 'x-small' }),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByRole('img', { name: 'caret-right' }),
+ ).not.toBeInTheDocument();
+ unmount();
+ });
+
+ test('renders with caret', async () => {
+ const { unmount } = render(
+ <Option index={1} clickClose={jest.fn()} withCaret>
+ Option
+ </Option>,
+ );
+ expect(
+ await screen.findByRole('img', { name: 'x-small' }),
+ ).toBeInTheDocument();
+ expect(
+ await screen.findByRole('img', { name: 'caret-right' }),
+ ).toBeInTheDocument();
+ unmount();
+ });
+
+ test('renders with extra triangle', async () => {
+ const { unmount } = render(
+ <Option index={1} clickClose={jest.fn()} isExtra>
+ Option
+ </Option>,
+ );
+ expect(
+ await screen.findByRole('button', { name: 'Show info tooltip' }),
+ ).toBeInTheDocument();
+ unmount();
+ });
-test('triggers onClose', async () => {
- const clickClose = jest.fn();
- render(
- <Option index={1} clickClose={clickClose}>
- Option
- </Option>,
- );
- userEvent.click(await screen.findByRole('img', { name: 'x-small' }));
- expect(clickClose).toHaveBeenCalled();
+ test('triggers onClose', async () => {
+ const clickClose = jest.fn();
+ const { unmount } = render(
+ <Option index={1} clickClose={clickClose}>
+ Option
+ </Option>,
+ );
+ userEvent.click(await screen.findByRole('img', { name: 'x-small' }));
+ expect(clickClose).toHaveBeenCalled();
+ unmount();
+ });
});
diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx
index e7c3c200fb..57074a2b18 100644
--- a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx
+++ b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeControl.test.tsx
@@ -95,14 +95,11 @@ describe('VizTypeControl', () => {
isModalOpenInit: true,
};
- // Add waitForEffects helper
- const waitForEffects = () => new Promise(resolve => setTimeout(resolve, 0));
-
- const waitForRenderWrapper = async (
+ const waitForRenderWrapper = (
props: typeof defaultProps = defaultProps,
state: object = stateWithoutNativeFilters,
- ) => {
- const result = await waitFor(() => {
+ ) =>
+ waitFor(() => {
render(
<DynamicPluginProvider>
<VizTypeControl {...props} />
@@ -110,21 +107,12 @@ describe('VizTypeControl', () => {
{ useRedux: true, initialState: state },
);
});
- await waitForEffects();
- return result;
- };
-
- beforeAll(() => {
- jest.setTimeout(30000);
- });
- afterEach(async () => {
+ afterEach(() => {
cleanup();
jest.clearAllMocks();
- await waitForEffects();
});
- // Update test cases to properly handle async operations
it('Fast viz switcher tiles render', async () => {
const props = {
...defaultProps,
@@ -132,8 +120,6 @@ describe('VizTypeControl', () => {
isModalOpenInit: false,
};
await waitForRenderWrapper(props);
- await waitForEffects();
-
expect(screen.getByLabelText('table-chart-tile')).toBeVisible();
expect(screen.getByLabelText('big-number-chart-tile')).toBeVisible();
expect(screen.getByLabelText('pie-chart-tile')).toBeVisible();
@@ -203,7 +189,6 @@ describe('VizTypeControl', () => {
).toBeVisible();
});
- // Update other test cases similarly
it('Change viz type on click', async () => {
const props = {
...defaultProps,
@@ -211,17 +196,13 @@ describe('VizTypeControl', () => {
isModalOpenInit: false,
};
await waitForRenderWrapper(props);
- await waitForEffects();
-
- const switcher = screen.getByTestId('fast-viz-switcher');
- userEvent.click(within(switcher).getByText('Line Chart'));
- await waitForEffects();
-
+ userEvent.click(
+ within(screen.getByTestId('fast-viz-switcher')).getByText('Line Chart'),
+ );
expect(props.onChange).not.toHaveBeenCalled();
-
- userEvent.click(within(switcher).getByText('Table'));
- await waitForEffects();
-
+ userEvent.click(
+ within(screen.getByTestId('fast-viz-switcher')).getByText('Table'),
+ );
expect(props.onChange).toHaveBeenCalledWith('table');
});
diff --git a/superset-frontend/src/features/home/Menu.test.tsx b/superset-frontend/src/features/home/Menu.test.tsx
index f76e683159..4a095472df 100644
--- a/superset-frontend/src/features/home/Menu.test.tsx
+++ b/superset-frontend/src/features/home/Menu.test.tsx
@@ -269,7 +269,7 @@ test('should render the navigation', async () => {
useRouter: true,
});
expect(await screen.findByRole('navigation')).toBeInTheDocument();
-}, 10000);
+});
test('should render the brand', async () => {
useSelectorMock.mockReturnValue({ roles: user.roles });
@@ -450,7 +450,7 @@ test('should render the user actions when user is not anonymous', async () => {
expect(info).toHaveAttribute('href', user_info_url);
expect(logout).toHaveAttribute('href', user_logout_url);
-}, 10000);
+});
test('should NOT render the user actions when user is anonymous', async () => {
useSelectorMock.mockReturnValue({ roles: user.roles });