chore(shared components): Migrate enzyme to RTL (#26258)

This commit is contained in:
JUST.in DO IT 2024-08-22 08:34:51 +09:00 committed by GitHub
parent 8e2f81816f
commit 1a1548da3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 497 additions and 628 deletions

View File

@ -16,10 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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 Button from '.'; import Button from '.';
import { import {
ButtonGallery, ButtonGallery,
@ -27,36 +24,27 @@ import {
STYLES as buttonStyles, STYLES as buttonStyles,
} from './Button.stories'; } from './Button.stories';
describe('Button', () => { test('works with an onClick handler', () => {
let wrapper: ReactWrapper; const mockAction = jest.fn();
const { getByRole } = render(<Button onClick={mockAction} />);
// test the basic component fireEvent.click(getByRole('button'));
it('renders the base component', () => { expect(mockAction).toHaveBeenCalled();
expect(isValidElement(<Button />)).toBe(true); });
});
test('does not handle onClicks when disabled', () => {
it('works with an onClick handler', () => { const mockAction = jest.fn();
const mockAction = jest.fn(); const { getByRole } = render(<Button onClick={mockAction} disabled />);
wrapper = mount(<Button onClick={mockAction} />); fireEvent.click(getByRole('button'));
wrapper.find('Button').first().simulate('click'); expect(mockAction).toHaveBeenCalledTimes(0);
expect(mockAction).toHaveBeenCalled(); });
});
// test stories from the storybook!
it('does not handle onClicks when disabled', () => { test('All the sorybook gallery variants mount', () => {
const mockAction = jest.fn(); const { getAllByRole } = render(<ButtonGallery />);
wrapper = mount(<Button onClick={mockAction} disabled />);
wrapper.find('Button').first().simulate('click'); const permutationCount =
expect(mockAction).toHaveBeenCalledTimes(0); Object.values(buttonStyles.options).filter(o => o).length *
}); Object.values(buttonSizes.options).length;
// test stories from the storybook! expect(getAllByRole('button')).toHaveLength(permutationCount);
it('All the sorybook gallery variants mount', () => {
wrapper = mount(<ButtonGallery />);
const permutationCount =
Object.values(buttonStyles.options).filter(o => o).length *
Object.values(buttonSizes.options).length;
expect(wrapper.find(Button).length).toEqual(permutationCount);
});
}); });

View File

@ -16,11 +16,22 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { shallow } from 'enzyme'; import { render } from 'spec/helpers/testing-library';
import { SuperChart } from '@superset-ui/core';
import ChartRenderer from 'src/components/Chart/ChartRenderer'; import ChartRenderer from 'src/components/Chart/ChartRenderer';
jest.mock('@superset-ui/core', () => ({
...jest.requireActual('@superset-ui/core'),
SuperChart: ({ formData }) => (
<div data-test="mock-super-chart">{JSON.stringify(formData)}</div>
),
}));
jest.mock(
'src/components/Chart/ChartContextMenu/ChartContextMenu',
() => () => <div data-test="mock-chart-context-menu" />,
);
const requiredProps = { const requiredProps = {
chartId: 1, chartId: 1,
datasource: {}, datasource: {},
@ -31,18 +42,18 @@ const requiredProps = {
vizType: 'table', vizType: 'table',
}; };
describe('ChartRenderer', () => { test('should render SuperChart', () => {
it('should render SuperChart', () => { const { getByTestId } = render(
const wrapper = shallow( <ChartRenderer {...requiredProps} chartIsStale={false} />,
<ChartRenderer {...requiredProps} chartIsStale={false} />, );
); expect(getByTestId('mock-super-chart')).toBeInTheDocument();
expect(wrapper.find(SuperChart)).toExist(); });
});
test('should use latestQueryFormData instead of formData when chartIsStale is true', () => {
it('should use latestQueryFormData instead of formData when chartIsStale is true', () => { const { getByTestId } = render(
const wrapper = shallow(<ChartRenderer {...requiredProps} chartIsStale />); <ChartRenderer {...requiredProps} chartIsStale />,
expect(wrapper.find(SuperChart).prop('formData')).toEqual({ );
testControl: 'bar', expect(getByTestId('mock-super-chart')).toHaveTextContent(
}); JSON.stringify({ testControl: 'bar' }),
}); );
}); });

View File

@ -16,62 +16,46 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { fireEvent, render } from 'spec/helpers/testing-library';
import { isValidElement } from 'react'; import Checkbox from 'src/components/Checkbox';
import { ReactWrapper } from 'enzyme';
import {
styledMount as mount,
styledShallow as shallow,
} from 'spec/helpers/theming';
import Checkbox, { jest.mock('src/components/Checkbox/CheckboxIcons', () => ({
CheckboxChecked, CheckboxChecked: () => <div data-test="mock-CheckboxChecked" />,
CheckboxUnchecked, CheckboxUnchecked: () => <div data-test="mock-CheckboxUnchecked" />,
} from 'src/components/Checkbox'; }));
describe('Checkbox', () => { describe('when unchecked', () => {
let wrapper: ReactWrapper; test('renders the unchecked component', () => {
const { getByTestId } = render(
it('renders the base component', () => { <Checkbox style={{}} checked={false} onChange={() => true} />,
expect(
isValidElement(
<Checkbox style={{}} checked={false} onChange={() => true} />,
),
).toBe(true);
});
describe('when unchecked', () => {
it('renders the unchecked component', () => {
const shallowWrapper = shallow(
<Checkbox style={{}} checked={false} onChange={() => true} />,
);
expect(shallowWrapper.dive().find(CheckboxUnchecked)).toExist();
});
});
describe('when checked', () => {
it('renders the checked component', () => {
const shallowWrapper = shallow(
<Checkbox style={{}} checked onChange={() => true} />,
);
expect(shallowWrapper.dive().find(CheckboxChecked)).toExist();
});
});
it('works with an onChange handler', () => {
const mockAction = jest.fn();
wrapper = mount(
<Checkbox style={{}} checked={false} onChange={mockAction} />,
); );
wrapper.find('Checkbox').first().simulate('click'); expect(getByTestId('mock-CheckboxUnchecked')).toBeInTheDocument();
expect(mockAction).toHaveBeenCalled();
});
it('renders custom Checkbox styles without melting', () => {
wrapper = mount(
<Checkbox onChange={() => true} checked={false} style={{ opacity: 1 }} />,
);
expect(wrapper.find('Checkbox')).toExist();
expect(wrapper.find('Checkbox')).toHaveStyle({ opacity: 1 });
}); });
}); });
describe('when checked', () => {
test('renders the checked component', () => {
const { getByTestId } = render(
<Checkbox style={{}} checked onChange={() => true} />,
);
expect(getByTestId('mock-CheckboxChecked')).toBeInTheDocument();
});
});
test('works with an onChange handler', () => {
const mockAction = jest.fn();
const { getByRole } = render(
<Checkbox style={{}} checked={false} onChange={mockAction} />,
);
fireEvent.click(getByRole('checkbox'));
expect(mockAction).toHaveBeenCalled();
});
test('renders custom Checkbox styles without melting', () => {
const { getByRole } = render(
<Checkbox onChange={() => true} checked={false} style={{ opacity: 1 }} />,
);
expect(getByRole('checkbox')).toBeInTheDocument();
expect(getByRole('checkbox')).toHaveStyle({ opacity: 1 });
});

View File

@ -16,48 +16,51 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { mount } from 'enzyme'; import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
import Button from 'src/components/Button'; 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 ConfirmStatusChange from 'src/components/ConfirmStatusChange';
import Modal from 'src/components/Modal';
describe('ConfirmStatusChange', () => { const mockedProps = {
const mockedProps = { title: 'please confirm',
title: 'please confirm', description: 'are you sure?',
description: 'are you sure?', onConfirm: jest.fn(),
onConfirm: jest.fn(), };
};
const wrapper = mount( test('opens a confirm modal', () => {
const { getByTestId } = render(
<ConfirmStatusChange {...mockedProps}> <ConfirmStatusChange {...mockedProps}>
{confirm => ( {confirm => (
<> <>
<Button id="btn1" onClick={confirm} /> <Button data-test="btn1" onClick={confirm} />
</> </>
)} )}
</ConfirmStatusChange>, </ConfirmStatusChange>,
{
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
},
); );
it('opens a confirm modal', () => { fireEvent.click(getByTestId('btn1'));
act(() => {
wrapper.find('#btn1').first().props().onClick('foo');
});
wrapper.update(); expect(getByTestId(`${mockedProps.title}-modal`)).toBeInTheDocument();
});
expect(wrapper.find(Modal)).toExist();
}); test('calls the function on confirm', async () => {
const { getByTestId, getByRole } = render(
it('calls the function on confirm', () => { <ConfirmStatusChange {...mockedProps}>
act(() => { {confirm => (
wrapper.find(Button).last().props().onClick(); <>
}); <Button data-test="btn1" onClick={() => confirm('foo')} />
</>
expect(mockedProps.onConfirm).toHaveBeenCalledWith('foo'); )}
}); </ConfirmStatusChange>,
);
fireEvent.click(getByTestId('btn1'));
const confirmInput = getByTestId('delete-modal-input');
fireEvent.change(confirmInput, { target: { value: 'DELETE' } });
const confirmButton = getByRole('button', { name: 'delete' });
fireEvent.click(confirmButton);
await waitFor(() => expect(mockedProps.onConfirm).toHaveBeenCalledTimes(1));
expect(mockedProps.onConfirm).toHaveBeenCalledWith('foo');
}); });

View File

@ -16,16 +16,12 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { mount } from 'enzyme'; import { waitFor, render, fireEvent } from 'spec/helpers/testing-library';
import configureStore from 'redux-mock-store'; import configureStore from 'redux-mock-store';
import fetchMock from 'fetch-mock'; import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import { act } from 'react-dom/test-utils';
import sinon from 'sinon'; import sinon from 'sinon';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
import Modal from 'src/components/Modal';
import { ChangeDatasourceModal } from 'src/components/Datasource'; import { ChangeDatasourceModal } from 'src/components/Datasource';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import mockDatasource from 'spec/fixtures/mockDatasource'; import mockDatasource from 'spec/fixtures/mockDatasource';
const mockStore = configureStore([thunk]); const mockStore = configureStore([thunk]);
@ -57,60 +53,40 @@ fetchMock.get(DATASOURCES_ENDPOINT, { result: [mockDatasource['7__table']] });
fetchMock.get(DATASOURCE_ENDPOINT, DATASOURCE_PAYLOAD); fetchMock.get(DATASOURCE_ENDPOINT, DATASOURCE_PAYLOAD);
fetchMock.get(INFO_ENDPOINT, {}); fetchMock.get(INFO_ENDPOINT, {});
async function mountAndWait(props = mockedProps) { afterEach(() => {
const mounted = mount(<ChangeDatasourceModal store={store} {...props} />, { fetchMock.resetHistory();
wrappingComponent: ThemeProvider, });
wrappingComponentProps: { theme: supersetTheme },
}); const setup = (props = mockedProps) =>
await waitForComponentToPaint(mounted); render(<ChangeDatasourceModal {...props} />, {
useRedux: true,
return mounted; store,
} });
describe('ChangeDatasourceModal', () => { test('renders', () => {
let wrapper; const { getByTestId } = setup();
expect(getByTestId('Swap dataset-modal')).toBeInTheDocument();
beforeEach(async () => { });
wrapper = await mountAndWait();
}); test('fetches datasources', async () => {
setup();
it('renders', () => { await waitFor(() => expect(fetchMock.calls(INFO_ENDPOINT)).toHaveLength(1));
expect(wrapper.find(ChangeDatasourceModal)).toHaveLength(1); });
});
test('renders confirmation message', async () => {
it('renders a Modal', () => { const { findByTestId, getByRole } = setup();
expect(wrapper.find(Modal)).toExist(); const confirmLink = await findByTestId('datasource-link');
}); fireEvent.click(confirmLink);
expect(getByRole('button', { name: 'Proceed' })).toBeInTheDocument();
it('fetches datasources', async () => { });
expect(fetchMock.calls(INFO_ENDPOINT)).toHaveLength(3);
}); test('changes the datasource', async () => {
const { findByTestId, getByRole } = setup();
it('renders confirmation message', async () => { const confirmLink = await findByTestId('datasource-link');
await waitForComponentToPaint(wrapper, 1000); fireEvent.click(confirmLink);
const proceedButton = getByRole('button', { name: 'Proceed' });
act(() => { fireEvent.click(proceedButton);
wrapper.find('[data-test="datasource-link"]').at(0).props().onClick(); await waitFor(() =>
}); expect(fetchMock.calls(/api\/v1\/dataset\/7/)).toHaveLength(1),
);
await waitForComponentToPaint(wrapper);
expect(wrapper.find('.proceed-btn')).toExist();
});
it('changes the datasource', async () => {
await waitForComponentToPaint(wrapper, 1000);
act(() => {
wrapper.find('[data-test="datasource-link"]').at(0).props().onClick();
});
await waitForComponentToPaint(wrapper);
act(() => {
wrapper.find('.proceed-btn').at(0).props().onClick(datasourceData);
});
await waitForComponentToPaint(wrapper);
expect(fetchMock.calls(/api\/v1\/dataset\/7/)).toHaveLength(1);
});
}); });

View File

@ -16,8 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { isValidElement } from 'react'; import { render } from 'spec/helpers/testing-library';
import { shallow } from 'enzyme';
import mockDatasource from 'spec/fixtures/mockDatasource'; import mockDatasource from 'spec/fixtures/mockDatasource';
import CollectionTable from './CollectionTable'; import CollectionTable from './CollectionTable';
@ -27,22 +26,13 @@ const props = {
tableColumns: ['column_name', 'type', 'groupby'], tableColumns: ['column_name', 'type', 'groupby'],
}; };
describe('CollectionTable', () => { test('renders a table', () => {
let wrapper; const { length } = mockDatasource['7__table'].columns;
let el; const { getByRole } = render(<CollectionTable {...props} />);
expect(getByRole('table')).toBeInTheDocument();
beforeEach(() => { expect(
el = <CollectionTable {...props} />; getByRole('table')
wrapper = shallow(el); .getElementsByTagName('tbody')[0]
}); .getElementsByClassName('row'),
).toHaveLength(length);
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);
});
}); });

