diff --git a/docs/README.md b/docs/README.md index 3150a7b..66e6923 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,6 +14,7 @@ * [hover](#hover) * [condensed](#condensed) * [cellEdit](#cellEdit) +* [selectRow](#selectRow) ### keyField(**required**) - [String] `keyField` is a prop to tell `react-bootstrap-table2` which column is unigue key. @@ -36,7 +37,7 @@ Same as `.table-hover` class for adding a hover effect (grey background color) o ### condensed - [Bool] Same as `.table-condensed` class for makeing a table more compact by cutting cell padding in half -### cellEdit - [Bool] +### cellEdit - [Object] Assign a valid `cellEdit` object can enable the cell editing on the cell. The default usage is click/dbclick to trigger cell editing and press `ENTER` to save cell or press `ESC` to cancel editing. > Note: The `keyField` column can't be edited @@ -63,4 +64,7 @@ Default is `false`, enable it will be able to save the cell automatically when b `cellEdit.nonEditableRows` accept a callback function and expect return an array which used to restrict all the columns of some rows as non-editable. So the each item in return array should be rowkey(`keyField`) #### cellEdit.timeToCloseMessage - [Function] -If a [`column.validator`](./columns.md#validator) defined and the new value is invalid, `react-bootstrap-table2` will popup a alert at the bottom of editor. `cellEdit.timeToCloseMessage` is a chance to let you decide how long the alert should be stay. Default is 3000 millisecond. \ No newline at end of file +If a [`column.validator`](./columns.md#validator) defined and the new value is invalid, `react-bootstrap-table2` will popup a alert at the bottom of editor. `cellEdit.timeToCloseMessage` is a chance to let you decide how long the alert should be stay. Default is 3000 millisecond. + +### selectRow - [Object] +Pass prop `selectRow` to enable row selection. For more detail, please navigate to [row selection document](./row-selection.md). diff --git a/docs/row-selection.md b/docs/row-selection.md new file mode 100644 index 0000000..3671093 --- /dev/null +++ b/docs/row-selection.md @@ -0,0 +1,48 @@ + +# Row selection +`react-bootstrap-table2` supports the row selection feature. By passing prop `selectRow ` to enable row selection. When you enable this feature, `react-bootstrap-table2` will append a new selection column at first. + + +## Available properties + +The following are available properties in `selectRow`: + +#### Required +* [mode (required)](#mode) + +#### Optional + +## selectRow.mode - [String] + +Specifying the selection way for `single(radio)` or `multiple(checkbox)`. If `radio` was assigned, there will be a radio button in the selection column; otherwise, the `checkbox` instead. + +#### values +* `radio` +* `checkbox` + +#### examples + +```js +const selectRowProp = { + mode: 'radio' // single row selection +}; + +``` + +```js +const selectRowProp = { + mode: 'checkbox' // multiple row selection +}; + + +``` diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/multiple-selection.js b/packages/react-bootstrap-table2-example/examples/row-selection/multiple-selection.js new file mode 100644 index 0000000..46f2453 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/multiple-selection.js @@ -0,0 +1,53 @@ +import React from 'react'; + +import { BootstrapTableful } from 'react-bootstrap-table2'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRowProp = { + mode: 'checkbox' +}; + +const sourceCode = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRowProp = { + mode: 'checkbox' +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/single-selection.js b/packages/react-bootstrap-table2-example/examples/row-selection/single-selection.js new file mode 100644 index 0000000..b02f058 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/single-selection.js @@ -0,0 +1,53 @@ +import React from 'react'; + +import { BootstrapTableful } from 'react-bootstrap-table2'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRowProp = { + mode: 'radio' +}; + +const sourceCode = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRowProp = { + mode: 'radio' +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 62829ef..4f0d85f 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -46,6 +46,10 @@ import CellLevelEditable from 'examples/cell-edit/cell-level-editable-table'; import CellEditHooks from 'examples/cell-edit/cell-edit-hooks-table'; import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table'; +// work on row selection +import SingleSelectionTable from 'examples/row-selection/single-selection'; +import MultipleSelectionTable from 'examples/row-selection/multiple-selection'; + // css style import 'bootstrap/dist/css/bootstrap.min.css'; import 'stories/stylesheet/tomorrow.min.css'; @@ -99,3 +103,7 @@ storiesOf('Cell Editing', module) .add('Cell Level Editable', () => ) .add('Rich Hook Functions', () => ) .add('Validation', () => ); + +storiesOf('Row Selection', module) + .add('Single selection', () => ) + .add('Multiple selection', () => ); diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index 76cbd22..9a0cbba 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -1,10 +1,13 @@ /* eslint react/prop-types: 0 */ +/* eslint react/require-default-props: 0 */ + import React from 'react'; import PropTypes from 'prop-types'; import _ from './utils'; import Row from './row'; import RowSection from './row-section'; +import Const from './const'; const Body = (props) => { const { @@ -14,7 +17,9 @@ const Body = (props) => { isEmpty, noDataIndication, visibleColumnSize, - cellEdit + cellEdit, + selectRow, + selectedRowKeys } = props; let content; @@ -25,7 +30,13 @@ const Body = (props) => { } else { content = data.map((row, index) => { const key = _.get(row, keyField); - const editable = !(cellEdit && cellEdit.nonEditableRows.indexOf(key) > -1); + const editable = !(cellEdit.mode !== Const.UNABLE_TO_CELL_EDIT && + cellEdit.nonEditableRows.indexOf(key) > -1); + + const selected = selectRow.mode !== Const.ROW_SELECT_DISABLED + ? selectedRowKeys.includes(key) + : null; + return ( { columns={ columns } cellEdit={ cellEdit } editable={ editable } + selected={ selected } + selectRow={ selectRow } /> ); }); @@ -48,7 +61,9 @@ const Body = (props) => { Body.propTypes = { keyField: PropTypes.string.isRequired, data: PropTypes.array.isRequired, - columns: PropTypes.array.isRequired + columns: PropTypes.array.isRequired, + selectRow: PropTypes.object, + selectedRowKeys: PropTypes.array }; export default Body; diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index f4aaf8b..2aaf510 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -22,8 +22,11 @@ class BootstrapTable extends PropsBaseResolver(Component) { this.startEditing = this.startEditing.bind(this); this.escapeEditing = this.escapeEditing.bind(this); this.completeEditing = this.completeEditing.bind(this); + this.handleRowSelect = this.handleRowSelect.bind(this); + this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this); this.state = { data: this.store.get(), + selectedRowKeys: this.store.getSelectedRowKeys(), currEditCell: { ridx: null, cidx: null @@ -56,6 +59,14 @@ class BootstrapTable extends PropsBaseResolver(Component) { onComplete: this.completeEditing }); + const cellSelectionInfo = this.resolveCellSelectionProps({ + onRowSelect: this.handleRowSelect + }); + + const headerCellSelectionInfo = this.resolveHeaderCellSelectionProps({ + onAllRowsSelect: this.handleAllRowsSelect + }); + return (
@@ -65,6 +76,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { sortField={ this.store.sortField } sortOrder={ this.store.sortOrder } onSort={ this.handleSort } + selectRow={ headerCellSelectionInfo } />
); } + /** + * 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 { mode } = this.props.selectRow; + const { ROW_SELECT_SINGLE } = Const; + + let currSelected = [...this.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); + } + + this.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 selected = this.store.isAnySelectedRow(); + + // set next status of all row selected by store.selected or customizing by user. + const result = option || !selected; + + const currSelected = result ? this.store.selectAllRowKeys() : []; + + this.store.setSelectedRowKeys(currSelected); + + this.setState(() => ({ + selectedRowKeys: currSelected + })); + } + handleSort(column) { this.store.sortBy(column); @@ -146,6 +205,9 @@ BootstrapTable.propTypes = { afterSaveCell: PropTypes.func, nonEditableRows: PropTypes.func, timeToCloseMessage: PropTypes.number + }), + selectRow: PropTypes.shape({ + mode: PropTypes.oneOf([Const.ROW_SELECT_SINGLE, Const.ROW_SELECT_MULTIPLE]).isRequired }) }; diff --git a/packages/react-bootstrap-table2/src/const.js b/packages/react-bootstrap-table2/src/const.js index 5391b82..4e10028 100644 --- a/packages/react-bootstrap-table2/src/const.js +++ b/packages/react-bootstrap-table2/src/const.js @@ -4,5 +4,11 @@ export default { UNABLE_TO_CELL_EDIT: 'none', CLICK_TO_CELL_EDIT: 'click', DBCLICK_TO_CELL_EDIT: 'dbclick', - TIME_TO_CLOSE_MESSAGE: 3000 + TIME_TO_CLOSE_MESSAGE: 3000, + ROW_SELECT_SINGLE: 'radio', + ROW_SELECT_MULTIPLE: 'checkbox', + ROW_SELECT_DISABLED: 'ROW_SELECT_DISABLED', + CHECKBOX_STATUS_CHECKED: 'checked', + CHECKBOX_STATUS_INDETERMINATE: 'indeterminate', + CHECKBOX_STATUS_UNCHECKED: 'unchecked' }; diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index 36752f1..8953c63 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -1,20 +1,28 @@ /* eslint react/require-default-props: 0 */ import React from 'react'; import PropTypes from 'prop-types'; +import Const from './const'; import HeaderCell from './header-cell'; - +import SelectionHeaderCell from './row-selection/selection-header-cell'; const Header = (props) => { + const { ROW_SELECT_DISABLED } = Const; + const { columns, onSort, sortField, - sortOrder + sortOrder, + selectRow } = props; + return ( + { + selectRow.mode === ROW_SELECT_DISABLED ? null : + } { columns.map((column, i) => { const currSort = column.dataField === sortField; @@ -38,7 +46,8 @@ Header.propTypes = { columns: PropTypes.array.isRequired, onSort: PropTypes.func, sortField: PropTypes.string, - sortOrder: PropTypes.string + sortOrder: PropTypes.string, + selectRow: PropTypes.object }; export default Header; diff --git a/packages/react-bootstrap-table2/src/props-resolver/index.js b/packages/react-bootstrap-table2/src/props-resolver/index.js index d7ea8af..f01790c 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/index.js +++ b/packages/react-bootstrap-table2/src/props-resolver/index.js @@ -40,4 +40,65 @@ export default ExtendBase => ...cellEditInfo }; } + + /** + * props resolver for cell selection + * @param {Object} options - addtional options like callback which are about to merge into props + * + * @returns {Object} result - props for cell selections + * @returns {String} result.mode - input type of row selection or disabled. + */ + resolveCellSelectionProps(options) { + const { selectRow } = this.props; + const { ROW_SELECT_DISABLED } = Const; + + if (_.isDefined(selectRow)) { + return { + ...selectRow, + ...options + }; + } + + return { + mode: ROW_SELECT_DISABLED + }; + } + + /** + * props resolver for header cell selection + * @param {Object} options - addtional options like callback which are about to merge into props + * + * @returns {Object} result - props for cell selections + * @returns {String} result.mode - input type of row selection or disabled. + * @returns {String} result.checkedStatus - checkbox status depending on selected rows counts + */ + resolveHeaderCellSelectionProps(options) { + const { selected } = this.store; + const { selectRow } = this.props; + const { + ROW_SELECT_DISABLED, CHECKBOX_STATUS_CHECKED, + CHECKBOX_STATUS_INDETERMINATE, CHECKBOX_STATUS_UNCHECKED + } = Const; + + if (_.isDefined(selectRow)) { + let checkedStatus; + + const allRowsSelected = this.store.isAllRowsSelected(); + + // checkbox status depending on selected rows counts + if (allRowsSelected) checkedStatus = CHECKBOX_STATUS_CHECKED; + else if (selected.length === 0) checkedStatus = CHECKBOX_STATUS_UNCHECKED; + else checkedStatus = CHECKBOX_STATUS_INDETERMINATE; + + return { + ...selectRow, + ...options, + checkedStatus + }; + } + + return { + mode: ROW_SELECT_DISABLED + }; + } }; diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js new file mode 100644 index 0000000..73b3e4a --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js @@ -0,0 +1,59 @@ +/* eslint + react/require-default-props: 0 + jsx-a11y/no-noninteractive-element-interactions: 0 +*/ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import Const from '../const'; + +export default class SelectionCell extends Component { + static propTypes = { + mode: PropTypes.string.isRequired, + rowKey: PropTypes.any, + selected: PropTypes.bool, + onRowSelect: PropTypes.func + } + + constructor() { + super(); + this.handleRowClick = this.handleRowClick.bind(this); + } + + shouldComponentUpdate(nextProps) { + const { selected } = this.props; + + return nextProps.selected !== selected; + } + + handleRowClick() { + const { ROW_SELECT_SINGLE } = Const; + const { + mode: inputType, + rowKey, + selected, + onRowSelect + } = this.props; + + const checked = inputType === ROW_SELECT_SINGLE + ? true + : !selected; + + onRowSelect(rowKey, checked); + } + + render() { + const { + mode: inputType, + selected + } = this.props; + + return ( + + + + ); + } +} diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js new file mode 100644 index 0000000..08516d1 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js @@ -0,0 +1,76 @@ +/* eslint react/require-default-props: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import Constant from '../const'; + +export const CheckBox = ({ checked, indeterminate }) => ( + { + if (input) input.indeterminate = indeterminate; // eslint-disable-line no-param-reassign + }} + /> +); + +CheckBox.propTypes = { + checked: PropTypes.bool.isRequired, + indeterminate: PropTypes.bool.isRequired +}; + +export default class SelectionHeaderCell extends Component { + static propTypes = { + mode: PropTypes.string.isRequired, + checkedStatus: PropTypes.string, + onAllRowsSelect: PropTypes.func + } + + constructor() { + super(); + this.handleCheckBoxClick = this.handleCheckBoxClick.bind(this); + } + + /** + * avoid updating if button is + * 1. radio + * 2. status was not changed. + */ + shouldComponentUpdate(nextProps) { + const { ROW_SELECT_SINGLE } = Constant; + const { mode, checkedStatus } = this.props; + + if (mode === ROW_SELECT_SINGLE) return false; + + return nextProps.checkedStatus !== checkedStatus; + } + + handleCheckBoxClick() { + const { onAllRowsSelect } = this.props; + + onAllRowsSelect(); + } + + render() { + const { + CHECKBOX_STATUS_CHECKED, CHECKBOX_STATUS_INDETERMINATE, ROW_SELECT_SINGLE + } = Constant; + + const { mode, checkedStatus } = this.props; + + const checked = checkedStatus === CHECKBOX_STATUS_CHECKED; + + const indeterminate = checkedStatus === CHECKBOX_STATUS_INDETERMINATE; + + return mode === ROW_SELECT_SINGLE + ? + : ( + + + + ); + } +} diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js index 117dce4..09d9d7b 100644 --- a/packages/react-bootstrap-table2/src/row.js +++ b/packages/react-bootstrap-table2/src/row.js @@ -4,17 +4,24 @@ import PropTypes from 'prop-types'; import _ from './utils'; import Cell from './cell'; +import SelectionCell from './row-selection/selection-cell'; import EditingCell from './editing-cell'; +import Const from './const'; const Row = (props) => { + const { ROW_SELECT_DISABLED } = Const; + const { row, columns, keyField, rowIndex, cellEdit, + selected, + selectRow, editable: editableRow } = props; + const { mode, onStart, @@ -22,8 +29,20 @@ const Row = (props) => { cidx: editingColIdx, ...rest } = cellEdit; + return ( + { + selectRow.mode === ROW_SELECT_DISABLED + ? null + : ( + + ) + } { columns.map((column, index) => { const { dataField } = column; diff --git a/packages/react-bootstrap-table2/src/store/base.js b/packages/react-bootstrap-table2/src/store/base.js index 2c92725..31a96cd 100644 --- a/packages/react-bootstrap-table2/src/store/base.js +++ b/packages/react-bootstrap-table2/src/store/base.js @@ -10,6 +10,7 @@ export default class Store { this.sortOrder = undefined; this.sortField = undefined; + this.selected = []; } isEmpty() { @@ -39,4 +40,24 @@ export default class Store { getRowByRowId(rowId) { return this.get().find(row => _.get(row, this.keyField) === rowId); } + + setSelectedRowKeys(selectedKeys) { + this.selected = selectedKeys; + } + + getSelectedRowKeys() { + return this.selected; + } + + selectAllRowKeys() { + return this.data.map(row => _.get(row, this.keyField)); + } + + isAllRowsSelected() { + return this.data.length === this.selected.length; + } + + isAnySelectedRow() { + return this.selected.length > 0; + } } diff --git a/packages/react-bootstrap-table2/style/react-bootstrap-table.scss b/packages/react-bootstrap-table2/style/react-bootstrap-table.scss index 9720bd0..1a87c51 100644 --- a/packages/react-bootstrap-table2/style/react-bootstrap-table.scss +++ b/packages/react-bootstrap-table2/style/react-bootstrap-table.scss @@ -22,6 +22,10 @@ margin: 10px 6.5px; } + th[data-row-selection] { + width: 30px; + } + td.react-bs-table-no-data { text-align: center; } diff --git a/packages/react-bootstrap-table2/test/body.test.js b/packages/react-bootstrap-table2/test/body.test.js index 54ba506..30f01dd 100644 --- a/packages/react-bootstrap-table2/test/body.test.js +++ b/packages/react-bootstrap-table2/test/body.test.js @@ -6,6 +6,7 @@ import Body from '../src/body'; import Row from '../src/row'; import Const from '../src/const'; import RowSection from '../src/row-section'; +import mockBodyResolvedProps from '../test/mock-data/body-resolved-props'; describe('Body', () => { let wrapper; @@ -27,7 +28,7 @@ describe('Body', () => { describe('simplest body', () => { beforeEach(() => { - wrapper = shallow(); + wrapper = shallow(); }); it('should render successfully', () => { @@ -41,6 +42,7 @@ describe('Body', () => { beforeEach(() => { wrapper = shallow( { emptyIndication = 'Table is empty'; wrapper = shallow( { emptyIndicationCallBack = sinon.stub().returns(content); wrapper = shallow( { beforeEach(() => { wrapper = shallow( { } }); }); + + describe('when selectRow.mode is checkbox or radio (row was selectable)', () => { + const keyField = 'id'; + const selectRow = { mode: 'checkbox' }; + + it('props selected should be true if all rows were selected', () => { + wrapper = shallow( + + ); + + expect(wrapper.find(Row).get(0).props.selected).toBe(true); + }); + + it('props selected should be false if all rows were not selected', () => { + wrapper = shallow( + + ); + + expect(wrapper.find(Row).get(0).props.selected).toBe(false); + }); + }); + + describe('when selectRow.mode is ROW_SELECT_DISABLED (row was un-selectable)', () => { + beforeEach(() => { + const keyField = 'id'; + wrapper = shallow( + + ); + }); + + it('prop selected should be null', () => { + expect(wrapper.find(Row).get(0).props.selected).toBeNull(); + }); + }); }); diff --git a/packages/react-bootstrap-table2/test/bootstrap-table.test.js b/packages/react-bootstrap-table2/test/bootstrap-table.test.js index e426a5a..2ee2b96 100644 --- a/packages/react-bootstrap-table2/test/bootstrap-table.test.js +++ b/packages/react-bootstrap-table2/test/bootstrap-table.test.js @@ -140,4 +140,111 @@ describe('BootstrapTable', () => { expect(body.props().cellEdit.onComplete).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', () => { + wrapper.instance().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', () => { + wrapper.instance().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', () => { + wrapper.instance().store.setSelectedRowKeys([1]); + + wrapper.instance().handleAllRowsSelect(false); + + expect(wrapper.state('selectedRowKeys').length).toBe(0); + }); + }); + }); + }); }); diff --git a/packages/react-bootstrap-table2/test/header.test.js b/packages/react-bootstrap-table2/test/header.test.js index 3f4e783..bd45f41 100644 --- a/packages/react-bootstrap-table2/test/header.test.js +++ b/packages/react-bootstrap-table2/test/header.test.js @@ -2,8 +2,10 @@ import React from 'react'; import { shallow } from 'enzyme'; import HeaderCell from '../src/header-cell'; +import SelectionHeaderCell from '../src//row-selection/selection-header-cell'; import Header from '../src/header'; import Const from '../src/const'; +import mockHeaderResolvedProps from '../test/mock-data/header-resolved-props'; describe('Header', () => { let wrapper; @@ -17,7 +19,7 @@ describe('Header', () => { describe('simplest header', () => { beforeEach(() => { - wrapper = shallow(
); + wrapper = shallow(
); }); it('should render successfully', () => { @@ -32,7 +34,12 @@ describe('Header', () => { beforeEach(() => { wrapper = shallow( -
); +
); }); it('The HeaderCell should receive correct sorting props', () => { @@ -43,4 +50,31 @@ describe('Header', () => { expect(headerCells.at(1).prop('sortOrder')).toBe(Const.SORT_ASC); }); }); + + describe('when the selectRow.mode is radio(single selection)', () => { + beforeEach(() => { + wrapper = shallow(
); + }); + + it('should not render ', () => { + expect(wrapper.find(SelectionHeaderCell).length).toBe(0); + }); + }); + + describe('when the selectRow.mode is checkbox(multiple selection)', () => { + beforeEach(() => { + const selectRow = { mode: 'checkbox' }; + wrapper = shallow( +
+ ); + }); + + it('should render ', () => { + expect(wrapper.find(SelectionHeaderCell).length).toBe(1); + }); + }); }); diff --git a/packages/react-bootstrap-table2/test/mock-data/body-resolved-props.js b/packages/react-bootstrap-table2/test/mock-data/body-resolved-props.js new file mode 100644 index 0000000..d95bf3e --- /dev/null +++ b/packages/react-bootstrap-table2/test/mock-data/body-resolved-props.js @@ -0,0 +1,16 @@ +import Const from '../../src/const'; + +const { ROW_SELECT_DISABLED, UNABLE_TO_CELL_EDIT } = Const; + +export const cellSelectionResolvedProps = { + mode: ROW_SELECT_DISABLED +}; + +export const cellEditResolvedProps = { + mode: UNABLE_TO_CELL_EDIT +}; + +export default { + cellEdit: cellEditResolvedProps, + selectRow: cellSelectionResolvedProps +}; diff --git a/packages/react-bootstrap-table2/test/mock-data/header-resolved-props.js b/packages/react-bootstrap-table2/test/mock-data/header-resolved-props.js new file mode 100644 index 0000000..23ab27e --- /dev/null +++ b/packages/react-bootstrap-table2/test/mock-data/header-resolved-props.js @@ -0,0 +1,11 @@ +import Const from '../../src/const'; + +const { ROW_SELECT_DISABLED } = Const; + +export const headerCellSelectionResolvedProps = { + mode: ROW_SELECT_DISABLED +}; + +export default { + selectRow: headerCellSelectionResolvedProps +}; diff --git a/packages/react-bootstrap-table2/test/props-resolver/index.test.js b/packages/react-bootstrap-table2/test/props-resolver/index.test.js index d27897a..e865126 100644 --- a/packages/react-bootstrap-table2/test/props-resolver/index.test.js +++ b/packages/react-bootstrap-table2/test/props-resolver/index.test.js @@ -22,6 +22,7 @@ describe('TableResolver', () => { id: 2, name: 'B' }]; + const ExtendBase = baseResolver(Component); const BootstrapTableMock = extendTo(ExtendBase); let wrapper; @@ -99,48 +100,264 @@ describe('TableResolver', () => { expect(cellEdit.cidx).toEqual(cidx); }); }); + + describe('if cellEdit prop defined', () => { + const expectNonEditableRows = [1, 2]; + const cellEdit = { + mode: Const.DBCLICK_TO_CELL_EDIT, + onEditing: sinon.stub(), + blurToSave: true, + beforeSaveCell: sinon.stub(), + afterSaveCell: sinon.stub(), + nonEditableRows: sinon.stub().returns(expectNonEditableRows) + }; + + beforeEach(() => { + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, cellEdit + }, null); + wrapper = shallow(mockElement); + }); + + it('should resolve a cellEdit correctly', () => { + const cellEditInfo = wrapper.instance().resolveCellEditProps(); + expect(cellEditInfo).toBeDefined(); + expect(cellEditInfo.ridx).toBeNull(); + expect(cellEditInfo.cidx).toBeNull(); + expect(cellEditInfo.mode).toEqual(cellEdit.mode); + expect(cellEditInfo.onEditing).toEqual(cellEdit.onEditing); + expect(cellEditInfo.blurToSave).toEqual(cellEdit.blurToSave); + expect(cellEditInfo.beforeSaveCell).toEqual(cellEdit.beforeSaveCell); + expect(cellEditInfo.afterSaveCell).toEqual(cellEdit.afterSaveCell); + expect(cellEditInfo.nonEditableRows).toEqual(expectNonEditableRows); + }); + + it('should attach options to cellEdit props', () => { + const something = { + test: 1, + cb: sinon.stub() + }; + const cellEditInfo = wrapper.instance().resolveCellEditProps(something); + expect(cellEditInfo).toBeDefined(); + expect(cellEditInfo.test).toEqual(something.test); + expect(cellEditInfo.cb).toEqual(something.cb); + }); + }); }); - describe('if cellEdit prop defined', () => { - const expectNonEditableRows = [1, 2]; - const cellEdit = { - mode: Const.DBCLICK_TO_CELL_EDIT, - onEditing: sinon.stub(), - blurToSave: true, - beforeSaveCell: sinon.stub(), - afterSaveCell: sinon.stub(), - nonEditableRows: sinon.stub().returns(expectNonEditableRows) - }; + describe('resolveCellSelectionProps', () => { + let cellSelectionInfo; + let selectRow; + + describe('if selectRow was not defined', () => { + beforeEach(() => { + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns + }, null); + wrapper = shallow(mockElement); + cellSelectionInfo = wrapper.instance().resolveCellSelectionProps(); + }); + + it('should return object', () => { + expect(cellSelectionInfo).toBeDefined(); + expect(cellSelectionInfo.constructor).toEqual(Object); + }); + + it('should contain mode in ROW_SELECT_DISABLED', () => { + expect(cellSelectionInfo.mode).toEqual(Const.ROW_SELECT_DISABLED); + }); + }); + + describe('if selectRow was defined', () => { + describe('when mode was defined', () => { + it('should return object which contains ROW_SELECT_SINGLE if mode is radio', () => { + selectRow = { mode: 'radio' }; + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, selectRow + }, null); + wrapper = shallow(mockElement); + cellSelectionInfo = wrapper.instance().resolveCellSelectionProps(); + + expect(cellSelectionInfo).toBeDefined(); + expect(cellSelectionInfo.constructor).toEqual(Object); + expect(cellSelectionInfo.mode).toEqual(Const.ROW_SELECT_SINGLE); + }); + + it('should return object which contains ROW_SELECT_MULTIPLE if mode is checkbox', () => { + selectRow = { mode: 'checkbox' }; + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, selectRow + }, null); + wrapper = shallow(mockElement); + cellSelectionInfo = wrapper.instance().resolveCellSelectionProps(); + + expect(cellSelectionInfo).toBeDefined(); + expect(cellSelectionInfo.constructor).toEqual(Object); + expect(cellSelectionInfo.mode).toEqual(Const.ROW_SELECT_MULTIPLE); + }); + }); + + describe('when options were given', () => { + beforeEach(() => { + selectRow = {}; + const mockOptions = { + foo: 'test', + bar: sinon.stub() + }; + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, selectRow + }, null); + wrapper = shallow(mockElement); + cellSelectionInfo = wrapper.instance().resolveCellSelectionProps(mockOptions); + }); + + it('should return object which contain options', () => { + expect(cellSelectionInfo).toEqual(expect.objectContaining({ + foo: 'test', + bar: expect.any(Function) + })); + }); + }); + }); + }); + + describe('resolveHeaderCellSelectionProps', () => { + let headerCellSelectionInfo; + let selectRow; beforeEach(() => { const mockElement = React.createElement(BootstrapTableMock, { - data, keyField, columns, cellEdit + data, keyField, columns }, null); wrapper = shallow(mockElement); + headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps(); }); - it('should resolve a cellEdit correctly', () => { - const cellEditInfo = wrapper.instance().resolveCellEditProps(); - expect(cellEditInfo).toBeDefined(); - expect(cellEditInfo.ridx).toBeNull(); - expect(cellEditInfo.cidx).toBeNull(); - expect(cellEditInfo.mode).toEqual(cellEdit.mode); - expect(cellEditInfo.onEditing).toEqual(cellEdit.onEditing); - expect(cellEditInfo.blurToSave).toEqual(cellEdit.blurToSave); - expect(cellEditInfo.beforeSaveCell).toEqual(cellEdit.beforeSaveCell); - expect(cellEditInfo.afterSaveCell).toEqual(cellEdit.afterSaveCell); - expect(cellEditInfo.nonEditableRows).toEqual(expectNonEditableRows); + describe('if selectRow was not defined', () => { + it('should return object', () => { + expect(headerCellSelectionInfo).toBeDefined(); + expect(headerCellSelectionInfo.constructor).toEqual(Object); + }); + + it('should contain mode in ROW_SELECT_DISABLED', () => { + expect(headerCellSelectionInfo.mode).toEqual(Const.ROW_SELECT_DISABLED); + }); }); - it('should attach options to cellEdit props', () => { - const something = { - test: 1, - cb: sinon.stub() - }; - const cellEditInfo = wrapper.instance().resolveCellEditProps(something); - expect(cellEditInfo).toBeDefined(); - expect(cellEditInfo.test).toEqual(something.test); - expect(cellEditInfo.cb).toEqual(something.cb); + describe('if selectRow was defined', () => { + describe('when mode was defined', () => { + it('should return object which contains ROW_SELECT_SINGLE if mode is radio', () => { + selectRow = { mode: 'radio' }; + const selectedRowKeys = []; + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, selectedRowKeys, selectRow + }, null); + wrapper = shallow(mockElement); + headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps(); + + expect(headerCellSelectionInfo).toBeDefined(); + expect(headerCellSelectionInfo.constructor).toEqual(Object); + expect(headerCellSelectionInfo.mode).toEqual(Const.ROW_SELECT_SINGLE); + }); + + it('should return object which contains ROW_SELECT_MULTIPLE if mode is checkbox', () => { + selectRow = { mode: 'checkbox' }; + const selectedRowKeys = []; + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, selectedRowKeys, selectRow + }, null); + wrapper = shallow(mockElement); + headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps(); + + expect(headerCellSelectionInfo).toBeDefined(); + expect(headerCellSelectionInfo.constructor).toEqual(Object); + expect(headerCellSelectionInfo.mode).toEqual(Const.ROW_SELECT_MULTIPLE); + }); + }); + + describe('when options were given', () => { + beforeEach(() => { + selectRow = {}; + const mockOptions = { + foo: 'test', + bar: sinon.stub() + }; + const selectedRowKeys = []; + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, selectedRowKeys, selectRow + }, null); + wrapper = shallow(mockElement); + headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps(mockOptions); + }); + + it('should return object which contain options', () => { + expect(headerCellSelectionInfo).toEqual(expect.objectContaining({ + foo: 'test', + bar: expect.any(Function) + })); + }); + }); + + describe('if all rows were selected', () => { + beforeEach(() => { + selectRow = {}; + const selectedRowKeys = [1, 2]; + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, selectRow + }, null); + + wrapper = shallow(mockElement); + wrapper.instance().store.setSelectedRowKeys(selectedRowKeys); + + headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps(); + }); + + it('should return checkedStatus which eqauls to checked', () => { + expect(headerCellSelectionInfo).toEqual(expect.objectContaining({ + checkedStatus: Const.CHECKBOX_STATUS_CHECKED + })); + }); + }); + describe('if part of rows were selected', () => { + beforeEach(() => { + selectRow = {}; + const selectedRowKeys = [1]; + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, selectRow + }, null); + + wrapper = shallow(mockElement); + wrapper.instance().store.setSelectedRowKeys(selectedRowKeys); + headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps(); + }); + + it('should return checkedStatus which eqauls to indeterminate', () => { + expect(headerCellSelectionInfo).toEqual(expect.objectContaining({ + checkedStatus: Const.CHECKBOX_STATUS_INDETERMINATE + })); + }); + }); + + describe('if none of row was selected', () => { + beforeEach(() => { + selectRow = {}; + const selectedRowKeys = []; + const mockElement = React.createElement(BootstrapTableMock, { + data, keyField, columns, selectRow + }, null); + + wrapper = shallow(mockElement); + wrapper.instance().store.setSelectedRowKeys(selectedRowKeys); + + headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps(); + }); + + it('should return checkedStatus which eqauls to unchecked', () => { + expect(headerCellSelectionInfo).toEqual(expect.objectContaining({ + checkedStatus: Const.CHECKBOX_STATUS_UNCHECKED + })); + }); + }); }); }); }); diff --git a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js new file mode 100644 index 0000000..ca64d3b --- /dev/null +++ b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js @@ -0,0 +1,136 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import sinon from 'sinon'; + +import SelectionCell from '../../src/row-selection/selection-cell'; + +describe('', () => { + const mode = 'checkbox'; + + let wrapper; + + describe('shouldComponentUpdate', () => { + const selected = true; + + describe('when selected prop has not been changed', () => { + it('should not update component', () => { + const nextProps = { selected }; + + wrapper = shallow(); + + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(false); + }); + }); + + describe('when selected prop has been changed', () => { + it('should update component', () => { + const nextProps = { selected: !selected }; + + wrapper = shallow(); + + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + }); + }); + + describe('handleRowClick', () => { + describe('when was been clicked', () => { + const rowKey = 1; + const mockOnRowSelect = sinon.stub(); + const spy = sinon.spy(SelectionCell.prototype, 'handleRowClick'); + + beforeEach(() => { + spy.reset(); + mockOnRowSelect.reset(); + }); + + it('should call handleRowClicked', () => { + wrapper = shallow( + + ); + + wrapper.find('td').simulate('click'); + + expect(spy.calledOnce).toBe(true); + expect(mockOnRowSelect.calledOnce).toBe(true); + }); + + describe('if selectRow.mode is radio', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should be called with correct paramters', () => { + // first click + wrapper.find('td').simulate('click'); + expect(mockOnRowSelect.callCount).toBe(1); + expect(mockOnRowSelect.calledWith(rowKey, true)).toBe(true); + + // second click + wrapper.find('td').simulate('click'); + expect(mockOnRowSelect.callCount).toBe(2); + expect(mockOnRowSelect.calledWith(rowKey, true)).toBe(true); + }); + }); + + describe('if selectRow.mode is checkbox', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should be called with correct paramters', () => { + // first click + wrapper.setProps({ selected: true }); + wrapper.find('td').simulate('click'); + expect(mockOnRowSelect.callCount).toBe(1); + expect(mockOnRowSelect.calledWith(rowKey, false)).toBe(true); + + // second click + wrapper.setProps({ selected: false }); + wrapper.find('td').simulate('click'); + expect(mockOnRowSelect.callCount).toBe(2); + expect(mockOnRowSelect.calledWith(rowKey, true)).toBe(true); + }); + }); + }); + }); + + describe('render', () => { + const selected = true; + + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render component correctly', () => { + expect(wrapper.find('td').length).toBe(1); + expect(wrapper.find('input').length).toBe(1); + expect(wrapper.find('input').get(0).props.type).toBe(mode); + expect(wrapper.find('input').get(0).props.checked).toBe(selected); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js b/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js new file mode 100644 index 0000000..ae3837b --- /dev/null +++ b/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js @@ -0,0 +1,144 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import sinon from 'sinon'; + +import Constant from '../../src/const'; +import SelectionHeaderCell, { CheckBox } from '../../src/row-selection/selection-header-cell'; + +let wrapper; + +describe('', () => { + describe('shouldComponentUpdate', () => { + describe('when props.mode is radio', () => { + it('should not update component', () => { + wrapper = shallow(); + + expect(wrapper.instance().shouldComponentUpdate({})).toBe(false); + }); + }); + + describe('when props.mode is checkbox', () => { + describe('if checkedStatus prop has not been changed', () => { + it('should not update component', () => { + const checkedStatus = Constant.CHECKBOX_STATUS_CHECKED; + const nextProps = { checkedStatus }; + + wrapper = shallow( + ); + + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(false); + }); + }); + + describe('if checkedStatus prop has been changed', () => { + it('should update component', () => { + const { CHECKBOX_STATUS_INDETERMINATE, CHECKBOX_STATUS_CHECKED } = Constant; + const checkedStatus = CHECKBOX_STATUS_CHECKED; + const nextProps = { checkedStatus }; + + wrapper = shallow( + ); + + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + }); + }); + }); + + describe('handleCheckBoxClick', () => { + describe('when was clicked', () => { + const spy = sinon.spy(SelectionHeaderCell.prototype, 'handleCheckBoxClick'); + const mockOnAllRowsSelect = sinon.stub(); + + beforeEach(() => { + spy.reset(); + mockOnAllRowsSelect.reset(); + }); + + describe('if props.mode is radio', () => { + beforeEach(() => { + wrapper = shallow( + ); + }); + + it('should do nothing', () => { + wrapper.find('th').simulate('click'); + + expect(spy.callCount).toBe(0); + expect(mockOnAllRowsSelect.callCount).toBe(0); + }); + }); + + describe('if props.mode is checkbox', () => { + beforeEach(() => { + wrapper = shallow( + ); + }); + + it('should call handleCheckBoxClick', () => { + wrapper.find('th').simulate('click'); + + expect(spy.calledOnce).toBe(true); + expect(mockOnAllRowsSelect.calledOnce).toBe(true); + }); + }); + }); + }); + + describe('render', () => { + describe('when props.mode is radio', () => { + beforeEach(() => { + const checkedStatus = Constant.CHECKBOX_STATUS_CHECKED; + + wrapper = shallow(); + }); + + it('should not render checkbox', () => { + expect(wrapper.find('th').length).toBe(1); + expect(wrapper.find('th[data-row-selection]').length).toBe(1); + expect(wrapper.find(CheckBox).length).toBe(0); + }); + }); + + describe('when props.mode is checkbox', () => { + const checkedStatus = Constant.CHECKBOX_STATUS_CHECKED; + + beforeEach(() => { + wrapper = shallow(); + }); + + it('should render checkbox', () => { + const checked = checkedStatus === Constant.CHECKBOX_STATUS_CHECKED; + const indeterminate = checkedStatus === Constant.CHECKBOX_STATUS_INDETERMINATE; + + expect(wrapper.find('th').length).toBe(1); + expect(wrapper.find('th[data-row-selection]').length).toBe(1); + expect(wrapper.find(CheckBox).length).toBe(1); + expect(wrapper.find(CheckBox).get(0).props.checked).toBe(checked); + expect(wrapper.find(CheckBox).get(0).props.indeterminate).toBe(indeterminate); + }); + }); + }); +}); + +describe('', () => { + describe('render', () => { + it('should render component correctly', () => { + const checked = true; + const indeterminate = false; + wrapper = shallow(); + + expect(wrapper.find('input').length).toBe(1); + expect(wrapper.find('input').prop('checked')).toBe(checked); + expect(wrapper.find('input').prop('type')).toBe('checkbox'); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row.test.js index dd46177..cdbf885 100644 --- a/packages/react-bootstrap-table2/test/row.test.js +++ b/packages/react-bootstrap-table2/test/row.test.js @@ -6,6 +6,8 @@ import Cell from '../src/cell'; import Row from '../src/row'; import Const from '../src/const'; import EditingCell from '../src/editing-cell'; +import SelectionCell from '../src//row-selection/selection-cell'; +import mockBodyResolvedProps from '../test/mock-data/body-resolved-props'; const defaultColumns = [{ dataField: 'id', @@ -30,7 +32,7 @@ describe('Row', () => { describe('simplest row', () => { beforeEach(() => { wrapper = shallow( - ); + ); }); it('should render successfully', () => { @@ -53,6 +55,7 @@ describe('Row', () => { }; wrapper = shallow( { columns[nonEditableColIndex].editable = false; wrapper = shallow( { columns[nonEditableColIndex].editable = editableCallBack; wrapper = shallow( { columns[nonEditableColIndex].editable = editableCallBack; wrapper = shallow( { beforeEach(() => { wrapper = shallow( { cellEdit.onEscape = sinon.stub(); wrapper = shallow( { }); it('should render EditingCell correctly', () => { + const complexComponents = wrapper.find('tr').children().findWhere( + n => n.type().name === 'Cell' || n.type().name === 'EditingCell'); + expect(wrapper.length).toBe(1); expect(wrapper.find(EditingCell).length).toBe(1); - expect(wrapper.find('tr').children().at(editingColIndex).type()).toEqual(EditingCell); + expect(complexComponents.at(editingColIndex).type()).toEqual(EditingCell); }); }); @@ -248,6 +259,7 @@ describe('Row', () => { cellEdit.onEscape = sinon.stub(); wrapper = shallow( { }); }); }); + + describe('when selectRow.mode is ROW_SELECT_DISABLED (row was un-selectable)', () => { + beforeEach(() => { + wrapper = shallow( + ); + }); + + it('should not render ', () => { + expect(wrapper.find(SelectionCell).length).toBe(0); + }); + }); + + describe('when selectRow.mode is checkbox or radio (row was selectable)', () => { + beforeEach(() => { + const selectRow = { mode: 'checkbox' }; + wrapper = shallow( + ); + }); + + it('should render ', () => { + expect(wrapper.find(SelectionCell).length).toBe(1); + }); + }); }); diff --git a/packages/react-bootstrap-table2/test/store/base.test.js b/packages/react-bootstrap-table2/test/store/base.test.js index 82e6d6c..d3b0ec3 100644 --- a/packages/react-bootstrap-table2/test/store/base.test.js +++ b/packages/react-bootstrap-table2/test/store/base.test.js @@ -1,5 +1,6 @@ import Base from '../../src/store/base'; import Const from '../../src/const'; +import _ from '../../src/utils'; describe('Store Base', () => { let store; @@ -109,4 +110,41 @@ describe('Store Base', () => { }).toThrow(); }); }); + + describe('selectAllRowKeys', () => { + it('should return all row keys', () => { + const rowKeys = store.selectAllRowKeys(); + + expect(Array.isArray(rowKeys)).toBeTruthy(); + expect(rowKeys).toEqual([3, 2, 4, 1]); + }); + }); + + describe('isAllRowsSelected', () => { + it('should return true when all rows was selected', () => { + store.selected = data.map(row => _.get(row, store.keyField)); + + expect(store.isAllRowsSelected()).toBeTruthy(); + }); + + it('should return false when all rows was not selected', () => { + store.selected = [1]; + + expect(store.isAllRowsSelected()).not.toBeTruthy(); + }); + }); + + describe('isAnySelectedRow', () => { + it('should return true when one or more than one rows were selected', () => { + store.selected = data.map(row => _.get(row, store.keyField)); + + expect(store.isAnySelectedRow()).toBeTruthy(); + }); + + it('should return false when none was selected', () => { + store.selected = []; + + expect(store.isAnySelectedRow()).not.toBeTruthy(); + }); + }); }); diff --git a/packages/react-bootstrap-table2/test/test-helpers/mock-component.js b/packages/react-bootstrap-table2/test/test-helpers/mock-component.js index 9f2d943..ca7114d 100644 --- a/packages/react-bootstrap-table2/test/test-helpers/mock-component.js +++ b/packages/react-bootstrap-table2/test/test-helpers/mock-component.js @@ -1,9 +1,15 @@ +import Store from '../../src/store/base'; + export const extendTo = Base => class MockComponent extends Base { constructor(props) { super(props); + + const { data } = props; + + this.store = new Store(props); this.state = { - data: this.props.data, + data, currEditCell: { ridx: null, cidx: null