Convert MeasurementList to TypeScript and add tests

pull/10616/head
Andrew Watkins 2018-03-03 21:56:50 -08:00
parent e2ceba3560
commit f4eca0a228
3 changed files with 212 additions and 55 deletions

View File

@ -23,10 +23,6 @@ interface DatabaseListState {
namespaces: Namespace[] namespaces: Namespace[]
} }
export interface DatabaseListContext {
source: Source
}
const {shape, string} = PropTypes const {shape, string} = PropTypes
class DatabaseList extends PureComponent<DatabaseListProps, DatabaseListState> { class DatabaseList extends PureComponent<DatabaseListProps, DatabaseListState> {

View File

@ -1,52 +1,56 @@
import React, {PropTypes} from 'react' import React, {PureComponent} from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import _ from 'lodash' import _ from 'lodash'
import {showMeasurements} from 'shared/apis/metaQuery' import {showMeasurements} from 'src/shared/apis/metaQuery'
import showMeasurementsParser from 'shared/parsing/showMeasurements' import showMeasurementsParser from 'src/shared/parsing/showMeasurements'
import {Query, Source} from 'src/types'
import TagList from 'src/data_explorer/components/TagList' import TagList from 'src/data_explorer/components/TagList'
import FancyScrollbar from 'shared/components/FancyScrollbar' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
const {func, shape, string} = PropTypes interface Props {
query: Query
querySource: Source
onChooseTag: () => void
onGroupByTag: () => void
onToggleTagAcceptance: () => void
onChooseMeasurement: (measurement: string) => void
}
const MeasurementList = React.createClass({ interface State {
propTypes: { measurements: string[]
query: shape({ filterText: string
database: string, }
measurement: string,
}).isRequired,
onChooseMeasurement: func.isRequired,
onChooseTag: func.isRequired,
onToggleTagAcceptance: func.isRequired,
onGroupByTag: func.isRequired,
querySource: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
}),
},
contextTypes: { const {shape, string} = PropTypes
class MeasurementList extends PureComponent<Props, State> {
constructor(props) {
super(props)
this.state = {
measurements: [],
filterText: '',
}
this.handleEscape = this.handleEscape.bind(this)
this.handleFilterText = this.handleFilterText.bind(this)
this.handleAcceptReject = this.handleAcceptReject.bind(this)
}
public static defaultProps: Partial<Props> = {
querySource: null,
}
public static contextTypes = {
source: shape({ source: shape({
links: shape({ links: shape({
proxy: string.isRequired, proxy: string.isRequired,
}).isRequired, }).isRequired,
}).isRequired, }).isRequired,
}, }
getInitialState() {
return {
measurements: [],
filterText: '',
}
},
getDefaultProps() {
return {
querySource: null,
}
},
componentDidMount() { componentDidMount() {
if (!this.props.query.database) { if (!this.props.query.database) {
@ -54,7 +58,7 @@ const MeasurementList = React.createClass({
} }
this._getMeasurements() this._getMeasurements()
}, }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const {query, querySource} = this.props const {query, querySource} = this.props
@ -71,14 +75,14 @@ const MeasurementList = React.createClass({
} }
this._getMeasurements() this._getMeasurements()
}, }
handleFilterText(e) { handleFilterText(e) {
e.stopPropagation() e.stopPropagation()
this.setState({ this.setState({
filterText: this.refs.filterText.value, filterText: e.target.value,
}) })
}, }
handleEscape(e) { handleEscape(e) {
if (e.key !== 'Escape') { if (e.key !== 'Escape') {
@ -89,12 +93,11 @@ const MeasurementList = React.createClass({
this.setState({ this.setState({
filterText: '', filterText: '',
}) })
}, }
handleAcceptReject(e) { handleAcceptReject() {
e.stopPropagation()
this.props.onToggleTagAcceptance() this.props.onToggleTagAcceptance()
}, }
render() { render() {
return ( return (
@ -105,14 +108,13 @@ const MeasurementList = React.createClass({
? <div className="query-builder--filter"> ? <div className="query-builder--filter">
<input <input
className="form-control input-sm" className="form-control input-sm"
ref="filterText"
placeholder="Filter" placeholder="Filter"
type="text" type="text"
value={this.state.filterText} value={this.state.filterText}
onChange={this.handleFilterText} onChange={this.handleFilterText}
onKeyUp={this.handleEscape} onKeyUp={this.handleEscape}
spellCheck={false} spellCheck={false}
autoComplete={false} autoComplete="false"
/> />
<span className="icon search" /> <span className="icon search" />
</div> </div>
@ -121,7 +123,7 @@ const MeasurementList = React.createClass({
{this.renderList()} {this.renderList()}
</div> </div>
) )
}, }
renderList() { renderList() {
if (!this.props.query.database) { if (!this.props.query.database) {
@ -193,7 +195,7 @@ const MeasurementList = React.createClass({
</FancyScrollbar> </FancyScrollbar>
</div> </div>
) )
}, }
_getMeasurements() { _getMeasurements() {
const {source} = this.context const {source} = this.context
@ -206,14 +208,14 @@ const MeasurementList = React.createClass({
const {errors, measurementSets} = showMeasurementsParser(resp.data) const {errors, measurementSets} = showMeasurementsParser(resp.data)
if (errors.length) { if (errors.length) {
// TODO: display errors in the UI. // TODO: display errors in the UI.
return console.error('InfluxDB returned error(s): ', errors) // eslint-disable-line no-console return console.error('InfluxDB returned error(s): ', errors)
} }
this.setState({ this.setState({
measurements: measurementSets[0].measurements, measurements: measurementSets[0].measurements,
}) })
}) })
}, }
}) }
export default MeasurementList export default MeasurementList

View File

@ -0,0 +1,159 @@
import React from 'react'
import MeasurementList from 'src/shared/components/MeasurementList'
import {shallow} from 'enzyme'
import {query, source} from 'test/resources'
const setup = (override = {}) => {
const props = {
query,
querySource: source,
onChooseTag: () => {},
onGroupByTag: () => {},
onToggleTagAcceptance: () => {},
onChooseMeasurement: () => {},
...override,
}
MeasurementList.prototype._getMeasurements = jest.fn(() => Promise.resolve())
const wrapper = shallow(<MeasurementList {...props} />, {
context: {source},
})
const instance = wrapper.instance() as MeasurementList
return {
props,
wrapper,
instance,
}
}
describe('Shared.Components.MeasurementList', () => {
describe('rendering', () => {
it('renders to the page', () => {
const {wrapper} = setup()
expect(wrapper.exists()).toBe(true)
})
})
describe('lifecycle methods', () => {
describe('componentDidMount', () => {
it('does not fire getMeasurements if there is no database', () => {
const _getMeasurements = jest.fn()
const {instance} = setup({query: {...query, database: ''}})
instance._getMeasurements = _getMeasurements
instance.componentDidMount()
expect(_getMeasurements).not.toHaveBeenCalled()
})
it('does fire _getMeasurements if there is a database', () => {
const _getMeasurements = jest.fn()
const {instance} = setup()
instance._getMeasurements = _getMeasurements
instance.componentDidMount()
expect(_getMeasurements).toHaveBeenCalled()
})
})
describe('componentDidUpdate', () => {
it('does not fire getMeasurements if there is no database', () => {
const _getMeasurements = jest.fn()
const {instance, props} = setup({query: {...query, database: ''}})
instance._getMeasurements = _getMeasurements
instance.componentDidUpdate(props)
expect(_getMeasurements).not.toHaveBeenCalled()
})
it('does not fire _getMeasurements if the database does not change and the sources are equal', () => {
const _getMeasurements = jest.fn()
const {instance, props} = setup()
instance._getMeasurements = _getMeasurements
instance.componentDidUpdate(props)
expect(_getMeasurements).not.toHaveBeenCalled()
})
it('it fires _getMeasurements if there is a change in database', () => {
const _getMeasurements = jest.fn()
const {instance, props} = setup()
instance._getMeasurements = _getMeasurements
instance.componentDidUpdate({
...props,
query: {...query, database: 'diffDb'},
})
expect(_getMeasurements).toHaveBeenCalled()
})
it('it calls _getMeasurements if there is a change in source', () => {
const _getMeasurements = jest.fn()
const {instance, props} = setup()
instance._getMeasurements = _getMeasurements
instance.componentDidUpdate({
...props,
querySource: {...source, id: 'newSource'},
})
expect(_getMeasurements).toHaveBeenCalled()
})
})
})
describe('instance methods', () => {
describe('handleFilterText', () => {
it('sets the filterText state to the event targets value', () => {
const {instance} = setup()
const value = 'spectacs'
const stopPropagation = () => {}
const event = {target: {value}, stopPropagation}
instance.handleFilterText(event)
expect(instance.state.filterText).toBe(value)
})
})
describe('handleEscape', () => {
it('resets fiterText when escape is pressed', () => {
const {instance} = setup()
const key = 'Escape'
const stopPropagation = () => {}
const event = {key, stopPropagation}
instance.setState({filterText: 'foo'})
expect(instance.state.filterText).toBe('foo')
instance.handleEscape(event)
expect(instance.state.filterText).toBe('')
})
it('does not reset fiterText when escape is pressed', () => {
const {instance} = setup()
const key = 'Enter'
const stopPropagation = () => {}
const event = {key, stopPropagation}
instance.setState({filterText: 'foo'})
instance.handleEscape(event)
expect(instance.state.filterText).toBe('foo')
})
})
describe('handleAcceptReject', () => {
it('it calls onToggleTagAcceptance', () => {
const onToggleTagAcceptance = jest.fn()
const {instance} = setup({onToggleTagAcceptance})
instance.handleAcceptReject()
expect(onToggleTagAcceptance).toHaveBeenCalledTimes(1)
})
})
})
})