Skip to content

Commit

Permalink
Explore view save modal spec (#3110)
Browse files Browse the repository at this point in the history
* split reducer logic for ExploreViewContainer

* fix saveModal component and unit tests

* revert changes in SaveModal_spec.
will make another commit just to improve test coverage for SaveModal component.

* improve test coverage for explore view components:
- SaveModal component
- URLShortLinkButton

* remove comment-out code

* [bugfix] wrong 'Cant have overlap between Series and Breakdowns' (#3254)

* [explore] make edit datasource a basic link (#3244)

* Relying on FAB for font-awesome.min.css (#3261)

* Modernize SQLA pessimistic handling (#3256)

Looks like SQLAlchemy has redefined the best practice around
pessimistic connection handling.

* [webpack] break CSS and JS files while webpackin' (#3262)

* [webpack] break CSS and JS files while webpackin'

* cleaning up some templates

* Fix pylint issue

* import logging (#3264)

* [bugfix] preserve order in groupby (#3268)

Recently in
4c3313b
I introduced an issue where the order of groupby fields might change.

This addresses this issue and will preserve ordering.

* Explicitly add Flask as dependancy (#3252)

* Use sane Celery defaults to prevent tasks from being delayed (#3267)

* Improve the chart type of Visualize in sqllab (#3241)

* Improve the chart type of Visualize in sqllab & Add some css & Fix the link address in the navbar

* add vizTypes filter

* Set default ports Druid (#3266)

For Druid set the default port for the broker and coordinator.

* [explore] Split large reducer logic in ExploreViewContainer (#3088)

* split reducer logic for ExploreViewContainer

* fix saveModal component and unit tests

* revert changes in SaveModal_spec.
will make another commit just to improve test coverage for SaveModal component.

* remove comment-out code

* fix merge confilicts
  • Loading branch information
Grace Guo authored and mistercrunch committed Aug 11, 2017
1 parent 3b24d7d commit b68084b
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 39 deletions.
38 changes: 38 additions & 0 deletions superset/assets/javascripts/explore/actions/exploreActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export function setDatasource(datasource) {
return { type: SET_DATASOURCE, datasource };
}

export const SET_DATASOURCES = 'SET_DATASOURCES';
export function setDatasources(datasources) {
return { type: SET_DATASOURCES, datasources };
}

export const FETCH_DATASOURCE_STARTED = 'FETCH_DATASOURCE_STARTED';
export function fetchDatasourceStarted() {
return { type: FETCH_DATASOURCE_STARTED };
Expand All @@ -29,6 +34,21 @@ export function fetchDatasourceFailed(error) {
return { type: FETCH_DATASOURCE_FAILED, error };
}

export const FETCH_DATASOURCES_STARTED = 'FETCH_DATASOURCES_STARTED';
export function fetchDatasourcesStarted() {
return { type: FETCH_DATASOURCES_STARTED };
}

export const FETCH_DATASOURCES_SUCCEEDED = 'FETCH_DATASOURCES_SUCCEEDED';
export function fetchDatasourcesSucceeded() {
return { type: FETCH_DATASOURCES_SUCCEEDED };
}

export const FETCH_DATASOURCES_FAILED = 'FETCH_DATASOURCES_FAILED';
export function fetchDatasourcesFailed(error) {
return { type: FETCH_DATASOURCES_FAILED, error };
}

export const RESET_FIELDS = 'RESET_FIELDS';
export function resetControls() {
return { type: RESET_FIELDS };
Expand Down Expand Up @@ -61,6 +81,24 @@ export function fetchDatasourceMetadata(datasourceKey, alsoTriggerQuery = false)
};
}

export function fetchDatasources() {
return function (dispatch) {
dispatch(fetchDatasourcesStarted());
const url = '/superset/datasources/';
$.ajax({
type: 'GET',
url,
success: (data) => {
dispatch(setDatasources(data));
dispatch(fetchDatasourcesSucceeded());
},
error(error) {
dispatch(fetchDatasourcesFailed(error.responseJSON.error));
},
});
};
}

export const TOGGLE_FAVE_STAR = 'TOGGLE_FAVE_STAR';
export function toggleFaveStar(isStarred) {
return { type: TOGGLE_FAVE_STAR, isStarred };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,30 @@ import { Checkbox } from 'react-bootstrap';
import sinon from 'sinon';
import { expect } from 'chai';
import { describe, it, beforeEach } from 'mocha';
import { mount } from 'enzyme';
import { shallow } from 'enzyme';

import CheckboxControl from '../../../../javascripts/explore/components/controls/CheckboxControl';
import ControlHeader from '../../../../javascripts/explore/components/ControlHeader';

const defaultProps = {
name: 'show_legend',
onChange: sinon.spy(),
value: false,
label: 'checkbox label',
};

describe('CheckboxControl', () => {
let wrapper;

beforeEach(() => {
wrapper = mount(<CheckboxControl {...defaultProps} />);
wrapper = shallow(<CheckboxControl {...defaultProps} />);
});

it('renders a Checkbox', () => {
expect(wrapper.find(Checkbox)).to.have.lengthOf(1);
const controlHeader = wrapper.find(ControlHeader);
expect(controlHeader).to.have.lengthOf(1);

const headerWrapper = controlHeader.shallow();
expect(headerWrapper.find(Checkbox)).to.have.length(1);
});
});
221 changes: 185 additions & 36 deletions superset/assets/spec/javascripts/explore/components/SaveModal_spec.jsx
Original file line number Diff line number Diff line change
@@ -1,76 +1,225 @@
import React from 'react';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';

import { expect } from 'chai';
import { describe, it, beforeEach } from 'mocha';
import { shallow } from 'enzyme';
import { shallow, mount } from 'enzyme';
import { Modal, Button, Radio } from 'react-bootstrap';
import sinon from 'sinon';

import { defaultFormData } from '../../../../javascripts/explore/stores/store';
import { SaveModal } from '../../../../javascripts/explore/components/SaveModal';

const defaultProps = {
can_edit: true,
onHide: () => ({}),
actions: {
saveSlice: sinon.spy(),
},
form_data: defaultFormData,
user_id: '1',
dashboards: [],
slice: {},
};
import * as exploreUtils from '../../../../javascripts/explore/exploreUtils';
import * as saveModalActions from '../../../../javascripts/explore/actions/saveModalActions';
import SaveModal from '../../../../javascripts/explore/components/SaveModal';

const $ = window.$ = require('jquery');

describe('SaveModal', () => {
let wrapper;
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);

beforeEach(() => {
wrapper = shallow(<SaveModal {...defaultProps} />);
});
const defaultProps = {
onHide: () => ({}),
actions: saveModalActions,
form_data: {},
};
const mockEvent = {
target: {
value: 'mock event target',
},
value: 'mock value',
};
const getWrapper = () => (shallow(<SaveModal {...defaultProps} />, {
context: { store },
}).dive());

it('renders a Modal with 7 inputs and 2 buttons', () => {
const wrapper = getWrapper();
expect(wrapper.find(Modal)).to.have.lengthOf(1);
expect(wrapper.find('input')).to.have.lengthOf(2);
expect(wrapper.find(Button)).to.have.lengthOf(2);
expect(wrapper.find(Radio)).to.have.lengthOf(5);
});

it('does not show overwrite option for new slice', () => {
defaultProps.slice = null;
const wrapperNewSlice = shallow(<SaveModal {...defaultProps} />);
const wrapperNewSlice = getWrapper();
wrapperNewSlice.setProps({ slice: null });
expect(wrapperNewSlice.find('#overwrite-radio')).to.have.lengthOf(0);
expect(wrapperNewSlice.find('#saveas-radio')).to.have.lengthOf(1);
});

it('disable overwrite option for non-owner', () => {
defaultProps.slice = {};
defaultProps.can_overwrite = false;
const wrapperForNonOwner = shallow(<SaveModal {...defaultProps} />);
const wrapperForNonOwner = getWrapper();
wrapperForNonOwner.setProps({ can_overwrite: false });
const overwriteRadio = wrapperForNonOwner.find('#overwrite-radio');
expect(overwriteRadio).to.have.lengthOf(1);
expect(overwriteRadio.prop('disabled')).to.equal(true);
});

it('saves a new slice', () => {
defaultProps.slice = {
slice_id: 1,
slice_name: 'title',
};
defaultProps.can_overwrite = false;
const wrapperForNewSlice = shallow(<SaveModal {...defaultProps} />);
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).to.equal('saveas');
});

it('overwrite a slice', () => {
defaultProps.slice = {
slice_id: 1,
slice_name: 'title',
};
defaultProps.can_overwrite = true;
const wrapperForOverwrite = shallow(<SaveModal {...defaultProps} />);
const wrapperForOverwrite = getWrapper();
const overwriteRadio = wrapperForOverwrite.find('#overwrite-radio');
overwriteRadio.simulate('click');
expect(wrapperForOverwrite.state().action).to.equal('overwrite');
});

it('componentDidMount', () => {
sinon.spy(SaveModal.prototype, 'componentDidMount');
sinon.spy(saveModalActions, 'fetchDashboards');
mount(<SaveModal {...defaultProps} />, {
context: { store },
});
expect(SaveModal.prototype.componentDidMount.calledOnce).to.equal(true);
expect(saveModalActions.fetchDashboards.calledOnce).to.equal(true);

SaveModal.prototype.componentDidMount.restore();
saveModalActions.fetchDashboards.restore();
});

it('onChange', () => {
const wrapper = getWrapper();

wrapper.instance().onChange('newSliceName', mockEvent);
expect(wrapper.state().newSliceName).to.equal(mockEvent.target.value);

wrapper.instance().onChange('saveToDashboardId', mockEvent);
expect(wrapper.state().saveToDashboardId).to.equal(mockEvent.value);

wrapper.instance().onChange('newDashboardName', mockEvent);
expect(wrapper.state().newDashboardName).to.equal(mockEvent.target.value);
});

describe('saveOrOverwrite', () => {
beforeEach(() => {
sinon.stub(exploreUtils, 'getExploreUrl').callsFake(() => ('mockURL'));
sinon.stub(saveModalActions, 'saveSlice').callsFake(() => {
const d = $.Deferred();
d.resolve('done');
return d.promise();
});
});
afterEach(() => {
exploreUtils.getExploreUrl.restore();
saveModalActions.saveSlice.restore();
});

it('should save slice', () => {
const wrapper = getWrapper();
wrapper.instance().saveOrOverwrite(true);
expect(saveModalActions.saveSlice.getCall(0).args[0]).to.equal('mockURL');
});
it('existing dashboard', () => {
const wrapper = getWrapper();
const saveToDashboardId = 100;

wrapper.setState({ addToDash: 'existing' });
wrapper.instance().saveOrOverwrite(true);
expect(wrapper.state().alert).to.equal('Please select a dashboard');

wrapper.setState({ saveToDashboardId });
wrapper.instance().saveOrOverwrite(true);
const args = exploreUtils.getExploreUrl.getCall(0).args;
expect(args[4].save_to_dashboard_id).to.equal(saveToDashboardId);
});
it('new dashboard', () => {
const wrapper = getWrapper();
const newDashboardName = 'new dashboard name';

wrapper.setState({ addToDash: 'new' });
wrapper.instance().saveOrOverwrite(true);
expect(wrapper.state().alert).to.equal('Please enter a dashboard name');

wrapper.setState({ newDashboardName });
wrapper.instance().saveOrOverwrite(true);
const args = exploreUtils.getExploreUrl.getCall(0).args;
expect(args[4].new_dashboard_name).to.equal(newDashboardName);
});
});

describe('should fetchDashboards', () => {
let dispatch;
let request;
let ajaxStub;
const userID = 1;
beforeEach(() => {
dispatch = sinon.spy();
ajaxStub = sinon.stub($, 'ajax');
});
afterEach(() => {
ajaxStub.restore();
});
const mockDashboardData = {
pks: ['value'],
result: [
{ dashboard_title: 'dashboard title' },
],
};
const makeRequest = () => {
request = saveModalActions.fetchDashboards(userID);
request(dispatch);
};

it('makes the ajax request', () => {
makeRequest();
expect(ajaxStub.callCount).to.equal(1);
});

it('calls correct url', () => {
const url = '/dashboardmodelviewasync/api/read?_flt_0_owners=' + userID;
makeRequest();
expect(ajaxStub.getCall(0).args[0].url).to.be.equal(url);
});

it('calls correct actions on error', () => {
ajaxStub.yieldsTo('error', { responseJSON: { error: 'error text' } });
makeRequest();
expect(dispatch.callCount).to.equal(1);
expect(dispatch.getCall(0).args[0].type).to.equal(saveModalActions.FETCH_DASHBOARDS_FAILED);
});

it('calls correct actions on success', () => {
ajaxStub.yieldsTo('success', mockDashboardData);
makeRequest();
expect(dispatch.callCount).to.equal(1);
expect(dispatch.getCall(0).args[0].type)
.to.equal(saveModalActions.FETCH_DASHBOARDS_SUCCEEDED);
});
});

it('removeAlert', () => {
sinon.spy(saveModalActions, 'removeSaveModalAlert');
const wrapper = getWrapper();
wrapper.setProps({ alert: 'old alert' });

wrapper.instance().removeAlert();
expect(saveModalActions.removeSaveModalAlert.callCount).to.equal(1);
expect(wrapper.state().alert).to.be.a('null');
saveModalActions.removeSaveModalAlert.restore();
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from 'react';
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { shallow } from 'enzyme';

import { OverlayTrigger } from 'react-bootstrap';
import URLShortLinkButton from '../../../../javascripts/explore/components/URLShortLinkButton';

describe('URLShortLinkButton', () => {
Expand All @@ -14,4 +16,8 @@ describe('URLShortLinkButton', () => {
it('renders', () => {
expect(React.isValidElement(<URLShortLinkButton {...defaultProps} />)).to.equal(true);
});
it('renders OverlayTrigger', () => {
const wrapper = shallow(<URLShortLinkButton {...defaultProps} />);
expect(wrapper.find(OverlayTrigger)).have.length(1);
});
});

0 comments on commit b68084b

Please sign in to comment.