View File

@ -16,40 +16,34 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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'; import Field from './Field';
describe('Field', () => { const defaultProps = {
const defaultProps = { fieldKey: 'mock',
fieldKey: 'mock', value: '',
value: '', label: 'mock',
label: 'mock', description: 'description',
description: 'description', control: <input type="text" data-test="mock-text-control" />,
control: <TextAreaControl />, onChange: jest.fn(),
onChange: jest.fn(), compact: false,
compact: false, inline: false,
inline: false, };
};
it('should render', () => { test('should render', () => {
const { container } = render(<Field {...defaultProps} />); const { container } = render(<Field {...defaultProps} />);
expect(container).toBeInTheDocument(); expect(container).toBeInTheDocument();
}); });
it('should call onChange', () => { test('should call onChange', () => {
const wrapper = shallow(<Field {...defaultProps} />); const { getByTestId } = render(<Field {...defaultProps} />);
const textArea = wrapper.find(TextAreaControl); const textArea = getByTestId('mock-text-control');
textArea.simulate('change', { target: { value: 'x' } }); fireEvent.change(textArea, { target: { value: 'x' } });
expect(defaultProps.onChange).toHaveBeenCalled(); expect(defaultProps.onChange).toHaveBeenCalled();
}); });
it('should render compact', () => { test('should render compact', () => {
render(<Field {...defaultProps} compact />); render(<Field {...defaultProps} compact />);
expect( expect(screen.queryByText(defaultProps.description)).not.toBeInTheDocument();
screen.queryByText(defaultProps.description),
).not.toBeInTheDocument();
});
}); });

