diff --git a/superset-frontend/src/explore/components/Control.test.tsx b/superset-frontend/src/explore/components/Control.test.tsx index 9b8b549858..b7b6536a84 100644 --- a/superset-frontend/src/explore/components/Control.test.tsx +++ b/superset-frontend/src/explore/components/Control.test.tsx @@ -16,14 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { mount } from 'enzyme'; -import { - ThemeProvider, - supersetTheme, - promiseTimeout, -} from '@superset-ui/core'; +import { ThemeProvider, supersetTheme } from '@superset-ui/core'; import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import Control, { ControlProps } from 'src/explore/components/Control'; const defaultProps: ControlProps = { @@ -41,54 +36,50 @@ const setup = (overrides = {}) => ( ); -describe('Control', () => { - it('render a control', () => { - render(setup()); +test('render a control', () => { + render(setup()); - const checkbox = screen.getByRole('checkbox'); - expect(checkbox).toBeVisible(); - }); - - it('render null if type is not exit', () => { - render( - setup({ - type: undefined, - }), - ); - expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); - }); - - it('render null if type is not valid', () => { - render( - setup({ - type: 'UnknownControl', - }), - ); - expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); - }); - - it('render null if isVisible is false', () => { - render( - setup({ - isVisible: false, - }), - ); - expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); - }); - - it('call setControlValue if isVisible is false', () => { - const wrapper = mount( - setup({ - isVisible: true, - default: false, - }), - ); - wrapper.setProps({ - isVisible: false, - default: false, - }); - promiseTimeout(() => { - expect(defaultProps.actions.setControlValue).toBeCalled(); - }, 100); - }); + const checkbox = screen.getByRole('checkbox'); + expect(checkbox).toBeVisible(); +}); + +test('render null if type is not exit', () => { + render( + setup({ + type: undefined, + }), + ); + expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); +}); + +test('render null if type is not valid', () => { + render( + setup({ + type: 'UnknownControl', + }), + ); + expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); +}); + +test('render null if isVisible is false', () => { + render( + setup({ + isVisible: false, + }), + ); + expect(screen.queryByRole('checkbox')).not.toBeInTheDocument(); +}); + +test('call setControlValue if isVisible is false', async () => { + const { rerender } = render( + setup({ + isVisible: true, + default: false, + }), + ); + expect(defaultProps.actions.setControlValue).not.toBeCalled(); + rerender(setup({ isVisible: false, default: false })); + await waitFor(() => + expect(defaultProps.actions.setControlValue).toBeCalled(), + ); }); diff --git a/superset-frontend/src/explore/components/SaveModal.test.jsx b/superset-frontend/src/explore/components/SaveModal.test.jsx index 29bb278269..d37760c53a 100644 --- a/superset-frontend/src/explore/components/SaveModal.test.jsx +++ b/superset-frontend/src/explore/components/SaveModal.test.jsx @@ -21,17 +21,26 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { bindActionCreators } from 'redux'; -import { shallow } from 'enzyme'; -import { Radio } from 'src/components/Radio'; -import Button from 'src/components/Button'; +import { + fireEvent, + render, + waitFor, + within, +} from 'spec/helpers/testing-library'; import fetchMock from 'fetch-mock'; import * as saveModalActions from 'src/explore/actions/saveModalActions'; -import SaveModal, { - PureSaveModal, - StyledModal, -} from 'src/explore/components/SaveModal'; -import { BrowserRouter } from 'react-router-dom'; +import SaveModal, { PureSaveModal } from 'src/explore/components/SaveModal'; + +jest.mock('src/components', () => ({ + ...jest.requireActual('src/components'), + AsyncSelect: ({ onChange }) => ( + onChange({ label: value, value })} + /> + ), +})); const middlewares = [thunk]; const mockStore = configureStore(middlewares); @@ -97,141 +106,191 @@ const queryStore = mockStore({ }, }); -const queryDefaultProps = { - ...defaultProps, - form_data: { datasource: '107__query', url_params: { foo: 'bar' } }, -}; - const fetchDashboardsEndpoint = `glob:*/dashboardasync/api/read?_flt_0_owners=${1}`; const fetchChartEndpoint = `glob:*/api/v1/chart/${1}*`; +const fetchDashboardEndpoint = `glob:*/api/v1/dashboard/*`; beforeAll(() => { fetchMock.get(fetchDashboardsEndpoint, mockDashboardData); fetchMock.get(fetchChartEndpoint, { id: 1, dashboards: [1] }); + fetchMock.get(fetchDashboardEndpoint, { + result: [{ id: 'id', dashboard_title: 'dashboard title' }], + }); }); afterAll(() => fetchMock.restore()); -const getWrapper = (props = defaultProps, store = initialStore) => - shallow( - - - , - ) - .dive() - .dive() - .dive() - .dive() - .dive() - .dive() - .dive() - .dive(); +const setup = (props = defaultProps, store = initialStore) => + render(, { + useRouter: true, + store, + }); test('renders a Modal with the right set of components', () => { - const wrapper = getWrapper(); - expect(wrapper.find(StyledModal)).toExist(); - expect(wrapper.find(Radio)).toHaveLength(2); - - const footerWrapper = shallow(wrapper.find(StyledModal).props().footer); - - expect(footerWrapper.find(Button)).toHaveLength(3); + const { getByRole, getByTestId } = setup(); + expect(getByRole('dialog', { name: 'Save chart' })).toBeInTheDocument(); + expect(getByRole('radio', { name: 'Save (Overwrite)' })).toBeInTheDocument(); + expect(getByRole('radio', { name: 'Save as...' })).toBeInTheDocument(); + expect( + within(getByTestId('save-modal-footer')).getAllByRole('button'), + ).toHaveLength(3); }); test('renders the right footer buttons', () => { - const wrapper = getWrapper(); - const footerWrapper = shallow(wrapper.find(StyledModal).props().footer); - const saveAndGoDash = footerWrapper - .find('#btn_modal_save_goto_dash') - .getElement(); - const save = footerWrapper.find('#btn_modal_save').getElement(); - expect(save.props.children).toBe('Save'); - expect(saveAndGoDash.props.children).toBe('Save & go to dashboard'); + const { getByTestId } = setup(); + expect( + within(getByTestId('save-modal-footer')).getByRole('button', { + name: 'Cancel', + }), + ).toBeInTheDocument(); + expect( + within(getByTestId('save-modal-footer')).getByRole('button', { + name: 'Save & go to dashboard', + }), + ).toBeInTheDocument(); + expect( + within(getByTestId('save-modal-footer')).getByRole('button', { + name: 'Save', + }), + ).toBeInTheDocument(); }); test('does not render a message when overriding', () => { - const wrapper = getWrapper(); - wrapper.setState({ - action: 'overwrite', - }); + const { getByRole, queryByRole } = setup(); + + fireEvent.click(getByRole('radio', { name: 'Save (Overwrite)' })); expect( - wrapper.find('[message="A new chart will be created."]'), - ).not.toExist(); + queryByRole('alert', { name: 'A new chart will be created.' }), + ).not.toBeInTheDocument(); }); test('renders a message when saving as', () => { - const wrapper = getWrapper(); - wrapper.setState({ - action: 'saveas', - }); - expect(wrapper.find('[message="A new chart will be created."]')).toExist(); + const { getByRole } = setup( + {}, + mockStore({ + ...initialState, + explore: { + ...initialState.explore, + slice: { + ...initialState.explore.slice, + is_managed_externally: true, + }, + }, + }), + ); + fireEvent.click(getByRole('radio', { name: 'Save as...' })); + expect(getByRole('alert')).toHaveTextContent('A new chart will be created.'); }); -test('renders a message when a new dashboard is selected', () => { - const wrapper = getWrapper(); - wrapper.setState({ - dashboard: { label: 'Test new dashboard', value: 'Test new dashboard' }, - }); - expect( - wrapper.find('[message="A new dashboard will be created."]'), - ).toExist(); +test('renders a message when a new dashboard is selected', async () => { + const { getByRole, getByTestId } = setup(); + + const selection = getByTestId('mock-async-select'); + fireEvent.change(selection, { target: { value: 'Test new dashboard' } }); + + expect(getByRole('alert')).toHaveTextContent( + 'A new dashboard will be created.', + ); }); test('renders a message when saving as with new dashboard', () => { - const wrapper = getWrapper(); - wrapper.setState({ - action: 'saveas', - dashboard: { label: 'Test new dashboard', value: 'Test new dashboard' }, - }); - expect( - wrapper.find('[message="A new chart and dashboard will be created."]'), - ).toExist(); + const { getByRole, getByTestId } = setup( + {}, + mockStore({ + ...initialState, + explore: { + ...initialState.explore, + slice: { + ...initialState.explore.slice, + is_managed_externally: true, + }, + }, + }), + ); + fireEvent.click(getByRole('radio', { name: 'Save as...' })); + const selection = getByTestId('mock-async-select'); + fireEvent.change(selection, { target: { value: 'Test new dashboard' } }); + + expect(getByRole('alert')).toHaveTextContent( + 'A new chart and dashboard will be created.', + ); }); test('disables overwrite option for new slice', () => { - const wrapper = getWrapper(); - wrapper.setProps({ slice: null }); - expect(wrapper.find('#overwrite-radio').prop('disabled')).toBe(true); + const { getByRole } = setup( + {}, + mockStore({ + ...initialState, + explore: { + ...initialState.explore, + slice: null, + }, + }), + ); + expect(getByRole('radio', { name: 'Save (Overwrite)' })).toBeDisabled(); }); test('disables overwrite option for non-owner', () => { - const wrapperForNonOwner = getWrapper(); - wrapperForNonOwner.setProps({ user: { userId: 2 } }); - const overwriteRadio = wrapperForNonOwner.find('#overwrite-radio'); - expect(overwriteRadio).toHaveLength(1); - expect(overwriteRadio.prop('disabled')).toBe(true); + const { getByRole } = setup( + {}, + mockStore({ + ...initialState, + user: { userId: 2 }, + }), + ); + expect(getByRole('radio', { name: 'Save (Overwrite)' })).toBeDisabled(); }); -test('sets action when saving as new slice', () => { - const wrapperForNewSlice = getWrapper(); - wrapperForNewSlice.setProps({ can_overwrite: false }); - wrapperForNewSlice.instance().changeAction('saveas'); - const saveasRadio = wrapperForNewSlice.find('#saveas-radio'); - saveasRadio.simulate('click'); - expect(wrapperForNewSlice.state().action).toBe('saveas'); -}); - -test('sets action when overwriting slice', () => { - const wrapperForOverwrite = getWrapper(); - const overwriteRadio = wrapperForOverwrite.find('#overwrite-radio'); - overwriteRadio.simulate('click'); - expect(wrapperForOverwrite.state().action).toBe('overwrite'); -}); - -test('updates slice name and selected dashboard', () => { - const wrapper = getWrapper(); +test('updates slice name and selected dashboard', async () => { const dashboardId = mockEvent.value; + const saveDataset = jest.fn().mockResolvedValue(); + const createDashboard = jest.fn().mockResolvedValue({ id: dashboardId }); + const saveSliceFailed = jest.fn(); + const setFormData = jest.fn(); + const createSlice = jest.fn().mockResolvedValue({ id: 1 }); - wrapper.instance().onSliceNameChange(mockEvent); - expect(wrapper.state().newSliceName).toBe(mockEvent.target.value); + const { getByRole, getByTestId } = setup( + { + actions: { + saveDataset, + createDashboard, + saveSliceFailed, + setFormData, + createSlice, + }, + }, + queryStore, + ); - wrapper.instance().onDashboardChange({ value: dashboardId }); - expect(wrapper.state().dashboard.value).toBe(dashboardId); + fireEvent.change(getByTestId('new-chart-name'), mockEvent); + fireEvent.change(getByTestId('new-dataset-name'), mockEvent); + const selection = getByTestId('mock-async-select'); + fireEvent.change(selection, { target: { value: dashboardId } }); + + expect(getByRole('button', { name: 'Save' })).toBeEnabled(); + + fireEvent.click(getByRole('button', { name: 'Save' })); + expect(saveDataset).toHaveBeenCalledWith( + expect.objectContaining({ + datasourceName: mockEvent.target.value, + }), + ); + await waitFor(() => + expect(fetchMock.calls(fetchDashboardEndpoint)).toHaveLength(1), + ); + expect(fetchMock.calls(fetchDashboardEndpoint)[0][0]).toEqual( + expect.stringContaining(`dashboard/${dashboardId}`), + ); + expect(createSlice).toHaveBeenCalledWith( + mockEvent.target.value, + expect.anything(), + expect.anything(), + ); }); test('set dataset name when chart source is query', () => { - const wrapper = getWrapper(queryDefaultProps, queryStore); - expect(wrapper.find('[data-test="new-dataset-name"]')).toExist(); - expect(wrapper.state().datasetName).toBe('test'); + const { getByTestId } = setup({}, queryStore); + expect(getByTestId('new-dataset-name')).toHaveValue('test'); }); test('make sure slice_id in the URLSearchParams before the redirect', () => {