diff --git a/superset-frontend/src/components/Button/Button.test.tsx b/superset-frontend/src/components/Button/Button.test.tsx
index f65267f9f1d43..bf037f104e757 100644
--- a/superset-frontend/src/components/Button/Button.test.tsx
+++ b/superset-frontend/src/components/Button/Button.test.tsx
@@ -16,10 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import { isValidElement } from 'react';
-import { ReactWrapper } from 'enzyme';
-import { styledMount as mount } from 'spec/helpers/theming';
+import { fireEvent, render } from 'spec/helpers/testing-library';
import Button from '.';
import {
ButtonGallery,
@@ -27,36 +24,27 @@ import {
STYLES as buttonStyles,
} from './Button.stories';
-describe('Button', () => {
- let wrapper: ReactWrapper;
-
- // test the basic component
- it('renders the base component', () => {
- expect(isValidElement()).toBe(true);
- });
-
- it('works with an onClick handler', () => {
- const mockAction = jest.fn();
- wrapper = mount();
- wrapper.find('Button').first().simulate('click');
- expect(mockAction).toHaveBeenCalled();
- });
+test('works with an onClick handler', () => {
+ const mockAction = jest.fn();
+ const { getByRole } = render();
+ fireEvent.click(getByRole('button'));
+ expect(mockAction).toHaveBeenCalled();
+});
- it('does not handle onClicks when disabled', () => {
- const mockAction = jest.fn();
- wrapper = mount();
- wrapper.find('Button').first().simulate('click');
- expect(mockAction).toHaveBeenCalledTimes(0);
- });
+test('does not handle onClicks when disabled', () => {
+ const mockAction = jest.fn();
+ const { getByRole } = render();
+ fireEvent.click(getByRole('button'));
+ expect(mockAction).toHaveBeenCalledTimes(0);
+});
- // test stories from the storybook!
- it('All the sorybook gallery variants mount', () => {
- wrapper = mount();
+// test stories from the storybook!
+test('All the sorybook gallery variants mount', () => {
+ const { getAllByRole } = render();
- const permutationCount =
- Object.values(buttonStyles.options).filter(o => o).length *
- Object.values(buttonSizes.options).length;
+ const permutationCount =
+ Object.values(buttonStyles.options).filter(o => o).length *
+ Object.values(buttonSizes.options).length;
- expect(wrapper.find(Button).length).toEqual(permutationCount);
- });
+ expect(getAllByRole('button')).toHaveLength(permutationCount);
});
diff --git a/superset-frontend/src/components/Chart/ChartRenderer.test.jsx b/superset-frontend/src/components/Chart/ChartRenderer.test.jsx
index 5f8a32bd4f058..53b35b6cf4157 100644
--- a/superset-frontend/src/components/Chart/ChartRenderer.test.jsx
+++ b/superset-frontend/src/components/Chart/ChartRenderer.test.jsx
@@ -16,11 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { shallow } from 'enzyme';
-import { SuperChart } from '@superset-ui/core';
+import { render } from 'spec/helpers/testing-library';
import ChartRenderer from 'src/components/Chart/ChartRenderer';
+jest.mock('@superset-ui/core', () => ({
+ ...jest.requireActual('@superset-ui/core'),
+ SuperChart: ({ formData }) => (
+
{JSON.stringify(formData)}
+ ),
+}));
+
+jest.mock(
+ 'src/components/Chart/ChartContextMenu/ChartContextMenu',
+ () => () => ,
+);
+
const requiredProps = {
chartId: 1,
datasource: {},
@@ -31,18 +42,18 @@ const requiredProps = {
vizType: 'table',
};
-describe('ChartRenderer', () => {
- it('should render SuperChart', () => {
- const wrapper = shallow(
- ,
- );
- expect(wrapper.find(SuperChart)).toExist();
- });
+test('should render SuperChart', () => {
+ const { getByTestId } = render(
+ ,
+ );
+ expect(getByTestId('mock-super-chart')).toBeInTheDocument();
+});
- it('should use latestQueryFormData instead of formData when chartIsStale is true', () => {
- const wrapper = shallow();
- expect(wrapper.find(SuperChart).prop('formData')).toEqual({
- testControl: 'bar',
- });
- });
+test('should use latestQueryFormData instead of formData when chartIsStale is true', () => {
+ const { getByTestId } = render(
+ ,
+ );
+ expect(getByTestId('mock-super-chart')).toHaveTextContent(
+ JSON.stringify({ testControl: 'bar' }),
+ );
});
diff --git a/superset-frontend/src/components/Checkbox/Checkbox.test.tsx b/superset-frontend/src/components/Checkbox/Checkbox.test.tsx
index 14edb4de989b0..f16eeae88ad98 100644
--- a/superset-frontend/src/components/Checkbox/Checkbox.test.tsx
+++ b/superset-frontend/src/components/Checkbox/Checkbox.test.tsx
@@ -16,62 +16,46 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { fireEvent, render } from 'spec/helpers/testing-library';
-import { isValidElement } from 'react';
-import { ReactWrapper } from 'enzyme';
-import {
- styledMount as mount,
- styledShallow as shallow,
-} from 'spec/helpers/theming';
+import Checkbox from 'src/components/Checkbox';
-import Checkbox, {
- CheckboxChecked,
- CheckboxUnchecked,
-} from 'src/components/Checkbox';
+jest.mock('src/components/Checkbox/CheckboxIcons', () => ({
+ CheckboxChecked: () => ,
+ CheckboxUnchecked: () => ,
+}));
-describe('Checkbox', () => {
- let wrapper: ReactWrapper;
-
- it('renders the base component', () => {
- expect(
- isValidElement(
- true} />,
- ),
- ).toBe(true);
- });
-
- describe('when unchecked', () => {
- it('renders the unchecked component', () => {
- const shallowWrapper = shallow(
- true} />,
- );
- expect(shallowWrapper.dive().find(CheckboxUnchecked)).toExist();
- });
- });
-
- describe('when checked', () => {
- it('renders the checked component', () => {
- const shallowWrapper = shallow(
- true} />,
- );
- expect(shallowWrapper.dive().find(CheckboxChecked)).toExist();
- });
- });
-
- it('works with an onChange handler', () => {
- const mockAction = jest.fn();
- wrapper = mount(
- ,
+describe('when unchecked', () => {
+ test('renders the unchecked component', () => {
+ const { getByTestId } = render(
+ true} />,
);
- wrapper.find('Checkbox').first().simulate('click');
- expect(mockAction).toHaveBeenCalled();
+ expect(getByTestId('mock-CheckboxUnchecked')).toBeInTheDocument();
});
+});
- it('renders custom Checkbox styles without melting', () => {
- wrapper = mount(
- true} checked={false} style={{ opacity: 1 }} />,
+describe('when checked', () => {
+ test('renders the checked component', () => {
+ const { getByTestId } = render(
+ true} />,
);
- expect(wrapper.find('Checkbox')).toExist();
- expect(wrapper.find('Checkbox')).toHaveStyle({ opacity: 1 });
+ expect(getByTestId('mock-CheckboxChecked')).toBeInTheDocument();
});
});
+
+test('works with an onChange handler', () => {
+ const mockAction = jest.fn();
+ const { getByRole } = render(
+ ,
+ );
+ fireEvent.click(getByRole('checkbox'));
+ expect(mockAction).toHaveBeenCalled();
+});
+
+test('renders custom Checkbox styles without melting', () => {
+ const { getByRole } = render(
+ true} checked={false} style={{ opacity: 1 }} />,
+ );
+ expect(getByRole('checkbox')).toBeInTheDocument();
+ expect(getByRole('checkbox')).toHaveStyle({ opacity: 1 });
+});
diff --git a/superset-frontend/src/components/ConfirmStatusChange/ConfirmStatusChange.test.jsx b/superset-frontend/src/components/ConfirmStatusChange/ConfirmStatusChange.test.jsx
index fea20af988ad1..416d1286fa18a 100644
--- a/superset-frontend/src/components/ConfirmStatusChange/ConfirmStatusChange.test.jsx
+++ b/superset-frontend/src/components/ConfirmStatusChange/ConfirmStatusChange.test.jsx
@@ -16,48 +16,51 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { mount } from 'enzyme';
+import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
import Button from 'src/components/Button';
-import { act } from 'react-dom/test-utils';
-import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import ConfirmStatusChange from 'src/components/ConfirmStatusChange';
-import Modal from 'src/components/Modal';
-
-describe('ConfirmStatusChange', () => {
- const mockedProps = {
- title: 'please confirm',
- description: 'are you sure?',
- onConfirm: jest.fn(),
- };
- const wrapper = mount(
+
+const mockedProps = {
+ title: 'please confirm',
+ description: 'are you sure?',
+ onConfirm: jest.fn(),
+};
+
+test('opens a confirm modal', () => {
+ const { getByTestId } = render(
{confirm => (
<>
-
+
>
)}
,
- {
- wrappingComponent: ThemeProvider,
- wrappingComponentProps: { theme: supersetTheme },
- },
);
- it('opens a confirm modal', () => {
- act(() => {
- wrapper.find('#btn1').first().props().onClick('foo');
- });
+ fireEvent.click(getByTestId('btn1'));
+
+ expect(getByTestId(`${mockedProps.title}-modal`)).toBeInTheDocument();
+});
+
+test('calls the function on confirm', async () => {
+ const { getByTestId, getByRole } = render(
+
+ {confirm => (
+ <>
+ ,
+ );
- wrapper.update();
+ fireEvent.click(getByTestId('btn1'));
- expect(wrapper.find(Modal)).toExist();
- });
+ const confirmInput = getByTestId('delete-modal-input');
+ fireEvent.change(confirmInput, { target: { value: 'DELETE' } });
- it('calls the function on confirm', () => {
- act(() => {
- wrapper.find(Button).last().props().onClick();
- });
+ const confirmButton = getByRole('button', { name: 'delete' });
+ fireEvent.click(confirmButton);
- expect(mockedProps.onConfirm).toHaveBeenCalledWith('foo');
- });
+ await waitFor(() => expect(mockedProps.onConfirm).toHaveBeenCalledTimes(1));
+ expect(mockedProps.onConfirm).toHaveBeenCalledWith('foo');
});
diff --git a/superset-frontend/src/components/Datasource/ChangeDatasourceModal.test.jsx b/superset-frontend/src/components/Datasource/ChangeDatasourceModal.test.jsx
index 2d30debf46709..64a9716ef4274 100644
--- a/superset-frontend/src/components/Datasource/ChangeDatasourceModal.test.jsx
+++ b/superset-frontend/src/components/Datasource/ChangeDatasourceModal.test.jsx
@@ -16,16 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { mount } from 'enzyme';
+import { waitFor, render, fireEvent } from 'spec/helpers/testing-library';
import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
-import { act } from 'react-dom/test-utils';
import sinon from 'sinon';
-import { supersetTheme, ThemeProvider } from '@superset-ui/core';
-import Modal from 'src/components/Modal';
import { ChangeDatasourceModal } from 'src/components/Datasource';
-import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import mockDatasource from 'spec/fixtures/mockDatasource';
const mockStore = configureStore([thunk]);
@@ -57,60 +53,40 @@ fetchMock.get(DATASOURCES_ENDPOINT, { result: [mockDatasource['7__table']] });
fetchMock.get(DATASOURCE_ENDPOINT, DATASOURCE_PAYLOAD);
fetchMock.get(INFO_ENDPOINT, {});
-async function mountAndWait(props = mockedProps) {
- const mounted = mount(, {
- wrappingComponent: ThemeProvider,
- wrappingComponentProps: { theme: supersetTheme },
- });
- await waitForComponentToPaint(mounted);
-
- return mounted;
-}
-
-describe('ChangeDatasourceModal', () => {
- let wrapper;
-
- beforeEach(async () => {
- wrapper = await mountAndWait();
- });
-
- it('renders', () => {
- expect(wrapper.find(ChangeDatasourceModal)).toHaveLength(1);
- });
-
- it('renders a Modal', () => {
- expect(wrapper.find(Modal)).toExist();
- });
-
- it('fetches datasources', async () => {
- expect(fetchMock.calls(INFO_ENDPOINT)).toHaveLength(3);
- });
-
- it('renders confirmation message', async () => {
- await waitForComponentToPaint(wrapper, 1000);
-
- act(() => {
- wrapper.find('[data-test="datasource-link"]').at(0).props().onClick();
- });
-
- await waitForComponentToPaint(wrapper);
+afterEach(() => {
+ fetchMock.resetHistory();
+});
- expect(wrapper.find('.proceed-btn')).toExist();
+const setup = (props = mockedProps) =>
+ render(, {
+ useRedux: true,
+ store,
});
- it('changes the datasource', async () => {
- await waitForComponentToPaint(wrapper, 1000);
+test('renders', () => {
+ const { getByTestId } = setup();
+ expect(getByTestId('Swap dataset-modal')).toBeInTheDocument();
+});
- act(() => {
- wrapper.find('[data-test="datasource-link"]').at(0).props().onClick();
- });
- await waitForComponentToPaint(wrapper);
+test('fetches datasources', async () => {
+ setup();
+ await waitFor(() => expect(fetchMock.calls(INFO_ENDPOINT)).toHaveLength(1));
+});
- act(() => {
- wrapper.find('.proceed-btn').at(0).props().onClick(datasourceData);
- });
- await waitForComponentToPaint(wrapper);
+test('renders confirmation message', async () => {
+ const { findByTestId, getByRole } = setup();
+ const confirmLink = await findByTestId('datasource-link');
+ fireEvent.click(confirmLink);
+ expect(getByRole('button', { name: 'Proceed' })).toBeInTheDocument();
+});
- expect(fetchMock.calls(/api\/v1\/dataset\/7/)).toHaveLength(1);
- });
+test('changes the datasource', async () => {
+ const { findByTestId, getByRole } = setup();
+ const confirmLink = await findByTestId('datasource-link');
+ fireEvent.click(confirmLink);
+ const proceedButton = getByRole('button', { name: 'Proceed' });
+ fireEvent.click(proceedButton);
+ await waitFor(() =>
+ expect(fetchMock.calls(/api\/v1\/dataset\/7/)).toHaveLength(1),
+ );
});
diff --git a/superset-frontend/src/components/Datasource/CollectionTable.test.jsx b/superset-frontend/src/components/Datasource/CollectionTable.test.jsx
index 6ed5b7125037a..ad4d7351ecf12 100644
--- a/superset-frontend/src/components/Datasource/CollectionTable.test.jsx
+++ b/superset-frontend/src/components/Datasource/CollectionTable.test.jsx
@@ -16,8 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { isValidElement } from 'react';
-import { shallow } from 'enzyme';
+import { render } from 'spec/helpers/testing-library';
import mockDatasource from 'spec/fixtures/mockDatasource';
import CollectionTable from './CollectionTable';
@@ -27,22 +26,13 @@ const props = {
tableColumns: ['column_name', 'type', 'groupby'],
};
-describe('CollectionTable', () => {
- let wrapper;
- let el;
-
- beforeEach(() => {
- el = ;
- wrapper = shallow(el);
- });
-
- it('is valid', () => {
- expect(isValidElement(el)).toBe(true);
- });
-
- it('renders a table', () => {
- const { length } = mockDatasource['7__table'].columns;
- expect(wrapper.find('table')).toExist();
- expect(wrapper.find('tbody tr.row')).toHaveLength(length);
- });
+test('renders a table', () => {
+ const { length } = mockDatasource['7__table'].columns;
+ const { getByRole } = render();
+ expect(getByRole('table')).toBeInTheDocument();
+ expect(
+ getByRole('table')
+ .getElementsByTagName('tbody')[0]
+ .getElementsByClassName('row'),
+ ).toHaveLength(length);
});
diff --git a/superset-frontend/src/components/Datasource/Field.test.tsx b/superset-frontend/src/components/Datasource/Field.test.tsx
index 3e8fdefeaec4d..2e696dca8cd31 100644
--- a/superset-frontend/src/components/Datasource/Field.test.tsx
+++ b/superset-frontend/src/components/Datasource/Field.test.tsx
@@ -16,40 +16,34 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { fireEvent, render, screen } from 'spec/helpers/testing-library';
-import { render, screen } from 'spec/helpers/testing-library';
-import { shallow } from 'enzyme';
-import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
import Field from './Field';
-describe('Field', () => {
- const defaultProps = {
- fieldKey: 'mock',
- value: '',
- label: 'mock',
- description: 'description',
- control: ,
- onChange: jest.fn(),
- compact: false,
- inline: false,
- };
+const defaultProps = {
+ fieldKey: 'mock',
+ value: '',
+ label: 'mock',
+ description: 'description',
+ control: ,
+ onChange: jest.fn(),
+ compact: false,
+ inline: false,
+};
- it('should render', () => {
- const { container } = render();
- expect(container).toBeInTheDocument();
- });
+test('should render', () => {
+ const { container } = render();
+ expect(container).toBeInTheDocument();
+});
- it('should call onChange', () => {
- const wrapper = shallow();
- const textArea = wrapper.find(TextAreaControl);
- textArea.simulate('change', { target: { value: 'x' } });
- expect(defaultProps.onChange).toHaveBeenCalled();
- });
+test('should call onChange', () => {
+ const { getByTestId } = render();
+ const textArea = getByTestId('mock-text-control');
+ fireEvent.change(textArea, { target: { value: 'x' } });
+ expect(defaultProps.onChange).toHaveBeenCalled();
+});
- it('should render compact', () => {
- render();
- expect(
- screen.queryByText(defaultProps.description),
- ).not.toBeInTheDocument();
- });
+test('should render compact', () => {
+ render();
+ expect(screen.queryByText(defaultProps.description)).not.toBeInTheDocument();
});
diff --git a/superset-frontend/src/components/EditableTitle/EditableTitle.test.tsx b/superset-frontend/src/components/EditableTitle/EditableTitle.test.tsx
index 56402502e9c66..12ecfacf780ef 100644
--- a/superset-frontend/src/components/EditableTitle/EditableTitle.test.tsx
+++ b/superset-frontend/src/components/EditableTitle/EditableTitle.test.tsx
@@ -16,88 +16,98 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { isValidElement } from 'react';
-import { shallow } from 'enzyme';
-import sinon from 'sinon';
+import { fireEvent, getByRole, render } from 'spec/helpers/testing-library';
import EditableTable from 'src/components/EditableTitle';
-describe('EditableTitle', () => {
- const callback = sinon.spy();
- const mockProps = {
- title: 'my title',
- canEdit: true,
- onSaveTitle: callback,
- };
- const mockEvent = {
- target: {
- value: 'new title',
- },
- };
- let editableWrapper = shallow();
- const notEditableWrapper = shallow(
- ,
+const mockEvent = {
+ target: {
+ value: 'new title',
+ },
+};
+const mockProps = {
+ title: 'my title',
+ canEdit: true,
+ onSaveTitle: jest.fn(),
+};
+
+test('should render title', () => {
+ const { getByRole } = render();
+ expect(getByRole('button')).toBeInTheDocument();
+ expect(getByRole('button')).toHaveValue(mockProps.title);
+});
+test('should not render an input if it is not editable', () => {
+ const { queryByRole } = render(
+ ,
);
- it('is valid', () => {
- expect(isValidElement()).toBe(true);
- });
- it('should render title', () => {
- const titleElement = editableWrapper.find('input');
- expect(titleElement.props().value).toBe('my title');
- expect(titleElement.props().type).toBe('button');
- });
- it('should not render an input if it is not editable', () => {
- expect(notEditableWrapper.find('input')).not.toExist();
+ expect(queryByRole('button')).not.toBeInTheDocument();
+});
+
+describe('should handle click', () => {
+ test('should change title', () => {
+ const { getByRole, container } = render();
+ fireEvent.click(getByRole('button'));
+ expect(container.querySelector('input')?.getAttribute('type')).toEqual(
+ 'text',
+ );
});
+});
- describe('should handle click', () => {
- it('should change title', () => {
- editableWrapper.find('input').simulate('click');
- expect(editableWrapper.find('input').props().type).toBe('text');
- });
+describe('should handle change', () => {
+ test('should change title', () => {
+ const { getByTestId, container } = render(
+ ,
+ );
+ fireEvent.change(getByTestId('editable-title-input'), mockEvent);
+ expect(container.querySelector('input')).toHaveValue('new title');
});
+});
- describe('should handle change', () => {
- afterEach(() => {
- editableWrapper = shallow();
- });
- it('should change title', () => {
- editableWrapper.find('input').simulate('change', mockEvent);
- expect(editableWrapper.find('input').props().value).toBe('new title');
- });
+describe('should handle blur', () => {
+ const setup = (overrides: Partial = {}) => {
+ const selectors = render();
+ fireEvent.click(selectors.getByRole('button'));
+ return selectors;
+ };
+
+ test('default input type should be text', () => {
+ const { container } = setup();
+ expect(container.querySelector('input')?.getAttribute('type')).toEqual(
+ 'text',
+ );
});
- describe('should handle blur', () => {
- beforeEach(() => {
- editableWrapper.find('input').simulate('click');
- });
- afterEach(() => {
- callback.resetHistory();
- editableWrapper = shallow();
- });
+ test('should trigger callback', () => {
+ const callback = jest.fn();
+ const { getByTestId, container } = setup({ onSaveTitle: callback });
+ fireEvent.change(getByTestId('editable-title-input'), mockEvent);
+ fireEvent.blur(getByTestId('editable-title-input'));
+ expect(callback).toHaveBeenCalledTimes(1);
+ expect(callback).toHaveBeenCalledWith('new title');
+ expect(container.querySelector('input')?.getAttribute('type')).toEqual(
+ 'button',
+ );
+ });
- it('default input type should be text', () => {
- expect(editableWrapper.find('input').props().type).toBe('text');
- });
+ test('should not trigger callback', () => {
+ const callback = jest.fn();
+ const { getByTestId, container } = setup({ onSaveTitle: callback });
+ fireEvent.blur(getByTestId('editable-title-input'));
+ expect(container.querySelector('input')?.getAttribute('type')).toEqual(
+ 'button',
+ );
+ // no change
+ expect(callback).not.toHaveBeenCalled();
+ });
- it('should trigger callback', () => {
- editableWrapper.find('input').simulate('change', mockEvent);
- editableWrapper.find('input').simulate('blur');
- expect(editableWrapper.find('input').props().type).toBe('button');
- expect(callback.callCount).toBe(1);
- expect(callback.getCall(0).args[0]).toBe('new title');
- });
- it('should not trigger callback', () => {
- editableWrapper.find('input').simulate('blur');
- expect(editableWrapper.find('input').props().type).toBe('button');
- // no change
- expect(callback.callCount).toBe(0);
- });
- it('should not save empty title', () => {
- editableWrapper.find('input').simulate('blur');
- expect(editableWrapper.find('input').props().type).toBe('button');
- expect(editableWrapper.find('input').props().value).toBe('my title');
- expect(callback.callCount).toBe(0);
- });
+ test('should not save empty title', () => {
+ const callback = jest.fn();
+ const { getByTestId, container } = setup({ onSaveTitle: callback });
+ fireEvent.blur(getByTestId('editable-title-input'));
+ expect(container.querySelector('input')?.getAttribute('type')).toEqual(
+ 'button',
+ );
+ expect(getByRole(container, 'button')).toHaveValue(mockProps.title);
+ expect(callback).not.toHaveBeenCalled();
});
});
diff --git a/superset-frontend/src/components/FormRow/FormRow.test.jsx b/superset-frontend/src/components/FormRow/FormRow.test.jsx
index 68d9c5691dcfd..62853a13b1082 100644
--- a/superset-frontend/src/components/FormRow/FormRow.test.jsx
+++ b/superset-frontend/src/components/FormRow/FormRow.test.jsx
@@ -16,42 +16,44 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { shallow } from 'enzyme';
+import { render } from 'spec/helpers/testing-library';
-import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls';
-import { Row, Col } from 'src/components';
import TextControl from 'src/explore/components/controls/TextControl';
import FormRow from 'src/components/FormRow';
+jest.mock('@superset-ui/chart-controls', () => ({
+ ...jest.requireActual('@superset-ui/chart-controls'),
+ InfoTooltipWithTrigger: () => ,
+}));
+jest.mock('src/components', () => ({
+ ...jest.requireActual('src/components'),
+ Row: ({ children }) => {children}
,
+ Col: ({ children }) => {children}
,
+}));
+
const defaultProps = {
label: 'Hello',
tooltip: 'A tooltip',
control: ,
};
-describe('FormRow', () => {
- let wrapper;
-
- const getWrapper = (overrideProps = {}) => {
- const props = {
- ...defaultProps,
- ...overrideProps,
- };
- return shallow();
+const setup = (overrideProps = {}) => {
+ const props = {
+ ...defaultProps,
+ ...overrideProps,
};
+ return render();
+};
- beforeEach(() => {
- wrapper = getWrapper();
- });
-
- it('renders an InfoTooltipWithTrigger only if needed', () => {
- expect(wrapper.find(InfoTooltipWithTrigger)).toExist();
- wrapper = getWrapper({ tooltip: null });
- expect(wrapper.find(InfoTooltipWithTrigger)).not.toExist();
- });
+test('renders an InfoTooltipWithTrigger only if needed', () => {
+ const { getByTestId, queryByTestId, rerender } = setup();
+ expect(getByTestId('mock-info-tooltip')).toBeInTheDocument();
+ rerender();
+ expect(queryByTestId('mock-info-tooltip')).not.toBeInTheDocument();
+});
- it('renders a Row and 2 Cols', () => {
- expect(wrapper.find(Row)).toExist();
- expect(wrapper.find(Col)).toHaveLength(2);
- });
+test('renders a Row and 2 Cols', () => {
+ const { getByTestId, getAllByTestId } = setup();
+ expect(getByTestId('mock-row')).toBeInTheDocument();
+ expect(getAllByTestId('mock-col')).toHaveLength(2);
});
diff --git a/superset-frontend/src/components/IconTooltip/IconTooltip.test.jsx b/superset-frontend/src/components/IconTooltip/IconTooltip.test.jsx
index e75bfcc4c142c..6aac65dd5851b 100644
--- a/superset-frontend/src/components/IconTooltip/IconTooltip.test.jsx
+++ b/superset-frontend/src/components/IconTooltip/IconTooltip.test.jsx
@@ -16,25 +16,29 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { isValidElement } from 'react';
-import { shallow } from 'enzyme';
-import { Tooltip } from 'src/components/Tooltip';
+import { render } from 'spec/helpers/testing-library';
import { IconTooltip } from 'src/components/IconTooltip';
-describe('IconTooltip', () => {
- const mockedProps = {
- tooltip: 'This is a tooltip',
- };
- it('renders', () => {
- expect(isValidElement(TEST)).toBe(true);
- });
- it('renders with props', () => {
- expect(
- isValidElement(TEST),
- ).toBe(true);
- });
- it('renders a tooltip', () => {
- const wrapper = shallow(TEST);
- expect(wrapper.find(Tooltip)).toExist();
- });
+jest.mock('src/components/Tooltip', () => ({
+ Tooltip: () => ,
+}));
+
+const mockedProps = {
+ tooltip: 'This is a tooltip',
+};
+test('renders', () => {
+ const { container } = render(TEST);
+ expect(container).toBeInTheDocument();
+});
+test('renders with props', () => {
+ const { container } = render(
+ TEST,
+ );
+ expect(container).toBeInTheDocument();
+});
+test('renders a tooltip', () => {
+ const { getByTestId } = render(
+ TEST,
+ );
+ expect(getByTestId('mock-tooltip')).toBeInTheDocument();
});
diff --git a/superset-frontend/src/components/ImportModal/ImportModal.test.tsx b/superset-frontend/src/components/ImportModal/ImportModal.test.tsx
index 3998890e3da5e..69bd1a8e1aa50 100644
--- a/superset-frontend/src/components/ImportModal/ImportModal.test.tsx
+++ b/superset-frontend/src/components/ImportModal/ImportModal.test.tsx
@@ -16,18 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { act } from 'react-dom/test-utils';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
-import { styledMount as mount } from 'spec/helpers/theming';
-import { ReactWrapper } from 'enzyme';
+import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
import fetchMock from 'fetch-mock';
-import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
-import { Upload } from 'src/components';
-import Button from 'src/components/Button';
import { ImportResourceName } from 'src/views/CRUD/types';
-import ImportModelsModal from 'src/components/ImportModal';
-import Modal from 'src/components/Modal';
+import ImportModelsModal, {
+ ImportModelsModalProps,
+} from 'src/components/ImportModal';
const mockStore = configureStore([thunk]);
const store = mockStore({});
@@ -48,148 +44,98 @@ const requiredProps = {
onHide: () => {},
};
-describe('ImportModelsModal', () => {
- let wrapper: ReactWrapper;
-
- beforeEach(() => {
- wrapper = mount(, {
- context: { store },
- });
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
+afterEach(() => {
+ jest.clearAllMocks();
+});
- it('renders', () => {
- expect(wrapper.find(ImportModelsModal)).toExist();
- });
+const setup = (overrides: Partial = {}) =>
+ render(, { store });
- it('renders a Modal', () => {
- expect(wrapper.find(Modal)).toExist();
- });
+test('renders', () => {
+ const { container } = setup();
+ expect(container).toBeInTheDocument();
+});
- it('renders "Import database" header', () => {
- expect(wrapper.find('h4').text()).toEqual('Import database');
- });
+test('renders a Modal', () => {
+ const { getByTestId } = setup();
+ expect(getByTestId('model-modal')).toBeInTheDocument();
+});
- it('renders a file input field', () => {
- expect(wrapper.find('input[type="file"]')).toExist();
- });
+test('renders "Import database" header', () => {
+ const { getByText } = setup();
+ expect(getByText('Import database')).toBeInTheDocument();
+});
- it('should render the close, file, import and cancel buttons', () => {
- expect(wrapper.find('button')).toHaveLength(4);
- });
+test('renders a file input field', () => {
+ setup();
+ expect(document.querySelector('input[type="file"]')).toBeInTheDocument();
+});
- it('should render the import button initially disabled', () => {
- expect(wrapper.find(Button).at(2).prop('disabled')).toBe(true);
- });
+test('should render the close, file, import and cancel buttons', () => {
+ setup();
+ expect(document.querySelectorAll('button')).toHaveLength(4);
+});
- it('should render the import button enabled when a file is selected', () => {
- const file = new File([new ArrayBuffer(1)], 'model_export.zip');
- act(() => {
- const handler = wrapper.find(Upload).prop('onChange');
- if (handler) {
- handler({
- fileList: [],
- file: {
- name: 'model_export.zip',
- originFileObj: file,
- uid: '-1',
- size: 0,
- type: 'zip',
- },
- });
- }
- });
- wrapper.update();
- expect(wrapper.find(Button).at(2).prop('disabled')).toBe(false);
- });
+test('should render the import button initially disabled', () => {
+ const { getByRole } = setup();
+ expect(getByRole('button', { name: 'Import' })).toBeDisabled();
+});
- it('should POST with request header `Accept: application/json`', async () => {
- const file = new File([new ArrayBuffer(1)], 'model_export.zip');
- act(() => {
- const handler = wrapper.find(Upload).prop('onChange');
- if (handler) {
- handler({
- fileList: [],
- file: {
- name: 'model_export.zip',
- originFileObj: file,
- uid: '-1',
- size: 0,
- type: 'zip',
- },
- });
- }
- });
- wrapper.update();
-
- wrapper.find(Button).at(2).simulate('click');
- await waitForComponentToPaint(wrapper);
- expect(fetchMock.calls(DATABASE_IMPORT_URL)[0][1]?.headers).toStrictEqual({
- Accept: 'application/json',
- 'X-CSRFToken': '1234',
- });
- });
+test('should render the import button enabled when a file is selected', async () => {
+ const file = new File([new ArrayBuffer(1)], 'model_export.zip');
+ const { getByTestId, getByRole } = setup();
+ await waitFor(() =>
+ fireEvent.change(getByTestId('model-file-input'), {
+ target: {
+ files: [file],
+ },
+ }),
+ );
+ expect(getByRole('button', { name: 'Import' })).toBeEnabled();
+});
- it('should render password fields when needed for import', () => {
- const wrapperWithPasswords = mount(
- ,
- {
- context: { store },
+test('should POST with request header `Accept: application/json`', async () => {
+ const file = new File([new ArrayBuffer(1)], 'model_export.zip');
+ const { getByTestId, getByRole } = setup();
+ await waitFor(() =>
+ fireEvent.change(getByTestId('model-file-input'), {
+ target: {
+ files: [file],
},
- );
- expect(wrapperWithPasswords.find('input[type="password"]')).toExist();
+ }),
+ );
+ fireEvent.click(getByRole('button', { name: 'Import' }));
+ await waitFor(() =>
+ expect(fetchMock.calls(DATABASE_IMPORT_URL)).toHaveLength(1),
+ );
+ expect(fetchMock.calls(DATABASE_IMPORT_URL)[0][1]?.headers).toStrictEqual({
+ Accept: 'application/json',
+ 'X-CSRFToken': '1234',
});
+});
- it('should render ssh_tunnel password fields when needed for import', () => {
- const wrapperWithPasswords = mount(
- ,
- {
- context: { store },
- },
- );
- expect(
- wrapperWithPasswords.find('[data-test="ssh_tunnel_password"]'),
- ).toExist();
+test('should render password fields when needed for import', () => {
+ setup({ passwordFields: ['databases/examples.yaml'] });
+ expect(document.querySelector('input[type="password"]')).toBeInTheDocument();
+});
+
+test('should render ssh_tunnel password fields when needed for import', () => {
+ const { getByTestId } = setup({
+ sshTunnelPasswordFields: ['databases/examples.yaml'],
});
+ expect(getByTestId('ssh_tunnel_password')).toBeInTheDocument();
+});
- it('should render ssh_tunnel private_key fields when needed for import', () => {
- const wrapperWithPasswords = mount(
- ,
- {
- context: { store },
- },
- );
- expect(
- wrapperWithPasswords.find('[data-test="ssh_tunnel_private_key"]'),
- ).toExist();
+test('should render ssh_tunnel private_key fields when needed for import', () => {
+ const { getByTestId } = setup({
+ sshTunnelPrivateKeyFields: ['databases/examples.yaml'],
});
+ expect(getByTestId('ssh_tunnel_private_key')).toBeInTheDocument();
+});
- it('should render ssh_tunnel private_key_password fields when needed for import', () => {
- const wrapperWithPasswords = mount(
- ,
- {
- context: { store },
- },
- );
- expect(
- wrapperWithPasswords.find(
- '[data-test="ssh_tunnel_private_key_password"]',
- ),
- ).toExist();
+test('should render ssh_tunnel private_key_password fields when needed for import', () => {
+ const { getByTestId } = setup({
+ sshTunnelPrivateKeyPasswordFields: ['databases/examples.yaml'],
});
+ expect(getByTestId('ssh_tunnel_private_key_password')).toBeInTheDocument();
});
diff --git a/superset-frontend/src/components/Label/Label.test.tsx b/superset-frontend/src/components/Label/Label.test.tsx
index 36758b5a3dbd2..fdf85b9c9336e 100644
--- a/superset-frontend/src/components/Label/Label.test.tsx
+++ b/superset-frontend/src/components/Label/Label.test.tsx
@@ -16,42 +16,28 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { fireEvent, render } from 'spec/helpers/testing-library';
-import { isValidElement } from 'react';
-import { ReactWrapper } from 'enzyme';
-import { styledMount as mount } from 'spec/helpers/theming';
import Label from '.';
import { LabelGallery, options } from './Label.stories';
-describe('Label', () => {
- let wrapper: ReactWrapper;
-
- // test the basic component
- it('renders the base component (no onClick)', () => {
- expect(isValidElement()).toBe(true);
- });
-
- it('renders with role=undefined when onClick is not present', () => {
- wrapper = mount();
- expect(wrapper.find('span').prop('role')).toBeUndefined();
- });
-
- it('renders with role="button" when onClick is present', () => {
- const mockAction = jest.fn();
- wrapper = mount();
- expect(wrapper.find('span').prop('role')).toBe('button');
- });
+// test the basic component
+test('renders the base component (no onClick)', () => {
+ const { container } = render();
+ expect(container).toBeInTheDocument();
+});
- it('works with an onClick handler', () => {
- const mockAction = jest.fn();
- wrapper = mount();
- wrapper.find(Label).simulate('click');
- expect(mockAction).toHaveBeenCalled();
- });
+test('works with an onClick handler', () => {
+ const mockAction = jest.fn();
+ const { getByText } = render();
+ fireEvent.click(getByText('test'));
+ expect(mockAction).toHaveBeenCalled();
+});
- // test stories from the storybook!
- it('renders all the storybook gallery variants', () => {
- wrapper = mount();
- expect(wrapper.find(Label).length).toEqual(options.length * 2);
- });
+// test stories from the storybook!
+test('renders all the storybook gallery variants', () => {
+ const { container } = render();
+ expect(container.querySelectorAll('.ant-tag')).toHaveLength(
+ options.length * 2,
+ );
});
diff --git a/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx b/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx
index a7a2b2c837d47..f020a47448c68 100644
--- a/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx
+++ b/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx
@@ -16,30 +16,22 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { fireEvent, render } from 'spec/helpers/testing-library';
-import { MouseEvent } from 'react';
-import { ReactWrapper } from 'enzyme';
-import { styledMount as mount } from 'spec/helpers/theming';
-import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import LastUpdated from '.';
-describe('LastUpdated', () => {
- let wrapper: ReactWrapper;
- const updatedAt = new Date('Sat Dec 12 2020 00:00:00 GMT-0800');
+const updatedAt = new Date('Sat Dec 12 2020 00:00:00 GMT-0800');
- it('renders the base component (no refresh)', () => {
- const wrapper = mount();
- expect(/^Last Updated .+$/.test(wrapper.text())).toBe(true);
- });
+test('renders the base component (no refresh)', () => {
+ const { getByText } = render();
+ expect(getByText(/^Last Updated .+$/)).toBeInTheDocument();
+});
- it('renders a refresh action', async () => {
- const mockAction = jest.fn();
- wrapper = mount();
- await waitForComponentToPaint(wrapper);
- const props = wrapper.find('[aria-label="refresh"]').first().props();
- if (props.onClick) {
- props.onClick({} as MouseEvent);
- }
- expect(mockAction).toHaveBeenCalled();
- });
+test('renders a refresh action', async () => {
+ const mockAction = jest.fn();
+ const { getByLabelText } = render(
+ ,
+ );
+ fireEvent.click(getByLabelText('refresh'));
+ expect(mockAction).toHaveBeenCalled();
});
diff --git a/superset-frontend/src/components/MessageToasts/Toast.test.jsx b/superset-frontend/src/components/MessageToasts/Toast.test.jsx
index 810c6a32baa2f..130eaf156fb71 100644
--- a/superset-frontend/src/components/MessageToasts/Toast.test.jsx
+++ b/superset-frontend/src/components/MessageToasts/Toast.test.jsx
@@ -16,10 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { mount } from 'enzyme';
-import { ThemeProvider, supersetTheme } from '@superset-ui/core';
+import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
import Toast from 'src/components/MessageToasts/Toast';
-import { act } from 'react-dom/test-utils';
import mockMessageToasts from './mockMessageToasts';
const props = {
@@ -27,35 +25,22 @@ const props = {
onCloseToast() {},
};
-const setup = overrideProps =>
- mount(, {
- wrappingComponent: ThemeProvider,
- wrappingComponentProps: { theme: supersetTheme },
- });
+const setup = overrideProps => render();
-describe('Toast', () => {
- it('should render', () => {
- const wrapper = setup();
- expect(wrapper.find('[data-test="toast-container"]')).toExist();
- });
-
- it('should render toastText within the div', () => {
- const wrapper = setup();
- const container = wrapper.find('[data-test="toast-container"]');
- expect(container.hostNodes().childAt(1).text()).toBe(props.toast.text);
- });
+test('should render', () => {
+ const { getByTestId } = setup();
+ expect(getByTestId('toast-container')).toBeInTheDocument();
+});
- it('should call onCloseToast upon toast dismissal', async () =>
- act(
- () =>
- new Promise(done => {
- const onCloseToast = id => {
- expect(id).toBe(props.toast.id);
- done();
- };
+test('should render toastText within the div', () => {
+ const { getByTestId } = setup();
+ expect(getByTestId('toast-container')).toHaveTextContent(props.toast.text);
+});
- const wrapper = setup({ onCloseToast });
- wrapper.find('[data-test="close-button"]').props().onClick();
- }),
- ));
+test('should call onCloseToast upon toast dismissal', async () => {
+ const onCloseToast = jest.fn();
+ const { getByTestId } = setup({ onCloseToast });
+ fireEvent.click(getByTestId('close-button'));
+ await waitFor(() => expect(onCloseToast).toHaveBeenCalledTimes(1));
+ expect(onCloseToast).toHaveBeenCalledWith(props.toast.id);
});
diff --git a/superset-frontend/src/components/MessageToasts/ToastPresenter.test.jsx b/superset-frontend/src/components/MessageToasts/ToastPresenter.test.jsx
index 73a9ac4b06e5d..f0b4ba738c0d3 100644
--- a/superset-frontend/src/components/MessageToasts/ToastPresenter.test.jsx
+++ b/superset-frontend/src/components/MessageToasts/ToastPresenter.test.jsx
@@ -16,35 +16,33 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { shallow } from 'enzyme';
-import Toast from 'src/components/MessageToasts/Toast';
+import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
+
import ToastPresenter from 'src/components/MessageToasts/ToastPresenter';
import mockMessageToasts from './mockMessageToasts';
-describe('ToastPresenter', () => {
- const props = {
- toasts: mockMessageToasts,
- removeToast() {},
- };
+const props = {
+ toasts: mockMessageToasts,
+ removeToast() {},
+};
- function setup(overrideProps) {
- const wrapper = shallow();
- return wrapper;
- }
+function setup(overrideProps) {
+ return render();
+}
- it('should render a div with id toast-presenter', () => {
- const wrapper = setup();
- expect(wrapper.find('#toast-presenter')).toExist();
- });
+test('should render a div with id toast-presenter', () => {
+ const { container } = setup();
+ expect(container.querySelector('#toast-presenter')).toBeInTheDocument();
+});
- it('should render a Toast for each toast object', () => {
- const wrapper = setup();
- expect(wrapper.find(Toast)).toHaveLength(props.toasts.length);
- });
+test('should render a Toast for each toast object', () => {
+ const { getAllByRole } = setup();
+ expect(getAllByRole('alert')).toHaveLength(props.toasts.length);
+});
- it('should pass removeToast to the Toast component', () => {
- const removeToast = () => {};
- const wrapper = setup({ removeToast });
- expect(wrapper.find(Toast).first().prop('onCloseToast')).toBe(removeToast);
- });
+test('should pass removeToast to the Toast component', async () => {
+ const removeToast = jest.fn();
+ const { getAllByTestId } = setup({ removeToast });
+ fireEvent.click(getAllByTestId('close-button')[0]);
+ await waitFor(() => expect(removeToast).toHaveBeenCalledTimes(1));
});