diff --git a/superset-frontend/packages/superset-ui-core/test/chart/components/createLoadableRenderer.test.tsx b/superset-frontend/packages/superset-ui-core/test/chart/components/createLoadableRenderer.test.tsx index 7057b7407..5fe263589 100644 --- a/superset-frontend/packages/superset-ui-core/test/chart/components/createLoadableRenderer.test.tsx +++ b/superset-frontend/packages/superset-ui-core/test/chart/components/createLoadableRenderer.test.tsx @@ -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( { }); const onRenderSuccess = jest.fn(); const onRenderFailure = jest.fn(); - shallow( + renderTestComponent( { loading, render, }); - shallow(); + renderTestComponent(); 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(); + renderTestComponent(); // 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: () =>
, }); - expect(() => shallow()).not.toThrow(); + expect(() => renderTestComponent()).not.toThrow(); }); }); }); diff --git a/superset-frontend/packages/superset-ui-core/test/style/index.test.tsx b/superset-frontend/packages/superset-ui-core/test/style/index.test.tsx index 6d747f7d8..352ea4b6a 100644 --- a/superset-frontend/packages/superset-ui-core/test/style/index.test.tsx +++ b/superset-frontend/packages/superset-ui-core/test/style/index.test.tsx @@ -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
test
; } - mount(, { - wrappingComponent: ({ children }) => ( + render(, { + wrapper: ({ children }) => ( {children} @@ -64,8 +64,8 @@ describe('@superset-ui/style package', () => { expect(useTheme).toThrow(/could not find a ThemeContext/); return
test
; } - mount(, { - wrappingComponent: ({ children }) =>
{children}
, + render(, { + wrapper: ({ children }) =>
{children}
, }); }); }); diff --git a/superset-frontend/src/SqlLab/components/ColumnElement/ColumnElement.test.tsx b/superset-frontend/src/SqlLab/components/ColumnElement/ColumnElement.test.tsx index 20abcc896..09105bb5f 100644 --- a/superset-frontend/src/SqlLab/components/ColumnElement/ColumnElement.test.tsx +++ b/superset-frontend/src/SqlLab/components/ColumnElement/ColumnElement.test.tsx @@ -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()).toBe(true); }); it('renders a proper primary key', () => { - const wrapper = mount(); - expect(wrapper.find('i.fa-key')).toExist(); - expect(wrapper.find('.col-name').first().text()).toBe('id'); + const { container } = render(); + expect(container.querySelector('i.fa-key')).toBeInTheDocument(); + expect(container.querySelector('.col-name')?.firstChild).toHaveTextContent( + 'id', + ); }); it('renders a multi-key column', () => { - const wrapper = mount(); - 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(); + 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(); - expect(wrapper.find('i')).not.toExist(); - expect(wrapper.find('.col-name').first().text()).toBe('last_name'); + const { container } = render(); + expect(container.querySelector('i')).not.toBeInTheDocument(); + expect(container.querySelector('.col-name')?.firstChild).toHaveTextContent( + 'last_name', + ); }); }); diff --git a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.jsx b/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.jsx index 7fb674fdc..130e96443 100644 --- a/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.jsx +++ b/superset-frontend/src/explore/components/controls/DatasourceControl/DatasourceControl.test.jsx @@ -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(, { - context: { store }, - wrappingComponent: ThemeProvider, - wrappingComponentProps: { theme: supersetTheme }, - }); - } + return { + rendered: render(, { + 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( -
- {wrapper.find('[data-test="datasource-menu"]').first().prop('overlay')} -
, - ); - 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(, { + 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( -
- {wrapper.find('[data-test="datasource-menu"]').first().prop('overlay')} -
, - ); - 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', () => { diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx index 1e352cc38..4bbc3af2f 100644 --- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx +++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx @@ -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) { ...overrides, validHandler, }; - const wrapper = shallow( - , - ); - return { wrapper, props }; + render(); + 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); diff --git a/superset-frontend/src/explore/components/controls/TextAreaControl.test.jsx b/superset-frontend/src/explore/components/controls/TextAreaControl.test.jsx index 402c69934..dff4d3ef5 100644 --- a/superset-frontend/src/explore/components/controls/TextAreaControl.test.jsx +++ b/superset-frontend/src/explore/components/controls/TextAreaControl.test.jsx @@ -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(); - }); - it('renders a FormControl', () => { - expect(wrapper.find(TextArea)).toExist(); + render(); + 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(); + 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(); - 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(); + 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(); - wrapper.simulate('change', { target: { value: 'x' } }); - expect(defaultProps.onChange.calledWith('x')).toBe(true); + const props = { ...defaultProps, language: 'markdown' }; + render(); + const textArea = screen.getByRole('textbox'); + fireEvent.change(textArea, { target: { value: 'x' } }); + expect(defaultProps.onChange).toHaveBeenCalledWith('x'); }); });