diff --git a/superset-frontend/src/dashboard/components/gridComponents/Header.test.jsx b/superset-frontend/src/dashboard/components/gridComponents/Header.test.jsx
index 59708224041..e015f161b37 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Header.test.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Header.test.jsx
@@ -17,16 +17,11 @@
* under the License.
*/
import { Provider } from 'react-redux';
-import { styledMount as mount } from 'spec/helpers/theming';
-import sinon from 'sinon';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
+import sinon from 'sinon';
-import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton';
-import EditableTitle from 'src/components/EditableTitle';
-import HoverMenu from 'src/dashboard/components/menu/HoverMenu';
-import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
-import { Draggable } from 'src/dashboard/components/dnd/DragDroppable';
+import { render, screen, fireEvent } from 'spec/helpers/testing-library';
import Header from 'src/dashboard/components/gridComponents/Header';
import newComponentFactory from 'src/dashboard/util/newComponentFactory';
import {
@@ -53,49 +48,53 @@ describe('Header', () => {
};
function setup(overrideProps) {
- // We have to wrap provide DragDropContext for the underlying DragDroppable
- // otherwise we cannot assert on DragDroppable children
- const wrapper = mount(
+ return render(
,
);
- return wrapper;
}
it('should render a Draggable', () => {
- const wrapper = setup();
- expect(wrapper.find(Draggable)).toBeTruthy();
+ setup();
+ expect(screen.getByTestId('dragdroppable-object')).toBeInTheDocument();
});
it('should render a WithPopoverMenu', () => {
- const wrapper = setup();
- expect(wrapper.find(WithPopoverMenu)).toBeTruthy();
+ setup();
+ expect(screen.getByRole('none')).toBeInTheDocument();
});
it('should render a HoverMenu in editMode', () => {
- let wrapper = setup();
- expect(wrapper.find(HoverMenu).length).toBe(0);
+ setup();
+ expect(screen.queryByTestId('hover-menu')).not.toBeInTheDocument();
- // we cannot set props on the Header because of the WithDragDropContext wrapper
- wrapper = setup({ editMode: true });
- expect(wrapper.find(HoverMenu).length).toBeGreaterThan(0);
+ setup({ editMode: true });
+ const hoverMenus = screen.getAllByTestId('hover-menu');
+ expect(hoverMenus[0]).toBeInTheDocument();
});
it('should render an EditableTitle with meta.text', () => {
- const wrapper = setup();
- expect(wrapper.find(EditableTitle)).toBeTruthy();
- expect(wrapper.find('.editable-title').text()).toBe(
- props.component.meta.text,
- );
+ setup();
+ const titleElement = screen.getByTestId('editable-title-input');
+ expect(titleElement).toBeInTheDocument();
+ expect(titleElement).toHaveTextContent(props.component.meta.text);
});
it('should call updateComponents when EditableTitle changes', () => {
const updateComponents = sinon.spy();
- const wrapper = setup({ editMode: true, updateComponents });
- wrapper.find(EditableTitle).prop('onSaveTitle')('New title');
+ setup({ editMode: true, updateComponents });
+
+ // First click to enter edit mode
+ const titleButton = screen.getByTestId('editable-title-input');
+ fireEvent.click(titleButton);
+
+ // Then change the input value and blur to trigger save
+ const titleInput = screen.getByTestId('editable-title-input');
+ fireEvent.change(titleInput, { target: { value: 'New title' } });
+ fireEvent.blur(titleInput);
const headerId = props.component.id;
expect(updateComponents.callCount).toBe(1);
@@ -105,33 +104,33 @@ describe('Header', () => {
});
it('should render a DeleteComponentButton when focused in editMode', () => {
- const wrapper = setup({ editMode: true });
- wrapper.find(WithPopoverMenu).simulate('click'); // focus
-
- expect(wrapper.find(DeleteComponentButton)).toBeTruthy();
+ setup({ editMode: true });
+ const trashButton = screen.getByRole('img', { name: 'trash' });
+ expect(trashButton).toBeInTheDocument();
});
it('should call deleteComponent when deleted', () => {
const deleteComponent = sinon.spy();
- const wrapper = setup({ editMode: true, deleteComponent });
- wrapper.find(WithPopoverMenu).simulate('click'); // focus
- wrapper.find(DeleteComponentButton).simulate('click');
+ setup({ editMode: true, deleteComponent });
+
+ const trashButton = screen.getByRole('img', { name: 'trash' });
+ fireEvent.click(trashButton.parentElement);
expect(deleteComponent.callCount).toBe(1);
});
it('should render the AnchorLink in view mode', () => {
- const wrapper = setup();
- expect(wrapper.find('AnchorLink')).toBeTruthy();
+ setup();
+ expect(screen.getByTestId('anchor-link')).toBeInTheDocument();
});
it('should not render the AnchorLink in edit mode', () => {
- const wrapper = setup({ editMode: true });
- expect(wrapper.find('AnchorLink').length).toBe(0);
+ setup({ editMode: true });
+ expect(screen.queryByTestId('anchor-link')).not.toBeInTheDocument();
});
it('should not render the AnchorLink in embedded mode', () => {
- const wrapper = setup({ embeddedMode: true });
- expect(wrapper.find('AnchorLink').length).toBe(0);
+ setup({ embeddedMode: true });
+ expect(screen.queryByTestId('anchor-link')).not.toBeInTheDocument();
});
});
diff --git a/superset-frontend/src/pages/AlertReportList/AlertReportList.test.jsx b/superset-frontend/src/pages/AlertReportList/AlertReportList.test.jsx
index 87ea8892834..ffa5717279a 100644
--- a/superset-frontend/src/pages/AlertReportList/AlertReportList.test.jsx
+++ b/superset-frontend/src/pages/AlertReportList/AlertReportList.test.jsx
@@ -18,18 +18,17 @@
*/
import fetchMock from 'fetch-mock';
import configureStore from 'redux-mock-store';
-import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
-import { styledMount as mount } from 'spec/helpers/theming';
-import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
-import { Switch } from 'src/components/Switch';
-import ListView from 'src/components/ListView';
-import SubMenu from 'src/features/home/SubMenu';
+import {
+ render,
+ screen,
+ fireEvent,
+ waitFor,
+} from 'spec/helpers/testing-library';
+import { MemoryRouter } from 'react-router-dom';
+import { QueryParamProvider } from 'use-query-params';
import AlertList from 'src/pages/AlertReportList';
-import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
-import { act } from 'spec/helpers/testing-library';
-// store needed for withToasts(AlertList)
const mockStore = configureStore([thunk]);
const store = mockStore({});
@@ -84,102 +83,153 @@ fetchMock.put(alertsEndpoint, { ...mockalerts[0], active: false });
fetchMock.delete(alertEndpoint, {});
fetchMock.delete(alertsEndpoint, {});
-async function mountAndWait(props = {}) {
- const mounted = mount(
-
-
- ,
+const renderAlertList = (props = {}) =>
+ render(
+
+
+
+
+ ,
+ {
+ useRedux: true,
+ store,
+ },
);
- await waitForComponentToPaint(mounted);
-
- return mounted;
-}
-
describe('AlertList', () => {
- let wrapper;
-
- beforeAll(async () => {
- wrapper = await mountAndWait();
+ beforeEach(() => {
+ fetchMock.resetHistory();
});
it('renders', async () => {
- expect(wrapper.find(AlertList)).toBeTruthy();
+ renderAlertList();
+ expect(await screen.findByText('Alerts & reports')).toBeInTheDocument();
});
it('renders a SubMenu', async () => {
- expect(wrapper.find(SubMenu)).toBeTruthy();
+ renderAlertList();
+ expect(await screen.findByRole('navigation')).toBeInTheDocument();
});
it('renders a ListView', async () => {
- expect(wrapper.find(ListView)).toBeTruthy();
+ renderAlertList();
+ expect(await screen.findByTestId('alerts-list-view')).toBeInTheDocument();
});
it('renders switches', async () => {
- expect(wrapper.find(Switch)).toHaveLength(3);
+ renderAlertList();
+ // Wait for the list to load first
+ await screen.findByTestId('alerts-list-view');
+ const switches = await screen.findAllByRole('switch');
+ expect(switches).toHaveLength(3);
});
it('deletes', async () => {
- act(() => {
- wrapper.find('[data-test="delete-action"]').first().props().onClick();
- });
- await waitForComponentToPaint(wrapper);
+ renderAlertList();
- act(() => {
- wrapper
- .find('#delete')
- .first()
- .props()
- .onChange({ target: { value: 'DELETE' } });
- });
- await waitForComponentToPaint(wrapper);
- act(() => {
- wrapper
- .find('[data-test="modal-confirm-button"]')
- .last()
- .props()
- .onClick();
- });
+ // Wait for list to load
+ await screen.findByTestId('alerts-list-view');
- await waitForComponentToPaint(wrapper);
+ // Find and click first delete button
+ const deleteButtons = await screen.findAllByTestId('delete-action');
+ fireEvent.click(deleteButtons[0]);
- expect(fetchMock.calls(/report\/0/, 'DELETE')).toHaveLength(1);
- });
+ // Wait for modal to appear and find the delete input
+ const deleteInput = await screen.findByTestId('delete-modal-input');
+ fireEvent.change(deleteInput, { target: { value: 'DELETE' } });
+
+ // Click confirm button
+ const confirmButton = await screen.findByTestId('modal-confirm-button');
+ fireEvent.click(confirmButton);
+
+ // Wait for delete request
+ await waitFor(() => {
+ expect(fetchMock.calls(/report\/0/, 'DELETE')).toHaveLength(1);
+ });
+ }, 15000);
it('shows/hides bulk actions when bulk actions is clicked', async () => {
- const button = wrapper.find('[data-test="bulk-select-toggle"]').first();
- act(() => {
- button.props().onClick();
- });
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(IndeterminateCheckbox)).toHaveLength(
- mockalerts.length + 1, // 1 for each row and 1 for select all
- );
- });
+ renderAlertList();
+
+ // Wait for list to load and initial state
+ await screen.findByTestId('alerts-list-view');
+ expect(
+ screen.queryByTestId('bulk-select-controls'),
+ ).not.toBeInTheDocument();
+
+ // Click bulk select toggle
+ const bulkSelectButton = await screen.findByTestId('bulk-select-toggle');
+ fireEvent.click(bulkSelectButton);
+
+ // Verify bulk select controls appear
+ expect(
+ await screen.findByTestId('bulk-select-controls'),
+ ).toBeInTheDocument();
+ }, 15000);
it('hides bulk actions when switch between alert and report list', async () => {
- expect(wrapper.find(IndeterminateCheckbox)).toHaveLength(
- mockalerts.length + 1,
- );
- expect(wrapper.find('[data-test="alert-list"]').hasClass('active')).toBe(
- true,
- );
- expect(wrapper.find('[data-test="report-list"]').hasClass('active')).toBe(
- false,
- );
+ // Start with alert list
+ renderAlertList();
- const reportWrapper = await mountAndWait({ isReportEnabled: true });
+ // Wait for list to load
+ await screen.findByTestId('alerts-list-view');
- expect(fetchMock.calls(/report\/\?q/)[2][0]).toMatchInlineSnapshot(
- `"http://localhost/api/v1/report/?q=(filters:!((col:type,opr:eq,value:Report)),order_column:name,order_direction:desc,page:0,page_size:25)"`,
- );
+ // Click bulk select to show controls
+ const bulkSelectButton = await screen.findByTestId('bulk-select-toggle');
+ fireEvent.click(bulkSelectButton);
+ // Verify bulk select controls appear
expect(
- reportWrapper.find('[data-test="report-list"]').hasClass('active'),
- ).toBe(true);
- expect(
- reportWrapper.find('[data-test="alert-list"]').hasClass('active'),
- ).toBe(false);
- expect(reportWrapper.find(IndeterminateCheckbox)).toHaveLength(0);
- });
+ await screen.findByTestId('bulk-select-controls'),
+ ).toBeInTheDocument();
+
+ // Verify alert tab is active
+ const alertTab = await screen.findByTestId('alert-list');
+ expect(alertTab).toHaveClass('active');
+ const reportTab = screen.getByTestId('report-list');
+ expect(reportTab).not.toHaveClass('active');
+
+ // Switch to report list
+ renderAlertList({ isReportEnabled: true });
+
+ // Wait for report list API call and tab states to update
+ await waitFor(async () => {
+ // Check API call
+ const calls = fetchMock.calls(/report\/\?q/);
+ const hasReportCall = calls.some(call =>
+ call[0].includes('filters:!((col:type,opr:eq,value:Report))'),
+ );
+
+ // Check tab states
+ const reportTabs = screen.getAllByTestId('report-list');
+ const alertTabs = screen.getAllByTestId('alert-list');
+ const hasActiveReport = reportTabs.some(tab =>
+ tab.classList.contains('active'),
+ );
+ const hasNoActiveAlert = alertTabs.every(
+ tab => !tab.classList.contains('active'),
+ );
+
+ return hasReportCall && hasActiveReport && hasNoActiveAlert;
+ });
+
+ // Click bulk select toggle again to hide controls
+ const bulkSelectButtons =
+ await screen.findAllByTestId('bulk-select-toggle');
+ fireEvent.click(bulkSelectButtons[0]);
+
+ // Verify final state
+ await waitFor(() => {
+ expect(
+ screen.queryByTestId('bulk-select-controls'),
+ ).not.toBeInTheDocument();
+ });
+
+ // Verify correct API call was made
+ const reportCalls = fetchMock.calls(/report\/\?q/);
+ const lastReportCall = reportCalls[reportCalls.length - 1][0];
+ expect(lastReportCall).toContain(
+ 'filters:!((col:type,opr:eq,value:Report))',
+ );
+ }, 15000);
});
diff --git a/superset-frontend/src/pages/AnnotationLayerList/AnnotationLayerList.test.jsx b/superset-frontend/src/pages/AnnotationLayerList/AnnotationLayerList.test.jsx
index bdb7eb9bd93..874e4a89dcf 100644
--- a/superset-frontend/src/pages/AnnotationLayerList/AnnotationLayerList.test.jsx
+++ b/superset-frontend/src/pages/AnnotationLayerList/AnnotationLayerList.test.jsx
@@ -19,21 +19,17 @@
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
-import { Provider } from 'react-redux';
-import { styledMount as mount } from 'spec/helpers/theming';
+import {
+ render,
+ screen,
+ fireEvent,
+ waitFor,
+} from 'spec/helpers/testing-library';
+import { MemoryRouter } from 'react-router-dom';
+import { QueryParamProvider } from 'use-query-params';
import AnnotationLayersList from 'src/pages/AnnotationLayerList';
-import AnnotationLayerModal from 'src/features/annotationLayers/AnnotationLayerModal';
-import SubMenu from 'src/features/home/SubMenu';
-import ListView from 'src/components/ListView';
-import Filters from 'src/components/ListView/Filters';
-import DeleteModal from 'src/components/DeleteModal';
-import Button from 'src/components/Button';
-import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
-import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
-import { act } from 'spec/helpers/testing-library';
-// store needed for withToasts(AnnotationLayersList)
const mockStore = configureStore([thunk]);
const store = mockStore({});
@@ -77,91 +73,127 @@ fetchMock.get(layersRelatedEndpoint, {
},
});
-describe('AnnotationLayersList', () => {
- const wrapper = mount(
-
-
- ,
+const renderAnnotationLayersList = (props = {}) =>
+ render(
+
+
+
+
+ ,
+ {
+ useRedux: true,
+ store,
+ },
);
- beforeAll(async () => {
- await waitForComponentToPaint(wrapper);
+
+describe('AnnotationLayersList', () => {
+ beforeEach(() => {
+ fetchMock.resetHistory();
});
- it('renders', () => {
- expect(wrapper.find(AnnotationLayersList)).toBeTruthy();
+ it('renders', async () => {
+ renderAnnotationLayersList();
+ expect(await screen.findByText('Annotation layers')).toBeInTheDocument();
});
- it('renders a SubMenu', () => {
- expect(wrapper.find(SubMenu)).toBeTruthy();
+ it('renders a SubMenu', async () => {
+ renderAnnotationLayersList();
+ expect(await screen.findByRole('navigation')).toBeInTheDocument();
});
- it('renders a ListView', () => {
- expect(wrapper.find(ListView)).toBeTruthy();
+ it('renders a ListView', async () => {
+ renderAnnotationLayersList();
+ expect(
+ await screen.findByTestId('annotation-layers-list-view'),
+ ).toBeInTheDocument();
});
- it('renders a modal', () => {
- expect(wrapper.find(AnnotationLayerModal)).toBeTruthy();
+ it('renders a modal', async () => {
+ renderAnnotationLayersList();
+ const addButton = await screen.findByRole('button', {
+ name: /annotation layer$/i,
+ });
+ fireEvent.click(addButton);
+ expect(await screen.findByRole('dialog')).toBeInTheDocument();
});
- it('fetches layers', () => {
- const callsQ = fetchMock.calls(/annotation_layer\/\?q/);
- expect(callsQ).toHaveLength(1);
- expect(callsQ[0][0]).toMatchInlineSnapshot(
- `"http://localhost/api/v1/annotation_layer/?q=(order_column:name,order_direction:desc,page:0,page_size:25)"`,
- );
+ it('fetches layers', async () => {
+ renderAnnotationLayersList();
+ await waitFor(() => {
+ const calls = fetchMock.calls(/annotation_layer\/\?q/);
+ expect(calls).toHaveLength(1);
+ expect(calls[0][0]).toContain(
+ 'order_column:name,order_direction:desc,page:0,page_size:25',
+ );
+ });
});
- it('renders Filters', () => {
- expect(wrapper.find(Filters)).toBeTruthy();
+ it('renders Filters', async () => {
+ renderAnnotationLayersList();
+ await screen.findByTestId('annotation-layers-list-view');
+ expect(screen.getByPlaceholderText(/type a value/i)).toBeInTheDocument();
});
it('searches', async () => {
- const filtersWrapper = wrapper.find(Filters);
- act(() => {
- filtersWrapper.find('[name="name"]').first().props().onSubmit('foo');
- });
- await waitForComponentToPaint(wrapper);
+ renderAnnotationLayersList();
- expect(fetchMock.lastCall()[0]).toMatchInlineSnapshot(
- `"http://localhost/api/v1/annotation_layer/?q=(filters:!((col:name,opr:ct,value:foo)),order_column:name,order_direction:desc,page:0,page_size:25)"`,
- );
+ // Wait for list to load
+ await screen.findByTestId('annotation-layers-list-view');
+
+ // Find and fill search input
+ const searchInput = screen.getByPlaceholderText(/type a value/i);
+ fireEvent.change(searchInput, { target: { value: 'foo' } });
+ fireEvent.keyDown(searchInput, { key: 'Enter', keyCode: 13 });
+
+ // Wait for search API call
+ await waitFor(() => {
+ const calls = fetchMock.calls(/annotation_layer\/\?q/);
+ const searchCall = calls.find(call =>
+ call[0].includes('filters:!((col:name,opr:ct,value:foo))'),
+ );
+ expect(searchCall).toBeTruthy();
+ });
});
it('deletes', async () => {
- act(() => {
- wrapper.find('[data-test="delete-action"]').first().props().onClick();
+ renderAnnotationLayersList();
+
+ // Wait for list to load
+ await screen.findByTestId('annotation-layers-list-view');
+
+ // Find and click delete button
+ const deleteButtons = await screen.findAllByTestId('delete-action');
+ fireEvent.click(deleteButtons[0]);
+
+ // Check delete modal content
+ const deleteModal = await screen.findByRole('dialog');
+ expect(deleteModal).toHaveTextContent(/permanently delete the layer/i);
+
+ // Type DELETE in confirmation input
+ const deleteInput = await screen.findByTestId('delete-modal-input');
+ fireEvent.change(deleteInput, { target: { value: 'DELETE' } });
+
+ // Click confirm button
+ const confirmButton = await screen.findByTestId('modal-confirm-button');
+ fireEvent.click(confirmButton);
+
+ // Wait for delete request
+ await waitFor(() => {
+ expect(fetchMock.calls(/annotation_layer\/0/, 'DELETE')).toHaveLength(1);
});
- await waitForComponentToPaint(wrapper);
-
- expect(
- wrapper.find(DeleteModal).first().props().description,
- ).toMatchInlineSnapshot(`"This action will permanently delete the layer."`);
-
- act(() => {
- wrapper
- .find('#delete')
- .first()
- .props()
- .onChange({ target: { value: 'DELETE' } });
- });
- await waitForComponentToPaint(wrapper);
- act(() => {
- wrapper.find('button').last().props().onClick();
- });
-
- await waitForComponentToPaint(wrapper);
-
- expect(fetchMock.calls(/annotation_layer\/0/, 'DELETE')).toHaveLength(1);
});
- it('shows/hides bulk actions when bulk actions is clicked', async () => {
- const button = wrapper.find(Button).at(1);
- act(() => {
- button.props().onClick();
- });
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(IndeterminateCheckbox)).toHaveLength(
- mocklayers.length + 1, // 1 for each row and 1 for select all
- );
- });
+ it('shows bulk actions when bulk select is clicked', async () => {
+ renderAnnotationLayersList();
+
+ // Wait for list to load
+ await screen.findByTestId('annotation-layers-list-view');
+
+ // Click bulk select toggle
+ const bulkSelectButton = screen.getByText(/bulk select/i);
+ fireEvent.click(bulkSelectButton);
+
+ // Wait for bulk select mode to be enabled
+ await screen.findByText('0 Selected');
+ }, 30000);
});
diff --git a/superset-frontend/src/pages/ChartList/ChartList.test.jsx b/superset-frontend/src/pages/ChartList/ChartList.test.jsx
index abfb454b182..3da03f035b5 100644
--- a/superset-frontend/src/pages/ChartList/ChartList.test.jsx
+++ b/superset-frontend/src/pages/ChartList/ChartList.test.jsx
@@ -22,34 +22,18 @@ import configureStore from 'redux-mock-store';
import * as reactRedux from 'react-redux';
import fetchMock from 'fetch-mock';
import { VizType, isFeatureEnabled } from '@superset-ui/core';
-import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
-import { styledMount as mount } from 'spec/helpers/theming';
import {
- act,
- cleanup,
render,
screen,
- userEvent,
+ fireEvent,
+ waitFor,
} from 'spec/helpers/testing-library';
import { QueryParamProvider } from 'use-query-params';
import ChartList from 'src/pages/ChartList';
-import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
-import ListView from 'src/components/ListView';
-import PropertiesModal from 'src/explore/components/PropertiesModal';
-import ListViewCard from 'src/components/ListViewCard';
-import FaveStar from 'src/components/FaveStar';
-import TableCollection from 'src/components/TableCollection';
-import CardCollection from 'src/components/ListView/CardCollection';
-const chartsInfoEndpoint = 'glob:*/api/v1/chart/_info*';
-const chartsOwnersEndpoint = 'glob:*/api/v1/chart/related/owners*';
-const chartsCreatedByEndpoint = 'glob:*/api/v1/chart/related/created_by*';
-const chartsEndpoint = 'glob:*/api/v1/chart/*';
-const chartsVizTypesEndpoint = 'glob:*/api/v1/chart/viz_types';
-const chartsDatasourcesEndpoint = 'glob:*/api/v1/chart/datasources';
-const chartFavoriteStatusEndpoint = 'glob:*/api/v1/chart/favorite_status*';
-const datasetEndpoint = 'glob:*/api/v1/dataset/*';
+// Increase default timeout for all tests
+jest.setTimeout(30000);
jest.mock('@superset-ui/core', () => ({
...jest.requireActual('@superset-ui/core'),
@@ -71,10 +55,18 @@ const mockUser = {
userId: 1,
};
+const chartsInfoEndpoint = 'glob:*/api/v1/chart/_info*';
+const chartsOwnersEndpoint = 'glob:*/api/v1/chart/related/owners*';
+const chartsCreatedByEndpoint = 'glob:*/api/v1/chart/related/created_by*';
+const chartsEndpoint = 'glob:*/api/v1/chart/*';
+const chartsVizTypesEndpoint = 'glob:*/api/v1/chart/viz_types';
+const chartsDatasourcesEndpoint = 'glob:*/api/v1/chart/datasources';
+const chartFavoriteStatusEndpoint = 'glob:*/api/v1/chart/favorite_status*';
+const datasetEndpoint = 'glob:*/api/v1/dataset/*';
+
fetchMock.get(chartsInfoEndpoint, {
permissions: ['can_read', 'can_write'],
});
-
fetchMock.get(chartsOwnersEndpoint, {
result: [],
});
@@ -82,23 +74,20 @@ fetchMock.get(chartsCreatedByEndpoint, {
result: [],
});
fetchMock.get(chartFavoriteStatusEndpoint, {
- result: [],
+ result: mockCharts.map(chart => ({ id: chart.id, value: true })),
});
fetchMock.get(chartsEndpoint, {
result: mockCharts,
chart_count: 3,
});
-
fetchMock.get(chartsVizTypesEndpoint, {
result: [],
count: 0,
});
-
fetchMock.get(chartsDatasourcesEndpoint, {
result: [],
count: 0,
});
-
fetchMock.get(datasetEndpoint, {});
global.URL.createObjectURL = jest.fn();
@@ -122,169 +111,225 @@ const user = {
username: 'admin',
};
-// store needed for withToasts(DatabaseList)
const mockStore = configureStore([thunk]);
const store = mockStore({ user });
const useSelectorMock = jest.spyOn(reactRedux, 'useSelector');
-describe('ChartList', () => {
- isFeatureEnabled.mockImplementation(
- feature => feature === 'LISTVIEWS_DEFAULT_CARD_VIEW',
+const renderChartList = (props = {}) =>
+ render(
+
+
+
+
+ ,
+ {
+ useRedux: true,
+ store,
+ },
);
+describe('ChartList', () => {
+ beforeEach(() => {
+ isFeatureEnabled.mockImplementation(
+ feature => feature === 'LISTVIEWS_DEFAULT_CARD_VIEW',
+ );
+ fetchMock.resetHistory();
+ useSelectorMock.mockClear();
+ });
+
afterAll(() => {
isFeatureEnabled.mockRestore();
});
- beforeEach(() => {
- // setup a DOM element as a render target
- useSelectorMock.mockClear();
+ it('renders', async () => {
+ renderChartList();
+ expect(await screen.findByText('Charts')).toBeInTheDocument();
});
- const mockedProps = {};
-
- let wrapper;
-
- beforeAll(async () => {
- wrapper = mount(
-
-
-
-
- ,
- );
-
- await waitForComponentToPaint(wrapper);
+ it('renders a ListView', async () => {
+ renderChartList();
+ expect(await screen.findByTestId('chart-list-view')).toBeInTheDocument();
});
- it('renders', () => {
- expect(wrapper.find(ChartList)).toBeTruthy();
+ it('fetches info', async () => {
+ renderChartList();
+ await waitFor(() => {
+ const calls = fetchMock.calls(/chart\/_info/);
+ expect(calls).toHaveLength(1);
+ });
});
- it('renders a ListView', () => {
- expect(wrapper.find(ListView)).toBeTruthy();
- });
-
- it('fetches info', () => {
- const callsI = fetchMock.calls(/chart\/_info/);
- expect(callsI).toHaveLength(1);
- });
-
- it('fetches data', () => {
- wrapper.update();
- const callsD = fetchMock.calls(/chart\/\?q/);
- expect(callsD).toHaveLength(1);
- expect(callsD[0][0]).toMatchInlineSnapshot(
- `"http://localhost/api/v1/chart/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
- );
- });
-
- it('renders a card view', () => {
- expect(wrapper.find(ListViewCard)).toBeTruthy();
- });
-
- it('renders a table view', async () => {
- wrapper.find('[aria-label="list-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find('table')).toBeTruthy();
- });
-
- it('edits', async () => {
- expect(wrapper.find(PropertiesModal).length).toBe(0);
- wrapper.find('[data-test="edit-alt"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(PropertiesModal).length).toBeGreaterThan(0);
- });
-
- it('delete', async () => {
- wrapper.find('[data-test="trash"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(ConfirmStatusChange)).toBeTruthy();
- });
-
- it('renders the Favorite Star column in list view for logged in user', async () => {
- wrapper.find('[aria-label="list-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(TableCollection).find(FaveStar)).toBeTruthy();
- });
-
- it('renders the Favorite Star in card view for logged in user', async () => {
- wrapper.find('[aria-label="card-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(CardCollection).find(FaveStar)).toBeTruthy();
- });
-});
-
-describe('RTL', () => {
- async function renderAndWait() {
- const mounted = act(async () => {
- const mockedProps = {};
- render(
-
-
- ,
- { useRedux: true, useRouter: true },
+ it('fetches data', async () => {
+ renderChartList();
+ await waitFor(() => {
+ const calls = fetchMock.calls(/chart\/\?q/);
+ expect(calls).toHaveLength(1);
+ expect(calls[0][0]).toContain(
+ 'order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25',
);
});
-
- return mounted;
- }
-
- beforeEach(async () => {
- isFeatureEnabled.mockImplementation(() => true);
- await renderAndWait();
});
- afterEach(() => {
- cleanup();
- isFeatureEnabled.mockRestore();
+ it('switches between card and table view', async () => {
+ renderChartList();
+
+ // Wait for list to load
+ await screen.findByTestId('chart-list-view');
+
+ // Find and click list view toggle
+ const listViewToggle = await screen.findByRole('img', {
+ name: 'list-view',
+ });
+ const listViewButton = listViewToggle.closest('[role="button"]');
+ fireEvent.click(listViewButton);
+
+ // Wait for list view to be active
+ await waitFor(() => {
+ const listViewToggle = screen.getByRole('img', { name: 'list-view' });
+ expect(listViewToggle.closest('[role="button"]')).toHaveClass('active');
+ });
+
+ // Find and click card view toggle
+ const cardViewToggle = screen.getByRole('img', { name: 'card-view' });
+ const cardViewButton = cardViewToggle.closest('[role="button"]');
+ fireEvent.click(cardViewButton);
+
+ // Wait for card view to be active
+ await waitFor(() => {
+ const cardViewToggle = screen.getByRole('img', { name: 'card-view' });
+ expect(cardViewToggle.closest('[role="button"]')).toHaveClass('active');
+ });
+ });
+
+ it('shows edit modal', async () => {
+ renderChartList();
+
+ // Wait for list to load
+ await screen.findByTestId('chart-list-view');
+
+ // Switch to list view
+ const listViewToggle = await screen.findByRole('img', {
+ name: 'list-view',
+ });
+ const listViewButton = listViewToggle.closest('[role="button"]');
+ fireEvent.click(listViewButton);
+
+ // Wait for list view to be active and data to load
+ await waitFor(() => {
+ expect(screen.getByText('cool chart 0')).toBeInTheDocument();
+ });
+
+ // Click edit button
+ const editButtons = await screen.findAllByTestId('edit-alt');
+ fireEvent.click(editButtons[0]);
+
+ // Verify modal appears
+ expect(await screen.findByRole('dialog')).toBeInTheDocument();
+ });
+
+ it('shows delete modal', async () => {
+ renderChartList();
+
+ // Wait for list to load
+ await screen.findByTestId('chart-list-view');
+
+ // Switch to list view
+ const listViewToggle = await screen.findByRole('img', {
+ name: 'list-view',
+ });
+ const listViewButton = listViewToggle.closest('[role="button"]');
+ fireEvent.click(listViewButton);
+
+ // Wait for list view to be active and data to load
+ await waitFor(() => {
+ expect(screen.getByText('cool chart 0')).toBeInTheDocument();
+ });
+
+ // Click delete button
+ const deleteButtons = await screen.findAllByTestId('trash');
+ fireEvent.click(deleteButtons[0]);
+
+ // Verify modal appears
+ expect(await screen.findByRole('dialog')).toBeInTheDocument();
+ });
+
+ it('shows favorite stars for logged in user', async () => {
+ renderChartList();
+
+ // Wait for list to load
+ await screen.findByTestId('chart-list-view');
+
+ // Switch to list view
+ const listViewToggle = await screen.findByRole('img', {
+ name: 'list-view',
+ });
+ const listViewButton = listViewToggle.closest('[role="button"]');
+ fireEvent.click(listViewButton);
+
+ // Wait for list view to be active and data to load
+ await waitFor(() => {
+ expect(screen.getByText('cool chart 0')).toBeInTheDocument();
+ });
+
+ // Wait for favorite stars to appear
+ await waitFor(() => {
+ const favoriteStars = screen.getAllByRole('img', {
+ name: 'favorite-selected',
+ });
+ expect(favoriteStars.length).toBeGreaterThan(0);
+ });
});
it('renders an "Import Chart" tooltip under import button', async () => {
- const importButton = await screen.findByTestId('import-button');
- userEvent.hover(importButton);
+ renderChartList();
- await screen.findByRole('tooltip');
- const importTooltip = screen.getByRole('tooltip', {
+ const importButton = await screen.findByTestId('import-button');
+ fireEvent.mouseEnter(importButton);
+
+ const importTooltip = await screen.findByRole('tooltip', {
name: 'Import charts',
});
-
expect(importTooltip).toBeInTheDocument();
});
});
describe('ChartList - anonymous view', () => {
- const mockedProps = {};
- const mockUserLoggedOut = {};
- let wrapper;
-
- beforeAll(async () => {
+ beforeEach(() => {
fetchMock.resetHistory();
- wrapper = mount(
-
-
-
-
- ,
+ // Reset favorite status for anonymous user
+ fetchMock.get(
+ chartFavoriteStatusEndpoint,
+ {
+ result: [],
+ },
+ { overwriteRoutes: true },
);
-
- await waitForComponentToPaint(wrapper);
});
- afterAll(() => {
- cleanup();
- fetchMock.reset();
- });
+ it('does not show favorite stars for anonymous user', async () => {
+ renderChartList({ user: {} });
- it('does not render the Favorite Star column in list view for anonymous user', async () => {
- wrapper.find('[aria-label="list-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(TableCollection).find(FaveStar).length).toBe(0);
- });
+ // Wait for list to load
+ await screen.findByTestId('chart-list-view');
- it('does not render the Favorite Star in card view for anonymous user', async () => {
- wrapper.find('[aria-label="card-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(CardCollection).find(FaveStar).length).toBe(0);
+ // Switch to list view
+ const listViewToggle = await screen.findByRole('img', {
+ name: 'list-view',
+ });
+ const listViewButton = listViewToggle.closest('[role="button"]');
+ fireEvent.click(listViewButton);
+
+ // Wait for list view to be active and data to load
+ await waitFor(() => {
+ expect(screen.getByText('cool chart 0')).toBeInTheDocument();
+ });
+
+ // Verify no selected favorite stars are present
+ await waitFor(() => {
+ const favoriteStars = screen.queryAllByRole('img', {
+ name: 'favorite-selected',
+ });
+ expect(favoriteStars).toHaveLength(0);
+ });
});
});
diff --git a/superset-frontend/src/pages/CssTemplateList/CssTemplateList.test.jsx b/superset-frontend/src/pages/CssTemplateList/CssTemplateList.test.jsx
index 6acc948f1ae..e2b237595fd 100644
--- a/superset-frontend/src/pages/CssTemplateList/CssTemplateList.test.jsx
+++ b/superset-frontend/src/pages/CssTemplateList/CssTemplateList.test.jsx
@@ -18,21 +18,18 @@
*/
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
-import { Provider } from 'react-redux';
import fetchMock from 'fetch-mock';
-import { styledMount as mount } from 'spec/helpers/theming';
+import {
+ render,
+ screen,
+ fireEvent,
+ waitFor,
+} from 'spec/helpers/testing-library';
+import { MemoryRouter } from 'react-router-dom';
+import { QueryParamProvider } from 'use-query-params';
import CssTemplatesList from 'src/pages/CssTemplateList';
-import SubMenu from 'src/features/home/SubMenu';
-import ListView from 'src/components/ListView';
-import Filters from 'src/components/ListView/Filters';
-import DeleteModal from 'src/components/DeleteModal';
-import Button from 'src/components/Button';
-import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
-import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
-import { act } from 'spec/helpers/testing-library';
-// store needed for withToasts(DatabaseList)
const mockStore = configureStore([thunk]);
const store = mockStore({});
@@ -75,98 +72,120 @@ fetchMock.get(templatesRelatedEndpoint, {
},
});
-describe('CssTemplatesList', () => {
- const wrapper = mount(
-
-
- ,
+const renderCssTemplatesList = (props = {}) =>
+ render(
+
+
+
+
+ ,
+ {
+ useRedux: true,
+ store,
+ },
);
- beforeAll(async () => {
- await waitForComponentToPaint(wrapper);
+describe('CssTemplatesList', () => {
+ beforeEach(() => {
+ fetchMock.resetHistory();
});
- it('renders', () => {
- expect(wrapper.find(CssTemplatesList)).toBeTruthy();
+ it('renders', async () => {
+ renderCssTemplatesList();
+ expect(await screen.findByText(/css templates/i)).toBeInTheDocument();
});
- it('renders a SubMenu', () => {
- expect(wrapper.find(SubMenu)).toBeTruthy();
+ it('renders a SubMenu', async () => {
+ renderCssTemplatesList();
+ expect(await screen.findByRole('navigation')).toBeInTheDocument();
});
- it('renders a ListView', () => {
- expect(wrapper.find(ListView)).toBeTruthy();
+ it('renders a ListView', async () => {
+ renderCssTemplatesList();
+ expect(
+ await screen.findByTestId('css-templates-list-view'),
+ ).toBeInTheDocument();
});
- it('fetches templates', () => {
- const callsQ = fetchMock.calls(/css_template\/\?q/);
- expect(callsQ).toHaveLength(1);
- expect(callsQ[0][0]).toMatchInlineSnapshot(
- `"http://localhost/api/v1/css_template/?q=(order_column:template_name,order_direction:desc,page:0,page_size:25)"`,
- );
+ it('fetches templates', async () => {
+ renderCssTemplatesList();
+ await waitFor(() => {
+ const calls = fetchMock.calls(/css_template\/\?q/);
+ expect(calls).toHaveLength(1);
+ expect(calls[0][0]).toContain(
+ 'order_column:template_name,order_direction:desc,page:0,page_size:25',
+ );
+ });
});
- it('renders Filters', () => {
- expect(wrapper.find(Filters)).toBeTruthy();
+ it('renders Filters', async () => {
+ renderCssTemplatesList();
+ await screen.findByTestId('css-templates-list-view');
+ expect(screen.getByPlaceholderText(/type a value/i)).toBeInTheDocument();
});
it('searches', async () => {
- const filtersWrapper = wrapper.find(Filters);
- act(() => {
- filtersWrapper
- .find('[name="template_name"]')
- .first()
- .props()
- .onSubmit('fooo');
+ renderCssTemplatesList();
+
+ // Wait for list to load
+ await screen.findByTestId('css-templates-list-view');
+
+ // Find and fill search input
+ const searchInput = screen.getByPlaceholderText(/type a value/i);
+ fireEvent.change(searchInput, { target: { value: 'fooo' } });
+ fireEvent.keyDown(searchInput, { key: 'Enter', keyCode: 13 });
+
+ // Wait for search API call
+ await waitFor(() => {
+ const calls = fetchMock.calls(/css_template\/\?q/);
+ const searchCall = calls.find(call =>
+ call[0].includes('filters:!((col:template_name,opr:ct,value:fooo))'),
+ );
+ expect(searchCall).toBeTruthy();
});
- await waitForComponentToPaint(wrapper);
-
- expect(fetchMock.lastCall()[0]).toMatchInlineSnapshot(
- `"http://localhost/api/v1/css_template/?q=(filters:!((col:template_name,opr:ct,value:fooo)),order_column:template_name,order_direction:desc,page:0,page_size:25)"`,
- );
- });
-
- it('renders a DeleteModal', () => {
- expect(wrapper.find(DeleteModal)).toBeTruthy();
});
it('deletes', async () => {
- act(() => {
- wrapper.find('[data-test="delete-action"]').first().props().onClick();
+ renderCssTemplatesList();
+
+ // Wait for list to load
+ await screen.findByTestId('css-templates-list-view');
+
+ // Find and click delete button
+ const deleteButtons = await screen.findAllByTestId('delete-action');
+ fireEvent.click(deleteButtons[0]);
+
+ // Check delete modal content
+ const deleteModal = await screen.findByRole('dialog');
+ expect(deleteModal).toHaveTextContent(/permanently delete the template/i);
+
+ // Type DELETE in confirmation input
+ const deleteInput = await screen.findByTestId('delete-modal-input');
+ fireEvent.change(deleteInput, { target: { value: 'DELETE' } });
+
+ // Click confirm button
+ const confirmButton = await screen.findByTestId('modal-confirm-button');
+ fireEvent.click(confirmButton);
+
+ // Wait for delete request
+ await waitFor(() => {
+ expect(fetchMock.calls(/css_template\/0/, 'DELETE')).toHaveLength(1);
});
- await waitForComponentToPaint(wrapper);
-
- expect(
- wrapper.find(DeleteModal).first().props().description,
- ).toMatchInlineSnapshot(
- `"This action will permanently delete the template."`,
- );
-
- act(() => {
- wrapper
- .find('#delete')
- .first()
- .props()
- .onChange({ target: { value: 'DELETE' } });
- });
- await waitForComponentToPaint(wrapper);
- act(() => {
- wrapper.find('button').last().props().onClick();
- });
-
- await waitForComponentToPaint(wrapper);
-
- expect(fetchMock.calls(/css_template\/0/, 'DELETE')).toHaveLength(1);
});
- it('shows/hides bulk actions when bulk actions is clicked', async () => {
- const button = wrapper.find(Button).at(1);
- act(() => {
- button.props().onClick();
+ it('shows bulk actions when bulk select is clicked', async () => {
+ renderCssTemplatesList();
+
+ // Wait for list to load
+ await screen.findByTestId('css-templates-list-view');
+
+ // Click bulk select toggle
+ const bulkSelectButton = screen.getByRole('button', {
+ name: /bulk select/i,
});
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(IndeterminateCheckbox)).toHaveLength(
- mocktemplates.length + 1, // 1 for each row and 1 for select all
- );
- });
+ fireEvent.click(bulkSelectButton);
+
+ // Wait for bulk select mode to be enabled
+ await screen.findByText('0 Selected');
+ }, 30000);
});
diff --git a/superset-frontend/src/pages/DashboardList/DashboardList.test.jsx b/superset-frontend/src/pages/DashboardList/DashboardList.test.jsx
index 7d710f8b2d0..3ea08cbe10f 100644
--- a/superset-frontend/src/pages/DashboardList/DashboardList.test.jsx
+++ b/superset-frontend/src/pages/DashboardList/DashboardList.test.jsx
@@ -17,31 +17,17 @@
* under the License.
*/
import { MemoryRouter } from 'react-router-dom';
-import thunk from 'redux-thunk';
-import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
-import * as reactRedux from 'react-redux';
import { isFeatureEnabled } from '@superset-ui/core';
-
-import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
-import { styledMount as mount } from 'spec/helpers/theming';
import {
- act,
- cleanup,
render,
screen,
- userEvent,
+ waitFor,
+ fireEvent,
} from 'spec/helpers/testing-library';
import { QueryParamProvider } from 'use-query-params';
-import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import DashboardList from 'src/pages/DashboardList';
-import ListView from 'src/components/ListView';
-import ListViewCard from 'src/components/ListViewCard';
-import PropertiesModal from 'src/dashboard/components/PropertiesModal';
-import FaveStar from 'src/components/FaveStar';
-import TableCollection from 'src/components/TableCollection';
-import CardCollection from 'src/components/ListView/CardCollection';
const dashboardsInfoEndpoint = 'glob:*/api/v1/dashboard/_info*';
const dashboardOwnersEndpoint = 'glob:*/api/v1/dashboard/related/owners*';
@@ -87,219 +73,163 @@ fetchMock.get(dashboardCreatedByEndpoint, {
fetchMock.get(dashboardFavoriteStatusEndpoint, {
result: [],
});
-
fetchMock.get(dashboardsEndpoint, {
result: mockDashboards,
dashboard_count: 3,
});
-
fetchMock.get(dashboardEndpoint, {
result: mockDashboards[0],
});
global.URL.createObjectURL = jest.fn();
fetchMock.get('/thumbnail', { body: new Blob(), sendAsJson: false });
-const user = {
- createdOn: '2021-04-27T18:12:38.952304',
- email: 'admin',
- firstName: 'admin',
- isActive: true,
- lastName: 'admin',
- permissions: {},
- roles: {
- Admin: [
- ['can_sqllab', 'Superset'],
- ['can_write', 'Dashboard'],
- ['can_write', 'Chart'],
- ],
- },
- userId: 1,
- username: 'admin',
-};
-
-// store needed for withToasts(DatabaseList)
-const mockStore = configureStore([thunk]);
-const store = mockStore({ user });
-const useSelectorMock = jest.spyOn(reactRedux, 'useSelector');
describe('DashboardList', () => {
- isFeatureEnabled.mockImplementation(
- feature => feature === 'LISTVIEWS_DEFAULT_CARD_VIEW',
- );
+ const renderDashboardList = (props = {}, userProp = mockUser) =>
+ render(
+
+
+
+
+ ,
+ { useRedux: true },
+ );
- afterAll(() => {
+ beforeEach(() => {
+ isFeatureEnabled.mockImplementation(
+ feature => feature === 'LISTVIEWS_DEFAULT_CARD_VIEW',
+ );
+ fetchMock.resetHistory();
+ });
+
+ afterEach(() => {
isFeatureEnabled.mockRestore();
});
- beforeEach(() => {
- // setup a DOM element as a render target
- useSelectorMock.mockClear();
+ it('renders', async () => {
+ renderDashboardList();
+ expect(await screen.findByText('Dashboards')).toBeInTheDocument();
});
- const mockedProps = {};
- let wrapper;
-
- beforeAll(async () => {
- fetchMock.resetHistory();
- wrapper = mount(
-
-
-
-
- ,
- );
-
- await waitForComponentToPaint(wrapper);
+ it('renders a ListView', async () => {
+ renderDashboardList();
+ expect(
+ await screen.findByTestId('dashboard-list-view'),
+ ).toBeInTheDocument();
});
- it('renders', () => {
- expect(wrapper.find(DashboardList)).toBeTruthy();
+ it('fetches info', async () => {
+ renderDashboardList();
+ await waitFor(() => {
+ const calls = fetchMock.calls(/dashboard\/_info/);
+ expect(calls).toHaveLength(1);
+ });
});
- it('renders a ListView', () => {
- expect(wrapper.find(ListView)).toBeTruthy();
- });
+ it('fetches data', async () => {
+ renderDashboardList();
+ await waitFor(() => {
+ const calls = fetchMock.calls(/dashboard\/\?q/);
+ expect(calls).toHaveLength(1);
+ });
- it('fetches info', () => {
- const callsI = fetchMock.calls(/dashboard\/_info/);
- expect(callsI).toHaveLength(1);
- });
-
- it('fetches data', () => {
- wrapper.update();
- const callsD = fetchMock.calls(/dashboard\/\?q/);
- expect(callsD).toHaveLength(1);
- expect(callsD[0][0]).toMatchInlineSnapshot(
+ const calls = fetchMock.calls(/dashboard\/\?q/);
+ expect(calls[0][0]).toMatchInlineSnapshot(
`"http://localhost/api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25,select_columns:!(id,dashboard_title,published,url,slug,changed_by,changed_by.id,changed_by.first_name,changed_by.last_name,changed_on_delta_humanized,owners,owners.id,owners.first_name,owners.last_name,tags.id,tags.name,tags.type,status,certified_by,certification_details,changed_on))"`,
);
});
- it('renders a card view', () => {
- expect(wrapper.find(ListViewCard)).toBeTruthy();
+ it('switches between card and table view', async () => {
+ renderDashboardList();
+
+ // Wait for the list to load
+ await screen.findByTestId('dashboard-list-view');
+
+ // Initially in card view
+ const cardViewIcon = screen.getByRole('img', { name: 'card-view' });
+ expect(cardViewIcon).toBeInTheDocument();
+
+ // Switch to table view
+ const listViewIcon = screen.getByRole('img', { name: 'list-view' });
+ const listViewButton = listViewIcon.closest('[role="button"]');
+ fireEvent.click(listViewButton);
+
+ // Switch back to card view
+ const cardViewButton = cardViewIcon.closest('[role="button"]');
+ fireEvent.click(cardViewButton);
});
- it('renders a table view', async () => {
- wrapper.find('[aria-label="list-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find('table')).toBeTruthy();
+ it('shows edit modal', async () => {
+ renderDashboardList();
+
+ // Wait for data to load
+ await screen.findByText('title 0');
+
+ // Find and click the first more options button
+ const moreIcons = await screen.findAllByRole('img', { name: 'more-vert' });
+ fireEvent.click(moreIcons[0]);
+
+ // Click edit from the dropdown
+ const editButton = await screen.findByTestId(
+ 'dashboard-card-option-edit-button',
+ );
+ fireEvent.click(editButton);
+
+ // Check for modal
+ expect(await screen.findByRole('dialog')).toBeInTheDocument();
});
- it('edits', async () => {
- expect(wrapper.find(PropertiesModal).length).toBe(0);
- wrapper.find('[data-test="edit-alt"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(PropertiesModal).length).toBeGreaterThan(0);
+ it('shows delete confirmation', async () => {
+ renderDashboardList();
+
+ // Wait for data to load
+ await screen.findByText('title 0');
+
+ // Find and click the first more options button
+ const moreIcons = await screen.findAllByRole('img', { name: 'more-vert' });
+ fireEvent.click(moreIcons[0]);
+
+ // Click delete from the dropdown
+ const deleteButton = await screen.findByTestId(
+ 'dashboard-card-option-delete-button',
+ );
+ fireEvent.click(deleteButton);
+
+ // Check for confirmation dialog
+ expect(
+ await screen.findByText(/Are you sure you want to delete/i),
+ ).toBeInTheDocument();
});
- it('card view edits', async () => {
- wrapper.find('[data-test="edit-alt"]').last().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(PropertiesModal)).toBeTruthy();
- });
+ it('renders an "Import Dashboard" tooltip', async () => {
+ renderDashboardList();
- it('delete', async () => {
- wrapper
- .find('[data-test="dashboard-list-trash-icon"]')
- .first()
- .simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(ConfirmStatusChange)).toBeTruthy();
- });
-
- it('card view delete', async () => {
- wrapper
- .find('[data-test="dashboard-list-trash-icon"]')
- .last()
- .simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(ConfirmStatusChange)).toBeTruthy();
- });
-
- it('renders the Favorite Star column in list view for logged in user', async () => {
- wrapper.find('[aria-label="list-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(TableCollection).find(FaveStar)).toBeTruthy();
- });
-
- it('renders the Favorite Star in card view for logged in user', async () => {
- wrapper.find('[aria-label="card-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(CardCollection).find(FaveStar)).toBeTruthy();
- });
-});
-
-describe('RTL', () => {
- async function renderAndWait() {
- const mounted = act(async () => {
- const mockedProps = {};
- render(
-
-
-
-
- ,
- { useRedux: true },
- );
- });
-
- return mounted;
- }
-
- beforeEach(async () => {
- isFeatureEnabled.mockImplementation(() => true);
- await renderAndWait();
- });
-
- afterEach(() => {
- cleanup();
- isFeatureEnabled.mockRestore();
- });
-
- it('renders an "Import Dashboard" tooltip under import button', async () => {
const importButton = await screen.findByTestId('import-button');
- userEvent.hover(importButton);
+ fireEvent.mouseOver(importButton);
- await screen.findByRole('tooltip');
- const importTooltip = screen.getByRole('tooltip', {
- name: 'Import dashboards',
- });
-
- expect(importTooltip).toBeInTheDocument();
+ expect(
+ await screen.findByRole('tooltip', {
+ name: 'Import dashboards',
+ }),
+ ).toBeInTheDocument();
});
});
describe('DashboardList - anonymous view', () => {
- const mockedProps = {};
- const mockUserLoggedOut = {};
- let wrapper;
-
- beforeAll(async () => {
- fetchMock.resetHistory();
- wrapper = mount(
+ it('does not render favorite stars for anonymous user', async () => {
+ render(
-
-
-
+
+
+
,
+ { useRedux: true },
);
- await waitForComponentToPaint(wrapper);
- });
-
- afterAll(() => {
- cleanup();
- fetchMock.reset();
- });
-
- it('does not render the Favorite Star column in list view for anonymous user', async () => {
- wrapper.find('[aria-label="list-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(TableCollection).find(FaveStar).length).toBe(0);
- });
-
- it('does not render the Favorite Star in card view for anonymous user', async () => {
- wrapper.find('[aria-label="card-view"]').first().simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(CardCollection).find(FaveStar).length).toBe(0);
+ await waitFor(() => {
+ expect(
+ screen.queryByRole('img', { name: /favorite/i }),
+ ).not.toBeInTheDocument();
+ });
});
});
diff --git a/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.jsx b/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.jsx
index 127449bc817..91ad7ecb47e 100644
--- a/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.jsx
+++ b/superset-frontend/src/pages/SavedQueryList/SavedQueryList.test.jsx
@@ -17,418 +17,215 @@
* under the License.
*/
import thunk from 'redux-thunk';
-import * as reactRedux from 'react-redux';
-import { BrowserRouter } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
-import { styledMount as mount } from 'spec/helpers/theming';
import {
- act,
- cleanup,
render,
screen,
- userEvent,
+ fireEvent,
waitFor,
} from 'spec/helpers/testing-library';
+import { MemoryRouter } from 'react-router-dom';
import { QueryParamProvider } from 'use-query-params';
-import { isFeatureEnabled } from '@superset-ui/core';
-import SavedQueryList from 'src/pages/SavedQueryList';
-import SubMenu from 'src/features/home/SubMenu';
-import ListView from 'src/components/ListView';
-import Filters from 'src/components/ListView/Filters';
-import ActionsBar from 'src/components/ListView/ActionsBar';
-import DeleteModal from 'src/components/DeleteModal';
-import Button from 'src/components/Button';
-import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox';
-import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
+import SavedQueryList from '.';
+
+// Increase default timeout
+jest.setTimeout(30000);
+
+const mockQueries = [...new Array(3)].map((_, i) => ({
+ created_by: { id: i, first_name: 'user', last_name: `${i}` },
+ created_on: `${i}-2020`,
+ database: { database_name: `db ${i}`, id: i },
+ changed_on_delta_humanized: '1 day ago',
+ db_id: i,
+ description: `SQL for ${i}`,
+ id: i,
+ label: `query ${i}`,
+ schema: 'public',
+ sql: `SELECT ${i} FROM table`,
+ sql_tables: [{ catalog: null, schema: null, table: `${i}` }],
+ tags: [],
+}));
+
+const mockUser = {
+ userId: 1,
+ firstName: 'admin',
+ lastName: 'admin',
+};
const queriesInfoEndpoint = 'glob:*/api/v1/saved_query/_info*';
const queriesEndpoint = 'glob:*/api/v1/saved_query/?*';
const queryEndpoint = 'glob:*/api/v1/saved_query/*';
-const queriesRelatedEndpoint = 'glob:*/api/v1/saved_query/related/database?*';
-const queriesDistinctEndpoint = 'glob:*/api/v1/saved_query/distinct/schema?*';
-
-const mockqueries = [...new Array(3)].map((_, i) => ({
- created_by: {
- id: i,
- first_name: `user`,
- last_name: `${i}`,
- },
- created_on: `${i}-2020`,
- database: {
- database_name: `db ${i}`,
- id: i,
- },
- changed_on_delta_humanized: '1 day ago',
- db_id: i,
- description: `SQL for ${i}`,
- id: i,
- label: `query ${i}`,
- schema: 'public',
- sql: `SELECT ${i} FROM table`,
- sql_tables: [
- {
- catalog: null,
- schema: null,
- table: `${i}`,
- },
- ],
-}));
-
-const user = {
- createdOn: '2021-04-27T18:12:38.952304',
- email: 'admin',
- firstName: 'admin',
- isActive: true,
- lastName: 'admin',
- permissions: {},
- roles: {
- Admin: [
- ['can_sqllab', 'Superset'],
- ['can_write', 'Dashboard'],
- ['can_write', 'Chart'],
- ],
- },
- userId: 1,
- username: 'admin',
-};
-
-// store needed for withToasts(DatabaseList)
-const mockStore = configureStore([thunk]);
-const store = mockStore({ user });
-
-const useSelectorMock = jest.spyOn(reactRedux, 'useSelector');
-
-// ---------- For import testing ----------
-// Create an one more mocked query than the original mocked query array
-const mockOneMoreQuery = [...new Array(mockqueries.length + 1)].map((_, i) => ({
- created_by: {
- id: i,
- first_name: `user`,
- last_name: `${i}`,
- },
- created_on: `${i}-2020`,
- database: {
- database_name: `db ${i}`,
- id: i,
- },
- changed_on_delta_humanized: '1 day ago',
- db_id: i,
- description: `SQL for ${i}`,
- id: i,
- label: `query ${i}`,
- schema: 'public',
- sql: `SELECT ${i} FROM table`,
- sql_tables: [
- {
- catalog: null,
- schema: null,
- table: `${i}`,
- },
- ],
-}));
-// Grab the last mocked query, to mock import
-const mockNewImportQuery = mockOneMoreQuery.pop();
-// Create a new file out of mocked import query to mock upload
-const mockImportFile = new File(
- [mockNewImportQuery],
- 'saved_query_import_mock.json',
-);
+const permalinkEndpoint = 'glob:*/api/v1/sqllab/permalink';
fetchMock.get(queriesInfoEndpoint, {
permissions: ['can_write', 'can_read', 'can_export'],
});
+
fetchMock.get(queriesEndpoint, {
- result: mockqueries,
- count: 3,
+ ids: [2, 0, 1],
+ result: mockQueries,
+ count: mockQueries.length,
+});
+
+fetchMock.post(permalinkEndpoint, {
+ url: 'http://localhost/permalink',
});
fetchMock.delete(queryEndpoint, {});
-fetchMock.delete(queriesEndpoint, {});
-fetchMock.get(queriesRelatedEndpoint, {
- count: 0,
- result: [],
-});
-
-fetchMock.get(queriesDistinctEndpoint, {
- count: 0,
- result: [],
-});
-
-// Mock utils module
-jest.mock('src/views/CRUD/utils');
-
-jest.mock('@superset-ui/core', () => ({
- ...jest.requireActual('@superset-ui/core'),
- isFeatureEnabled: jest.fn(),
-}));
-
-describe('SavedQueryList', () => {
- const wrapper = mount(
-
-
- ,
+const renderList = (props = {}, storeOverrides = {}) =>
+ render(
+
+
+
+
+ ,
+ {
+ useRedux: true,
+ store: configureStore([thunk])({
+ user: {
+ ...mockUser,
+ roles: { Admin: [['can_write', 'SavedQuery']] },
+ },
+ ...storeOverrides,
+ }),
+ },
);
+describe('SavedQueryList', () => {
beforeEach(() => {
- // setup a DOM element as a render target
- useSelectorMock.mockClear();
+ fetchMock.resetHistory();
});
- beforeAll(async () => {
- await waitForComponentToPaint(wrapper);
+ it('renders', async () => {
+ renderList();
+ expect(await screen.findByText('Saved queries')).toBeInTheDocument();
});
- it('renders', () => {
- expect(wrapper.find(SavedQueryList)).toBeTruthy();
- });
-
- it('renders a SubMenu', () => {
- expect(wrapper.find(SubMenu)).toBeTruthy();
- });
-
- it('renders a SubMenu with Saved queries and Query History links', () => {
- expect(wrapper.find(SubMenu).props().tabs).toEqual(
- expect.arrayContaining([
- expect.objectContaining({ label: 'Saved queries' }),
- expect.objectContaining({ label: 'Query history' }),
- ]),
- );
- });
-
- it('renders a SubMenu without Databases and Datasets links', () => {
- expect(wrapper.find(SubMenu).props().tabs).not.toEqual(
- expect.arrayContaining([
- expect.objectContaining({ label: 'Databases' }),
- expect.objectContaining({ label: 'Datasets' }),
- ]),
- );
- });
-
- it('renders a ListView', () => {
- expect(wrapper.find(ListView)).toBeTruthy();
- });
-
- it('fetches saved queries', () => {
- const callsQ = fetchMock.calls(/saved_query\/\?q/);
- expect(callsQ).toHaveLength(1);
- expect(callsQ[0][0]).toMatchInlineSnapshot(
- `"http://localhost/api/v1/saved_query/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
- );
- });
-
- it('renders ActionsBar in table', () => {
- expect(wrapper.find(ActionsBar)).toBeTruthy();
- expect(wrapper.find(ActionsBar)).toHaveLength(3);
- });
-
- it('deletes', async () => {
- act(() => {
- wrapper.find('span[data-test="delete-action"]').first().props().onClick();
- });
- await waitForComponentToPaint(wrapper);
-
+ it('renders a ListView', async () => {
+ renderList();
expect(
- wrapper.find(DeleteModal).first().props().description,
- ).toMatchInlineSnapshot(
- `"This action will permanently delete the saved query."`,
- );
-
- act(() => {
- wrapper
- .find('#delete')
- .first()
- .props()
- .onChange({ target: { value: 'DELETE' } });
- });
- await waitForComponentToPaint(wrapper);
- act(() => {
- wrapper.find('button').last().props().onClick();
- });
-
- await waitForComponentToPaint(wrapper);
-
- expect(fetchMock.calls(/saved_query\/0/, 'DELETE')).toHaveLength(1);
+ await screen.findByTestId('saved_query-list-view'),
+ ).toBeInTheDocument();
});
- it('copies a query link when the API succeeds', async () => {
- Object.assign(navigator, {
- clipboard: {
- writeText: jest.fn(),
- },
- });
+ it('renders query information', async () => {
+ renderList();
- fetchMock.get('glob:*/api/v1/saved_query', {
- result: [
- {
- id: 1,
- label: 'Test Query',
- db_id: 1,
- schema: 'public',
- sql: 'SELECT * FROM table',
- },
- ],
- count: 1,
- });
- fetchMock.post('glob:*/api/v1/sqllab/permalink', {
- body: { url: 'http://example.com/permalink' },
- status: 200,
- });
+ // Wait for list to load
+ await screen.findByTestId('saved_query-list-view');
- render(
-
-
-
-
- ,
- { store },
- );
-
- const copyActionButton = await waitFor(
- () => screen.getAllByTestId('copy-action')[0],
- );
- userEvent.hover(copyActionButton);
-
- userEvent.click(copyActionButton);
+ // Wait for data to load
await waitFor(() => {
- expect(fetchMock.calls('glob:*/api/v1/sqllab/permalink').length).toBe(1);
+ mockQueries.forEach(query => {
+ expect(screen.getByText(query.label)).toBeInTheDocument();
+ expect(
+ screen.getByText(query.database.database_name),
+ ).toBeInTheDocument();
+ expect(screen.getAllByText(query.schema)[0]).toBeInTheDocument();
+ });
});
-
- expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
- 'http://example.com/permalink',
- );
});
- it('shows/hides bulk actions when bulk actions is clicked', async () => {
- const button = wrapper.find(Button).at(0);
- act(() => {
- button.props().onClick();
+ it('handles query deletion', async () => {
+ renderList();
+
+ // Wait for list to load
+ await screen.findByTestId('saved_query-list-view');
+
+ // Wait for data and find delete button
+ const deleteButtons = await screen.findAllByTestId('delete-action');
+ fireEvent.click(deleteButtons[0]);
+
+ // Confirm deletion
+ const deleteInput = screen.getByTestId('delete-modal-input');
+ fireEvent.change(deleteInput, { target: { value: 'DELETE' } });
+
+ const confirmButton = screen.getByRole('button', { name: /delete/i });
+ fireEvent.click(confirmButton);
+
+ // Verify API call
+ await waitFor(() => {
+ expect(fetchMock.calls(/saved_query\/0/, 'DELETE')).toHaveLength(1);
});
- await waitForComponentToPaint(wrapper);
- expect(wrapper.find(IndeterminateCheckbox)).toHaveLength(
- mockqueries.length + 1, // 1 for each row and 1 for select all
- );
});
- it('searches', async () => {
- const filtersWrapper = wrapper.find(Filters);
- act(() => {
- filtersWrapper.find('[name="label"]').first().props().onSubmit('fooo');
- });
- await waitForComponentToPaint(wrapper);
+ it('handles search filtering', async () => {
+ renderList();
- expect(fetchMock.lastCall()[0]).toMatchInlineSnapshot(
- `"http://localhost/api/v1/saved_query/?q=(filters:!((col:label,opr:all_text,value:fooo)),order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:25)"`,
+ // Wait for list to load
+ await screen.findByTestId('saved_query-list-view');
+
+ // Find and use search input
+ const searchInput = screen.getByTestId('filters-search');
+ fireEvent.change(searchInput, { target: { value: 'test query' } });
+ fireEvent.keyDown(searchInput, { key: 'Enter' });
+
+ // Verify API call
+ await waitFor(() => {
+ const calls = fetchMock.calls(/saved_query\/\?q/);
+ expect(calls.length).toBeGreaterThan(0);
+ const lastCall = calls[calls.length - 1][0];
+ expect(lastCall).toContain('order_column');
+ expect(lastCall).toContain('page');
+ });
+ });
+
+ it('fetches data', async () => {
+ renderList();
+ await waitFor(() => {
+ const calls = fetchMock.calls(/saved_query\/\?q/);
+ expect(calls).toHaveLength(1);
+ expect(calls[0][0]).toContain('order_column');
+ expect(calls[0][0]).toContain('page');
+ });
+ });
+
+ it('handles sorting', async () => {
+ renderList();
+
+ // Wait for list to load
+ await screen.findByTestId('saved_query-list-view');
+
+ // Find and click sort header
+ const sortHeaders = screen.getAllByTestId('sort-header');
+ fireEvent.click(sortHeaders[0]);
+
+ // Verify API call includes sorting
+ await waitFor(() => {
+ const calls = fetchMock.calls(/saved_query\/\?q/);
+ const lastCall = calls[calls.length - 1][0];
+ expect(lastCall).toContain('order_column:label');
+ });
+ });
+
+ it('shows/hides elements based on permissions', async () => {
+ // Mock info response without write permission
+ fetchMock.get(
+ queriesInfoEndpoint,
+ { permissions: ['can_read'] },
+ { overwriteRoutes: true },
);
- });
-});
-
-describe('RTL', () => {
- function renderAndWait() {
- return render(, {
- useRedux: true,
- useRouter: true,
- useQueryParams: true,
- });
- }
-
- beforeEach(async () => {
- isFeatureEnabled.mockImplementation(() => true);
- renderAndWait();
- });
-
- afterEach(() => {
- cleanup();
- isFeatureEnabled.mockRestore();
- });
- it('renders an export button in the bulk actions', async () => {
- const bulkSelectButton = screen.getByRole('button', {
- name: /bulk select/i,
- });
- userEvent.click(bulkSelectButton);
- const checkbox = await screen.findByTestId('header-toggle-all');
- userEvent.click(checkbox);
-
- const exportButton = screen.getByRole('button', {
- name: /export/i,
- });
- expect(exportButton).toBeVisible();
- });
-
- it('renders an export button in the actions bar', async () => {
- // Grab Export action button and mock mouse hovering over it
- const exportActionButton = screen.getAllByTestId('export-action')[0];
- userEvent.hover(exportActionButton);
-
- // Wait for the tooltip to pop up
- await screen.findByRole('tooltip');
-
- // Grab and assert that "Export Query" tooltip is in the document
- const exportTooltip = screen.getByRole('tooltip', {
- name: /export query/i,
- });
- expect(exportTooltip).toBeInTheDocument();
- });
-
- it('renders a copy button in the actions bar', async () => {
- // Grab copy action button and mock mouse hovering over it
- const copyActionButton = screen.getAllByTestId('copy-action')[0];
- userEvent.hover(copyActionButton);
-
- // Wait for the tooltip to pop up
- await screen.findByRole('tooltip');
-
- // Grab and assert that "Copy query URl" tooltip is in the document
- const copyTooltip = screen.getByRole('tooltip', {
- name: /Copy query URL/i,
- });
- expect(copyTooltip).toBeInTheDocument();
- });
-
- it('renders an import button in the submenu', async () => {
- // Grab and assert that import saved query button is visible
- const importButton = await screen.findByTestId('import-button');
- expect(importButton).toBeVisible();
- });
-
- it('renders an "Import Saved Query" tooltip under import button', async () => {
- const importButton = await screen.findByTestId('import-icon');
- userEvent.hover(importButton);
-
- const importTooltip = await screen.findByRole('tooltip', {
- name: 'Import queries',
- });
- expect(importTooltip).toBeInTheDocument();
- });
-
- it('renders an import modal when import button is clicked', async () => {
- // Grab and click import saved query button to reveal modal
- expect(
- screen.queryByRole('heading', { name: 'Import queries' }),
- ).not.toBeInTheDocument();
- const importButton = await screen.findByTestId('import-button');
- userEvent.click(importButton);
-
- // Grab and assert that saved query import modal's heading is visible
- const importSavedQueryModalHeading = screen.getByRole('heading', {
- name: 'Import queries',
- });
- expect(importSavedQueryModalHeading).toBeInTheDocument();
- });
-
- it('imports a saved query', async () => {
- // Grab and click import saved query button to reveal modal
- const importButton = await screen.findByTestId('import-button');
- userEvent.click(importButton);
-
- // Grab "Choose File" input from import modal
- const chooseFileInput = screen.getByTestId('model-file-input');
- // Upload mocked import file
- userEvent.upload(chooseFileInput, mockImportFile);
-
- expect(chooseFileInput.files[0]).toStrictEqual(mockImportFile);
- expect(chooseFileInput.files.item(0)).toStrictEqual(mockImportFile);
- expect(chooseFileInput.files).toHaveLength(1);
+
+ // Mock list response
+ fetchMock.get(
+ queriesEndpoint,
+ { result: mockQueries, count: mockQueries.length },
+ { overwriteRoutes: true },
+ );
+
+ renderList();
+
+ // Wait for list to load
+ await screen.findByTestId('saved_query-list-view');
+
+ // Wait for data to load
+ await waitFor(() => {
+ expect(screen.getByText(mockQueries[0].label)).toBeInTheDocument();
+ });
+
+ // Verify delete buttons are not shown
+ expect(screen.queryByTestId('delete-action')).not.toBeInTheDocument();
});
});