View File

@ -16,88 +16,98 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { isValidElement } from 'react'; import { fireEvent, getByRole, render } from 'spec/helpers/testing-library';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import EditableTable from 'src/components/EditableTitle'; import EditableTable from 'src/components/EditableTitle';
describe('EditableTitle', () => { const mockEvent = {
const callback = sinon.spy(); target: {
const mockProps = { value: 'new title',
title: 'my title', },
canEdit: true, };
onSaveTitle: callback, const mockProps = {
}; title: 'my title',
const mockEvent = { canEdit: true,
target: { onSaveTitle: jest.fn(),
value: 'new title', };
},
}; test('should render title', () => {
let editableWrapper = shallow(<EditableTable {...mockProps} />); const { getByRole } = render(<EditableTable {...mockProps} />);
const notEditableWrapper = shallow( expect(getByRole('button')).toBeInTheDocument();
<EditableTable title="my title" onSaveTitle={callback} />, expect(getByRole('button')).toHaveValue(mockProps.title);
});
test('should not render an input if it is not editable', () => {
const { queryByRole } = render(
<EditableTable title="my title" onSaveTitle={jest.fn()} />,
); );
it('is valid', () => { expect(queryByRole('button')).not.toBeInTheDocument();
expect(isValidElement(<EditableTable {...mockProps} />)).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();
});
describe('should handle click', () => { describe('should handle click', () => {
it('should change title', () => { test('should change title', () => {
editableWrapper.find('input').simulate('click'); const { getByRole, container } = render(<EditableTable {...mockProps} />);
expect(editableWrapper.find('input').props().type).toBe('text'); fireEvent.click(getByRole('button'));
}); expect(container.querySelector('input')?.getAttribute('type')).toEqual(
}); 'text',
);
describe('should handle change', () => { });
afterEach(() => { });
editableWrapper = shallow(<EditableTable {...mockProps} />);
}); describe('should handle change', () => {
it('should change title', () => { test('should change title', () => {
editableWrapper.find('input').simulate('change', mockEvent); const { getByTestId, container } = render(
expect(editableWrapper.find('input').props().value).toBe('new title'); <EditableTable {...mockProps} editing />,
}); );
}); fireEvent.change(getByTestId('editable-title-input'), mockEvent);
expect(container.querySelector('input')).toHaveValue('new title');
describe('should handle blur', () => { });
beforeEach(() => { });
editableWrapper.find('input').simulate('click');
}); describe('should handle blur', () => {
afterEach(() => { const setup = (overrides: Partial<typeof mockProps> = {}) => {
callback.resetHistory(); const selectors = render(<EditableTable {...mockProps} {...overrides} />);
editableWrapper = shallow(<EditableTable {...mockProps} />); fireEvent.click(selectors.getByRole('button'));
}); return selectors;
};
it('default input type should be text', () => {
expect(editableWrapper.find('input').props().type).toBe('text'); test('default input type should be text', () => {
}); const { container } = setup();
expect(container.querySelector('input')?.getAttribute('type')).toEqual(
it('should trigger callback', () => { 'text',
editableWrapper.find('input').simulate('change', mockEvent); );
editableWrapper.find('input').simulate('blur'); });
expect(editableWrapper.find('input').props().type).toBe('button');
expect(callback.callCount).toBe(1); test('should trigger callback', () => {
expect(callback.getCall(0).args[0]).toBe('new title'); const callback = jest.fn();
}); const { getByTestId, container } = setup({ onSaveTitle: callback });
it('should not trigger callback', () => { fireEvent.change(getByTestId('editable-title-input'), mockEvent);
editableWrapper.find('input').simulate('blur'); fireEvent.blur(getByTestId('editable-title-input'));
expect(editableWrapper.find('input').props().type).toBe('button'); expect(callback).toHaveBeenCalledTimes(1);
// no change expect(callback).toHaveBeenCalledWith('new title');
expect(callback.callCount).toBe(0); expect(container.querySelector('input')?.getAttribute('type')).toEqual(
}); 'button',
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'); test('should not trigger callback', () => {
expect(callback.callCount).toBe(0); 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();
});
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();
}); });
}); });

View File

@ -16,42 +16,44 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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 TextControl from 'src/explore/components/controls/TextControl';
import FormRow from 'src/components/FormRow'; import FormRow from 'src/components/FormRow';
jest.mock('@superset-ui/chart-controls', () => ({
...jest.requireActual('@superset-ui/chart-controls'),
InfoTooltipWithTrigger: () => <div data-test="mock-info-tooltip" />,
}));
jest.mock('src/components', () => ({
...jest.requireActual('src/components'),
Row: ({ children }) => <div data-test="mock-row">{children}</div>,
Col: ({ children }) => <div data-test="mock-col">{children}</div>,
}));
const defaultProps = { const defaultProps = {
label: 'Hello', label: 'Hello',
tooltip: 'A tooltip', tooltip: 'A tooltip',
control: <TextControl label="test_cbox" />, control: <TextControl label="test_cbox" />,
}; };
describe('FormRow', () => { const setup = (overrideProps = {}) => {
let wrapper; const props = {
...defaultProps,
const getWrapper = (overrideProps = {}) => { ...overrideProps,
const props = {
...defaultProps,
...overrideProps,
};
return shallow(<FormRow {...props} />);
}; };
return render(<FormRow {...props} />);
};
beforeEach(() => { test('renders an InfoTooltipWithTrigger only if needed', () => {
wrapper = getWrapper(); const { getByTestId, queryByTestId, rerender } = setup();
}); expect(getByTestId('mock-info-tooltip')).toBeInTheDocument();
rerender(<FormRow {...defaultProps} tooltip={null} />);
it('renders an InfoTooltipWithTrigger only if needed', () => { expect(queryByTestId('mock-info-tooltip')).not.toBeInTheDocument();
expect(wrapper.find(InfoTooltipWithTrigger)).toExist(); });
wrapper = getWrapper({ tooltip: null });
expect(wrapper.find(InfoTooltipWithTrigger)).not.toExist(); test('renders a Row and 2 Cols', () => {
}); const { getByTestId, getAllByTestId } = setup();
expect(getByTestId('mock-row')).toBeInTheDocument();
it('renders a Row and 2 Cols', () => { expect(getAllByTestId('mock-col')).toHaveLength(2);
expect(wrapper.find(Row)).toExist();
expect(wrapper.find(Col)).toHaveLength(2);
});
}); });

View File

@ -16,25 +16,29 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { isValidElement } from 'react'; import { render } from 'spec/helpers/testing-library';
import { shallow } from 'enzyme';
import { Tooltip } from 'src/components/Tooltip';
import { IconTooltip } from 'src/components/IconTooltip'; import { IconTooltip } from 'src/components/IconTooltip';
describe('IconTooltip', () => { jest.mock('src/components/Tooltip', () => ({
const mockedProps = { Tooltip: () => <div data-test="mock-tooltip" />,
tooltip: 'This is a tooltip', }));
};
it('renders', () => { const mockedProps = {
expect(isValidElement(<IconTooltip>TEST</IconTooltip>)).toBe(true); tooltip: 'This is a tooltip',
}); };
it('renders with props', () => { test('renders', () => {
expect( const { container } = render(<IconTooltip>TEST</IconTooltip>);
isValidElement(<IconTooltip {...mockedProps}>TEST</IconTooltip>), expect(container).toBeInTheDocument();
).toBe(true); });
}); test('renders with props', () => {
it('renders a tooltip', () => { const { container } = render(
const wrapper = shallow(<IconTooltip {...mockedProps}>TEST</IconTooltip>); <IconTooltip {...mockedProps}>TEST</IconTooltip>,
expect(wrapper.find(Tooltip)).toExist(); );
}); expect(container).toBeInTheDocument();
});
test('renders a tooltip', () => {
const { getByTestId } = render(
<IconTooltip {...mockedProps}>TEST</IconTooltip>,
);
expect(getByTestId('mock-tooltip')).toBeInTheDocument();
}); });

View File

@ -16,18 +16,14 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { act } from 'react-dom/test-utils';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store'; import configureStore from 'redux-mock-store';
import { styledMount as mount } from 'spec/helpers/theming'; import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
import { ReactWrapper } from 'enzyme';
import fetchMock from 'fetch-mock'; 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 { ImportResourceName } from 'src/views/CRUD/types';
import ImportModelsModal from 'src/components/ImportModal'; import ImportModelsModal, {
import Modal from 'src/components/Modal'; ImportModelsModalProps,
} from 'src/components/ImportModal';
const mockStore = configureStore([thunk]); const mockStore = configureStore([thunk]);
const store = mockStore({}); const store = mockStore({});
@ -48,148 +44,98 @@ const requiredProps = {
onHide: () => {}, onHide: () => {},
}; };
describe('ImportModelsModal', () => { afterEach(() => {
let wrapper: ReactWrapper; jest.clearAllMocks();
});
beforeEach(() => { const setup = (overrides: Partial<ImportModelsModalProps> = {}) =>
wrapper = mount(<ImportModelsModal {...requiredProps} />, { render(<ImportModelsModal {...requiredProps} {...overrides} />, { store });
context: { store },
});
});
afterEach(() => { test('renders', () => {
jest.clearAllMocks(); const { container } = setup();
}); expect(container).toBeInTheDocument();
});
it('renders', () => { test('renders a Modal', () => {
expect(wrapper.find(ImportModelsModal)).toExist(); const { getByTestId } = setup();
}); expect(getByTestId('model-modal')).toBeInTheDocument();
});
it('renders a Modal', () => { test('renders "Import database" header', () => {
expect(wrapper.find(Modal)).toExist(); const { getByText } = setup();
}); expect(getByText('Import database')).toBeInTheDocument();
});
it('renders "Import database" header', () => { test('renders a file input field', () => {
expect(wrapper.find('h4').text()).toEqual('Import database'); setup();
}); expect(document.querySelector('input[type="file"]')).toBeInTheDocument();
});
it('renders a file input field', () => { test('should render the close, file, import and cancel buttons', () => {
expect(wrapper.find('input[type="file"]')).toExist(); setup();
}); expect(document.querySelectorAll('button')).toHaveLength(4);
});
it('should render the close, file, import and cancel buttons', () => { test('should render the import button initially disabled', () => {
expect(wrapper.find('button')).toHaveLength(4); const { getByRole } = setup();
}); expect(getByRole('button', { name: 'Import' })).toBeDisabled();
});
it('should render the import button initially disabled', () => { test('should render the import button enabled when a file is selected', async () => {
expect(wrapper.find(Button).at(2).prop('disabled')).toBe(true); const file = new File([new ArrayBuffer(1)], 'model_export.zip');
}); const { getByTestId, getByRole } = setup();
await waitFor(() =>
it('should render the import button enabled when a file is selected', () => { fireEvent.change(getByTestId('model-file-input'), {
const file = new File([new ArrayBuffer(1)], 'model_export.zip'); target: {
act(() => { files: [file],
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);
});
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',
});
});
it('should render password fields when needed for import', () => {
const wrapperWithPasswords = mount(
<ImportModelsModal
{...requiredProps}
passwordFields={['databases/examples.yaml']}
/>,
{
context: { store },
}, },
); }),
expect(wrapperWithPasswords.find('input[type="password"]')).toExist(); );
}); expect(getByRole('button', { name: 'Import' })).toBeEnabled();
});
it('should render ssh_tunnel password fields when needed for import', () => { test('should POST with request header `Accept: application/json`', async () => {
const wrapperWithPasswords = mount( const file = new File([new ArrayBuffer(1)], 'model_export.zip');
<ImportModelsModal const { getByTestId, getByRole } = setup();
{...requiredProps} await waitFor(() =>
sshTunnelPasswordFields={['databases/examples.yaml']} fireEvent.change(getByTestId('model-file-input'), {
/>, target: {
{ files: [file],
context: { store },
}, },
); }),
expect( );
wrapperWithPasswords.find('[data-test="ssh_tunnel_password"]'), fireEvent.click(getByRole('button', { name: 'Import' }));
).toExist(); await waitFor(() =>
}); expect(fetchMock.calls(DATABASE_IMPORT_URL)).toHaveLength(1),
);
it('should render ssh_tunnel private_key fields when needed for import', () => { expect(fetchMock.calls(DATABASE_IMPORT_URL)[0][1]?.headers).toStrictEqual({
const wrapperWithPasswords = mount( Accept: 'application/json',
<ImportModelsModal 'X-CSRFToken': '1234',
{...requiredProps}
sshTunnelPrivateKeyFields={['databases/examples.yaml']}
/>,
{
context: { store },
},
);
expect(
wrapperWithPasswords.find('[data-test="ssh_tunnel_private_key"]'),
).toExist();
});
it('should render ssh_tunnel private_key_password fields when needed for import', () => {
const wrapperWithPasswords = mount(
<ImportModelsModal
{...requiredProps}
sshTunnelPrivateKeyPasswordFields={['databases/examples.yaml']}
/>,
{
context: { store },
},
);
expect(
wrapperWithPasswords.find(
'[data-test="ssh_tunnel_private_key_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();
});
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();
});
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();
});

View File

@ -16,42 +16,28 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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 Label from '.';
import { LabelGallery, options } from './Label.stories'; import { LabelGallery, options } from './Label.stories';
describe('Label', () => { // test the basic component
let wrapper: ReactWrapper; test('renders the base component (no onClick)', () => {
const { container } = render(<Label />);
// test the basic component expect(container).toBeInTheDocument();
it('renders the base component (no onClick)', () => { });
expect(isValidElement(<Label />)).toBe(true);
}); test('works with an onClick handler', () => {
const mockAction = jest.fn();
it('renders with role=undefined when onClick is not present', () => { const { getByText } = render(<Label onClick={mockAction}>test</Label>);
wrapper = mount(<Label />); fireEvent.click(getByText('test'));
expect(wrapper.find('span').prop('role')).toBeUndefined(); expect(mockAction).toHaveBeenCalled();
}); });
it('renders with role="button" when onClick is present', () => { // test stories from the storybook!
const mockAction = jest.fn(); test('renders all the storybook gallery variants', () => {
wrapper = mount(<Label onClick={mockAction} />); const { container } = render(<LabelGallery />);
expect(wrapper.find('span').prop('role')).toBe('button'); expect(container.querySelectorAll('.ant-tag')).toHaveLength(
}); options.length * 2,
);
it('works with an onClick handler', () => {
const mockAction = jest.fn();
wrapper = mount(<Label onClick={mockAction} />);
wrapper.find(Label).simulate('click');
expect(mockAction).toHaveBeenCalled();
});
// test stories from the storybook!
it('renders all the storybook gallery variants', () => {
wrapper = mount(<LabelGallery />);
expect(wrapper.find(Label).length).toEqual(options.length * 2);
});
}); });

View File

@ -16,30 +16,22 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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 '.'; import LastUpdated from '.';
describe('LastUpdated', () => { const updatedAt = new Date('Sat Dec 12 2020 00:00:00 GMT-0800');
let wrapper: ReactWrapper;
const updatedAt = new Date('Sat Dec 12 2020 00:00:00 GMT-0800');
it('renders the base component (no refresh)', () => { test('renders the base component (no refresh)', () => {
const wrapper = mount(<LastUpdated updatedAt={updatedAt} />); const { getByText } = render(<LastUpdated updatedAt={updatedAt} />);
expect(/^Last Updated .+$/.test(wrapper.text())).toBe(true); expect(getByText(/^Last Updated .+$/)).toBeInTheDocument();
}); });
it('renders a refresh action', async () => { test('renders a refresh action', async () => {
const mockAction = jest.fn(); const mockAction = jest.fn();
wrapper = mount(<LastUpdated updatedAt={updatedAt} update={mockAction} />); const { getByLabelText } = render(
await waitForComponentToPaint(wrapper); <LastUpdated updatedAt={updatedAt} update={mockAction} />,
const props = wrapper.find('[aria-label="refresh"]').first().props(); );
if (props.onClick) { fireEvent.click(getByLabelText('refresh'));
props.onClick({} as MouseEvent); expect(mockAction).toHaveBeenCalled();
}
expect(mockAction).toHaveBeenCalled();
});
}); });

View File

@ -16,10 +16,8 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { mount } from 'enzyme'; import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
import { ThemeProvider, supersetTheme } from '@superset-ui/core';
import Toast from 'src/components/MessageToasts/Toast'; import Toast from 'src/components/MessageToasts/Toast';
import { act } from 'react-dom/test-utils';
import mockMessageToasts from './mockMessageToasts'; import mockMessageToasts from './mockMessageToasts';
const props = { const props = {
@ -27,35 +25,22 @@ const props = {
onCloseToast() {}, onCloseToast() {},
}; };
const setup = overrideProps => const setup = overrideProps => render(<Toast {...props} {...overrideProps} />);
mount(<Toast {...props} {...overrideProps} />, {
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
});
describe('Toast', () => { test('should render', () => {
it('should render', () => { const { getByTestId } = setup();
const wrapper = setup(); expect(getByTestId('toast-container')).toBeInTheDocument();
expect(wrapper.find('[data-test="toast-container"]')).toExist(); });
});
test('should render toastText within the div', () => {
it('should render toastText within the div', () => { const { getByTestId } = setup();
const wrapper = setup(); expect(getByTestId('toast-container')).toHaveTextContent(props.toast.text);
const container = wrapper.find('[data-test="toast-container"]'); });
expect(container.hostNodes().childAt(1).text()).toBe(props.toast.text);
}); test('should call onCloseToast upon toast dismissal', async () => {
const onCloseToast = jest.fn();
it('should call onCloseToast upon toast dismissal', async () => const { getByTestId } = setup({ onCloseToast });
act( fireEvent.click(getByTestId('close-button'));
() => await waitFor(() => expect(onCloseToast).toHaveBeenCalledTimes(1));
new Promise(done => { expect(onCloseToast).toHaveBeenCalledWith(props.toast.id);
const onCloseToast = id => {
expect(id).toBe(props.toast.id);
done();
};
const wrapper = setup({ onCloseToast });
wrapper.find('[data-test="close-button"]').props().onClick();
}),
));
}); });

View File

@ -16,35 +16,33 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { shallow } from 'enzyme'; import { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
import Toast from 'src/components/MessageToasts/Toast';
import ToastPresenter from 'src/components/MessageToasts/ToastPresenter'; import ToastPresenter from 'src/components/MessageToasts/ToastPresenter';
import mockMessageToasts from './mockMessageToasts'; import mockMessageToasts from './mockMessageToasts';
describe('ToastPresenter', () => { const props = {
const props = { toasts: mockMessageToasts,
toasts: mockMessageToasts, removeToast() {},
removeToast() {}, };
};
function setup(overrideProps) { function setup(overrideProps) {
const wrapper = shallow(<ToastPresenter {...props} {...overrideProps} />); return render(<ToastPresenter {...props} {...overrideProps} />);
return wrapper; }
}
it('should render a div with id toast-presenter', () => { test('should render a div with id toast-presenter', () => {
const wrapper = setup(); const { container } = setup();
expect(wrapper.find('#toast-presenter')).toExist(); expect(container.querySelector('#toast-presenter')).toBeInTheDocument();
}); });
it('should render a Toast for each toast object', () => { test('should render a Toast for each toast object', () => {
const wrapper = setup(); const { getAllByRole } = setup();
expect(wrapper.find(Toast)).toHaveLength(props.toasts.length); expect(getAllByRole('alert')).toHaveLength(props.toasts.length);
}); });
it('should pass removeToast to the Toast component', () => { test('should pass removeToast to the Toast component', async () => {
const removeToast = () => {}; const removeToast = jest.fn();
const wrapper = setup({ removeToast }); const { getAllByTestId } = setup({ removeToast });
expect(wrapper.find(Toast).first().prop('onCloseToast')).toBe(removeToast); fireEvent.click(getAllByTestId('close-button')[0]);
}); await waitFor(() => expect(removeToast).toHaveBeenCalledTimes(1));
}); });