Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve visualize modal test coverage #2811

Merged
merged 11 commits into from
May 31, 2017
11 changes: 6 additions & 5 deletions superset/assets/javascripts/SqlLab/components/VisualizeModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class VisualizeModal extends React.PureComponent {
this.setStateFromProps(nextProps);
}
setStateFromProps(props) {
if (
if (!props ||
!props.query ||
!props.query.results ||
!props.query.results.columns) {
Expand Down Expand Up @@ -118,16 +118,17 @@ class VisualizeModal extends React.PureComponent {
}
return columns;
}
visualize() {
const vizOptions = {
buildVizOptions() {
return {
chartType: this.state.chartType.value,
datasourceName: this.state.datasourceName,
columns: this.state.columns,
sql: this.props.query.sql,
dbId: this.props.query.dbId,
};

this.props.actions.createDatasource(vizOptions, this)
}
visualize() {
this.props.actions.createDatasource(this.buildVizOptions(), this)
.done(() => {
const columns = Object.keys(this.state.columns).map(k => this.state.columns[k]);
const mainMetric = columns.filter(d => d.agg)[0];
Expand Down
2 changes: 1 addition & 1 deletion superset/assets/spec/javascripts/explorev2/actions_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('reducers', () => {
describe('runQuery', () => {
it('should handle query timeout', () => {
const dispatch = sinon.spy();
const urlStub = sinon.stub(exploreUtils, 'getExploreUrl', () => ('mockURL'));
const urlStub = sinon.stub(exploreUtils, 'getExploreUrl').callsFake(() => ('mockURL'));
const ajaxStub = sinon.stub($, 'ajax');
ajaxStub.yieldsTo('error', { statusText: 'timeout' });

Expand Down
228 changes: 211 additions & 17 deletions superset/assets/spec/javascripts/sqllab/VisualizeModal_spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import { expect } from 'chai';
import sinon from 'sinon';

import $ from 'jquery';
import shortid from 'shortid';
import { queries } from './fixtures';
import { sqlLabReducer } from '../../../javascripts/SqlLab/reducers';
import * as actions from '../../../javascripts/SqlLab/actions';
import VisualizeModal from '../../../javascripts/SqlLab/components/VisualizeModal';
import * as exploreUtils from '../../../javascripts/explorev2/exploreUtils';

Expand Down Expand Up @@ -47,7 +49,16 @@ describe('VisualizeModal', () => {
requiresTime: false,
value: 'dist_bar',
};

const mockChartTypeTB = {
label: 'Time Series - Bar Chart',
requiresTime: true,
value: 'bar',
};
const mockEvent = {
target: {
value: 'mock event value',
},
};
const getVisualizeModalWrapper = () => (
shallow(<VisualizeModal {...mockedProps} />, {
context: { store },
Expand All @@ -66,47 +77,230 @@ describe('VisualizeModal', () => {
expect(wrapper.find(Modal)).to.have.length(1);
});

describe('visualize', () => {
describe('setStateFromProps', () => {
const wrapper = getVisualizeModalWrapper();
const sampleQuery = queries[0];

wrapper.setState({
chartType: mockChartTypeBarChart,
columns: mockColumns,
datasourceName: 'mockDatasourceName',
it('should require valid props parameters', () => {
const spy = sinon.spy(wrapper.instance(), 'setState');
wrapper.instance().setStateFromProps();
expect(spy.callCount).to.equal(0);
spy.restore();
});
it('should set columns state', () => {
wrapper.instance().setStateFromProps({ query: sampleQuery });
expect(wrapper.state().columns).to.deep.equal(mockColumns);
});
});

describe('datasourceName', () => {
const wrapper = getVisualizeModalWrapper();
let stub;
beforeEach(() => {
stub = sinon.stub(shortid, 'generate').returns('abcd');
});
afterEach(() => {
stub.restore();
});

it('should generate data source name from query', () => {
const sampleQuery = queries[0];
const name = wrapper.instance().datasourceName();
expect(name).to.equal(`${sampleQuery.user}-${sampleQuery.db}-${sampleQuery.tab}-abcd`);
});
it('should generate data source name with empty query', () => {
wrapper.setProps({ query: {} });
const name = wrapper.instance().datasourceName();
expect(name).to.equal('undefined-abcd');
});
});

describe('mergedColumns', () => {
const wrapper = getVisualizeModalWrapper();
const oldColumns = {
ds: 1,
gender: 2,
};

it('should merge by column name', () => {
wrapper.setState({ columns: {} });
const mc = wrapper.instance().mergedColumns();
expect(mc).to.deep.equal(mockColumns);
});
it('should not override current state', () => {
wrapper.setState({ columns: oldColumns });

const mc = wrapper.instance().mergedColumns();
expect(mc.ds).to.equal(oldColumns.ds);
expect(mc.gender).to.equal(oldColumns.gender);
});
});

describe('validate', () => {
const wrapper = getVisualizeModalWrapper();
let columnsStub;
beforeEach(() => {
columnsStub = sinon.stub(wrapper.instance(), 'mergedColumns');
});
afterEach(() => {
columnsStub.restore();
});

it('should validate column name', () => {
columnsStub.returns(mockColumns);
wrapper.instance().validate();
expect(wrapper.state().hints).to.have.length(0);
wrapper.instance().mergedColumns.restore();
});
it('should hint invalid column name', () => {
columnsStub.returns({
'&': 1,
});
wrapper.instance().validate();
expect(wrapper.state().hints).to.have.length(1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we assert that the correct hint is in the array like you did here: https://github.com/airbnb/superset/pull/2811/files#diff-5fd795ed9490b587cec77d05effde1d1R169

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

wrapper.instance().mergedColumns.restore();
});
it('should hint empty chartType', () => {
columnsStub.returns(mockColumns);
wrapper.setState({ chartType: null });
wrapper.instance().validate();
expect(wrapper.state().hints).to.have.length(1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we be more specific and check that the hints array contains the correct hint...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now I wrap validation hint message text into constants file, so that in unit test I can check hint message matched with specific content.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

});
it('should check time series', () => {
columnsStub.returns(mockColumns);
wrapper.setState({ chartType: mockChartTypeTB });
wrapper.instance().validate();
expect(wrapper.state().hints).to.have.length(0);

// no is_date columns
columnsStub.returns({
ds: {
is_date: false,
is_dim: false,
name: 'ds',
type: 'STRING',
},
gender: {
is_date: false,
is_dim: true,
name: 'gender',
type: 'STRING',
},
});
wrapper.setState({ chartType: mockChartTypeTB });
wrapper.instance().validate();
expect(wrapper.state().hints).to.have.length(1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we be more specific and check that the hints array contains the correct hint...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

});
it('should validate after change checkbox', () => {
const spy = sinon.spy(wrapper.instance(), 'validate');
columnsStub.returns(mockColumns);

wrapper.instance().changeCheckbox('is_dim', 'gender', mockEvent);
expect(spy.callCount).to.equal(1);
spy.restore();
});
it('should validate after change Agg function', () => {
const spy = sinon.spy(wrapper.instance(), 'validate');
columnsStub.returns(mockColumns);

wrapper.instance().changeAggFunction('num', { label: 'MIN(x)', value: 'min' });
expect(spy.callCount).to.equal(1);
spy.restore();
});
});

it('should validate after change chart type', () => {
const wrapper = getVisualizeModalWrapper();
wrapper.setState({ chartType: mockChartTypeTB });
const spy = sinon.spy(wrapper.instance(), 'validate');

wrapper.instance().changeChartType(mockChartTypeBarChart);
expect(spy.callCount).to.equal(1);
expect(wrapper.state().chartType).to.equal(mockChartTypeBarChart);
});

it('should validate after change datasource name', () => {
const wrapper = getVisualizeModalWrapper();
const spy = sinon.spy(wrapper.instance(), 'validate');

wrapper.instance().changeDatasourceName(mockEvent);
expect(spy.callCount).to.equal(1);
expect(wrapper.state().datasourceName).to.equal(mockEvent.target.value);
});

const vizOptions = {
it('should build viz options', () => {
const wrapper = getVisualizeModalWrapper();
wrapper.setState({ chartType: mockChartTypeTB });
const spy = sinon.spy(wrapper.instance(), 'buildVizOptions');
wrapper.instance().buildVizOptions();
expect(spy.returnValues[0]).to.deep.equal({
chartType: wrapper.state().chartType.value,
datasourceName: wrapper.state().datasourceName,
columns: wrapper.state().columns,
sql: wrapper.instance().props.query.sql,
dbId: wrapper.instance().props.query.dbId,
};
});
});

let spy;
let server;
describe('visualize', () => {
const wrapper = getVisualizeModalWrapper();
const mockOptions = { attr: 'mockOptions' };
wrapper.setState({
chartType: mockChartTypeBarChart,
columns: mockColumns,
datasourceName: 'mockDatasourceName',
});

let ajaxSpy;
beforeEach(() => {
spy = sinon.spy($, 'ajax');
server = sinon.fakeServer.create();
ajaxSpy = sinon.spy($, 'ajax');
sinon.stub(JSON, 'parse').callsFake(() => ({ table_id: 107 }));
sinon.stub(exploreUtils, 'getExploreUrl').callsFake(() => ('mockURL'));
sinon.stub(wrapper.instance(), 'buildVizOptions').callsFake(() => (mockOptions));
sinon.spy(window, 'open');
});
afterEach(() => {
spy.restore();
server.restore();
ajaxSpy.restore();
JSON.parse.restore();
exploreUtils.getExploreUrl.restore();
wrapper.instance().buildVizOptions.restore();
window.open.restore();
});

it('should build request', () => {
wrapper.instance().visualize();
expect(spy.callCount).to.equal(1);
expect(ajaxSpy.callCount).to.equal(1);

const spyCall = spy.getCall(0);
const spyCall = ajaxSpy.getCall(0);
expect(spyCall.args[0].type).to.equal('POST');
expect(spyCall.args[0].url).to.equal('/superset/sqllab_viz/');
expect(spyCall.args[0].data.data).to.equal(JSON.stringify(vizOptions));
expect(spyCall.args[0].data.data).to.equal(JSON.stringify(mockOptions));
});
it('should open new window', () => {
const datasourceSpy = sinon.stub(actions, 'createDatasource').callsFake(() => {
const d = $.Deferred();
d.resolve('done');
return d.promise();
});
wrapper.setProps({ actions: { createDatasource: datasourceSpy } });

wrapper.instance().visualize();
expect(window.open.callCount).to.equal(1);
datasourceSpy.restore();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line could be added to the afterEach block

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

});
it('should notify error', () => {
const datasourceSpy = sinon.stub(actions, 'createDatasource').callsFake(() => {
const d = $.Deferred();
d.reject('error message');
return d.promise();
});
wrapper.setProps({ actions: { createDatasource: datasourceSpy } });
sinon.spy(notify, 'error');

wrapper.instance().visualize();
expect(window.open.callCount).to.equal(0);
expect(notify.error.callCount).to.equal(1);
datasourceSpy.restore();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line could be added to the afterEach block

});
});
});
12 changes: 11 additions & 1 deletion superset/assets/spec/javascripts/sqllab/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,17 @@ export const queries = [
serverId: 141,
resultsKey: null,
results: {
columns: ['col1', 'col2'],
columns: [{
is_date: true,
is_dim: false,
name: 'ds',
type: 'STRING',
}, {
is_date: false,
is_dim: true,
name: 'gender',
type: 'STRING',
}],
data: [
{ col1: 0, col2: 1 },
{ col1: 2, col2: 3 },
Expand Down