Skip to content

Commit

Permalink
Added prop noMatchFound
Browse files Browse the repository at this point in the history
First unit tests
Updated dependencies
  • Loading branch information
Sharlaan committed Feb 16, 2017
1 parent 84f2848 commit 6984851
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 17 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ This component requires 4 dependencies :
| name | string | | Required to differentiate between multiple instances of superSelectField in same page. |
| hintText | string | 'Click me' | Placeholder text for the main selections display. |
| hintTextAutocomplete | string | 'Type something' | Placeholder text for the autocomplete. |
| noMatchFound | string | 'No match found' | Placeholder text when the autocomplete filter fails. |
| multiple | bool | false | Include this property to turn superSelectField into a multi-selection dropdown. Checkboxes will appear.|
| value | null, object, object[] | | Selected value(s).<br>/!\ REQUIRED: each object must expose a 'value' property. |
| value | null, object, object[] | null | Selected value(s).<br>/!\ REQUIRED: each object must expose a 'value' property. |
| onChange | function | | Triggers when selecting/unselecting an option from the Menu.<br>signature: (selectedValues, name) with `selectedValues` array of selected values based on selected nodes' value property, and `name` the value of the superSelectField instance's name property |
| children | any | | Datasource is an array of any type of nodes, styled at your convenience.<br>/!\ REQUIRED: each node must expose a `value` property. This value property will be used by default for both option's value and label.<br>A `label` property can be provided to specify a different value than `value`. |
| children | any | [] | Datasource is an array of any type of nodes, styled at your convenience.<br>/!\ REQUIRED: each node must expose a `value` property. This value property will be used by default for both option's value and label.<br>A `label` property can be provided to specify a different value than `value`. |
| nb2show | number | 5 | Number of options displayed from the menu. |
| elementHeight | number | 58 | Height in pixels of one option element. |
| showAutocompleteTreshold | number | 10 | Maximum number of options from which to display the autocomplete search field. |
Expand Down Expand Up @@ -88,8 +89,9 @@ It should open a new page on your default browser @ localhost:3000


## Tests


```
npm test
```
## Contributing
In lieu of a formal style guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code.

Expand Down Expand Up @@ -125,3 +127,5 @@ In lieu of a formal style guide, take care to maintain the existing coding style

- [ ] implement a checkboxRenderer for MenuItem (or expose 2 properties CheckIconFalse & CheckIconTrue)
- [ ] make a PR reimplementing MenuItem.insetChildren replaced with checkPosition={'left'(default) or 'right'}

- [ ] add an example with GooglePlaces
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,20 @@
},
"devDependencies": {
"babel-preset-stage-2": "^6.22.0",
"enzyme": "^2.7.1",
"flag-icon-css": "^2.8.0",
"gh-pages": "^0.12.0",
"material-ui": "^0.16.7",
"material-ui": "^0.17.0",
"react": "^15.4.2",
"react-addons-test-utils": "^15.4.2",
"react-dom": "^15.4.2",
"react-router": "^3.0.2",
"react-scripts": "^0.8.5",
"react-scripts": "^0.9.0",
"react-tap-event-plugin": "^2.0.1",
"standard": "^8.6.0"
},
"peerDependencies": {
"material-ui": ">= 0.15.0 <= 0.16.x",
"material-ui": ">= 0.15.0 <= 0.1x",
"react": "^15.x",
"react-dom": "^15.3.2",
"react-tap-event-plugin": "^1.x || ^2.x"
Expand Down
12 changes: 9 additions & 3 deletions src/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import React from 'react'
import Link from 'react-router/lib/Link'
import './Link.css'

export default ({ style, label, to, activeClassName = 'active' }) => (
<Link to={to} className='base' style={style} activeClassName={activeClassName}>{label}</Link>
)
export default ({ style, label, to, activeClassName = 'active' }) =>
<Link
to={to}
className='base'
style={{ paddingLeft: 16, ...style }}
activeClassName={activeClassName}
>
{label}
</Link>
27 changes: 20 additions & 7 deletions src/SuperSelectField.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class SelectField extends Component {
this.state = {
isOpen: false,
itemsLength: this.getChildrenLength(props.children),
menuItemsfocusState: [],
searchText: ''
}
}
Expand Down Expand Up @@ -166,15 +167,18 @@ class SelectField extends Component {
}

focusTextField () {
if (this.state.itemsLength > 10) {
if (this.state.itemsLength > this.props.showAutocompleteTreshold) {
const input = findDOMNode(this.searchTextField).getElementsByTagName('input')[0]
input.focus()
}
}

focusFirstMenuItem () {
const firstMenuItem = findDOMNode(this.menu).querySelector('[tabindex="0"]')
const firstMenuItem = findDOMNode(this.menu).querySelector('span')
firstMenuItem.focus()
/* const firstMenuItem = this.menuItems.find(item => item !== null)
this.setState({ menuItemsfocusState: [...this.state.menuItemsfocusState] })
firstMenuItem.props.focusState = 'keyboard-focused' */
}

focusLastMenuItem () {
Expand All @@ -183,6 +187,8 @@ class SelectField extends Component {
lastMenuItem.focus()
}

menuItems = []

/**
* Main Component Wrapper methods
*/
Expand Down Expand Up @@ -272,7 +278,7 @@ class SelectField extends Component {
}

render () {
const { value, hintText, hintTextAutocomplete, multiple, children, nb2show,
const { value, hintText, hintTextAutocomplete, noMatchFound, multiple, children, nb2show,
showAutocompleteTreshold, autocompleteFilter, selectionsRenderer,
style, menuStyle, elementHeight, innerDivStyle, selectedMenuItemStyle, menuGroupStyle } = this.props

Expand All @@ -298,6 +304,8 @@ class SelectField extends Component {
<MenuItem
key={groupIndex + index}
tabIndex={index}
ref={ref => (this.menuItems[groupIndex + index] = ref)}
focusState={this.state.menuItemsfocusState[groupIndex + index]}
checked={multiple && isSelected}
leftIcon={(multiple && !isSelected) ? <UnCheckedIcon /> : null}
primaryText={child}
Expand Down Expand Up @@ -376,7 +384,7 @@ class SelectField extends Component {
>
{menuItems}
</InfiniteScroller>
: <MenuItem primaryText='No match found' style={{ cursor: 'default' }} disabled />
: <MenuItem primaryText={noMatchFound} style={{ cursor: 'default' }} disabled />
}
</div>
</Popover>
Expand Down Expand Up @@ -419,6 +427,7 @@ SelectField.propTypes = {
name: PropTypes.string,
hintText: PropTypes.string,
hintTextAutocomplete: PropTypes.string,
noMatchFound: PropTypes.string,
showAutocompleteTreshold: PropTypes.number,
elementHeight: PropTypes.number,
nb2show: PropTypes.number,
Expand All @@ -439,8 +448,8 @@ SelectField.propTypes = {
}
} else if (value !== null && (typeof value !== 'object' || !('value' in value))) {
return new Error(`
'value' of '${componentName}' must be an object including a 'value' property.
Validation failed.`
'value' of '${componentName}' must be an object including a 'value' property.
Validation failed.`
)
}
},
Expand All @@ -456,9 +465,13 @@ SelectField.defaultProps = {
nb2show: 5,
hintText: 'Click me',
hintTextAutocomplete: 'Type something',
noMatchFound: 'No match found',
showAutocompleteTreshold: 10,
elementHeight: 58,
autocompleteFilter: (searchText, text) => !text || (text + '').toLowerCase().includes(searchText.toLowerCase())
autocompleteFilter: (searchText, text) => !text || (text + '').toLowerCase().includes(searchText.toLowerCase()),
value: null,
onChange: () => {},
children: []
}

export default SelectField
131 changes: 131 additions & 0 deletions src/SuperSelectField.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Jest API: https://facebook.github.io/jest/docs/expect.html#content
// Enzyme API: http://airbnb.io/enzyme/docs/api/shallow.html
// How to test a React Component basing on a "Contract":
// https://medium.freecodecamp.com/the-right-way-to-test-react-components-548a4736ab22#.hqprrvawg

/* eslint-env jest */
import React, { PropTypes } from 'react'
import { render } from 'react-dom'
import { shallow, mount } from 'enzyme'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import SuperSelectField from './SuperSelectField'

import injectTapEventPlugin from 'react-tap-event-plugin'
injectTapEventPlugin()

const muiTheme = getMuiTheme()
const shallowWithContext = node => shallow(node, {context: {muiTheme}})
const mountWithContext = node => mount(node, {
context: {muiTheme},
childContextTypes: {muiTheme: PropTypes.object}
})
const testChildren = [
<div key='0' value='1'>Test Child</div>,
<div key='1' value='2'>Test Child</div>
]

describe('Default states, styles, and behaviors', () => {
it('renders without crashing', () => {
const root = document.createElement('div')
render(
<MuiThemeProvider>
<SuperSelectField />
</MuiThemeProvider>
, root)
})

it('expects the menu to be closed by default', () => {
const wrapper = shallowWithContext(<SuperSelectField />)
const menu = wrapper.find('Popover')
expect(menu.props().open).toBe(false)
// can also use wrapper.state('isOpen')
})

it('expects the menu to open when clicked', () => {
const wrapper = shallowWithContext(<SuperSelectField />)
wrapper.simulate('click')
expect(wrapper.find('Popover').props().open).toBe(true)
})

it('expects the menu to render children', () => {
const wrapper = shallowWithContext(<SuperSelectField>{testChildren}</SuperSelectField>)
wrapper.simulate('click') // opens menu
const firstChild = wrapper.find('MenuItem').first()
expect(firstChild.props().primaryText).toBe(testChildren[0])
})

it('should display [hintText] when nothing selected')

it('should pass the default selected item to the underlying MenuItem')
})

describe('When selecting an option', () => {
it('expects the menu to close', () => {
const wrapper = shallowWithContext(<SuperSelectField>{testChildren}</SuperSelectField>)
wrapper.simulate('click') // opens menu
const children = wrapper.find('MenuItem')
expect(children).toHaveLength(2)
children.first().simulate('touchTap')
console.log('children', children)
// expect(wrapper.find('Popover').props().open).toBe(false)
})

it('expects the menu to close when clicking outside')

/* it('expects the onChange handler to be called', () => {
const callback = jest.fn()
const wrapper = shallowWithContext(<SuperSelectField onChange={callback}>{testChildren}</SuperSelectField>)
wrapper.setState({ isOpen: true })
expect(callback).not.toHaveBeenCalled()
wrapper.find('MenuItem').first().simulate('touchTap')
expect(callback).toHaveBeenCalledTimes(1)
// expect(callback).toHaveBeenCalledWith(arg1, arg2, ...)
}) */
})

describe('Children composition', () => {
it('should throw if no `value` prop detected on custom tag')

it('should detect custom html tag with `value` prop')

it('should detect `optgroup` tag')

it('should detect `value` prop on custom tag under `optgroup` tag')
})

describe('Autocomplete usage', () => {
it('should show if more than [showAutocompleteTreshold] items')

it('should display the default [hintTextAutocomplete]')

it('should display the custom [hintTextAutocomplete]')

it('use the default case insensitive filter properly')

it('executes custom filter function properly')

it('should display `No match` when the filter returns null')
})

describe('Selections presenter', () => {
it('should display the default [hintText]')

it('should display custom [hintText] properly')

it('use the default selection renderer properly')

it('executes custom selection renderer function properly')

it('')
})

describe('Focus and keystrokes handling', () => {})

/*
describe('', () => {
it('')
it('')
})
*/

0 comments on commit 6984851

Please sign in to comment.