refactor(frontend): migrate 6 Enzyme-based tests to RTL, part 2 (#30281)

Signed-off-by: hainenber <dotronghai96@gmail.com>
This commit is contained in:
Đỗ Trọng Hải 2024-10-02 03:27:47 +07:00 committed by GitHub
parent 15f3ea8d05
commit da7a74e604
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 135 additions and 123 deletions

View File

@ -17,9 +17,10 @@
* under the License.
*/
import '@testing-library/jest-dom';
import { ComponentType } from 'react';
import { shallow } from 'enzyme';
import mockConsole, { RestoreConsole } from 'jest-mock-console';
import { render as renderTestComponent, screen } from '@testing-library/react';
import createLoadableRenderer, {
LoadableRenderer as LoadableRendererType,
} from '../../../src/chart/components/createLoadableRenderer';
@ -67,7 +68,7 @@ describe('createLoadableRenderer', () => {
it('calls onRenderSuccess when succeeds', async () => {
const onRenderSuccess = jest.fn();
const onRenderFailure = jest.fn();
shallow(
renderTestComponent(
<LoadableRenderer
onRenderSuccess={onRenderSuccess}
onRenderFailure={onRenderFailure}
@ -95,7 +96,7 @@ describe('createLoadableRenderer', () => {
});
const onRenderSuccess = jest.fn();
const onRenderFailure = jest.fn();
shallow(
renderTestComponent(
<FailedRenderer
onRenderSuccess={onRenderSuccess}
onRenderFailure={onRenderFailure}
@ -122,7 +123,7 @@ describe('createLoadableRenderer', () => {
loading,
render,
});
shallow(<FailedRenderer />);
renderTestComponent(<FailedRenderer />);
expect(loadChartFailure).toHaveBeenCalledTimes(1);
setTimeout(() => {
expect(render).not.toHaveBeenCalled();
@ -132,12 +133,12 @@ describe('createLoadableRenderer', () => {
it('renders the lazy-load components', () =>
new Promise(done => {
const wrapper = shallow(<LoadableRenderer />);
renderTestComponent(<LoadableRenderer />);
// lazy-loaded component not rendered immediately
expect(wrapper.find(TestComponent)).toHaveLength(0);
expect(screen.queryByText('test')).not.toBeInTheDocument();
setTimeout(() => {
// but rendered after the component is loaded.
expect(wrapper.find(TestComponent)).toHaveLength(1);
expect(screen.queryByText('test')).toBeInTheDocument();
done(undefined);
}, 10);
}));
@ -149,7 +150,7 @@ describe('createLoadableRenderer', () => {
render: () => <div />,
});
expect(() => shallow(<NeverLoadingRenderer />)).not.toThrow();
expect(() => renderTestComponent(<NeverLoadingRenderer />)).not.toThrow();
});
});
});

View File

@ -17,7 +17,6 @@
* under the License.
*/
import { mount } from 'enzyme';
import {
styled,
supersetTheme,
@ -27,6 +26,7 @@ import {
EmotionCacheProvider,
emotionCache,
} from '@superset-ui/core';
import { render } from '@testing-library/react';
describe('@superset-ui/style package', () => {
it('exports a theme', () => {
@ -50,8 +50,8 @@ describe('@superset-ui/style package', () => {
expect(useTheme()).toStrictEqual(supersetTheme);
return <div>test</div>;
}
mount(<ThemeUser />, {
wrappingComponent: ({ children }) => (
render(<ThemeUser />, {
wrapper: ({ children }) => (
<EmotionCacheProvider value={emotionCache}>
<ThemeProvider theme={supersetTheme}>{children}</ThemeProvider>
</EmotionCacheProvider>
@ -64,8 +64,8 @@ describe('@superset-ui/style package', () => {
expect(useTheme).toThrow(/could not find a ThemeContext/);
return <div>test</div>;
}
mount(<ThemeUser />, {
wrappingComponent: ({ children }) => <div>{children}</div>,
render(<ThemeUser />, {
wrapper: ({ children }) => <div>{children}</div>,
});
});
});

View File

@ -17,7 +17,7 @@
* under the License.
*/
import { isValidElement } from 'react';
import { styledMount as mount } from 'spec/helpers/theming';
import { render } from 'spec/helpers/testing-library';
import ColumnElement from 'src/SqlLab/components/ColumnElement';
import { mockedActions, table } from 'src/SqlLab/fixtures';
@ -30,19 +30,25 @@ describe('ColumnElement', () => {
expect(isValidElement(<ColumnElement {...mockedProps} />)).toBe(true);
});
it('renders a proper primary key', () => {
const wrapper = mount(<ColumnElement column={table.columns[0]} />);
expect(wrapper.find('i.fa-key')).toExist();
expect(wrapper.find('.col-name').first().text()).toBe('id');
const { container } = render(<ColumnElement column={table.columns[0]} />);
expect(container.querySelector('i.fa-key')).toBeInTheDocument();
expect(container.querySelector('.col-name')?.firstChild).toHaveTextContent(
'id',
);
});
it('renders a multi-key column', () => {
const wrapper = mount(<ColumnElement column={table.columns[1]} />);
expect(wrapper.find('i.fa-link')).toExist();
expect(wrapper.find('i.fa-bookmark')).toExist();
expect(wrapper.find('.col-name').first().text()).toBe('first_name');
const { container } = render(<ColumnElement column={table.columns[1]} />);
expect(container.querySelector('i.fa-link')).toBeInTheDocument();
expect(container.querySelector('i.fa-bookmark')).toBeInTheDocument();
expect(container.querySelector('.col-name')?.firstChild).toHaveTextContent(
'first_name',
);
});
it('renders a column with no keys', () => {
const wrapper = mount(<ColumnElement column={table.columns[2]} />);
expect(wrapper.find('i')).not.toExist();
expect(wrapper.find('.col-name').first().text()).toBe('last_name');
const { container } = render(<ColumnElement column={table.columns[2]} />);
expect(container.querySelector('i')).not.toBeInTheDocument();
expect(container.querySelector('.col-name')?.firstChild).toHaveTextContent(
'last_name',
);
});
});

View File

@ -16,24 +16,18 @@
* specific language governing permissions and limitations
* under the License.
*/
import sinon from 'sinon';
import configureStore from 'redux-mock-store';
import { mount, shallow } from 'enzyme';
import { DatasourceType } from '@superset-ui/core';
import {
supersetTheme,
ThemeProvider,
DatasourceType,
} from '@superset-ui/core';
import { Menu } from 'src/components/Menu';
import {
DatasourceModal,
ChangeDatasourceModal,
} from 'src/components/Datasource';
fireEvent,
waitFor,
screen,
render,
} from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import DatasourceControl, {
getDatasourceTitle,
} from 'src/explore/components/controls/DatasourceControl';
import Icons from 'src/components/Icons';
import { Tooltip } from 'src/components/Tooltip';
const defaultProps = {
name: 'datasource',
@ -54,9 +48,9 @@ const defaultProps = {
health_check_message: 'Warning message!',
},
actions: {
setDatasource: sinon.spy(),
setDatasource: jest.fn(),
},
onChange: sinon.spy(),
onChange: jest.fn(),
user: {
createdOn: '2021-04-27T18:12:38.952304',
email: 'admin',
@ -71,59 +65,74 @@ const defaultProps = {
};
describe('DatasourceControl', () => {
function setup(overrideProps) {
const setup = (overrideProps = {}) => {
const mockStore = configureStore([]);
const store = mockStore({});
const props = {
...defaultProps,
...overrideProps,
};
return mount(<DatasourceControl {...props} />, {
context: { store },
wrappingComponent: ThemeProvider,
wrappingComponentProps: { theme: supersetTheme },
});
}
return {
rendered: render(<DatasourceControl {...props} />, {
useRedux: true,
useRouter: true,
store,
}),
store,
props,
};
};
it('should not render Modal', () => {
const wrapper = setup();
expect(wrapper.find(DatasourceModal)).toHaveLength(0);
setup();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
it('should not render ChangeDatasourceModal', () => {
const wrapper = setup();
expect(wrapper.find(ChangeDatasourceModal)).toHaveLength(0);
setup();
expect(screen.queryByTestId('Swap dataset-modal')).not.toBeInTheDocument();
});
it('show or hide Edit Datasource option', () => {
let wrapper = setup();
expect(wrapper.find('[data-test="datasource-menu"]')).toExist();
let menuWrapper = shallow(
<div>
{wrapper.find('[data-test="datasource-menu"]').first().prop('overlay')}
</div>,
);
expect(menuWrapper.find(Menu.Item)).toHaveLength(3);
wrapper = setup({
isEditable: false,
it('show or hide Edit Datasource option', async () => {
const {
rendered: { container, rerender },
store,
props,
} = setup();
expect(
container.querySelector('[data-test="datasource-menu-trigger"]'),
).toBeInTheDocument();
userEvent.click(screen.getByLabelText('more-vert'));
await waitFor(() => {
expect(screen.queryAllByRole('menuitem')).toHaveLength(3);
});
rerender(<DatasourceControl {...{ ...props, isEditable: false }} />, {
useRedux: true,
useRouter: true,
store,
});
expect(
container.querySelector('[data-test="datasource-menu-trigger"]'),
).toBeInTheDocument();
userEvent.click(screen.getByLabelText('more-vert'));
await waitFor(() => {
expect(screen.queryAllByRole('menuitem')).toHaveLength(2);
});
expect(wrapper.find('[data-test="datasource-menu"]')).toExist();
menuWrapper = shallow(
<div>
{wrapper.find('[data-test="datasource-menu"]').first().prop('overlay')}
</div>,
);
expect(menuWrapper.find(Menu.Item)).toHaveLength(2);
});
it('should render health check message', () => {
const wrapper = setup();
expect(wrapper.find(Icons.AlertSolid)).toExist();
const tooltip = wrapper.find(Tooltip).at(0);
expect(tooltip.prop('title')).toBe(
defaultProps.datasource.health_check_message,
);
it('should render health check message', async () => {
setup();
const modalTrigger = screen.getByLabelText('alert-solid');
expect(modalTrigger).toBeInTheDocument();
// Hover the modal so healthcheck message can show up
fireEvent.mouseOver(modalTrigger);
await waitFor(() => {
expect(
screen.getByText(defaultProps.datasource.health_check_message),
).toBeInTheDocument();
});
});
it('Gets Datasource Title', () => {

View File

@ -18,7 +18,7 @@
*/
import * as redux from 'react-redux';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import { render, screen, act, waitFor } from 'spec/helpers/testing-library';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
@ -30,7 +30,6 @@ import {
OPERATOR_ENUM_TO_OPERATOR_TYPE,
} from 'src/explore/constants';
import AdhocMetric from 'src/explore/components/controls/MetricControl/AdhocMetric';
import { render, screen, act, waitFor } from '@testing-library/react';
import { supersetTheme, FeatureFlag, ThemeProvider } from '@superset-ui/core';
import * as uiCore from '@superset-ui/core';
import userEvent from '@testing-library/user-event';
@ -133,20 +132,17 @@ function setup(overrides?: Record<string, any>) {
...overrides,
validHandler,
};
const wrapper = shallow(
<AdhocFilterEditPopoverSimpleTabContent {...props} />,
);
return { wrapper, props };
render(<AdhocFilterEditPopoverSimpleTabContent {...props} />);
return props;
}
describe('AdhocFilterEditPopoverSimpleTabContent', () => {
it('renders the simple tab form', () => {
const { wrapper } = setup();
expect(wrapper).toExist();
it('can render the simple tab form', () => {
expect(() => setup()).not.toThrow();
});
it('shows boolean only operators when subject is boolean', () => {
const { props } = setup({
const props = setup({
adhocFilter: new AdhocFilter({
expressionType: ExpressionTypes.Simple,
subject: 'value',
@ -174,7 +170,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
].map(operator => expect(isOperatorRelevant(operator, 'value')).toBe(true));
});
it('shows boolean only operators when subject is number', () => {
const { props } = setup({
const props = setup({
adhocFilter: new AdhocFilter({
expressionType: ExpressionTypes.Simple,
subject: 'value',
@ -203,7 +199,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
});
it('will convert from individual comparator to array if the operator changes to multi', () => {
const { props } = setup();
const props = setup();
const { onOperatorChange } = useSimpleTabFilterProps(props);
onOperatorChange(Operators.In);
expect(props.onChange.calledOnce).toBe(true);
@ -212,7 +208,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
});
it('will convert from array to individual comparators if the operator changes from multi', () => {
const { props } = setup({
const props = setup({
adhocFilter: simpleMultiAdhocFilter,
});
const { onOperatorChange } = useSimpleTabFilterProps(props);
@ -228,7 +224,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
});
it('passes the new adhocFilter to onChange after onComparatorChange', () => {
const { props } = setup();
const props = setup();
const { onComparatorChange } = useSimpleTabFilterProps(props);
onComparatorChange('20');
expect(props.onChange.calledOnce).toBe(true);
@ -238,13 +234,13 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
});
it('will filter operators for table datasources', () => {
const { props } = setup({ datasource: { type: 'table' } });
const props = setup({ datasource: { type: 'table' } });
const { isOperatorRelevant } = useSimpleTabFilterProps(props);
expect(isOperatorRelevant(Operators.Like, 'value')).toBe(true);
});
it('will show LATEST PARTITION operator', () => {
const { props } = setup({
const props = setup({
datasource: {
type: 'table',
datasource_name: 'table1',
@ -263,7 +259,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
expressionType: ExpressionTypes.Simple,
subject: 'ds',
});
const { props } = setup({
const props = setup({
datasource: {
type: 'table',
datasource_name: 'table1',
@ -288,7 +284,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
);
});
it('will not display boolean operators when column type is string', () => {
const { props } = setup({
const props = setup({
datasource: {
type: 'table',
datasource_name: 'table1',
@ -304,7 +300,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
});
});
it('will display boolean operators when column is an expression', () => {
const { props } = setup({
const props = setup({
datasource: {
type: 'table',
datasource_name: 'table1',
@ -325,7 +321,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
});
});
it('sets comparator to true when operator is IS_TRUE', () => {
const { props } = setup();
const props = setup();
const { onOperatorChange } = useSimpleTabFilterProps(props);
onOperatorChange(Operators.IsTrue);
expect(props.onChange.calledOnce).toBe(true);
@ -334,7 +330,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
expect(props.onChange.lastCall.args[0].comparator).toBe(true);
});
it('sets comparator to false when operator is IS_FALSE', () => {
const { props } = setup();
const props = setup();
const { onOperatorChange } = useSimpleTabFilterProps(props);
onOperatorChange(Operators.IsFalse);
expect(props.onChange.calledOnce).toBe(true);
@ -343,7 +339,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
expect(props.onChange.lastCall.args[0].comparator).toBe(false);
});
it('sets comparator to null when operator is IS_NULL or IS_NOT_NULL', () => {
const { props } = setup();
const props = setup();
const { onOperatorChange } = useSimpleTabFilterProps(props);
[Operators.IsNull, Operators.IsNotNull].forEach(op => {
onOperatorChange(op);

View File

@ -16,48 +16,48 @@
* specific language governing permissions and limitations
* under the License.
*/
import sinon from 'sinon';
import { styledMount as mount } from 'spec/helpers/theming';
import { TextAreaEditor } from 'src/components/AsyncAceEditor';
import { TextArea } from 'src/components/Input';
import {
fireEvent,
render,
screen,
waitFor,
} from 'spec/helpers/testing-library';
import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
const defaultProps = {
name: 'x_axis_label',
label: 'X Axis Label',
onChange: sinon.spy(),
onChange: jest.fn(),
};
describe('TextArea', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<TextAreaControl {...defaultProps} />);
});
it('renders a FormControl', () => {
expect(wrapper.find(TextArea)).toExist();
render(<TextAreaControl {...defaultProps} />);
expect(screen.getByRole('textbox')).toBeVisible();
});
it('calls onChange when toggled', () => {
const select = wrapper.find(TextArea);
select.simulate('change', { target: { value: 'x' } });
expect(defaultProps.onChange.calledWith('x')).toBe(true);
render(<TextAreaControl {...defaultProps} />);
const textArea = screen.getByRole('textbox');
fireEvent.change(textArea, { target: { value: 'x' } });
expect(defaultProps.onChange).toHaveBeenCalledWith('x');
});
it('renders a AceEditor when language is specified', () => {
const props = { ...defaultProps };
props.language = 'markdown';
wrapper = mount(<TextAreaControl {...props} />);
expect(wrapper.find(TextArea)).not.toExist();
expect(wrapper.find(TextAreaEditor)).toExist();
it('renders a AceEditor when language is specified', async () => {
const props = { ...defaultProps, language: 'markdown' };
const { container } = render(<TextAreaControl {...props} />);
expect(screen.queryByRole('textbox')).not.toBeInTheDocument();
await waitFor(() => {
expect(container.querySelector('.ace_text-input')).toBeInTheDocument();
});
});
it('calls onAreaEditorChange when entering in the AceEditor', () => {
const props = { ...defaultProps };
props.language = 'markdown';
wrapper = mount(<TextAreaControl {...props} />);
wrapper.simulate('change', { target: { value: 'x' } });
expect(defaultProps.onChange.calledWith('x')).toBe(true);
const props = { ...defaultProps, language: 'markdown' };
render(<TextAreaControl {...props} />);
const textArea = screen.getByRole('textbox');
fireEvent.change(textArea, { target: { value: 'x' } });
expect(defaultProps.onChange).toHaveBeenCalledWith('x');
});
});