mirror of
https://github.com/gosticks/react-bootstrap-table2.git
synced 2025-10-16 11:55:39 +00:00
fix #50
* implement row single and multiple selection * radio button for single, checkbox for multiple * update component if status was changing * implement header cell for row selection * render checkbox for multiple, nothing for single * default css for th[data-th-row-selection] * update component if status was changing * transform cursor to pointer when hover button radio and checkbox * story for single and multiple rows selection * remove props required field and turn off eslint * [test] adapt with other component * props resolver for cell selection * if row selection was disabled, return mode 'ROW_SELECT_DISABLED' * refactor row selection cell * rename to selection-cell * remove unnecessary props * better coding style * props resolver for header cell selection * refactor row selection for header cell * rename to selection-header-cell * remove unnecessary props * better coding style * new logic for handleSelectAllRows * tunning for multi selection logic * allow user to customize select all result * remove cursor point * remove uncessary utils * tunning for function naming * mock data for resolved props including both body and header * judge cell-editable and row-selectable with mode * [test] unit test for props-resolver * move position of test case of cellEdit * add test for resolveCellSelectionProps * add test for resolveHeaderCellSelectionProps * accept row keys for mock-component * [test] add test for body * [test] add test for header * [test] add test for row * [test] add test for selection-cell * fix typo * [test] add test for selection-header-cell * add test for checkbox in selection-header-cell * [test] add test for bootstrap-table * test for handleRowSelect * test for handleAllRowsSelect * remove uncessary prop * remove unnecessary dafault mode for selectRow * add description for props shape * remove uncessary declaration of inputType * add isRequred for selectRow.mode * [test] verify the correctness of params when clicking on selection cell * [test] modification for test wording and unmatched data type * handle logic of row selection inside the store * ignore the situation of pagination * correct the tests * [test] add test for store/base.js * Document for row selection * modication for defects * simplify proptypes to basic data type * row selection document in README * refactor all function test with sinon * refactor all mock function to sinon.stub() instead jest.fn() * fix conflict
This commit is contained in:
parent
30d2645e4a
commit
877259158e
@ -14,6 +14,7 @@
|
||||
* [hover](#hover)
|
||||
* [condensed](#condensed)
|
||||
* [cellEdit](#cellEdit)
|
||||
* [selectRow](#selectRow)
|
||||
|
||||
### <a name='keyField'>keyField(**required**) - [String]</a>
|
||||
`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
|
||||
### <a name='condensed'>condensed - [Bool]</a>
|
||||
Same as `.table-condensed` class for makeing a table more compact by cutting cell padding in half
|
||||
|
||||
### <a name='cellEdit'>cellEdit - [Bool]</a>
|
||||
### <a name='cellEdit'>cellEdit - [Object]</a>
|
||||
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
|
||||
@ -64,3 +65,6 @@ Default is `false`, enable it will be able to save the cell automatically when b
|
||||
|
||||
#### <a name='cellEdit.timeToCloseMessage'>cellEdit.timeToCloseMessage - [Function]</a>
|
||||
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.
|
||||
|
||||
### <a name='selectRow'>selectRow - [Object]</a>
|
||||
Pass prop `selectRow` to enable row selection. For more detail, please navigate to [row selection document](./row-selection.md).
|
||||
|
||||
48
docs/row-selection.md
Normal file
48
docs/row-selection.md
Normal file
@ -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
|
||||
|
||||
## <a name="mode">selectRow.mode - [String]</a>
|
||||
|
||||
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
|
||||
};
|
||||
<BootstrapTableful
|
||||
keyField='id'
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
selectRow={ selectRowProp }
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
const selectRowProp = {
|
||||
mode: 'checkbox' // multiple row selection
|
||||
};
|
||||
|
||||
<BootstrapTableful
|
||||
keyField='id'
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
selectRow={ selectRowProp }
|
||||
/>
|
||||
```
|
||||
53
packages/react-bootstrap-table2-example/examples/row-selection/multiple-selection.js
vendored
Normal file
53
packages/react-bootstrap-table2-example/examples/row-selection/multiple-selection.js
vendored
Normal file
@ -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'
|
||||
};
|
||||
|
||||
<BootstrapTableful
|
||||
keyField='id'
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
selectRow={ selectRowProp }
|
||||
/>
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTableful keyField="id" data={ products } columns={ columns } selectRow={ selectRowProp } />
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
53
packages/react-bootstrap-table2-example/examples/row-selection/single-selection.js
vendored
Normal file
53
packages/react-bootstrap-table2-example/examples/row-selection/single-selection.js
vendored
Normal file
@ -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'
|
||||
};
|
||||
|
||||
<BootstrapTableful
|
||||
keyField='id'
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
selectRow={ selectRowProp }
|
||||
/>
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTableful keyField="id" data={ products } columns={ columns } selectRow={ selectRowProp } />
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
@ -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', () => <CellLevelEditable />)
|
||||
.add('Rich Hook Functions', () => <CellEditHooks />)
|
||||
.add('Validation', () => <CellEditValidator />);
|
||||
|
||||
storiesOf('Row Selection', module)
|
||||
.add('Single selection', () => <SingleSelectionTable />)
|
||||
.add('Multiple selection', () => <MultipleSelectionTable />);
|
||||
|
||||
21
packages/react-bootstrap-table2/src/body.js
vendored
21
packages/react-bootstrap-table2/src/body.js
vendored
@ -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 (
|
||||
<Row
|
||||
key={ key }
|
||||
@ -35,6 +46,8 @@ const Body = (props) => {
|
||||
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;
|
||||
|
||||
@ -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 (
|
||||
<div className="react-bootstrap-table-container">
|
||||
<table className={ tableClass }>
|
||||
@ -65,6 +76,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
|
||||
sortField={ this.store.sortField }
|
||||
sortOrder={ this.store.sortOrder }
|
||||
onSort={ this.handleSort }
|
||||
selectRow={ headerCellSelectionInfo }
|
||||
/>
|
||||
<Body
|
||||
data={ this.state.data }
|
||||
@ -74,12 +86,59 @@ class BootstrapTable extends PropsBaseResolver(Component) {
|
||||
visibleColumnSize={ this.visibleColumnSize() }
|
||||
noDataIndication={ noDataIndication }
|
||||
cellEdit={ cellEditInfo }
|
||||
selectRow={cellSelectionInfo}
|
||||
selectedRowKeys={this.state.selectedRowKeys}
|
||||
/>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
8
packages/react-bootstrap-table2/src/const.js
vendored
8
packages/react-bootstrap-table2/src/const.js
vendored
@ -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'
|
||||
};
|
||||
|
||||
15
packages/react-bootstrap-table2/src/header.js
vendored
15
packages/react-bootstrap-table2/src/header.js
vendored
@ -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 (
|
||||
<thead>
|
||||
<tr>
|
||||
{
|
||||
selectRow.mode === ROW_SELECT_DISABLED ? null : <SelectionHeaderCell {...selectRow} />
|
||||
}
|
||||
{
|
||||
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;
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
59
packages/react-bootstrap-table2/src/row-selection/selection-cell.js
vendored
Normal file
59
packages/react-bootstrap-table2/src/row-selection/selection-cell.js
vendored
Normal file
@ -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 (
|
||||
<td onClick={this.handleRowClick}>
|
||||
<input
|
||||
type={inputType}
|
||||
checked={selected}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
76
packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js
vendored
Normal file
76
packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js
vendored
Normal file
@ -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 }) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
ref={(input) => {
|
||||
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
|
||||
? <th data-row-selection />
|
||||
: (
|
||||
<th data-row-selection onClick={this.handleCheckBoxClick}>
|
||||
<CheckBox
|
||||
{...this.props}
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
/>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
}
|
||||
19
packages/react-bootstrap-table2/src/row.js
vendored
19
packages/react-bootstrap-table2/src/row.js
vendored
@ -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 (
|
||||
<tr>
|
||||
{
|
||||
selectRow.mode === ROW_SELECT_DISABLED
|
||||
? null
|
||||
: (
|
||||
<SelectionCell
|
||||
{ ...selectRow }
|
||||
rowKey={_.get(row, keyField)}
|
||||
selected={selected}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
columns.map((column, index) => {
|
||||
const { dataField } = column;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,10 @@
|
||||
margin: 10px 6.5px;
|
||||
}
|
||||
|
||||
th[data-row-selection] {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
td.react-bs-table-no-data {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -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(<Body keyField="id" columns={ columns } data={ data } />);
|
||||
wrapper = shallow(<Body {...mockBodyResolvedProps} keyField="id" columns={ columns } data={ data } />);
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
@ -41,6 +42,7 @@ describe('Body', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Body
|
||||
{...mockBodyResolvedProps}
|
||||
keyField="id"
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
@ -65,6 +67,7 @@ describe('Body', () => {
|
||||
emptyIndication = 'Table is empty';
|
||||
wrapper = shallow(
|
||||
<Body
|
||||
{...mockBodyResolvedProps}
|
||||
keyField="id"
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
@ -90,6 +93,7 @@ describe('Body', () => {
|
||||
emptyIndicationCallBack = sinon.stub().returns(content);
|
||||
wrapper = shallow(
|
||||
<Body
|
||||
{...mockBodyResolvedProps}
|
||||
keyField="id"
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
@ -123,6 +127,7 @@ describe('Body', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Body
|
||||
{...mockBodyResolvedProps}
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
keyField={ keyField }
|
||||
@ -143,4 +148,58 @@ describe('Body', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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(
|
||||
<Body
|
||||
{...mockBodyResolvedProps}
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
keyField={ keyField }
|
||||
selectedRowKeys={[1, 2]}
|
||||
selectRow={selectRow}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.find(Row).get(0).props.selected).toBe(true);
|
||||
});
|
||||
|
||||
it('props selected should be false if all rows were not selected', () => {
|
||||
wrapper = shallow(
|
||||
<Body
|
||||
{...mockBodyResolvedProps}
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
keyField={ keyField }
|
||||
selectedRowKeys={[]}
|
||||
selectRow={selectRow}
|
||||
/>
|
||||
);
|
||||
|
||||
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(
|
||||
<Body
|
||||
{...mockBodyResolvedProps}
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
keyField={ keyField }
|
||||
selectedRowKeys={[]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('prop selected should be null', () => {
|
||||
expect(wrapper.find(Row).get(0).props.selected).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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(
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
selectRow={{ mode: 'radio' }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
selectRow={{ mode: 'checkbox' }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
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(
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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(<Header columns={ columns } />);
|
||||
wrapper = shallow(<Header {...mockHeaderResolvedProps} columns={ columns } />);
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
@ -32,7 +34,12 @@ describe('Header', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Header columns={ columns } sortField={ sortField } sortOrder={ Const.SORT_ASC } />);
|
||||
<Header
|
||||
{...mockHeaderResolvedProps}
|
||||
columns={ columns }
|
||||
sortField={ sortField }
|
||||
sortOrder={ Const.SORT_ASC }
|
||||
/>);
|
||||
});
|
||||
|
||||
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(<Header {...mockHeaderResolvedProps} columns={ columns } />);
|
||||
});
|
||||
|
||||
it('should not render <SelectionHeaderCell />', () => {
|
||||
expect(wrapper.find(SelectionHeaderCell).length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the selectRow.mode is checkbox(multiple selection)', () => {
|
||||
beforeEach(() => {
|
||||
const selectRow = { mode: 'checkbox' };
|
||||
wrapper = shallow(
|
||||
<Header
|
||||
{...mockHeaderResolvedProps}
|
||||
columns={ columns }
|
||||
selectRow={selectRow}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should render <SelectionHeaderCell />', () => {
|
||||
expect(wrapper.find(SelectionHeaderCell).length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
16
packages/react-bootstrap-table2/test/mock-data/body-resolved-props.js
vendored
Normal file
16
packages/react-bootstrap-table2/test/mock-data/body-resolved-props.js
vendored
Normal file
@ -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
|
||||
};
|
||||
11
packages/react-bootstrap-table2/test/mock-data/header-resolved-props.js
vendored
Normal file
11
packages/react-bootstrap-table2/test/mock-data/header-resolved-props.js
vendored
Normal file
@ -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
|
||||
};
|
||||
@ -22,6 +22,7 @@ describe('TableResolver', () => {
|
||||
id: 2,
|
||||
name: 'B'
|
||||
}];
|
||||
|
||||
const ExtendBase = baseResolver(Component);
|
||||
const BootstrapTableMock = extendTo(ExtendBase);
|
||||
let wrapper;
|
||||
@ -99,7 +100,6 @@ describe('TableResolver', () => {
|
||||
expect(cellEdit.cidx).toEqual(cidx);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('if cellEdit prop defined', () => {
|
||||
const expectNonEditableRows = [1, 2];
|
||||
@ -143,4 +143,221 @@ describe('TableResolver', () => {
|
||||
expect(cellEditInfo.cb).toEqual(something.cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
}, null);
|
||||
wrapper = shallow(mockElement);
|
||||
headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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('<SelectionCell />', () => {
|
||||
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(<SelectionCell rowKey={1} mode={mode} selected={selected} />);
|
||||
|
||||
expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when selected prop has been changed', () => {
|
||||
it('should update component', () => {
|
||||
const nextProps = { selected: !selected };
|
||||
|
||||
wrapper = shallow(<SelectionCell rowKey={1} mode={mode} selected={selected} />);
|
||||
|
||||
expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleRowClick', () => {
|
||||
describe('when <input /> 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(
|
||||
<SelectionCell
|
||||
selected
|
||||
rowKey={rowKey}
|
||||
mode={mode}
|
||||
onRowSelect={mockOnRowSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
wrapper.find('td').simulate('click');
|
||||
|
||||
expect(spy.calledOnce).toBe(true);
|
||||
expect(mockOnRowSelect.calledOnce).toBe(true);
|
||||
});
|
||||
|
||||
describe('if selectRow.mode is radio', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<SelectionCell
|
||||
selected
|
||||
rowKey={rowKey}
|
||||
mode="radio"
|
||||
onRowSelect={mockOnRowSelect}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
<SelectionCell
|
||||
rowKey={rowKey}
|
||||
mode="checkbox"
|
||||
onRowSelect={mockOnRowSelect}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
<SelectionCell
|
||||
rowKey={1}
|
||||
mode={mode}
|
||||
selected={selected}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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('<SelectionHeaderCell />', () => {
|
||||
describe('shouldComponentUpdate', () => {
|
||||
describe('when props.mode is radio', () => {
|
||||
it('should not update component', () => {
|
||||
wrapper = shallow(<SelectionHeaderCell mode="radio" />);
|
||||
|
||||
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(
|
||||
<SelectionHeaderCell mode="checkbox" checkedStatus={checkedStatus} />);
|
||||
|
||||
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(
|
||||
<SelectionHeaderCell mode="checkbox" checkedStatus={CHECKBOX_STATUS_INDETERMINATE} />);
|
||||
|
||||
expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCheckBoxClick', () => {
|
||||
describe('when <th /> 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(
|
||||
<SelectionHeaderCell
|
||||
mode="radio"
|
||||
checkedStatus={Constant.CHECKBOX_STATUS_CHECKED}
|
||||
onAllRowsSelect={mockOnAllRowsSelect}
|
||||
/>);
|
||||
});
|
||||
|
||||
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(
|
||||
<SelectionHeaderCell
|
||||
mode="checkbox"
|
||||
checkedStatus={Constant.CHECKBOX_STATUS_CHECKED}
|
||||
onAllRowsSelect={mockOnAllRowsSelect}
|
||||
/>);
|
||||
});
|
||||
|
||||
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(<SelectionHeaderCell mode="radio" checkedStatus={checkedStatus} />);
|
||||
});
|
||||
|
||||
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(<SelectionHeaderCell mode="checkbox" checkedStatus={checkedStatus} />);
|
||||
});
|
||||
|
||||
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('<CheckBox />', () => {
|
||||
describe('render', () => {
|
||||
it('should render component correctly', () => {
|
||||
const checked = true;
|
||||
const indeterminate = false;
|
||||
wrapper = shallow(<CheckBox checked={checked} indeterminate={indeterminate} />);
|
||||
|
||||
expect(wrapper.find('input').length).toBe(1);
|
||||
expect(wrapper.find('input').prop('checked')).toBe(checked);
|
||||
expect(wrapper.find('input').prop('type')).toBe('checkbox');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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(
|
||||
<Row rowIndex={ 1 } columns={ defaultColumns } row={ row } cellEdit={ {} } />);
|
||||
<Row {...mockBodyResolvedProps} rowIndex={ 1 } columns={ defaultColumns } row={ row } />);
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
@ -53,6 +55,7 @@ describe('Row', () => {
|
||||
};
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{...mockBodyResolvedProps}
|
||||
row={ row }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ columns }
|
||||
@ -92,6 +95,7 @@ describe('Row', () => {
|
||||
columns[nonEditableColIndex].editable = false;
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{...mockBodyResolvedProps}
|
||||
row={ row }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ columns }
|
||||
@ -128,6 +132,7 @@ describe('Row', () => {
|
||||
columns[nonEditableColIndex].editable = editableCallBack;
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{...mockBodyResolvedProps}
|
||||
row={ row }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ columns }
|
||||
@ -160,6 +165,7 @@ describe('Row', () => {
|
||||
columns[nonEditableColIndex].editable = editableCallBack;
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{...mockBodyResolvedProps}
|
||||
row={ row }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ columns }
|
||||
@ -193,6 +199,7 @@ describe('Row', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{...mockBodyResolvedProps}
|
||||
row={ row }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ columns }
|
||||
@ -222,6 +229,7 @@ describe('Row', () => {
|
||||
cellEdit.onEscape = sinon.stub();
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{...mockBodyResolvedProps}
|
||||
row={ row }
|
||||
rowIndex={ 1 }
|
||||
columns={ columns }
|
||||
@ -233,9 +241,12 @@ describe('Row', () => {
|
||||
});
|
||||
|
||||
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(
|
||||
<Row
|
||||
{...mockBodyResolvedProps}
|
||||
row={ row }
|
||||
rowIndex={ 1 }
|
||||
columns={ columns }
|
||||
@ -266,4 +278,33 @@ describe('Row', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when selectRow.mode is ROW_SELECT_DISABLED (row was un-selectable)', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Row {...mockBodyResolvedProps} rowIndex={ 1 } columns={ defaultColumns } row={ row } />);
|
||||
});
|
||||
|
||||
it('should not render <SelectionCell />', () => {
|
||||
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(
|
||||
<Row
|
||||
{...mockBodyResolvedProps}
|
||||
rowIndex={ 1 }
|
||||
columns={ defaultColumns }
|
||||
row={ row }
|
||||
selectRow={selectRow}
|
||||
/>);
|
||||
});
|
||||
|
||||
it('should render <SelectionCell />', () => {
|
||||
expect(wrapper.find(SelectionCell).length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user