| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| import React from 'react'; |
| import configureStore from 'redux-mock-store'; |
| import thunk from 'redux-thunk'; |
| import { bindActionCreators } from 'redux'; |
| import { Provider } from 'react-redux'; |
| |
| import { shallow } from 'enzyme'; |
| import { styledMount as mount } from 'spec/helpers/theming'; |
| import { FormControl } from 'react-bootstrap'; |
| import { Radio } from 'src/common/components/Radio'; |
| import Button from 'src/components/Button'; |
| import sinon from 'sinon'; |
| import fetchMock from 'fetch-mock'; |
| |
| import * as exploreUtils from 'src/explore/exploreUtils'; |
| import * as saveModalActions from 'src/explore/actions/saveModalActions'; |
| import SaveModal, { StyledModal } from 'src/explore/components/SaveModal'; |
| |
| describe('SaveModal', () => { |
| const middlewares = [thunk]; |
| const mockStore = configureStore(middlewares); |
| const initialState = { |
| chart: {}, |
| saveModal: { |
| dashboards: [], |
| }, |
| explore: { |
| can_overwrite: true, |
| user_id: '1', |
| datasource: {}, |
| slice: { |
| slice_id: 1, |
| slice_name: 'title', |
| }, |
| alert: null, |
| }, |
| }; |
| const store = mockStore(initialState); |
| |
| const defaultProps = { |
| onHide: () => ({}), |
| actions: bindActionCreators(saveModalActions, arg => { |
| if (typeof arg === 'function') { |
| return arg(jest.fn); |
| } |
| return arg; |
| }), |
| form_data: { datasource: '107__table' }, |
| }; |
| const mockEvent = { |
| target: { |
| value: 'mock event target', |
| }, |
| value: 10, |
| }; |
| |
| const mockDashboardData = { |
| pks: ['id'], |
| result: [{ id: 'id', dashboard_title: 'dashboard title' }], |
| }; |
| |
| const saveEndpoint = `glob:*/dashboardasync/api/read?_flt_0_owners=${1}`; |
| |
| beforeAll(() => fetchMock.get(saveEndpoint, mockDashboardData)); |
| |
| afterAll(() => fetchMock.restore()); |
| |
| const getWrapper = () => |
| shallow(<SaveModal {...defaultProps} store={store} />) |
| .dive() |
| .dive(); |
| |
| it('renders a Modal with the right set of components', () => { |
| const wrapper = getWrapper(); |
| expect(wrapper.find(StyledModal)).toExist(); |
| expect(wrapper.find(FormControl)).toExist(); |
| expect(wrapper.find(Radio)).toHaveLength(2); |
| |
| const footerWrapper = shallow(wrapper.find(StyledModal).props().footer); |
| expect(footerWrapper.find(Button)).toHaveLength(3); |
| }); |
| |
| it('overwrite radio button is disabled for new slice', () => { |
| const wrapper = getWrapper(); |
| wrapper.setProps({ slice: null }); |
| expect(wrapper.find('#overwrite-radio').prop('disabled')).toBe(true); |
| }); |
| |
| it('disable overwrite option for non-owner', () => { |
| const wrapperForNonOwner = getWrapper(); |
| wrapperForNonOwner.setProps({ can_overwrite: false }); |
| const overwriteRadio = wrapperForNonOwner.find('#overwrite-radio'); |
| expect(overwriteRadio).toHaveLength(1); |
| expect(overwriteRadio.prop('disabled')).toBe(true); |
| }); |
| |
| it('saves a 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'); |
| }); |
| |
| it('overwrite a slice', () => { |
| const wrapperForOverwrite = getWrapper(); |
| const overwriteRadio = wrapperForOverwrite.find('#overwrite-radio'); |
| overwriteRadio.simulate('click'); |
| expect(wrapperForOverwrite.state().action).toBe('overwrite'); |
| }); |
| |
| it('componentDidMount', () => { |
| sinon.spy(defaultProps.actions, 'fetchDashboards'); |
| mount( |
| <Provider store={store}> |
| <SaveModal {...defaultProps} /> |
| </Provider>, |
| ); |
| expect(defaultProps.actions.fetchDashboards.calledOnce).toBe(true); |
| |
| defaultProps.actions.fetchDashboards.restore(); |
| }); |
| |
| it('onChange', () => { |
| const wrapper = getWrapper(); |
| |
| wrapper.instance().onSliceNameChange(mockEvent); |
| expect(wrapper.state().newSliceName).toBe(mockEvent.target.value); |
| |
| wrapper.instance().onDashboardSelectChange(mockEvent); |
| expect(wrapper.state().saveToDashboardId).toBe(mockEvent.value); |
| }); |
| |
| describe('saveOrOverwrite', () => { |
| beforeEach(() => { |
| sinon.stub(exploreUtils, 'getExploreUrl').callsFake(() => 'mockURL'); |
| |
| sinon.stub(defaultProps.actions, 'saveSlice').callsFake(() => |
| Promise.resolve({ |
| dashboard_url: 'http://localhost/mock_dashboard/', |
| slice: { slice_url: '/mock_slice/' }, |
| }), |
| ); |
| }); |
| |
| afterEach(() => { |
| exploreUtils.getExploreUrl.restore(); |
| defaultProps.actions.saveSlice.restore(); |
| }); |
| |
| it('should save slice', () => { |
| const wrapper = getWrapper(); |
| wrapper.instance().saveOrOverwrite(true); |
| const { args } = defaultProps.actions.saveSlice.getCall(0); |
| expect(args[0]).toEqual(defaultProps.form_data); |
| }); |
| |
| it('existing dashboard', () => { |
| const wrapper = getWrapper(); |
| const saveToDashboardId = 100; |
| |
| wrapper.setState({ saveToDashboardId }); |
| wrapper.instance().saveOrOverwrite(true); |
| const { args } = defaultProps.actions.saveSlice.getCall(0); |
| expect(args[1].save_to_dashboard_id).toBe(saveToDashboardId); |
| }); |
| |
| it('new dashboard', () => { |
| const wrapper = getWrapper(); |
| const newDashboardName = 'new dashboard name'; |
| |
| wrapper.setState({ newDashboardName }); |
| wrapper.instance().saveOrOverwrite(true); |
| const { args } = defaultProps.actions.saveSlice.getCall(0); |
| expect(args[1].new_dashboard_name).toBe(newDashboardName); |
| }); |
| |
| describe('should always reload or redirect', () => { |
| const originalLocation = window.location; |
| delete window.location; |
| window.location = { assign: jest.fn() }; |
| const stub = sinon.stub(window.location, 'assign'); |
| |
| afterAll(() => { |
| delete window.location; |
| window.location = originalLocation; |
| }); |
| |
| let wrapper; |
| |
| beforeEach(() => { |
| stub.resetHistory(); |
| wrapper = getWrapper(); |
| }); |
| |
| it('Save & go to dashboard', () => |
| new Promise(done => { |
| wrapper.instance().saveOrOverwrite(true); |
| defaultProps.actions.saveSlice().then(() => { |
| expect(window.location.assign.callCount).toEqual(1); |
| expect(window.location.assign.getCall(0).args[0]).toEqual( |
| 'http://localhost/mock_dashboard/', |
| ); |
| done(); |
| }); |
| })); |
| |
| it('saveas new slice', () => |
| new Promise(done => { |
| wrapper.setState({ |
| action: 'saveas', |
| newSliceName: 'new slice name', |
| }); |
| wrapper.instance().saveOrOverwrite(false); |
| defaultProps.actions.saveSlice().then(() => { |
| expect(window.location.assign.callCount).toEqual(1); |
| expect(window.location.assign.getCall(0).args[0]).toEqual( |
| '/mock_slice/', |
| ); |
| done(); |
| }); |
| })); |
| |
| it('overwrite original slice', () => |
| new Promise(done => { |
| wrapper.setState({ action: 'overwrite' }); |
| wrapper.instance().saveOrOverwrite(false); |
| defaultProps.actions.saveSlice().then(() => { |
| expect(window.location.assign.callCount).toEqual(1); |
| expect(window.location.assign.getCall(0).args[0]).toEqual( |
| '/mock_slice/', |
| ); |
| done(); |
| }); |
| })); |
| }); |
| }); |
| |
| describe('fetchDashboards', () => { |
| let dispatch; |
| let actionThunk; |
| const userID = 1; |
| |
| beforeEach(() => { |
| fetchMock.resetHistory(); |
| dispatch = sinon.spy(); |
| }); |
| |
| const makeRequest = () => { |
| actionThunk = saveModalActions.fetchDashboards(userID); |
| return actionThunk(dispatch); |
| }; |
| |
| it('makes the fetch request', () => |
| makeRequest().then(() => { |
| expect(fetchMock.calls(saveEndpoint)).toHaveLength(1); |
| |
| return Promise.resolve(); |
| })); |
| |
| it('calls correct actions on success', () => |
| makeRequest().then(() => { |
| expect(dispatch.callCount).toBe(1); |
| expect(dispatch.getCall(0).args[0].type).toBe( |
| saveModalActions.FETCH_DASHBOARDS_SUCCEEDED, |
| ); |
| |
| return Promise.resolve(); |
| })); |
| |
| it('calls correct actions on error', () => { |
| fetchMock.get( |
| saveEndpoint, |
| { throws: 'error' }, |
| { overwriteRoutes: true }, |
| ); |
| |
| return makeRequest().then(() => { |
| expect(dispatch.callCount).toBe(1); |
| expect(dispatch.getCall(0).args[0].type).toBe( |
| saveModalActions.FETCH_DASHBOARDS_FAILED, |
| ); |
| |
| fetchMock.get(saveEndpoint, mockDashboardData, { |
| overwriteRoutes: true, |
| }); |
| |
| return Promise.resolve(); |
| }); |
| }); |
| }); |
| |
| it('removeAlert', () => { |
| sinon.spy(defaultProps.actions, 'removeSaveModalAlert'); |
| const wrapper = getWrapper(); |
| wrapper.setProps({ alert: 'old alert' }); |
| |
| wrapper.instance().removeAlert(); |
| expect(defaultProps.actions.removeSaveModalAlert.callCount).toBe(1); |
| expect(wrapper.state().alert).toBeNull(); |
| defaultProps.actions.removeSaveModalAlert.restore(); |
| }); |
| }); |