-
Notifications
You must be signed in to change notification settings - Fork 14.4k
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
Changes from all commits
2353248
5eec1d6
31cbba8
132e402
472bcb9
8a4f189
532845d
2644115
bfe2497
9483f36
e7ddd93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,11 @@ 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 { VISUALIZE_VALIDATION_ERRORS } from '../../../javascripts/SqlLab/constants'; | ||
import VisualizeModal from '../../../javascripts/SqlLab/components/VisualizeModal'; | ||
import * as exploreUtils from '../../../javascripts/explore/exploreUtils'; | ||
|
||
|
@@ -47,7 +50,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 }, | ||
|
@@ -66,47 +78,234 @@ 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); | ||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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... There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
expect(wrapper.state().hints[0]) | ||
.to.have.string(VISUALIZE_VALIDATION_ERRORS.REQUIRE_CHART_TYPE); | ||
}); | ||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's check this hint string as well like you did here: https://github.com/airbnb/superset/pull/2811/files#diff-5fd795ed9490b587cec77d05effde1d1R169 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
expect(wrapper.state().hints[0]).to.have.string(VISUALIZE_VALIDATION_ERRORS.REQUIRE_TIME); | ||
}); | ||
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; | ||
let datasourceSpy; | ||
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'); | ||
datasourceSpy = sinon.stub(actions, 'createDatasource'); | ||
}); | ||
afterEach(() => { | ||
spy.restore(); | ||
server.restore(); | ||
ajaxSpy.restore(); | ||
JSON.parse.restore(); | ||
exploreUtils.getExploreUrl.restore(); | ||
wrapper.instance().buildVizOptions.restore(); | ||
window.open.restore(); | ||
datasourceSpy.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', () => { | ||
datasourceSpy.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); | ||
}); | ||
it('should notify error', () => { | ||
datasourceSpy.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); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed