From 1e8ffdd10bca5760577201f58c8754faa7314d62 Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Fri, 11 Dec 2015 11:03:59 -0800 Subject: [PATCH] update(specs): Add more test coverage to EditableList + refactors --- spec/components/editable-list-spec.jsx | 82 ++++++++++++++++++++++++-- src/components/editable-list.jsx | 33 ++++++----- 2 files changed, 97 insertions(+), 18 deletions(-) diff --git a/spec/components/editable-list-spec.jsx b/spec/components/editable-list-spec.jsx index 9177bab4c..72ab472c9 100644 --- a/spec/components/editable-list-spec.jsx +++ b/spec/components/editable-list-spec.jsx @@ -26,6 +26,60 @@ describe('EditableList', ()=> { }); }); + describe('_onItemEdit', ()=> { + it('enters editing mode when double click when item is not React Element', ()=> { + const list = makeList(['1', '2']); + spyOn(list, 'setState'); + const item = scryRenderedDOMComponentsWithClass(list, 'editable-item')[0]; + + Simulate.doubleClick(item); + + expect(list.setState).toHaveBeenCalledWith({editing: 0}); + }); + + it('enters editing mode when edit icon clicked when item is not React Element', ()=> { + const list = makeList(['1', '2']); + spyOn(list, 'setState'); + const editIcon = scryRenderedDOMComponentsWithClass(list, 'edit-icon')[0]; + + Simulate.click(editIcon); + + expect(list.setState).toHaveBeenCalledWith({editing: 0}); + }); + + it('does not enters editing mode when item is React Element', ()=> { + const item =
; + const list = makeList(['1', item]); + spyOn(list, 'setState'); + list._onItemEdit({}, item, 1); + expect(list.setState).not.toHaveBeenCalled(); + }); + }); + + describe('_onListBlur', ()=> { + it('clears selection when requirements met', ()=> { + const list = makeList(['1', '2'], {allowEmptySelection: true}); + const innerList = findRenderedDOMComponentWithClass(list, 'items-wrapper'); + list._beganEditing = false; + spyOn(list, 'setState'); + + Simulate.blur(innerList); + + expect(list.setState).toHaveBeenCalledWith({selected: null}); + }); + + it('does not clear selection when entering edit mode', ()=> { + const list = makeList(['1', '2'], {allowEmptySelection: true}); + const innerList = findRenderedDOMComponentWithClass(list, 'items-wrapper'); + list._beganEditing = true; + spyOn(list, 'setState'); + + Simulate.blur(innerList); + + expect(list.setState).not.toHaveBeenCalled(); + }); + }); + describe('_onListKeyDown', ()=> { it('calls onItemSelected', ()=> { const onItemSelected = jasmine.createSpy('onItemSelected'); @@ -36,6 +90,26 @@ describe('EditableList', ()=> { expect(onItemSelected).toHaveBeenCalledWith('2', 1); }); + + it('does not select an item when at the bottom of the list and moves down', ()=> { + const onItemSelected = jasmine.createSpy('onItemSelected'); + const list = makeList(['1', '2'], {initialState: {selected: 1}, onItemSelected}); + const innerList = findRenderedDOMComponentWithClass(list, 'items-wrapper'); + + Simulate.keyDown(innerList, {key: 'ArrowDown'}); + + expect(onItemSelected).not.toHaveBeenCalled(); + }); + + it('does not select an item when at the top of the list and moves up', ()=> { + const onItemSelected = jasmine.createSpy('onItemSelected'); + const list = makeList(['1', '2'], {initialState: {selected: 0}, onItemSelected}); + const innerList = findRenderedDOMComponentWithClass(list, 'items-wrapper'); + + Simulate.keyDown(innerList, {key: 'ArrowUp'}); + + expect(onItemSelected).not.toHaveBeenCalled(); + }); }); describe('_onCreateInputKeyDown', ()=> { @@ -79,16 +153,16 @@ describe('EditableList', ()=> { it('binds correct click callbacks', ()=> { const onClick = jasmine.createSpy('onClick'); - const onDoubleClick = jasmine.createSpy('onDoubleClick'); - const item = makeItem('item 1', 0, {}, {onClick, onDoubleClick}); + const onEdit = jasmine.createSpy('onEdit'); + const item = makeItem('item 1', 0, {}, {onClick, onEdit}); Simulate.click(item); expect(onClick.calls[0].args[1]).toEqual('item 1'); expect(onClick.calls[0].args[2]).toEqual(0); Simulate.doubleClick(item); - expect(onDoubleClick.calls[0].args[1]).toEqual('item 1'); - expect(onDoubleClick.calls[0].args[2]).toEqual(0); + expect(onEdit.calls[0].args[1]).toEqual('item 1'); + expect(onEdit.calls[0].args[2]).toEqual(0); }); it('renders correctly when item is selected', ()=> { diff --git a/src/components/editable-list.jsx b/src/components/editable-list.jsx index 51083e7c1..22c8f3729 100644 --- a/src/components/editable-list.jsx +++ b/src/components/editable-list.jsx @@ -59,10 +59,12 @@ class EditableList extends Component { */ /** * If provided, this function will be called when an item is selected via click or arrow - * keys. + * keys. If the selection is cleared, it will receive null. * @callback props.onItemSelected - * @param {(Component|string|number)} selectedItem - The selected item - * @param {number} idx - The index of the selected item + * @param {(Component|string|number)} selectedItem - The selected item or null + * when selection cleared + * @param {number} idx - The index of the selected item or null when selection + * cleared */ /** * If provided, this function will be called when the user has entered a value to create @@ -103,7 +105,7 @@ class EditableList extends Component { constructor(props) { super(props); - this._doubleClickedItem = false; + this._beganEditing = false; this.state = props.initialState || { editing: null, selected: (props.allowEmptySelection ? null : 0), @@ -127,9 +129,11 @@ class EditableList extends Component { } _selectItem = (item, idx)=> { - this.setState({selected: idx}, ()=> { - this.props.onItemSelected(item, idx); - }); + if (this.state.selected !== idx) { + this.setState({selected: idx}, ()=> { + this.props.onItemSelected(item, idx); + }); + } } /** @@ -151,7 +155,7 @@ class EditableList extends Component { } _onEditInputFocus = ()=> { - this._doubleClickedItem = false; + this._beganEditing = false; } _onEditInputKeyDown = (event, item, idx)=> { @@ -180,15 +184,15 @@ class EditableList extends Component { this._selectItem(item, idx); } - _onItemDoubleClick = (event, item, idx)=> { + _onItemEdit = (event, item, idx)=> { if (!React.isValidElement(item)) { - this._doubleClickedItem = true; + this._beganEditing = true; this.setState({editing: idx}); } } _onListBlur = ()=> { - if (!this._doubleClickedItem && this.props.allowEmptySelection) { + if (!this._beganEditing && this.props.allowEmptySelection) { this.setState({selected: null}); } } @@ -265,9 +269,10 @@ class EditableList extends Component { ); } + // handlers object for testing _renderItem = (item, idx, {editing, selected} = this.state, handlers = {})=> { const onClick = handlers.onClick || this._onItemClick; - const onDoubleClick = handlers.onDoubleClick || this._onItemDoubleClick; + const onEdit = handlers.onEdit || this._onItemEdit; const classes = classNames({ 'list-item': true, @@ -288,14 +293,14 @@ class EditableList extends Component { className={classes} key={idx} onClick={_.partial(onClick, _, item, idx)} - onDoubleClick={_.partial(onDoubleClick, _, item, idx)}> + onDoubleClick={_.partial(onEdit, _, item, idx)}> {itemToRender} + onClick={_.partial(onEdit, _, item, idx)} /> ); }