From 0ca6335d92c89982c16467066a820afc481cdabb Mon Sep 17 00:00:00 2001 From: Allen Date: Tue, 17 Oct 2017 10:23:36 -0500 Subject: [PATCH] refine row selection to wrapper * refine row selection code base to wrapper * refine testing for moving row selection code base to wrapper --- .../src/bootstrap-table.js | 59 +------- .../react-bootstrap-table2/src/container.js | 24 +++- .../src/row-selection/wrapper.js | 77 +++++++++++ .../test/bootstrap-table.test.js | 110 --------------- .../test/row-selection/wrapper.test.js | 126 ++++++++++++++++++ 5 files changed, 230 insertions(+), 166 deletions(-) create mode 100644 packages/react-bootstrap-table2/src/row-selection/wrapper.js create mode 100644 packages/react-bootstrap-table2/test/row-selection/wrapper.test.js diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index d0332c0..41ef625 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -15,11 +15,8 @@ class BootstrapTable extends PropsBaseResolver(Component) { this.validateProps(); this.handleSort = this.handleSort.bind(this); - this.handleRowSelect = this.handleRowSelect.bind(this); - this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this); this.state = { - data: props.store.get(), - selectedRowKeys: props.store.getSelectedRowKeys() + data: props.store.get() }; } @@ -57,11 +54,11 @@ class BootstrapTable extends PropsBaseResolver(Component) { }); const cellSelectionInfo = this.resolveCellSelectionProps({ - onRowSelect: this.handleRowSelect + onRowSelect: this.props.onRowSelect }); const headerCellSelectionInfo = this.resolveHeaderCellSelectionProps({ - onAllRowsSelect: this.handleAllRowsSelect, + onAllRowsSelect: this.props.onAllRowsSelect, selected: store.selected, allRowsSelected: store.isAllRowsSelected() }); @@ -86,59 +83,13 @@ class BootstrapTable extends PropsBaseResolver(Component) { noDataIndication={ noDataIndication } cellEdit={ cellEditInfo } selectRow={cellSelectionInfo} - selectedRowKeys={this.state.selectedRowKeys} + selectedRowKeys={store.getSelectedRowKeys()} /> ); } - /** - * row selection handler - * @param {String} rowKey - row key of what was selected. - * @param {Boolean} checked - next checked status of input button. - */ - handleRowSelect(rowKey, checked) { - const { selectRow: { mode }, store } = this.props; - const { ROW_SELECT_SINGLE } = Const; - - let currSelected = [...store.getSelectedRowKeys()]; - - if (mode === ROW_SELECT_SINGLE) { // when select mode is radio - currSelected = [rowKey]; - } else if (checked) { // when select mode is checkbox - currSelected.push(rowKey); - } else { - currSelected = currSelected.filter(value => value !== rowKey); - } - - store.setSelectedRowKeys(currSelected); - - this.setState(() => ({ - selectedRowKeys: currSelected - })); - } - - /** - * handle all rows selection on header cell by store.selected or given specific result. - * @param {Boolean} option - customized result for all rows selection - */ - handleAllRowsSelect(option) { - const { store } = this.props; - const selected = store.isAnySelectedRow(); - - // set next status of all row selected by store.selected or customizing by user. - const result = option || !selected; - - const currSelected = result ? store.selectAllRowKeys() : []; - - store.setSelectedRowKeys(currSelected); - - this.setState(() => ({ - selectedRowKeys: currSelected - })); - } - handleSort(column) { const { store } = this.props; store.sortBy(column); @@ -180,6 +131,8 @@ BootstrapTable.propTypes = { selectRow: PropTypes.shape({ mode: PropTypes.oneOf([Const.ROW_SELECT_SINGLE, Const.ROW_SELECT_MULTIPLE]).isRequired }), + onRowSelect: PropTypes.func, + onAllRowsSelect: PropTypes.func, onCellUpdate: PropTypes.func, onStartEditing: PropTypes.func, onEscapeEditing: PropTypes.func, diff --git a/packages/react-bootstrap-table2/src/container.js b/packages/react-bootstrap-table2/src/container.js index f635789..fe35f9f 100644 --- a/packages/react-bootstrap-table2/src/container.js +++ b/packages/react-bootstrap-table2/src/container.js @@ -5,6 +5,7 @@ import React, { Component } from 'react'; import Store from './store/base'; import CellEditWrapper from './cell-edit/wrapper'; +import RowSelectionWrapper from './row-selection/wrapper'; import _ from './utils'; const withDataStore = (Base) => { @@ -35,10 +36,10 @@ const withDataStore = (Base) => { if (_.isObject(response)) { const { value } = response; this.store.edit(rowId, dataField, value || newValue); - this.table.completeEditing(); + this.cellEditWrapper.completeEditing(); } }).catch((e) => { - this.table.updateEditingWithErr(e.message); + this.cellEditWrapper.updateEditingWithErr(e.message); }); } return false; @@ -49,13 +50,24 @@ const withDataStore = (Base) => { this.table = node } + ref={ node => this.cellEditWrapper = node } elem={ elem } onUpdateCell={ this.handleUpdateCell } /> ); } + renderRowSelection(elem) { + return ( + + ); + } + render() { const baseProps = { ...this.props, @@ -63,9 +75,15 @@ const withDataStore = (Base) => { }; let element = React.createElement(Base, baseProps); + + if (this.props.selectRow) { + element = this.renderRowSelection(element); + } + if (this.props.cellEdit) { element = this.renderCellEdit(element); } + return element; } }; diff --git a/packages/react-bootstrap-table2/src/row-selection/wrapper.js b/packages/react-bootstrap-table2/src/row-selection/wrapper.js new file mode 100644 index 0000000..ab2fb9f --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-selection/wrapper.js @@ -0,0 +1,77 @@ +/* eslint arrow-body-style: 0 */ +/* eslint react/prop-types: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import Const from '../const'; + +class RowSelectionWrapper extends Component { + constructor(props) { + super(props); + this.handleRowSelect = this.handleRowSelect.bind(this); + this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this); + this.state = { + selectedRowKeys: props.store.getSelectedRowKeys() + }; + } + + /** + * row selection handler + * @param {String} rowKey - row key of what was selected. + * @param {Boolean} checked - next checked status of input button. + */ + handleRowSelect(rowKey, checked) { + const { selectRow: { mode }, store } = this.props; + const { ROW_SELECT_SINGLE } = Const; + + let currSelected = [...store.getSelectedRowKeys()]; + + if (mode === ROW_SELECT_SINGLE) { // when select mode is radio + currSelected = [rowKey]; + } else if (checked) { // when select mode is checkbox + currSelected.push(rowKey); + } else { + currSelected = currSelected.filter(value => value !== rowKey); + } + + store.setSelectedRowKeys(currSelected); + + this.setState(() => ({ + selectedRowKeys: currSelected + })); + } + + /** + * handle all rows selection on header cell by store.selected or given specific result. + * @param {Boolean} option - customized result for all rows selection + */ + handleAllRowsSelect(option) { + const { store } = this.props; + const selected = store.isAnySelectedRow(); + + // set next status of all row selected by store.selected or customizing by user. + const result = option || !selected; + + const currSelected = result ? store.selectAllRowKeys() : []; + + store.setSelectedRowKeys(currSelected); + + this.setState(() => ({ + selectedRowKeys: currSelected + })); + } + + render() { + return React.cloneElement(this.props.elem, { + onRowSelect: this.handleRowSelect, + onAllRowsSelect: this.handleAllRowsSelect + }); + } +} + +RowSelectionWrapper.propTypes = { + elem: PropTypes.element.isRequired, + store: PropTypes.object.isRequired +}; + +export default RowSelectionWrapper; diff --git a/packages/react-bootstrap-table2/test/bootstrap-table.test.js b/packages/react-bootstrap-table2/test/bootstrap-table.test.js index 95cc99e..36241dc 100644 --- a/packages/react-bootstrap-table2/test/bootstrap-table.test.js +++ b/packages/react-bootstrap-table2/test/bootstrap-table.test.js @@ -161,114 +161,4 @@ describe('BootstrapTable', () => { expect(body.props().cellEdit.onUpdate).toBeDefined(); }); }); - - describe('handleRowSelect', () => { - const rowKey = 1; - - describe('when selectRow.mode is radio', () => { - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('state.selectedRowKeys should contain only single key', () => { - wrapper.instance().handleRowSelect(rowKey); - expect(wrapper.state('selectedRowKeys')).toEqual([rowKey]); - - wrapper.instance().handleRowSelect(rowKey); - expect(wrapper.state('selectedRowKeys')).toEqual([rowKey]); - }); - }); - - describe('when selectRow.mode is checbox', () => { - beforeEach(() => { - wrapper = shallow( - - ); - }); - describe('if checked is false', () => { - it('state.selectedRowKeys should pop selected row key', () => { - wrapper.instance().handleRowSelect(rowKey, false); - - expect(wrapper.state('selectedRowKeys')).not.toContain(rowKey); - }); - }); - - describe('if checked is true', () => { - it('state.selectedRowKeys should push one extra key', () => { - wrapper.instance().handleRowSelect(rowKey, true); - - expect(wrapper.state('selectedRowKeys')).toContain(rowKey); - }); - }); - }); - }); - - describe('handleAllRowsSelect', () => { - beforeEach(() => { - wrapper = shallow( - - ); - }); - - describe('when customized option was not given', () => { - describe('when nothing was selected', () => { - it('should select all rows', () => { - store.setSelectedRowKeys([]); - - wrapper.instance().handleAllRowsSelect(); - - expect(wrapper.state('selectedRowKeys').length).toBe(data.length); - }); - }); - - describe('when one or more than one row was selected', () => { - it('should unselect all rows', () => { - store.setSelectedRowKeys([1]); - - wrapper.instance().handleAllRowsSelect(); - - expect(wrapper.state('selectedRowKeys').length).toBe(0); - }); - }); - }); - - describe('when customized option was given', () => { - describe('when option is truthy', () => { - it('should select all rows', () => { - wrapper.instance().handleAllRowsSelect(true); - - expect(wrapper.state('selectedRowKeys').length).toBe(data.length); - }); - }); - - describe('when option is falsy', () => { - it('should unselect all rows', () => { - store.setSelectedRowKeys([1]); - - wrapper.instance().handleAllRowsSelect(false); - - expect(wrapper.state('selectedRowKeys').length).toBe(0); - }); - }); - }); - }); }); diff --git a/packages/react-bootstrap-table2/test/row-selection/wrapper.test.js b/packages/react-bootstrap-table2/test/row-selection/wrapper.test.js new file mode 100644 index 0000000..1404259 --- /dev/null +++ b/packages/react-bootstrap-table2/test/row-selection/wrapper.test.js @@ -0,0 +1,126 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import Store from '../../src/store/base'; +import BootstrapTable from '../../src/bootstrap-table'; +import RowSelectionWrapper from '../../src/row-selection/wrapper'; + +describe('RowSelectionWrapper', () => { + let wrapper; + let elem; + + const columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + + const selectRow = { + mode: 'radio' + }; + + const keyField = 'id'; + + const store = new Store({ data, keyField }); + + beforeEach(() => { + elem = React.createElement(BootstrapTable, { data, selectRow, columns, keyField, store }); + wrapper = shallow( + + ); + }); + + it('should render RowSelectionWrapper correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find(BootstrapTable)).toBeDefined(); + }); + + it('should have correct state', () => { + expect(wrapper.state().selectedRowKeys).toBeDefined(); + expect(wrapper.state().selectedRowKeys.length).toEqual(0); + }); + + it('should inject correct props to elem', () => { + expect(wrapper.props().onRowSelect).toBeDefined(); + expect(wrapper.props().onAllRowsSelect).toBeDefined(); + }); + + describe('when selectRow.mode is \'radio\'', () => { + const firstSelectedRow = data[0][keyField]; + const secondSelectedRow = data[1][keyField]; + + it('call handleRowSelect function should seting correct state.selectedRowKeys', () => { + wrapper.instance().handleRowSelect(firstSelectedRow); + expect(wrapper.state('selectedRowKeys')).toEqual([firstSelectedRow]); + + wrapper.instance().handleRowSelect(secondSelectedRow); + expect(wrapper.state('selectedRowKeys')).toEqual([secondSelectedRow]); + }); + }); + + describe('when selectRow.mode is \'checkbox\'', () => { + const firstSelectedRow = data[0][keyField]; + const secondSelectedRow = data[1][keyField]; + + beforeEach(() => { + selectRow.mode = 'checkbox'; + elem = React.createElement(BootstrapTable, { data, selectRow, columns, keyField, store }); + wrapper = shallow( + + ); + }); + + it('call handleRowSelect function should seting correct state.selectedRowKeys', () => { + wrapper.instance().handleRowSelect(firstSelectedRow, true); + expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow])); + + wrapper.instance().handleRowSelect(secondSelectedRow, true); + expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow])); + + wrapper.instance().handleRowSelect(firstSelectedRow, false); + expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([secondSelectedRow])); + + wrapper.instance().handleRowSelect(secondSelectedRow, false); + expect(wrapper.state('selectedRowKeys')).toEqual([]); + }); + + it('call handleAllRowsSelect function should seting correct state.selectedRowKeys', () => { + wrapper.instance().handleAllRowsSelect(); + expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow])); + + wrapper.instance().handleAllRowsSelect(); + expect(wrapper.state('selectedRowKeys')).toEqual([]); + }); + + it('call handleAllRowsSelect function with a bool args should seting correct state.selectedRowKeys', () => { + wrapper.instance().handleAllRowsSelect(true); + expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow])); + + wrapper.instance().handleAllRowsSelect(false); + expect(wrapper.state('selectedRowKeys')).toEqual([]); + }); + }); +});