From 3a8faf8170078c7d2983a8422c2dd39c77d1a54c Mon Sep 17 00:00:00 2001 From: Chun-MingChen Date: Sun, 7 Oct 2018 23:57:16 +0800 Subject: [PATCH 01/38] Add favicon --- .../public/favicon.ico | Bin 0 -> 17542 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/public/favicon.ico diff --git a/packages/react-bootstrap-table2-example/public/favicon.ico b/packages/react-bootstrap-table2-example/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8fc5cecac4430bbf902619d075d39674a19b114e GIT binary patch literal 17542 zcmeI3Ux-yz6vnr9hD^i;5%FQrK};lk5DSSxoE{{?2N97RB*f|^d=ME1lMsyxk&qrl zqPa*#L`X#RASxmu8tOx-^bq1h$utfkB9nzoI^EOn+vlt^XYS0s-h1cVe{m0dtiAWy zd#!J+z0N-S?m6S!EEl-WPKWGvZwJo3;+$K&II16h#<}lk>+X)~C+N3!zH_Tr$Mxr& z`)!GHD;dKrG(_!)x?Cf<4hI%gYb+jr2l)|LRE?h7xsFwgJpo2<=Yp#BFg~TH22=K(|t^0e4%FpxMTHFl5r0F4Z5r!W0!!vRxc%|8-J?Lt)l%{sd08MJ)ZLO z;2SWCPi6UEF#ZVjkg*@bVV>D2C2c%$T%&%}*MW2BxLInP-HXS6f_wlz1xpya0$Eo4 zQ+m1?lLON8eWl~4(KBRi)kxyO3XuI3hRzB9fboNrmxAA&drbYYB=twV$j_Y{qn@W8 zQnqngXGDzU$FEn}Gi-j1tT)SeKIhhoDZ2ZyF^t?@3p*o&LC@{M;06f6Ft#$cgN*?0 zJHQPPf??XQ6J#1UxKFaf`agVoaAyAS>`%Gi9}ue@skps9rBj&=tWP^x9}i$>PT>&j z^hEIrW}?5J^&>?8s;T+|>DQd-5w7Y&Z&Cfkf9}uk%$|e&g;gnM5M%qXF@-;E0ob8; z1DIKSQu>(-uNNu3dVdXL=cQ`-Gd78jIOESv%?JALP~VOIDf3>5EyYLr(6tJkvnijA z^*=-&qdnP6RHE1VGQJ%LY=OJZnQ>(hr(JwvN_z}Jv(B&^l+PB9xs-NFJ zw-CLD!2_WAya2o%Z1kuN+g0l~r_r$peTUKgz;o{NcHZ^n8RVzwHz_;v2V2^k$e)n< zjQN{>lj!ySpj&n_*Pqyl{8?u8|Dz1qFeNxhc`3LI{siM74-Np`liUa10&m3S^~m|) z0vH7vlhUbuST}eX=yzlLYTXF%F4k}8jbL^oyol@v_mJ1YR`yhN<-wj%{*M6d6GQC( zLfcl#b>lHI*wMwl@hCPQ8V~lX#G}qUavfdRKZ@;yhvEV18xP}=jzU?OvyC3&`}nfW9gDhu_<2H-EL~K9sdxOO@&D)3ywp z0mfmRvd$&ffJ`sx1C!!!258sszEeQ<#YHk__p^}bOWXgD`lK_W`2Jet4?rAhk^eVi z-O@h-_JGMewz*AlpG@s5hUC{gbngW^Bd|HwIYh#PJ~gv%^N789*eT@ULP*-v;(%*)-_cINqKwn|AKkBR)KdHjoUiFsta0u{5?Zl7rcI5U`hU88yt z+yZ00VVHlG!Uy1wFue9x465pPO>Nw}N}sDsBdPjj+Aq>~6pX(>$EUzWK)vA7ImK$Q z6SU7M2txa6N1z>nBm(*^$8Y;X$Q4Or3l%*7Trk9$#2Dqz_>H{0h3MAh*n)G38Zj_|Y*Rp8I*G?^;e#w%mHeym&w8)6|a#u}|d{{FAv*xpmen`B5sb zn41>KtH93(zTC6CGRbp(rV+`+?>U`4RCJN|^ND%#oRdq+t+N#=$*41D5;{Bj0w31P zmr5@D*b8i(Y6yRE=6qFW|Ks>@4!NvcAK*Ta5^9RszAKB3LN4WzB<b^-pKO#z;3es6y^{0+1%H6uJ83 zM&F@Y3}+%0f8smw{z~m1;HP_Q@#!Se&x7W|z|C&Z`#TpY{VElDxvFmWaRcH%T5@ULCQvR+PlQeYGRd zjzE%f@$r(RI!LO6m$wc^a?Hzo zI3CH~Liw-npV&jK#^rnSasPOHZ=^5Z8|lxlkL=~wMfMYE?bgr6Esga&9oY|O#q#`q fZzmk>isZP9WN7kyTpx{PJTBC~mdW3U?-S&I+dEV< literal 0 HcmV?d00001 From 7a31729ebb7803599b9c4f4fcace29ab0045edb1 Mon Sep 17 00:00:00 2001 From: Chun-MingChen Date: Mon, 8 Oct 2018 00:07:44 +0800 Subject: [PATCH 02/38] Introduce logo to --- .../examples/welcome.js | 13 +++++++++++-- .../public/images/logo-color-square.svg | 1 + .../stories/stylesheet/welcome/_index.scss | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/public/images/logo-color-square.svg diff --git a/packages/react-bootstrap-table2-example/examples/welcome.js b/packages/react-bootstrap-table2-example/examples/welcome.js index 41f3547..18a643e 100644 --- a/packages/react-bootstrap-table2-example/examples/welcome.js +++ b/packages/react-bootstrap-table2-example/examples/welcome.js @@ -1,6 +1,8 @@ import React from 'react'; import Typed from 'typed.js'; +const PROJECT_NAME = 'react-bootstrap-table2'; + export default class Welcome extends React.Component { componentDidMount() { // type.js config @@ -21,13 +23,20 @@ export default class Welcome extends React.Component { return (
-

react-bootstrap-table2

+
+ + { + +

+ {PROJECT_NAME} +

+
{ this.el = el; } } />
- +
- } if (props.selectRow) { - this.SelectionContext = createSelectionContext(dataOperator); + this.SelectionContext = SelectionContext; } if (props.expandRow) { @@ -62,13 +62,11 @@ const withContext = Base => searchProps, sortProps, paginationProps, - expandProps, - selectionProps + expandProps ) => ( this.table = n } { ...this.props } - { ...selectionProps } { ...sortProps } { ...cellEditProps } { ...filterProps } @@ -96,20 +94,17 @@ const withContext = Base => selectRow={ this.props.selectRow } data={ rootProps.getData(filterProps, searchProps, sortProps, paginationProps) } > - - { - selectionProps => base( - rootProps, - cellEditProps, - filterProps, - searchProps, - sortProps, - paginationProps, - expandProps, - selectionProps - ) - } - + { + base( + rootProps, + cellEditProps, + filterProps, + searchProps, + sortProps, + paginationProps, + expandProps + ) + } ); } diff --git a/packages/react-bootstrap-table2/src/contexts/selection-context.js b/packages/react-bootstrap-table2/src/contexts/selection-context.js index 12a7af8..964b704 100644 --- a/packages/react-bootstrap-table2/src/contexts/selection-context.js +++ b/packages/react-bootstrap-table2/src/contexts/selection-context.js @@ -3,113 +3,132 @@ import React from 'react'; import PropTypes from 'prop-types'; import Const from '../const'; -export default ( - dataOperator -) => { - const SelectionContext = React.createContext(); +import dataOperator from '../store/operators'; +import { getSelectionSummary } from '../store/selection'; - class SelectionProvider extends React.Component { - static propTypes = { - children: PropTypes.node.isRequired, - data: PropTypes.array.isRequired, - keyField: PropTypes.string.isRequired - } +const SelectionContext = React.createContext(); +class SelectionProvider extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + data: PropTypes.array.isRequired, + keyField: PropTypes.string.isRequired + } - constructor(props) { - super(props); - if (props.registerExposedAPI) { - const getSelected = () => this.getSelected(); - props.registerExposedAPI(getSelected); - } - } - - state = { selected: (this.props.selectRow && this.props.selectRow.selected) || [] }; - - componentWillReceiveProps(nextProps) { - if (nextProps.selectRow) { - this.setState(() => ({ - selected: nextProps.selectRow.selected || this.state.selected - })); - } - } - - // exposed API - getSelected() { - return this.state.selected; - } - - handleRowSelect = (rowKey, checked, rowIndex, e) => { - const { data, keyField, selectRow: { mode, onSelect } } = this.props; - const { ROW_SELECT_SINGLE } = Const; - - let currSelected = [...this.state.selected]; - - 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); - } - - if (onSelect) { - const row = dataOperator.getRowByRowId(data, keyField, rowKey); - onSelect(row, checked, rowIndex, e); - } - - this.setState(() => ({ selected: currSelected })); - } - - handleAllRowsSelect = (e, isUnSelect) => { - const { - data, - keyField, - selectRow: { - onSelectAll, - nonSelectable - } - } = this.props; - const { selected } = this.state; - - let currSelected; - - if (!isUnSelect) { - currSelected = selected.concat(dataOperator.selectableKeys(data, keyField, nonSelectable)); - } else { - currSelected = selected.filter(s => typeof data.find(d => d[keyField] === s) === 'undefined'); - } - - if (onSelectAll) { - onSelectAll( - !isUnSelect, - dataOperator.getSelectedRows( - data, - keyField, - isUnSelect ? this.state.selected : currSelected - ), - e - ); - } - - this.setState(() => ({ selected: currSelected })); - } - - render() { - return ( - - { this.props.children } - - ); + constructor(props) { + super(props); + if (props.registerExposedAPI) { + const getSelected = () => this.getSelected(); + props.registerExposedAPI(getSelected); } } - return { - Provider: SelectionProvider, - Consumer: SelectionContext.Consumer - }; + + state = { selected: (this.props.selectRow && this.props.selectRow.selected) || [] }; + + componentWillReceiveProps(nextProps) { + if (nextProps.selectRow) { + this.setState(() => ({ + selected: nextProps.selectRow.selected || this.state.selected + })); + } + } + + // exposed API + getSelected() { + return this.state.selected; + } + + handleRowSelect = (rowKey, checked, rowIndex, e) => { + const { data, keyField, selectRow: { mode, onSelect } } = this.props; + const { ROW_SELECT_SINGLE } = Const; + + let currSelected = [...this.state.selected]; + + 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); + } + + if (onSelect) { + const row = dataOperator.getRowByRowId(data, keyField, rowKey); + onSelect(row, checked, rowIndex, e); + } + + this.setState(() => ({ selected: currSelected })); + } + + handleAllRowsSelect = (e, isUnSelect) => { + const { + data, + keyField, + selectRow: { + onSelectAll, + nonSelectable + } + } = this.props; + const { selected } = this.state; + + let currSelected; + + if (!isUnSelect) { + currSelected = selected.concat(dataOperator.selectableKeys(data, keyField, nonSelectable)); + } else { + currSelected = selected.filter(s => typeof data.find(d => d[keyField] === s) === 'undefined'); + } + + if (onSelectAll) { + onSelectAll( + !isUnSelect, + dataOperator.getSelectedRows( + data, + keyField, + isUnSelect ? this.state.selected : currSelected + ), + e + ); + } + + this.setState(() => ({ selected: currSelected })); + } + + render() { + const { + allRowsSelected, + allRowsNotSelected + } = getSelectionSummary( + this.props.data, + this.props.keyField, + this.state.selected + ); + + let checkedStatus; + + // checkbox status depending on selected rows counts + if (allRowsSelected) checkedStatus = Const.CHECKBOX_STATUS_CHECKED; + else if (allRowsNotSelected) checkedStatus = Const.CHECKBOX_STATUS_UNCHECKED; + else checkedStatus = Const.CHECKBOX_STATUS_INDETERMINATE; + + return ( + + { this.props.children } + + ); + } +} + +export default { + Provider: SelectionProvider, + Consumer: SelectionContext.Consumer }; diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index 4549343..9120c77 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -6,6 +6,7 @@ import Const from './const'; import HeaderCell from './header-cell'; import SelectionHeaderCell from './row-selection/selection-header-cell'; import ExpandHeaderCell from './row-expand/expand-header-cell'; +import bindSelection from './row-selection/selection-header-cell-binder'; const Header = (props) => { const { ROW_SELECT_DISABLED } = Const; @@ -23,6 +24,12 @@ const Header = (props) => { bootstrap4 } = props; + let SelectionHeaderCellComp = () => {}; + + if (selectRow) { + SelectionHeaderCellComp = bindSelection(SelectionHeaderCell); + } + return ( @@ -35,8 +42,8 @@ const Header = (props) => { /> : null } { - (selectRow.mode !== ROW_SELECT_DISABLED && !selectRow.hideSelectColumn) - ? : null + (selectRow.mode !== ROW_SELECT_DISABLED && !selectRow.hideSelectColumn) ? + : null } { columns.map((column, i) => { diff --git a/packages/react-bootstrap-table2/src/props-resolver/index.js b/packages/react-bootstrap-table2/src/props-resolver/index.js index 2154ce3..105c3ea 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/index.js +++ b/packages/react-bootstrap-table2/src/props-resolver/index.js @@ -1,7 +1,5 @@ import ColumnResolver from './column-resolver'; import ExpandRowResolver from './expand-row-resolver'; -import Const from '../const'; -import _ from '../utils'; export default ExtendBase => class TableResolver extends @@ -19,63 +17,4 @@ export default ExtendBase => isEmpty() { return this.props.data.length === 0; } - - /** - * 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. - */ - resolveSelectRowProps(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 - */ - resolveSelectRowPropsForHeader(options = {}) { - const { selectRow } = this.props; - const { allRowsSelected, allRowsNotSelected, ...rest } = options; - const { - ROW_SELECT_DISABLED, CHECKBOX_STATUS_CHECKED, - CHECKBOX_STATUS_INDETERMINATE, CHECKBOX_STATUS_UNCHECKED - } = Const; - - if (_.isDefined(selectRow)) { - let checkedStatus; - - // checkbox status depending on selected rows counts - if (allRowsSelected) checkedStatus = CHECKBOX_STATUS_CHECKED; - else if (allRowsNotSelected) checkedStatus = CHECKBOX_STATUS_UNCHECKED; - else checkedStatus = CHECKBOX_STATUS_INDETERMINATE; - - return { - ...selectRow, - ...rest, - checkedStatus - }; - } - - return { - mode: ROW_SELECT_DISABLED - }; - } }; diff --git a/packages/react-bootstrap-table2/src/row-aggregator.js b/packages/react-bootstrap-table2/src/row-aggregator.js new file mode 100644 index 0000000..fc36355 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-aggregator.js @@ -0,0 +1,140 @@ +/* eslint react/prop-types: 0 */ +/* eslint react/no-array-index-key: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import _ from './utils'; +import Row from './row'; +import ExpandCell from './row-expand/expand-cell'; +import SelectionCell from './row-selection/selection-cell'; + +export default class RowAggregator extends React.Component { + static propTypes = { + attrs: PropTypes.object + } + static defaultProps = { + attrs: {} + } + + constructor(props) { + super(props); + this.clickNum = 0; + this.createClickEventHandler = this.createClickEventHandler.bind(this); + } + + createClickEventHandler(cb) { + return (e) => { + const { + row, + selected, + keyField, + selectable, + expandable, + rowIndex, + expanded, + expandRow, + selectRow, + cellEdit: { + mode, + DBCLICK_TO_CELL_EDIT, + DELAY_FOR_DBCLICK + } + } = this.props; + + const clickFn = () => { + if (cb) { + cb(e, row, rowIndex); + } + const key = _.get(row, keyField); + if (expandRow && expandable) { + expandRow.onRowExpand(key, !expanded, rowIndex, e); + } + if (selectable) { + selectRow.onRowSelect(key, !selected, rowIndex, e); + } + }; + + if (mode === DBCLICK_TO_CELL_EDIT && selectRow.clickToEdit) { + this.clickNum += 1; + _.debounce(() => { + if (this.clickNum === 1) { + clickFn(); + } + this.clickNum = 0; + }, DELAY_FOR_DBCLICK)(); + } else { + clickFn(); + } + }; + } + + render() { + const { + row, + columns, + keyField, + rowIndex, + style, + className, + attrs, + cellEdit, + selectRow, + expandRow, + selected, + selectable, + expandRowEnabled + } = this.props; + + const key = _.get(row, keyField); + const { hideSelectColumn } = selectRow; + const { showExpandColumn } = expandRow || {}; + + const nonEditableRows = cellEdit.nonEditableRows || []; + const editable = !(nonEditableRows.length > 0 && nonEditableRows.indexOf(key) > -1); + // const expandable = expandRowEnabled && !expandRow.nonExpandable.includes(key); + const expanded = expandRowEnabled && expandRow.expanded.includes(key); + + const newAttrs = { ...attrs }; + if (selectRow.clickToSelect || expandRowEnabled) { + newAttrs.onClick = this.createClickEventHandler(attrs.onClick); + } + + return ( + + { + showExpandColumn ? ( + + ) : null + } + { + !hideSelectColumn + ? ( + + ) + : null + } + + ); + } +} diff --git a/packages/react-bootstrap-table2/src/row-event-delegater.js b/packages/react-bootstrap-table2/src/row-event-delegater.js index ee62606..4727134 100644 --- a/packages/react-bootstrap-table2/src/row-event-delegater.js +++ b/packages/react-bootstrap-table2/src/row-event-delegater.js @@ -1,6 +1,3 @@ -import _ from './utils'; -import Const from './const'; - const events = [ 'onClick', 'onDoubleClick', @@ -15,7 +12,6 @@ export default ExtendBase => super(props); this.clickNum = 0; this.createDefaultEventHandler = this.createDefaultEventHandler.bind(this); - this.createClickEventHandler = this.createClickEventHandler.bind(this); } createDefaultEventHandler(cb) { @@ -25,65 +21,11 @@ export default ExtendBase => }; } - createClickEventHandler(cb) { - return (e) => { - const { - row, - selected, - keyField, - selectable, - expandable, - rowIndex, - expanded, - expandRow, - selectRow, - cellEdit: { - mode, - DBCLICK_TO_CELL_EDIT, - DELAY_FOR_DBCLICK - } - } = this.props; - - const clickFn = () => { - if (cb) { - cb(e, row, rowIndex); - } - const key = _.get(row, keyField); - if (expandRow && expandable) { - expandRow.onRowExpand(key, !expanded, rowIndex, e); - } - if (selectRow.mode !== Const.ROW_SELECT_DISABLED && selectable) { - selectRow.onRowSelect(key, !selected, rowIndex, e); - } - }; - - if (mode === DBCLICK_TO_CELL_EDIT && selectRow.clickToEdit) { - this.clickNum += 1; - _.debounce(() => { - if (this.clickNum === 1) { - clickFn(); - } - this.clickNum = 0; - }, DELAY_FOR_DBCLICK)(); - } else { - clickFn(); - } - }; - } - delegate(attrs = {}) { - const newAttrs = {}; - const { expandRow, selectRow } = this.props; - if (expandRow || (selectRow && selectRow.clickToSelect)) { - newAttrs.onClick = this.createClickEventHandler(attrs.onClick); - } + const newAttrs = { ...attrs }; Object.keys(attrs).forEach((attr) => { - if (!newAttrs[attr]) { - if (events.includes(attr)) { - newAttrs[attr] = this.createDefaultEventHandler(attrs[attr]); - } else { - newAttrs[attr] = attrs[attr]; - } + if (events.includes(attr)) { + newAttrs[attr] = this.createDefaultEventHandler(attrs[attr]); } }); return newAttrs; diff --git a/packages/react-bootstrap-table2/src/row-selection/row-binder.js b/packages/react-bootstrap-table2/src/row-selection/row-binder.js new file mode 100644 index 0000000..f0ad227 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-selection/row-binder.js @@ -0,0 +1,57 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import cs from 'classnames'; +import _ from '../utils'; +import SelectionContext from '../contexts/selection-context'; + +export default (Component) => { + const renderWithSelection = (props, selectRow) => { + const key = _.get(props.row, props.keyField); + const selected = selectRow.selected.includes(key); + const selectable = !selectRow.nonSelectable || !selectRow.nonSelectable.includes(key); + + let { + style, + className + } = props; + + if (selected) { + const selectedStyle = _.isFunction(selectRow.style) + ? selectRow.style(props.row, props.rowIndex) + : selectRow.style; + + const selectedClasses = _.isFunction(selectRow.classes) + ? selectRow.classes(props.row, props.rowIndex) + : selectRow.classes; + + style = { + ...style, + ...selectedStyle + }; + className = cs(className, selectedClasses); + + if (selectRow.bgColor) { + style = style || {}; + style.backgroundColor = _.isFunction(selectRow.bgColor) + ? selectRow.bgColor(props.row, props.rowIndex) + : selectRow.bgColor; + } + } + + return ( + + ); + }; + return props => ( + + { selectRow => renderWithSelection(props, selectRow) } + + ); +}; diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell-binder.js b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell-binder.js new file mode 100644 index 0000000..de7d3c3 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell-binder.js @@ -0,0 +1,8 @@ +import React from 'react'; +import SelectionContext from '../contexts/selection-context'; + +export default Component => () => ( + + { selectRow => } + +); diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js index e3254b5..8de768f 100644 --- a/packages/react-bootstrap-table2/src/row.js +++ b/packages/react-bootstrap-table2/src/row.js @@ -5,10 +5,7 @@ import PropTypes from 'prop-types'; import _ from './utils'; import Cell from './cell'; -import SelectionCell from './row-selection/selection-cell'; -import ExpandCell from './row-expand/expand-cell'; import eventDelegater from './row-event-delegater'; -import Const from './const'; class Row extends eventDelegater(Component) { render() { @@ -21,11 +18,6 @@ class Row extends eventDelegater(Component) { style, attrs, cellEdit, - selected, - selectRow, - expanded, - expandRow, - selectable, editable: editableRow } = this.props; @@ -39,37 +31,11 @@ class Row extends eventDelegater(Component) { DBCLICK_TO_CELL_EDIT, ...rest } = cellEdit; - - const key = _.get(row, keyField); - const { hideSelectColumn } = selectRow; - const { showExpandColumn } = expandRow || {}; const trAttrs = this.delegate(attrs); return ( - { - showExpandColumn ? ( - - ) : null - } - { - (selectRow.mode !== Const.ROW_SELECT_DISABLED && !hideSelectColumn) - ? ( - - ) - : null - } + { this.props.children } { columns.map((column, index) => { if (!column.hidden) { From 41cc6b01afaff5bbfc62f2a1234dbdcf6a0c84ea Mon Sep 17 00:00:00 2001 From: AllenFang Date: Wed, 15 Aug 2018 22:51:59 +0800 Subject: [PATCH 05/38] patch row event example --- .../examples/rows/row-event.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/react-bootstrap-table2-example/examples/rows/row-event.js b/packages/react-bootstrap-table2-example/examples/rows/row-event.js index 1a57fb6..591805f 100644 --- a/packages/react-bootstrap-table2-example/examples/rows/row-event.js +++ b/packages/react-bootstrap-table2-example/examples/rows/row-event.js @@ -1,5 +1,5 @@ /* eslint no-unused-vars: 0 */ -/* eslint no-alert: 0 */ +/* eslint no-console: 0 */ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; @@ -21,7 +21,10 @@ const columns = [{ const rowEvents = { onClick: (e, row, rowIndex) => { - alert(`clicked on row with index: ${rowIndex}`); + console.log(`clicked on row with index: ${rowIndex}`); + }, + onMouseEnter: (e, row, rowIndex) => { + console.log(`enter on row with index: ${rowIndex}`); } }; @@ -41,7 +44,10 @@ const columns = [{ const rowEvents = { onClick: (e, row, rowIndex) => { - alert(\`clicked on row with index: \${rowIndex}\`); + console.log(\`clicked on row with index: \${rowIndex}\`); + }, + onMouseEnter: (e, row, rowIndex) => { + console.log(\`enter on row with index: \${rowIndex}\`); } }; @@ -50,7 +56,7 @@ const rowEvents = { export default () => (
-

Try to click on any rows

+

Try to click or hover on any rows

{ sourceCode }
From 02d78e510449eb50a6b9a8d347261f76356664fd Mon Sep 17 00:00:00 2001 From: AllenFang Date: Thu, 16 Aug 2018 16:32:40 +0800 Subject: [PATCH 06/38] patch tests for refactoring row component with selection --- .../react-bootstrap-table2/test/body.test.js | 368 ++---------------- .../test/contexts/selection-context.test.js | 9 +- .../test/header.test.js | 78 ++-- .../test/props-resolver/index.test.js | 229 ----------- .../test/row-aggregator.test.js | 167 ++++++++ .../test/row-selection/row-binder.test.js | 349 +++++++++++++++++ .../react-bootstrap-table2/test/row.test.js | 342 ---------------- 7 files changed, 606 insertions(+), 936 deletions(-) create mode 100644 packages/react-bootstrap-table2/test/row-aggregator.test.js create mode 100644 packages/react-bootstrap-table2/test/row-selection/row-binder.test.js diff --git a/packages/react-bootstrap-table2/test/body.test.js b/packages/react-bootstrap-table2/test/body.test.js index ea624e5..2be26b1 100644 --- a/packages/react-bootstrap-table2/test/body.test.js +++ b/packages/react-bootstrap-table2/test/body.test.js @@ -1,11 +1,14 @@ +import 'jsdom-global/register'; import React from 'react'; import sinon from 'sinon'; -import { shallow } from 'enzyme'; +import { shallow, mount } from 'enzyme'; import Body from '../src/body'; import Row from '../src/row'; +import RowAggregator from '../src/row-aggregator'; import Const from '../src/const'; import RowSection from '../src/row-section'; +import SelectionContext from '../src/contexts/selection-context'; import mockBodyResolvedProps from './test-helpers/mock/body-resolved-props'; describe('Body', () => { @@ -169,92 +172,6 @@ describe('Body', () => { }); }); }); - - describe('when selectRow.style is defined', () => { - const selectedRowKey = data[0][keyField]; - const selectedRowKeys = [selectedRowKey]; - const selectedStyle = { backgroundColor: 'green', fontWeight: 'bold' }; - const selectRow = { mode: 'radio', style: selectedStyle }; - - beforeEach(() => { - wrapper = shallow( - ); - }); - - it('should rendering selected Row component with mixing selectRow.style correctly', () => { - const selectedRow = wrapper.find(Row).get(0); - expect(JSON.stringify(selectedRow.props.style)).toBe(JSON.stringify({ - ...rowStyle, - ...selectedStyle - })); - }); - - describe('and selectRow.bgColor is also defined', () => { - beforeEach(() => { - selectRow.bgColor = 'gray'; - wrapper = shallow( - ); - }); - - it('should rendering selected Row component with mixing selectRow.style correctly', () => { - const selectedRow = wrapper.find(Row).get(0); - expect(JSON.stringify(selectedRow.props.style)).toBe(JSON.stringify({ - ...rowStyle, - ...selectedStyle, - backgroundColor: selectRow.bgColor - })); - }); - - it('should render selected Row component with correct style.backgroundColor', () => { - const selectedRow = wrapper.find(Row).get(0); - expect(selectedRow.props.style.backgroundColor).toEqual(selectRow.bgColor); - }); - }); - }); - - describe('when selectRow.bgColor is defined', () => { - const selectedRowKey = data[0][keyField]; - const selectedRowKeys = [selectedRowKey]; - const selectRow = { mode: 'radio', bgColor: 'gray' }; - - beforeEach(() => { - selectRow.bgColor = 'gray'; - wrapper = shallow( - ); - }); - - it('should rendering selected Row component with correct style', () => { - const selectedRow = wrapper.find(Row).get(0); - expect(JSON.stringify(selectedRow.props.style)).toBe(JSON.stringify({ - ...rowStyle, - backgroundColor: selectRow.bgColor - })); - }); - }); }); describe('when rowClasses prop is defined', () => { @@ -310,31 +227,6 @@ describe('Body', () => { }); }); }); - - describe('when selectRow.classes is defined', () => { - const selectedRowKey = data[0][keyField]; - const selectedRowKeys = [selectedRowKey]; - const selectedClasses = 'selected-classes'; - const selectRow = { mode: 'radio', classes: selectedClasses }; - - beforeEach(() => { - wrapper = shallow( - ); - }); - - it('should rendering selected Row component with mixing selectRow.classes correctly', () => { - const selectedRow = wrapper.find(Row).get(0); - expect(selectedRow.props.className).toBe(`${rowClasses} ${selectedClasses}`); - }); - }); }); describe('when rowEvents prop is defined', () => { @@ -392,231 +284,6 @@ describe('Body', () => { }); }); - describe('when selectRow.mode is checkbox or radio (row was selectable)', () => { - const selectRow = { mode: 'checkbox' }; - const selectedRowKey = data[0][keyField]; - const selectedRowKeys = [selectedRowKey]; - - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('should render Row component with correct selected prop', () => { - const rows = wrapper.find(Row); - for (let i = 0; i < rows.length; i += 1) { - const row = rows.get(i); - expect(row.props.selected).toBe(selectedRowKeys.indexOf(row.props.row[keyField]) > -1); - } - }); - - describe('if selectRow.style is defined as an object', () => { - const style = { backgroundColor: 'red' }; - - beforeEach(() => { - selectRow.style = style; - wrapper = shallow( - - ); - }); - - it('should render Row component with correct style prop', () => { - expect(JSON.stringify(wrapper.find(Row).get(0).props.style)).toBe(JSON.stringify(style)); - }); - }); - - describe('if selectRow.style is defined as a function', () => { - const style = { backgroundColor: 'red' }; - const styleCallBack = sinon.stub().returns(style); - - beforeEach(() => { - selectRow.style = styleCallBack; - wrapper = shallow( - - ); - }); - - it('should calling style callback correctly', () => { - expect(styleCallBack.callCount).toBe(1); - expect(styleCallBack.calledWith(data[0]), 1); - }); - - it('should render Row component with correct style prop', () => { - expect(JSON.stringify(wrapper.find(Row).get(0).props.style)).toBe(JSON.stringify(style)); - }); - }); - - describe('if selectRow.classes is defined as a string', () => { - const className = 'custom-class'; - - beforeEach(() => { - selectRow.classes = className; - wrapper = shallow( - - ); - }); - - it('should render Row component with correct className prop', () => { - expect(wrapper.find(Row).get(0).props.className).toEqual(className); - }); - }); - - describe('if selectRow.classes is defined as a function', () => { - const className = 'custom-class'; - const classesCallBack = sinon.stub().returns(className); - - beforeEach(() => { - selectRow.classes = classesCallBack; - wrapper = shallow( - - ); - }); - - it('should calling style callback correctly', () => { - expect(classesCallBack.callCount).toBe(1); - expect(classesCallBack.calledWith(data[0]), 1); - }); - - it('should render Row component with correct style prop', () => { - expect(wrapper.find(Row).get(0).props.className).toEqual(className); - }); - }); - - describe('if selectRow.bgColor is defined as a string', () => { - const bgColor = 'red'; - - beforeEach(() => { - selectRow.bgColor = bgColor; - wrapper = shallow( - - ); - }); - - it('should render Row component with correct style.backgroundColor prop', () => { - expect(wrapper.find(Row).get(0).props.style).toEqual({ backgroundColor: bgColor }); - }); - }); - - describe('if selectRow.bgColor is defined as a string', () => { - const bgColor = 'red'; - const bgColorCallBack = sinon.stub().returns(bgColor); - - beforeEach(() => { - selectRow.bgColor = bgColorCallBack; - wrapper = shallow( - - ); - }); - - it('should calling selectRow.bgColor callback correctly', () => { - expect(bgColorCallBack.calledOnce).toBeTruthy(); - expect(bgColorCallBack.calledWith(data[0]), 1).toBeTruthy(); - }); - - it('should render Row component with correct style.backgroundColor prop', () => { - expect(wrapper.find(Row).get(0).props.style).toEqual({ backgroundColor: bgColor }); - }); - }); - - describe('if selectRow.bgColor defined and selectRow.style.backgroundColor defined', () => { - const bgColor = 'yellow'; - const style = { backgroundColor: 'red' }; - - beforeEach(() => { - selectRow.style = style; - selectRow.bgColor = bgColor; - wrapper = shallow( - - ); - }); - - it('should take selectRow.bgColor as higher priority', () => { - expect(wrapper.find(Row).get(0).props.style.backgroundColor).toBe(bgColor); - }); - }); - - describe('if selectRow.nonSelectable is defined', () => { - const nonSelectableRowIndex = 1; - const nonSelectable = [data[nonSelectableRowIndex][keyField]]; - - beforeEach(() => { - selectRow.nonSelectable = nonSelectable; - wrapper = shallow( - - ); - }); - - it('should render Row component with correct selectable prop', () => { - expect(wrapper.find(Row).get(0).props.selectable).toBeTruthy(); - expect(wrapper.find(Row).get(nonSelectableRowIndex).props.selectable).toBeFalsy(); - }); - }); - }); - describe('when selectRow.mode is ROW_SELECT_DISABLED (row was un-selectable)', () => { beforeEach(() => { wrapper = shallow( @@ -625,13 +292,34 @@ describe('Body', () => { data={ data } columns={ columns } keyField={ keyField } - selectedRowKeys={ [] } /> ); }); - it('prop selected should be null', () => { - expect(wrapper.find(Row).get(0).props.selected).toBeNull(); + it('prop selectRowEnabled on Row Component should be undefined', () => { + expect(wrapper.find(Row).get(0).props.selectRowEnabled).not.toBeDefined(); + }); + }); + + describe('when selectRow.mode is defined correctly', () => { + const selectRow = { mode: 'checkbox' }; + + beforeEach(() => { + wrapper = mount( + + + + ); + }); + + it('prop selectRowEnabled on RowAggregator Component should be defined', () => { + expect(wrapper.find(RowAggregator).get(0).props.selectRowEnabled).toBeTruthy(); }); }); }); diff --git a/packages/react-bootstrap-table2/test/contexts/selection-context.test.js b/packages/react-bootstrap-table2/test/contexts/selection-context.test.js index f53432e..7a3e0d6 100644 --- a/packages/react-bootstrap-table2/test/contexts/selection-context.test.js +++ b/packages/react-bootstrap-table2/test/contexts/selection-context.test.js @@ -4,7 +4,7 @@ import { shallow } from 'enzyme'; import dataOperator from '../../src/store/operators'; import BootstrapTable from '../../src/bootstrap-table'; -import createSelectionContext from '../../src/contexts/selection-context'; +import SelectionContext from '../../src/contexts/selection-context'; describe('DataContext', () => { let wrapper; @@ -42,7 +42,6 @@ describe('DataContext', () => { const defaultSelectRow = { mode: 'checkbox' }; - const SelectionContext = createSelectionContext(dataOperator); function shallowContext(selectRow = defaultSelectRow) { return ( @@ -81,9 +80,13 @@ describe('DataContext', () => { it('should pass correct sort props to children element', () => { expect(wrapper.length).toBe(1); expect(mockBase).toHaveBeenCalledWith({ + ...defaultSelectRow, selected: wrapper.state().selected, onRowSelect: wrapper.instance().handleRowSelect, - onAllRowsSelect: wrapper.instance().handleAllRowsSelect + onAllRowsSelect: wrapper.instance().handleAllRowsSelect, + allRowsNotSelected: true, + allRowsSelected: false, + checkedStatus: 'unchecked' }); }); }); diff --git a/packages/react-bootstrap-table2/test/header.test.js b/packages/react-bootstrap-table2/test/header.test.js index d8db9cd..b278988 100644 --- a/packages/react-bootstrap-table2/test/header.test.js +++ b/packages/react-bootstrap-table2/test/header.test.js @@ -1,8 +1,10 @@ +import 'jsdom-global/register'; import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, mount } from 'enzyme'; import HeaderCell from '../src/header-cell'; -import SelectionHeaderCell from '../src//row-selection/selection-header-cell'; +import SelectionHeaderCell from '../src/row-selection/selection-header-cell'; +import SelectionContext from '../src/contexts/selection-context'; import Header from '../src/header'; import Const from '../src/const'; import mockHeaderResolvedProps from './test-helpers/mock/header-resolved-props'; @@ -17,6 +19,16 @@ describe('Header', () => { text: 'Name' }]; + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + + const keyField = 'id'; + describe('simplest header', () => { beforeEach(() => { wrapper = shallow(
); @@ -89,12 +101,18 @@ describe('Header', () => { describe('when selectRow.mode is radio (single selection)', () => { beforeEach(() => { const selectRow = { mode: 'radio' }; - wrapper = shallow( -
+ > +
+ ); }); @@ -105,12 +123,18 @@ describe('Header', () => { describe('when selectRow.hideSelectColumn is true', () => { beforeEach(() => { const selectRow = { mode: 'radio', hideSelectColumn: true }; - wrapper = shallow( -
+ > +
+ ); }); @@ -146,12 +170,17 @@ describe('Header', () => { describe('when selectRow.mode is checkbox (multiple selection)', () => { beforeEach(() => { const selectRow = { mode: 'checkbox' }; - wrapper = shallow( -
+ > +
+ ); }); @@ -162,12 +191,17 @@ describe('Header', () => { describe('when selectRow.hideSelectColumn is true', () => { beforeEach(() => { const selectRow = { mode: 'checkbox', hideSelectColumn: true }; - wrapper = shallow( -
+ > +
+ ); }); 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 d12ed1f..4543041 100644 --- a/packages/react-bootstrap-table2/test/props-resolver/index.test.js +++ b/packages/react-bootstrap-table2/test/props-resolver/index.test.js @@ -1,10 +1,8 @@ import React, { Component } from 'react'; -import sinon from 'sinon'; import { shallow } from 'enzyme'; import { extendTo } from '../test-helpers/mock-component'; import baseResolver from '../../src/props-resolver/index'; -import Const from '../../src/const'; describe('TableResolver', () => { const keyField = 'id'; @@ -71,231 +69,4 @@ describe('TableResolver', () => { }); }); }); - - describe('resolveSelectRowProps', () => { - 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().resolveSelectRowProps(); - }); - - 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().resolveSelectRowProps(); - - 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().resolveSelectRowProps(); - - 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().resolveSelectRowProps(mockOptions); - }); - - it('should return object which contain options', () => { - expect(cellSelectionInfo).toEqual(expect.objectContaining({ - foo: 'test', - bar: expect.any(Function) - })); - }); - }); - }); - }); - - describe('resolveSelectRowPropsForHeader', () => { - let headerCellSelectionInfo; - let selectRow; - - beforeEach(() => { - const mockElement = React.createElement(BootstrapTableMock, { - data, keyField, columns - }, null); - wrapper = shallow(mockElement); - headerCellSelectionInfo = wrapper.instance().resolveSelectRowPropsForHeader(); - }); - - 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().resolveSelectRowPropsForHeader(); - - 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().resolveSelectRowPropsForHeader(); - - 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(), - allRowsSelected: false, - selected: [] - }; - const selectedRowKeys = []; - const mockElement = React.createElement(BootstrapTableMock, { - data, keyField, columns, selectedRowKeys, selectRow - }, null); - wrapper = shallow(mockElement); - headerCellSelectionInfo = wrapper.instance().resolveSelectRowPropsForHeader(mockOptions); - }); - - it('should return object which contain specified options', () => { - expect(headerCellSelectionInfo).toEqual(expect.objectContaining({ - foo: 'test', - bar: expect.any(Function) - })); - }); - }); - - describe('if options.allRowsSelected is true', () => { - beforeEach(() => { - selectRow = {}; - const selectedRowKeys = [1, 2]; - const mockElement = React.createElement(BootstrapTableMock, { - data, keyField, columns, selectRow - }, null); - - wrapper = shallow(mockElement); - - headerCellSelectionInfo = wrapper.instance().resolveSelectRowPropsForHeader({ - allRowsSelected: true, - selected: selectedRowKeys - }); - }); - - it('should return checkedStatus which eqauls to checked', () => { - expect(headerCellSelectionInfo).toEqual(expect.objectContaining({ - checkedStatus: Const.CHECKBOX_STATUS_CHECKED - })); - }); - }); - - describe('if options.allRowsSelected and options.allRowsNotSelected both are false', () => { - beforeEach(() => { - selectRow = {}; - const selectedRowKeys = [1]; - const mockElement = React.createElement(BootstrapTableMock, { - data, keyField, columns, selectRow - }, null); - - wrapper = shallow(mockElement); - headerCellSelectionInfo = wrapper.instance().resolveSelectRowPropsForHeader({ - allRowsSelected: false, - allRowsNotSelected: false, - selected: selectedRowKeys - }); - }); - - it('should return checkedStatus which eqauls to indeterminate', () => { - expect(headerCellSelectionInfo).toEqual(expect.objectContaining({ - checkedStatus: Const.CHECKBOX_STATUS_INDETERMINATE - })); - }); - }); - - describe('if options.allRowsNotSelected is true', () => { - beforeEach(() => { - selectRow = {}; - const selectedRowKeys = []; - const mockElement = React.createElement(BootstrapTableMock, { - data, keyField, columns, selectRow - }, null); - - wrapper = shallow(mockElement); - - headerCellSelectionInfo = wrapper.instance().resolveSelectRowPropsForHeader({ - allRowsSelected: false, - allRowsNotSelected: true, - selected: selectedRowKeys - }); - }); - - 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-aggregator.test.js b/packages/react-bootstrap-table2/test/row-aggregator.test.js new file mode 100644 index 0000000..f46d084 --- /dev/null +++ b/packages/react-bootstrap-table2/test/row-aggregator.test.js @@ -0,0 +1,167 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { mount } from 'enzyme'; +import SelectionContext from '../src/contexts/selection-context'; +import bindSelection from '../src/row-selection/row-binder'; +import SelectionCell from '../src/row-selection/selection-cell'; +import RowAggregator from '../src/row-aggregator'; +import Row from '../src/row'; + +describe('Row Aggregator', () => { + let wrapper; + let rowAggregator; + const RowAggregatorWithSelection = bindSelection(RowAggregator); + + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }, { + id: 3, + name: 'C' + }]; + const columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + const rowIndex = 1; + const row = data[rowIndex]; + const keyField = 'id'; + + const getBaseProps = () => ({ + row, + columns, + keyField, + rowIndex + }); + + describe('when props.selectRow is defeind', () => { + describe('if props.selectRow.hideSelectColumn is false', () => { + beforeEach(() => { + const selectRow = { mode: 'radio' }; + wrapper = mount( + + + + ); + }); + + it('should render RowAggregator correctly', () => { + rowAggregator = wrapper.find(RowAggregator); + expect(rowAggregator).toHaveLength(1); + }); + + it('should render selection column correctly', () => { + const selectionCell = wrapper.find(SelectionCell); + expect(selectionCell).toHaveLength(1); + expect(selectionCell.props().selected).toEqual(rowAggregator.props().selected); + expect(selectionCell.props().disabled).toEqual(!rowAggregator.props().selectable); + }); + }); + + describe('if props.selectRow.hideSelectColumn is true', () => { + beforeEach(() => { + const selectRow = { mode: 'radio', hideSelectColumn: true }; + wrapper = mount( + + + + ); + }); + + it('should render RowAggregator correctly', () => { + rowAggregator = wrapper.find(RowAggregator); + expect(rowAggregator).toHaveLength(1); + }); + + it('should not render selection column', () => { + const selectionCell = wrapper.find(SelectionCell); + expect(selectionCell).toHaveLength(0); + }); + }); + + describe('if props.selectRow.clickToSelect is defined', () => { + beforeEach(() => { + const selectRow = { mode: 'radio', clickToSelect: true }; + wrapper = mount( + + + + ); + }); + + it('should render RowAggregator correctly', () => { + rowAggregator = wrapper.find(RowAggregator); + expect(rowAggregator).toHaveLength(1); + }); + + it('should add onClick prop to Row Component', () => { + const rowComp = wrapper.find(Row); + expect(rowComp).toHaveLength(1); + expect(rowComp.props().attrs.onClick).toBeDefined(); + }); + }); + }); + + describe('createClickEventHandler', () => { + describe('if props.attrs.onClick is defined', () => { + const attrs = { onClick: jest.fn() }; + + beforeEach(() => { + const selectRow = { mode: 'radio' }; + wrapper = mount( + + + + ); + wrapper.find('tr').simulate('click'); + }); + + it('should add onClick prop to Row Component', () => { + expect(attrs.onClick).toHaveBeenCalledTimes(1); + }); + }); + + describe('if props.selectRow.clickToSelect is defined', () => { + const selectRow = { mode: 'radio', clickToSelect: true }; + beforeEach(() => { + wrapper = mount( + + + + ); + wrapper.find(RowAggregator).props().selectRow.onRowSelect = jest.fn(); + wrapper.find('tr').simulate('click'); + }); + + it('should add onClick prop to Row Component', () => { + expect(wrapper.find(RowAggregator).props().selectRow.onRowSelect).toHaveBeenCalledTimes(1); + }); + }); + + describe('if props.attrs.onClick and props.selectRow.clickToSelect both are defined', () => { + const attrs = { onClick: jest.fn() }; + const selectRow = { mode: 'radio', clickToSelect: true }; + + beforeEach(() => { + wrapper = mount( + + + + ); + wrapper.find(RowAggregator).props().selectRow.onRowSelect = jest.fn(); + wrapper.find('tr').simulate('click'); + }); + + it('should add onClick prop to Row Component', () => { + expect(attrs.onClick).toHaveBeenCalledTimes(1); + expect(wrapper.find(RowAggregator).props().selectRow.onRowSelect).toHaveBeenCalledTimes(1); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/row-selection/row-binder.test.js b/packages/react-bootstrap-table2/test/row-selection/row-binder.test.js new file mode 100644 index 0000000..7cc0a4c --- /dev/null +++ b/packages/react-bootstrap-table2/test/row-selection/row-binder.test.js @@ -0,0 +1,349 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { mount } from 'enzyme'; + +import SelectionContext from '../../src/contexts/selection-context'; +import bindSelection from '../../src/row-selection/row-binder'; + +describe('Selection Row Binder', () => { + let wrapper; + let selectRow; + const BaseComponent = () => null; + const WithSelectionComponent = bindSelection(props => ); + + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }, { + id: 3, + name: 'C' + }]; + const rowIndex = 1; + const row = data[rowIndex]; + const keyField = 'id'; + + describe('if current row is selected', () => { + beforeEach(() => { + selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]] }; + wrapper = mount( + + + + ); + }); + + it('should inject selected prop as true to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('selected')).toBeTruthy(); + }); + }); + + describe('if current row is not selected', () => { + beforeEach(() => { + selectRow = { mode: 'checkbox', selected: [] }; + wrapper = mount( + + + + ); + }); + + it('should inject selected prop as false to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('selected')).toBeFalsy(); + }); + }); + + describe('if current row is selectable', () => { + beforeEach(() => { + selectRow = { mode: 'checkbox', nonSelectable: [] }; + wrapper = mount( + + + + ); + }); + + it('should inject selectable prop as true to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('selectable')).toBeTruthy(); + }); + }); + + describe('if current row is non selectable', () => { + beforeEach(() => { + selectRow = { mode: 'checkbox', nonSelectable: [data[rowIndex][keyField]] }; + wrapper = mount( + + + + ); + }); + + it('should inject selectable prop as false to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('selectable')).toBeFalsy(); + }); + }); + + describe('if current row is selected', () => { + const selectedStyle = { backgroundColor: 'green', fontWeight: 'bold' }; + describe('when selectRow.style is defined as an object', () => { + beforeEach(() => { + selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]], style: selectedStyle }; + wrapper = mount( + + + + ); + }); + + it('should inject style prop correctly', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('style')).toEqual(selectedStyle); + }); + + describe('and props.style is also defined', () => { + const componentStype = { fontSize: '16px' }; + beforeEach(() => { + wrapper = mount( + + + + ); + }); + + it('should inject style prop correctly', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('style')).toEqual({ + ...selectedStyle, + ...componentStype + }); + }); + }); + + describe('and selectRow.bgColor is also defined as an object', () => { + beforeEach(() => { + selectRow.bgColor = 'gray'; + wrapper = mount( + + + + ); + }); + + it('should inject style prop with correct backgroundColor', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('style')).toEqual({ + ...selectedStyle, + backgroundColor: selectRow.bgColor + }); + }); + }); + + describe('and selectRow.bgColor is also defined as a function', () => { + const color = 'gray'; + beforeEach(() => { + selectRow.bgColor = jest.fn().mockReturnValue(color); + wrapper = mount( + + + + ); + }); + + it('should inject style prop with correct backgroundColor', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('style')).toEqual({ + ...selectedStyle, + backgroundColor: color + }); + }); + + it('should call selectRow.bgColor function correctly', () => { + expect(selectRow.bgColor).toHaveBeenCalledTimes(1); + expect(selectRow.bgColor).toHaveBeenCalledWith(row, rowIndex); + }); + }); + }); + + describe('when selectRow.style is defined as a function', () => { + beforeEach(() => { + selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]], style: jest.fn().mockReturnValue(selectedStyle) }; + wrapper = mount( + + + + ); + }); + + it('should inject style prop correctly', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('style')).toEqual(selectedStyle); + }); + + it('should call selectRow.style function correctly', () => { + expect(selectRow.style).toHaveBeenCalledTimes(1); + expect(selectRow.style).toHaveBeenCalledWith(row, rowIndex); + }); + + describe('and props.style is also defined', () => { + const componentStype = { fontSize: '16px' }; + beforeEach(() => { + wrapper = mount( + + + + ); + }); + + it('should inject style prop correctly', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('style')).toEqual({ + ...selectedStyle, + ...componentStype + }); + }); + }); + + describe('and selectRow.bgColor is also defined as an object', () => { + beforeEach(() => { + selectRow.bgColor = 'gray'; + wrapper = mount( + + + + ); + }); + + it('should inject style prop with correct backgroundColor', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('style')).toEqual({ + ...selectedStyle, + backgroundColor: selectRow.bgColor + }); + }); + }); + + describe('and selectRow.bgColor is also defined as a function', () => { + const color = 'gray'; + beforeEach(() => { + selectRow.bgColor = jest.fn().mockReturnValue(color); + wrapper = mount( + + + + ); + }); + + it('should inject style prop with correct backgroundColor', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('style')).toEqual({ + ...selectedStyle, + backgroundColor: color + }); + }); + + it('should call selectRow.bgColor function correctly', () => { + expect(selectRow.bgColor).toHaveBeenCalledTimes(1); + expect(selectRow.bgColor).toHaveBeenCalledWith(row, rowIndex); + }); + }); + }); + }); + + describe('if current row is selected', () => { + const selectedClassName = 'select-classname'; + describe('when selectRow.style is defined as an object', () => { + beforeEach(() => { + selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]], classes: selectedClassName }; + wrapper = mount( + + + + ); + }); + + it('should inject className prop correctly', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('className')).toEqual(selectedClassName); + }); + + describe('and props.className is also defined', () => { + const componentClassName = 'component-classname'; + beforeEach(() => { + wrapper = mount( + + + + ); + }); + + it('should inject style prop correctly', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('className')).toEqual(`${componentClassName} ${selectedClassName}`); + }); + }); + }); + + describe('when selectRow.style is defined as a function', () => { + beforeEach(() => { + selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]], classes: jest.fn().mockReturnValue(selectedClassName) }; + wrapper = mount( + + + + ); + }); + + it('should inject className prop correctly', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('className')).toEqual(selectedClassName); + }); + + it('should call selectRow.classes function correctly', () => { + expect(selectRow.classes).toHaveBeenCalledTimes(1); + expect(selectRow.classes).toHaveBeenCalledWith(row, rowIndex); + }); + + describe('and props.className is also defined', () => { + const componentClassName = 'component-classname'; + beforeEach(() => { + wrapper = mount( + + + + ); + }); + + it('should inject style prop correctly', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('className')).toEqual(`${componentClassName} ${selectedClassName}`); + }); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row.test.js index 621f140..43bd193 100644 --- a/packages/react-bootstrap-table2/test/row.test.js +++ b/packages/react-bootstrap-table2/test/row.test.js @@ -542,348 +542,6 @@ describe('Row', () => { }); }); - describe('selectRow', () => { - let selectRow; - - describe('when selectRow.mode is ROW_SELECT_DISABLED (row is not able to select)', () => { - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('should not render ', () => { - expect(wrapper.find(SelectionCell).length).toBe(0); - }); - }); - - describe('when selectRow.mode was defined (single or multiple selection)', () => { - describe('if selectRow.mode is radio (single selection)', () => { - beforeEach(() => { - selectRow = { mode: 'radio' }; - wrapper = shallow( - ); - }); - - it('should render ', () => { - expect(wrapper.find(SelectionCell).length).toBe(1); - }); - - it('should render with correct props', () => { - expect(wrapper.find(SelectionCell).props().selected).toBeTruthy(); - expect(wrapper.find(SelectionCell).props().disabled).toBeFalsy(); - expect(wrapper.find(SelectionCell).props().mode).toEqual(selectRow.mode); - }); - - describe('when selectRow.hideSelectColumn is true', () => { - beforeEach(() => { - selectRow = { mode: 'radio', hideSelectColumn: true }; - wrapper = shallow( - ); - }); - - it('should not render ', () => { - expect(wrapper.find(SelectionCell).length).toBe(0); - }); - }); - }); - - describe('if selectRow.mode is checkbox (multiple selection)', () => { - beforeEach(() => { - selectRow = { mode: 'checkbox' }; - wrapper = shallow( - ); - }); - - it('should render ', () => { - expect(wrapper.find(SelectionCell).length).toBe(1); - }); - - it('should render with correct props', () => { - expect(wrapper.find(SelectionCell).props().selected).toBeTruthy(); - expect(wrapper.find(SelectionCell).props().disabled).toBeFalsy(); - expect(wrapper.find(SelectionCell).props().mode).toEqual(selectRow.mode); - }); - - describe('when selectRow.hideSelectColumn is true', () => { - beforeEach(() => { - selectRow = { mode: 'checkbox', hideSelectColumn: true }; - wrapper = shallow( - ); - }); - - it('should not render ', () => { - expect(wrapper.find(SelectionCell).length).toBe(0); - }); - }); - }); - - describe('if selectable prop is false', () => { - beforeEach(() => { - selectRow = { mode: 'checkbox' }; - wrapper = shallow( - ); - }); - - it('should render SelectionCell component with correct disable prop correctly', () => { - expect(wrapper.find(SelectionCell).length).toBe(1); - expect(wrapper.find(SelectionCell).prop('disabled')).toBeTruthy(); - }); - }); - - describe('if selectable prop is true', () => { - beforeEach(() => { - selectRow = { mode: 'checkbox' }; - wrapper = shallow( - ); - }); - - it('should render SelectionCell component with correct disable prop correctly', () => { - expect(wrapper.find(SelectionCell).length).toBe(1); - expect(wrapper.find(SelectionCell).prop('disabled')).toBeFalsy(); - }); - }); - - describe('if selectRow.clickToSelect is true', () => { - beforeEach(() => { - selectRow = { mode: 'checkbox' }; - selectRow.clickToSelect = true; - wrapper = shallow( - ); - }); - - it('should render Row component successfully with onClick event', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find('tr').prop('onClick')).toBeDefined(); - }); - }); - }); - }); - - describe('handleRowClick', () => { - let selectRow; - let onRowSelectCallBack; - - describe('selectable prop is false', () => { - beforeEach(() => { - onRowSelectCallBack = sinon.stub(); - selectRow = { - mode: 'checkbox', - clickToSelect: true, - onRowSelect: onRowSelectCallBack - }; - wrapper = shallow( - ); - wrapper.find('tr').simulate('click'); - }); - - it('should not calling selectRow.onRowSelect callback', () => { - expect(onRowSelectCallBack.callCount).toEqual(0); - }); - }); - - describe('selectable prop is true', () => { - describe('and selected prop is true', () => { - beforeEach(() => { - onRowSelectCallBack = sinon.stub(); - selectRow = { - mode: 'checkbox', - clickToSelect: true, - onRowSelect: onRowSelectCallBack - }; - wrapper = shallow( - ); - wrapper.find('tr').simulate('click'); - }); - - it('should calling selectRow.onRowSelect callback', () => { - expect(onRowSelectCallBack.callCount).toEqual(1); - }); - - it('should calling selectRow.onRowSelect with correct argument', () => { - expect(onRowSelectCallBack.calledWith(row[keyField], false, rowIndex)).toBeTruthy(); - }); - }); - - describe('and selected prop is false', () => { - beforeEach(() => { - onRowSelectCallBack = sinon.stub(); - selectRow = { - mode: 'checkbox', - clickToSelect: true, - onRowSelect: onRowSelectCallBack - }; - wrapper = shallow( - ); - wrapper.find('tr').simulate('click'); - }); - - it('should calling selectRow.onRowSelect callback', () => { - expect(onRowSelectCallBack.callCount).toEqual(1); - }); - - it('should calling selectRow.onRowSelect with correct argument', () => { - expect(onRowSelectCallBack.calledWith(row[keyField], true, rowIndex)).toBeTruthy(); - }); - }); - }); - - describe('if cellEdit.mode is dbclick and selectRow.clickToEdit is true', () => { - beforeEach(() => { - onRowSelectCallBack = sinon.stub(); - const cellEdit = { - mode: Const.DBCLICK_TO_CELL_EDIT, - ridx: undefined, - cidx: undefined, - onStart: sinon.stub() - }; - selectRow = { - mode: 'checkbox', - clickToSelect: true, - clickToEdit: true, - onRowSelect: onRowSelectCallBack - }; - wrapper = shallow( - ); - // console.log(wrapper.instance()); - const rowClick = wrapper.instance().createClickEventHandler(); - rowClick(); - rowClick(); - }); - - it('should increase clickNum as 2', () => { - expect(wrapper.instance().clickNum).toEqual(2); - }); - }); - - describe('when attrs.onClick prop is defined', () => { - const customClickCallBack = sinon.stub(); - const attrs = { onClick: customClickCallBack }; - - beforeEach(() => { - onRowSelectCallBack = sinon.stub(); - selectRow = { - mode: 'checkbox', - clickToSelect: true, - onRowSelect: onRowSelectCallBack - }; - wrapper = shallow( - ); - wrapper.find('tr').simulate('click'); - }); - - it('should calling attrs.onClick callback', () => { - expect(customClickCallBack.callCount).toEqual(1); - }); - - it('should calling selectRow.onRowSelect callback', () => { - expect(onRowSelectCallBack.callCount).toEqual(1); - }); - }); - }); - describe('when column.style prop is defined', () => { let columns; const columnIndex = 1; From 154f1c91c3d3d0ad30c93ab7ba5d52910c91cdaa Mon Sep 17 00:00:00 2001 From: AllenFang Date: Fri, 17 Aug 2018 14:40:42 +0800 Subject: [PATCH 07/38] upgrade enzyme for new context API --- enzyme-setup.js | 2 +- package.json | 4 +- yarn.lock | 99 +++++++++++++++++++++++++++++++++++-------------- 3 files changed, 74 insertions(+), 31 deletions(-) diff --git a/enzyme-setup.js b/enzyme-setup.js index e952b30..ee38481 100644 --- a/enzyme-setup.js +++ b/enzyme-setup.js @@ -1,4 +1,4 @@ -import Adapter from 'enzyme-adapter-react-16'; +import Adapter from 'enzyme-adapter-react-16.3'; import { configure } from 'enzyme'; const configureEnzyme = () => { diff --git a/package.json b/package.json index 4437f6e..45bef02 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "babel-preset-stage-0": "6.24.1", "babel-register": "6.24.1", "css-loader": "0.28.1", - "enzyme": "3.3.0", - "enzyme-adapter-react-16": "1.1.1", + "enzyme": "3.4.0", + "enzyme-adapter-react-16.3": "1.0.0", "enzyme-to-json": "3.3.4", "eslint": "4.5.0", "eslint-config-airbnb": "15.1.0", diff --git a/yarn.lock b/yarn.lock index b44934f..99271d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -332,6 +332,14 @@ array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" +array.prototype.flat@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz#812db8f02cad24d3fab65dd67eabe3b8903494a4" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.10.0" + function-bind "^1.1.1" + arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -2692,24 +2700,26 @@ entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" -enzyme-adapter-react-16@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4" - dependencies: - enzyme-adapter-utils "^1.3.0" - lodash "^4.17.4" - object.assign "^4.0.4" - object.values "^1.0.4" - prop-types "^15.6.0" - react-reconciler "^0.7.0" - react-test-renderer "^16.0.0-0" - -enzyme-adapter-utils@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.4.0.tgz#c403b81e8eb9953658569e539780964bdc98de62" +enzyme-adapter-react-16.3@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16.3/-/enzyme-adapter-react-16.3-1.0.0.tgz#d3992301aba46c8cceab21c1d201e85f01c93bfc" dependencies: + enzyme-adapter-utils "^1.5.0" + function.prototype.name "^1.1.0" object.assign "^4.1.0" - prop-types "^15.6.0" + object.values "^1.0.4" + prop-types "^15.6.2" + react-is "^16.4.1" + react-reconciler "^0.7.0" + react-test-renderer "~16.3.0-0" + +enzyme-adapter-utils@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.5.0.tgz#a020ab3ae79bb1c85e1d51f48f35e995e0eed810" + dependencies: + function.prototype.name "^1.1.0" + object.assign "^4.1.0" + prop-types "^15.6.2" enzyme-to-json@3.3.4: version "3.3.4" @@ -2717,20 +2727,21 @@ enzyme-to-json@3.3.4: dependencies: lodash "^4.17.4" -enzyme@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.3.0.tgz#0971abd167f2d4bf3f5bd508229e1c4b6dc50479" +enzyme@3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.4.0.tgz#085c66fe647d8c9c4becd1fee3042c040cda88a6" dependencies: + array.prototype.flat "^1.2.1" cheerio "^1.0.0-rc.2" - function.prototype.name "^1.0.3" - has "^1.0.1" + function.prototype.name "^1.1.0" + has "^1.0.3" is-boolean-object "^1.0.0" - is-callable "^1.1.3" + is-callable "^1.1.4" is-number-object "^1.0.3" is-string "^1.0.4" is-subset "^0.1.1" lodash "^4.17.4" - object-inspect "^1.5.0" + object-inspect "^1.6.0" object-is "^1.0.1" object.assign "^4.1.0" object.entries "^1.0.4" @@ -2750,6 +2761,16 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +es-abstract@^1.10.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.10.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" @@ -3499,7 +3520,7 @@ function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" -function.prototype.name@^1.0.3: +function.prototype.name@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327" dependencies: @@ -3973,6 +3994,12 @@ has@^1.0.1: dependencies: function-bind "^1.0.2" +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + dependencies: + function-bind "^1.1.1" + hash-base@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" @@ -4323,6 +4350,10 @@ is-callable@^1.1.1, is-callable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + is-ci@^1.0.10: version "1.1.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" @@ -6002,7 +6033,7 @@ object-hash@^1.1.4: version "1.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.2.0.tgz#e96af0e96981996a1d47f88ead8f74f1ebc4422b" -object-inspect@^1.5.0: +object-inspect@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" @@ -6761,6 +6792,13 @@ prop-types@^15.6.0: loose-envify "^1.3.1" object-assign "^4.1.1" +prop-types@^15.6.2: + version "15.6.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" + dependencies: + loose-envify "^1.3.1" + object-assign "^4.1.1" + proxy-addr@~2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" @@ -6929,6 +6967,10 @@ react-dom@16.3.2: object-assign "^4.1.1" prop-types "^15.6.0" +react-is@^16.3.2, react-is@^16.4.1: + version "16.4.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.2.tgz#84891b56c2b6d9efdee577cc83501dfc5ecead88" + react-reconciler@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d" @@ -6945,13 +6987,14 @@ react-test-renderer@16.0.0: fbjs "^0.8.16" object-assign "^4.1.1" -react-test-renderer@^16.0.0-0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.2.0.tgz#bddf259a6b8fcd8555f012afc8eacc238872a211" +react-test-renderer@~16.3.0-0: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.3.2.tgz#3d1ed74fda8db42521fdf03328e933312214749a" dependencies: fbjs "^0.8.16" object-assign "^4.1.1" prop-types "^15.6.0" + react-is "^16.3.2" react@16.3.2: version "16.3.2" From 6fce0d7066212007a5486d1a47dbfef2bdccf85e Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 18 Aug 2018 16:42:47 +0800 Subject: [PATCH 08/38] add story for combine selection and expansion --- .../row-selection/selection-with-expansion.js | 84 +++++++++++++++++++ .../stories/index.js | 2 + 2 files changed, 86 insertions(+) create mode 100644 packages/react-bootstrap-table2-example/examples/row-selection/selection-with-expansion.js diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/selection-with-expansion.js b/packages/react-bootstrap-table2-example/examples/row-selection/selection-with-expansion.js new file mode 100644 index 0000000..e1cd4a3 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/selection-with-expansion.js @@ -0,0 +1,84 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +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 selectRow = { + mode: 'checkbox' +}; + +const expandRow = { + showExpandColumn: true, + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ) +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true +}; + +const expandRow = { + showExpandColumn: true, + renderer: row => ( +
+

{ \`This Expand row is belong to rowKey $\{row.id}\` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ) +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 2941f37..7e0aca7 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -115,6 +115,7 @@ import ClickToSelectTable from 'examples/row-selection/click-to-select'; import DefaultSelectTable from 'examples/row-selection/default-select'; import SelectionManagement from 'examples/row-selection/selection-management'; import ClickToSelectWithCellEditTable from 'examples/row-selection/click-to-select-with-cell-edit'; +import SelectionWithExpansionTable from 'examples/row-selection/selection-with-expansion'; import SelectionNoDataTable from 'examples/row-selection/selection-no-data'; import SelectionStyleTable from 'examples/row-selection/selection-style'; import SelectionClassTable from 'examples/row-selection/selection-class'; @@ -303,6 +304,7 @@ storiesOf('Row Selection', module) .add('Default Select', () => ) .add('Selection Management', () => ) .add('Click to Select and Edit Cell', () => ) + .add('Row Select and Expand', () => ) .add('Selection without Data', () => ) .add('Selection Style', () => ) .add('Selection Class', () => ) From 21e7c3a53a0d68f929dcdccb0457866638bdf2e9 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 18 Aug 2018 17:50:36 +0800 Subject: [PATCH 09/38] refine expand row consumer --- packages/react-bootstrap-table2/src/body.js | 38 ++-- .../src/bootstrap-table.js | 12 +- .../src/contexts/index.js | 35 ++-- .../src/contexts/row-expand-context.js | 163 +++++++++--------- packages/react-bootstrap-table2/src/header.js | 22 +-- .../src/row-aggregator.js | 14 +- .../row-expand/expand-header-cell-binder.js | 8 + .../src/row-expand/expand-header-cell.js | 18 +- .../src/row-expand/row-binder.js | 35 ++++ 9 files changed, 182 insertions(+), 163 deletions(-) create mode 100644 packages/react-bootstrap-table2/src/row-expand/expand-header-cell-binder.js create mode 100644 packages/react-bootstrap-table2/src/row-expand/row-binder.js diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index eb4138a..4da1be7 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -7,10 +7,10 @@ import PropTypes from 'prop-types'; import _ from './utils'; import Row from './row'; import RowAggregator from './row-aggregator'; -import ExpandRow from './row-expand/expand-row'; import RowSection from './row-section'; import Const from './const'; import bindSelection from './row-selection/row-binder'; +import bindExpansion from './row-expand/row-binder'; const Body = (props) => { const { @@ -40,10 +40,13 @@ const Body = (props) => { let RowComponent = Row; const nonEditableRows = cellEdit.nonEditableRows || []; const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED; - const expandRowEnabled = !!expandRow; + const expandRowEnabled = !!expandRow.renderer; + if (expandRowEnabled) { + RowComponent = bindExpansion(RowAggregator, visibleColumnSize); + } if (selectRowEnabled) { - RowComponent = bindSelection(RowAggregator); + RowComponent = bindSelection(expandRowEnabled ? RowComponent : RowAggregator); } content = data.map((row, index) => { @@ -55,12 +58,9 @@ const Body = (props) => { const classes = (_.isFunction(rowClasses) ? rowClasses(row, index) : rowClasses); // refine later - const expanded = expandRowEnabled && expandRow.expanded.includes(key); - - // refine later - const result = [ + const result = selectRowEnabled || expandRowEnabled ? - { className={ classes } attrs={ attrs } cellEdit={ cellEdit } - selectRowEnabled={ selectRowEnabled } - expandRowEnabled={ expandRowEnabled } - /> : - ) : + ( { style={ style } className={ classes } attrs={ attrs } - /> - ]; - - if (expanded) { - result.push(( - - { expandRow.renderer(row) } - - )); - } + />); return result; }); diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index aada564..387ae02 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -148,7 +148,7 @@ BootstrapTable.propTypes = { selectionHeaderRenderer: PropTypes.func }), expandRow: PropTypes.shape({ - renderer: PropTypes.func.isRequired, + renderer: PropTypes.func, expanded: PropTypes.array, onExpand: PropTypes.func, onExpandAll: PropTypes.func, @@ -158,9 +158,6 @@ BootstrapTable.propTypes = { expandColumnRenderer: PropTypes.func, expandHeaderColumnRenderer: PropTypes.func }), - onRowExpand: PropTypes.func, - onAllRowExpand: PropTypes.func, - isAnyExpands: PropTypes.bool, rowStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), rowEvents: PropTypes.object, rowClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), @@ -193,7 +190,12 @@ BootstrapTable.defaultProps = { noDataIndication: null, selectRow: { mode: Const.ROW_SELECT_DISABLED, - selected: [] + selected: [], + hideSelectColumn: true + }, + expandRow: { + renderer: undefined, + expanded: [] } }; diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 4d365bc..08d600f 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -5,7 +5,7 @@ import _ from '../utils'; import createDataContext from './data-context'; import createSortContext from './sort-context'; import SelectionContext from './selection-context'; -import createRowExpandContext from './row-expand-context'; +import RowExpandContext from './row-expand-context'; import remoteResolver from '../props-resolver/remote-resolver'; import { BootstrapContext } from './bootstrap'; import dataOperator from '../store/operators'; @@ -26,7 +26,7 @@ const withContext = Base => } if (props.expandRow) { - this.RowExpandContext = createRowExpandContext(dataOperator); + this.RowExpandContext = RowExpandContext; } if (props.cellEdit && props.cellEdit.createContext) { @@ -62,7 +62,6 @@ const withContext = Base => searchProps, sortProps, paginationProps, - expandProps ) => ( this.table = n } @@ -72,7 +71,6 @@ const withContext = Base => { ...filterProps } { ...searchProps } { ...paginationProps } - { ...expandProps } data={ rootProps.getData(filterProps, searchProps, sortProps, paginationProps) } /> ); @@ -85,8 +83,7 @@ const withContext = Base => filterProps, searchProps, sortProps, - paginationProps, - expandProps + paginationProps ) => ( filterProps, searchProps, sortProps, - paginationProps, - expandProps + paginationProps ) } @@ -124,19 +120,16 @@ const withContext = Base => expandRow={ this.props.expandRow } data={ rootProps.getData(filterProps, searchProps, sortProps, paginationProps) } > - - { - expandProps => base( - rootProps, - cellEditProps, - filterProps, - searchProps, - sortProps, - paginationProps, - expandProps - ) - } - + { + base( + rootProps, + cellEditProps, + filterProps, + searchProps, + sortProps, + paginationProps + ) + } ); } diff --git a/packages/react-bootstrap-table2/src/contexts/row-expand-context.js b/packages/react-bootstrap-table2/src/contexts/row-expand-context.js index 3e882c6..5a3728b 100644 --- a/packages/react-bootstrap-table2/src/contexts/row-expand-context.js +++ b/packages/react-bootstrap-table2/src/contexts/row-expand-context.js @@ -1,92 +1,91 @@ /* eslint react/prop-types: 0 */ import React from 'react'; import PropTypes from 'prop-types'; +import dataOperator from '../store/operators'; -export default ( - dataOperator -) => { - const RowExpandContext = React.createContext(); +const RowExpandContext = React.createContext(); - class RowExpandProvider extends React.Component { - static propTypes = { - children: PropTypes.node.isRequired, - data: PropTypes.array.isRequired, - keyField: PropTypes.string.isRequired - } +class RowExpandProvider extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + data: PropTypes.array.isRequired, + keyField: PropTypes.string.isRequired + } - state = { expanded: this.props.expandRow.expanded || [] }; + state = { expanded: this.props.expandRow.expanded || [] }; - componentWillReceiveProps(nextProps) { - if (nextProps.expandRow) { - this.setState(() => ({ - expanded: nextProps.expandRow.expanded || this.state.expanded - })); - } - } - - handleRowExpand = (rowKey, expanded, rowIndex, e) => { - const { data, keyField, expandRow: { onExpand, onlyOneExpanding } } = this.props; - - let currExpanded = [...this.state.expanded]; - - if (expanded) { - if (onlyOneExpanding) currExpanded = [rowKey]; - else currExpanded.push(rowKey); - } else { - currExpanded = currExpanded.filter(value => value !== rowKey); - } - - if (onExpand) { - const row = dataOperator.getRowByRowId(data, keyField, rowKey); - onExpand(row, expanded, rowIndex, e); - } - this.setState(() => ({ expanded: currExpanded })); - } - - handleAllRowExpand = (e, expandAll) => { - const { - data, - keyField, - expandRow: { - onExpandAll, - nonExpandable - } - } = this.props; - const { expanded } = this.state; - - let currExpanded; - - if (expandAll) { - currExpanded = expanded.concat(dataOperator.expandableKeys(data, keyField, nonExpandable)); - } else { - currExpanded = expanded.filter(s => typeof data.find(d => d[keyField] === s) === 'undefined'); - } - - if (onExpandAll) { - onExpandAll(expandAll, dataOperator.getExpandedRows(data, keyField, currExpanded), e); - } - - this.setState(() => ({ expanded: currExpanded })); - } - - render() { - const { data, keyField } = this.props; - return ( - - { this.props.children } - - ); + componentWillReceiveProps(nextProps) { + if (nextProps.expandRow) { + this.setState(() => ({ + expanded: nextProps.expandRow.expanded || this.state.expanded + })); } } - return { - Provider: RowExpandProvider, - Consumer: RowExpandContext.Consumer - }; + + handleRowExpand = (rowKey, expanded, rowIndex, e) => { + const { data, keyField, expandRow: { onExpand, onlyOneExpanding } } = this.props; + + let currExpanded = [...this.state.expanded]; + + if (expanded) { + if (onlyOneExpanding) currExpanded = [rowKey]; + else currExpanded.push(rowKey); + } else { + currExpanded = currExpanded.filter(value => value !== rowKey); + } + + if (onExpand) { + const row = dataOperator.getRowByRowId(data, keyField, rowKey); + onExpand(row, expanded, rowIndex, e); + } + this.setState(() => ({ expanded: currExpanded })); + } + + handleAllRowExpand = (e, expandAll) => { + const { + data, + keyField, + expandRow: { + onExpandAll, + nonExpandable + } + } = this.props; + const { expanded } = this.state; + + let currExpanded; + + if (expandAll) { + currExpanded = expanded.concat(dataOperator.expandableKeys(data, keyField, nonExpandable)); + } else { + currExpanded = expanded.filter(s => typeof data.find(d => d[keyField] === s) === 'undefined'); + } + + if (onExpandAll) { + onExpandAll(expandAll, dataOperator.getExpandedRows(data, keyField, currExpanded), e); + } + + this.setState(() => ({ expanded: currExpanded })); + } + + render() { + const { data, keyField } = this.props; + return ( + + { this.props.children } + + ); + } +} + +export default { + Provider: RowExpandProvider, + Consumer: RowExpandContext.Consumer }; diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index 9120c77..06a38a3 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -1,16 +1,14 @@ /* 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'; import ExpandHeaderCell from './row-expand/expand-header-cell'; import bindSelection from './row-selection/selection-header-cell-binder'; +import bindExpansion from './row-expand/expand-header-cell-binder'; const Header = (props) => { - const { ROW_SELECT_DISABLED } = Const; - const { className, columns, @@ -24,7 +22,12 @@ const Header = (props) => { bootstrap4 } = props; - let SelectionHeaderCellComp = () => {}; + let SelectionHeaderCellComp = () => null; + let ExpansionHeaderCellComp = () => null; + + if (expandRow.showExpandColumn) { + ExpansionHeaderCellComp = bindExpansion(ExpandHeaderCell); + } if (selectRow) { SelectionHeaderCellComp = bindSelection(SelectionHeaderCell); @@ -33,16 +36,9 @@ const Header = (props) => { return ( + { - (expandRow && expandRow.showExpandColumn) - ? : null - } - { - (selectRow.mode !== ROW_SELECT_DISABLED && !selectRow.hideSelectColumn) ? + !selectRow.hideSelectColumn ? : null } { diff --git a/packages/react-bootstrap-table2/src/row-aggregator.js b/packages/react-bootstrap-table2/src/row-aggregator.js index fc36355..e72f6a1 100644 --- a/packages/react-bootstrap-table2/src/row-aggregator.js +++ b/packages/react-bootstrap-table2/src/row-aggregator.js @@ -48,7 +48,7 @@ export default class RowAggregator extends React.Component { if (expandRow && expandable) { expandRow.onRowExpand(key, !expanded, rowIndex, e); } - if (selectable) { + if (selectRow.clickToSelect && selectable) { selectRow.onRowSelect(key, !selected, rowIndex, e); } }; @@ -79,22 +79,20 @@ export default class RowAggregator extends React.Component { cellEdit, selectRow, expandRow, + expanded, selected, - selectable, - expandRowEnabled + selectable } = this.props; const key = _.get(row, keyField); - const { hideSelectColumn } = selectRow; - const { showExpandColumn } = expandRow || {}; + const { hideSelectColumn, clickToSelect } = selectRow; + const { showExpandColumn } = expandRow; const nonEditableRows = cellEdit.nonEditableRows || []; const editable = !(nonEditableRows.length > 0 && nonEditableRows.indexOf(key) > -1); - // const expandable = expandRowEnabled && !expandRow.nonExpandable.includes(key); - const expanded = expandRowEnabled && expandRow.expanded.includes(key); const newAttrs = { ...attrs }; - if (selectRow.clickToSelect || expandRowEnabled) { + if (clickToSelect || !!expandRow.renderer) { newAttrs.onClick = this.createClickEventHandler(attrs.onClick); } diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-header-cell-binder.js b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell-binder.js new file mode 100644 index 0000000..4460b44 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell-binder.js @@ -0,0 +1,8 @@ +import React from 'react'; +import ExpansionContext from '../contexts/row-expand-context'; + +export default Component => () => ( + + { expandRow => } + +); diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js index 65c099b..e30fd9a 100644 --- a/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js +++ b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js @@ -3,11 +3,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -export default class SelectionHeaderCell extends Component { +export default class ExpansionHeaderCell extends Component { static propTypes = { - anyExpands: PropTypes.bool.isRequired, + isAnyExpands: PropTypes.bool.isRequired, onAllRowExpand: PropTypes.func.isRequired, - renderer: PropTypes.func + expandHeaderColumnRenderer: PropTypes.func } constructor() { @@ -16,13 +16,13 @@ export default class SelectionHeaderCell extends Component { } handleCheckBoxClick(e) { - const { anyExpands, onAllRowExpand } = this.props; + const { isAnyExpands, onAllRowExpand } = this.props; - onAllRowExpand(e, !anyExpands); + onAllRowExpand(e, !isAnyExpands); } render() { - const { anyExpands, renderer } = this.props; + const { isAnyExpands, expandHeaderColumnRenderer } = this.props; const attrs = { onClick: this.handleCheckBoxClick }; @@ -30,9 +30,9 @@ export default class SelectionHeaderCell extends Component { return ( { - renderer ? - renderer({ isAnyExpands: anyExpands }) : - (anyExpands ? '(-)' : '(+)') + expandHeaderColumnRenderer ? + expandHeaderColumnRenderer({ isAnyExpands }) : + (isAnyExpands ? '(-)' : '(+)') } ); diff --git a/packages/react-bootstrap-table2/src/row-expand/row-binder.js b/packages/react-bootstrap-table2/src/row-expand/row-binder.js new file mode 100644 index 0000000..64eda2c --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-expand/row-binder.js @@ -0,0 +1,35 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import _ from '../utils'; +import ExpandRow from './expand-row'; +import ExpansionContext from '../contexts/row-expand-context'; + +export default (Component, visibleColumnSize) => { + const renderWithExpansion = (props, expandRow) => { + const key = _.get(props.row, props.keyField); + + const expanded = expandRow.expanded.includes(key); + const expandable = !expandRow.nonExpandable || !expandRow.nonExpandable.includes(key); + + return [ + , + expanded ? + { expandRow.renderer(props.row) } + : null + ]; + }; + return props => ( + + { expandRow => renderWithExpansion(props, expandRow) } + + ); +}; From 4b790e4bec16367248c7400c999efe3533ea67cb Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 18 Aug 2018 17:50:58 +0800 Subject: [PATCH 10/38] patch test for refining expand row consumer --- .../react-bootstrap-table2/test/body.test.js | 41 ++++- .../test/header.test.js | 44 +++++ .../test/row-aggregator.test.js | 152 ++++++++++++++++-- .../test-helpers/mock/body-resolved-props.js | 10 +- .../mock/header-resolved-props.js | 12 +- 5 files changed, 239 insertions(+), 20 deletions(-) diff --git a/packages/react-bootstrap-table2/test/body.test.js b/packages/react-bootstrap-table2/test/body.test.js index 2be26b1..ba71bda 100644 --- a/packages/react-bootstrap-table2/test/body.test.js +++ b/packages/react-bootstrap-table2/test/body.test.js @@ -9,6 +9,7 @@ import RowAggregator from '../src/row-aggregator'; import Const from '../src/const'; import RowSection from '../src/row-section'; import SelectionContext from '../src/contexts/selection-context'; +import ExpansionContext from '../src/contexts/row-expand-context'; import mockBodyResolvedProps from './test-helpers/mock/body-resolved-props'; describe('Body', () => { @@ -284,7 +285,7 @@ describe('Body', () => { }); }); - describe('when selectRow.mode is ROW_SELECT_DISABLED (row was un-selectable)', () => { + describe('when selectRow.mode is ROW_SELECT_DISABLED or expandRow.renderer is undefined', () => { beforeEach(() => { wrapper = shallow( { ); }); - it('prop selectRowEnabled on Row Component should be undefined', () => { - expect(wrapper.find(Row).get(0).props.selectRowEnabled).not.toBeDefined(); + it('shouldn\'t render RowAggregator component', () => { + expect(wrapper.find(RowAggregator)).toHaveLength(0); }); }); @@ -318,8 +319,38 @@ describe('Body', () => { ); }); - it('prop selectRowEnabled on RowAggregator Component should be defined', () => { - expect(wrapper.find(RowAggregator).get(0).props.selectRowEnabled).toBeTruthy(); + it('should render RowAggregator component correctly', () => { + const rowAggregator = wrapper.find(RowAggregator); + + expect(rowAggregator.get(0).props.selectRow.mode) + .not.toEqual(Const.ROW_SELECT_DISABLED); + expect(rowAggregator.get(0).props.selected).toBeDefined(); + expect(rowAggregator.get(0).props.selectable).toBeDefined(); + }); + }); + + describe('when expandRow.renderer is defined correctly', () => { + const expandRow = { renderer: jest.fn() }; + + beforeEach(() => { + wrapper = mount( + + + + ); + }); + + it('should render RowAggregator component correctly', () => { + const rowAggregator = wrapper.find(RowAggregator); + expect(rowAggregator.get(0).props.expandRow.renderer).toEqual(expandRow.renderer); + expect(rowAggregator.get(0).props.expanded).toBeDefined(); + expect(rowAggregator.get(0).props.expandable).toBeDefined(); }); }); }); diff --git a/packages/react-bootstrap-table2/test/header.test.js b/packages/react-bootstrap-table2/test/header.test.js index b278988..ecb2318 100644 --- a/packages/react-bootstrap-table2/test/header.test.js +++ b/packages/react-bootstrap-table2/test/header.test.js @@ -4,7 +4,9 @@ import { shallow, mount } from 'enzyme'; import HeaderCell from '../src/header-cell'; import SelectionHeaderCell from '../src/row-selection/selection-header-cell'; +import ExpandHeaderCell from '../src/row-expand/expand-header-cell'; import SelectionContext from '../src/contexts/selection-context'; +import ExpansionContext from '../src/contexts/row-expand-context'; import Header from '../src/header'; import Const from '../src/const'; import mockHeaderResolvedProps from './test-helpers/mock/header-resolved-props'; @@ -177,6 +179,7 @@ describe('Header', () => { selectRow={ selectRow } >
@@ -198,6 +201,7 @@ describe('Header', () => { selectRow={ selectRow } >
@@ -211,4 +215,44 @@ describe('Header', () => { }); }); }); + + describe('expandRow', () => { + describe('when expandRow.showExpandColumn is false', () => { + beforeEach(() => { + wrapper = shallow( +
+ ); + }); + + it('should not render ', () => { + expect(wrapper.find(ExpandHeaderCell).length).toBe(0); + }); + }); + + describe('when expandRow.showExpandColumn is true', () => { + beforeEach(() => { + const expandRow = { renderer: jest.fn(), expanded: [], showExpandColumn: true }; + wrapper = mount( + +
+ + ); + }); + + it('should render correctly', () => { + expect(wrapper.find(ExpandHeaderCell).length).toBe(1); + }); + }); + }); }); diff --git a/packages/react-bootstrap-table2/test/row-aggregator.test.js b/packages/react-bootstrap-table2/test/row-aggregator.test.js index f46d084..a98bc95 100644 --- a/packages/react-bootstrap-table2/test/row-aggregator.test.js +++ b/packages/react-bootstrap-table2/test/row-aggregator.test.js @@ -1,8 +1,12 @@ import 'jsdom-global/register'; import React from 'react'; import { mount } from 'enzyme'; +import mockBodyResolvedProps from './test-helpers/mock/body-resolved-props'; import SelectionContext from '../src/contexts/selection-context'; +import ExpansionContext from '../src/contexts/row-expand-context'; import bindSelection from '../src/row-selection/row-binder'; +import bindExpansion from '../src/row-expand/row-binder'; +import ExpandCell from '../src/row-expand/expand-cell'; import SelectionCell from '../src/row-selection/selection-cell'; import RowAggregator from '../src/row-aggregator'; import Row from '../src/row'; @@ -11,6 +15,7 @@ describe('Row Aggregator', () => { let wrapper; let rowAggregator; const RowAggregatorWithSelection = bindSelection(RowAggregator); + const RowAggregatorWithExpansion = bindExpansion(RowAggregator); const data = [{ id: 1, @@ -37,16 +42,17 @@ describe('Row Aggregator', () => { row, columns, keyField, - rowIndex + rowIndex, + ...mockBodyResolvedProps }); - describe('when props.selectRow is defeind', () => { + describe('when selectRow is enable', () => { describe('if props.selectRow.hideSelectColumn is false', () => { beforeEach(() => { const selectRow = { mode: 'radio' }; wrapper = mount( - + ); }); @@ -69,7 +75,7 @@ describe('Row Aggregator', () => { const selectRow = { mode: 'radio', hideSelectColumn: true }; wrapper = mount( - + ); }); @@ -90,7 +96,7 @@ describe('Row Aggregator', () => { const selectRow = { mode: 'radio', clickToSelect: true }; wrapper = mount( - + ); }); @@ -108,6 +114,51 @@ describe('Row Aggregator', () => { }); }); + describe('when expandRow is enable', () => { + describe('if props.expandRow.showExpandColumn is false', () => { + beforeEach(() => { + const expandRow = { renderer: jest.fn() }; + wrapper = mount( + + + + ); + }); + + it('should render RowAggregator correctly', () => { + rowAggregator = wrapper.find(RowAggregator); + expect(rowAggregator).toHaveLength(1); + }); + + it('should not render expansion column', () => { + const expandCell = wrapper.find(ExpandCell); + expect(expandCell).toHaveLength(0); + }); + }); + + describe('if props.expandRow.showExpandColumn is true', () => { + beforeEach(() => { + const expandRow = { renderer: jest.fn(), showExpandColumn: true }; + wrapper = mount( + + + + ); + }); + + it('should render RowAggregator correctly', () => { + rowAggregator = wrapper.find(RowAggregator); + expect(rowAggregator).toHaveLength(1); + }); + + it('should render expansion column correctly', () => { + const expandCell = wrapper.find(ExpandCell); + expect(expandCell).toHaveLength(1); + expect(expandCell.props().expanded).toEqual(rowAggregator.props().expanded); + }); + }); + }); + describe('createClickEventHandler', () => { describe('if props.attrs.onClick is defined', () => { const attrs = { onClick: jest.fn() }; @@ -116,34 +167,111 @@ describe('Row Aggregator', () => { const selectRow = { mode: 'radio' }; wrapper = mount( - + ); wrapper.find('tr').simulate('click'); }); - it('should add onClick prop to Row Component', () => { + it('should call attrs.onClick correctly', () => { expect(attrs.onClick).toHaveBeenCalledTimes(1); }); }); - describe('if props.selectRow.clickToSelect is defined', () => { + describe('if props.selectRow.clickToSelect is true', () => { const selectRow = { mode: 'radio', clickToSelect: true }; beforeEach(() => { wrapper = mount( - + ); wrapper.find(RowAggregator).props().selectRow.onRowSelect = jest.fn(); wrapper.find('tr').simulate('click'); }); - it('should add onClick prop to Row Component', () => { + it('should call selectRow.onRowSelect correctly', () => { expect(wrapper.find(RowAggregator).props().selectRow.onRowSelect).toHaveBeenCalledTimes(1); }); }); + describe('if props.selectRow.clickToSelect is true', () => { + describe('but selectable props is false', () => { + const selectRow = { mode: 'radio', clickToSelect: true, nonSelectable: [row[keyField]] }; + beforeEach(() => { + wrapper = mount( + + + + ); + wrapper.find(RowAggregator).props().selectRow.onRowSelect = jest.fn(); + wrapper.find('tr').simulate('click'); + }); + + it('should call selectRow.onRowSelect correctly', () => { + expect(wrapper.find(RowAggregator).props().selectRow.onRowSelect) + .toHaveBeenCalledTimes(0); + }); + }); + }); + + describe('if props.expandRow.renderer is defined', () => { + describe('but expandable props is false', () => { + const expandRow = { renderer: jest.fn(), nonExpandable: [row[keyField]] }; + beforeEach(() => { + wrapper = mount( + + + + ); + wrapper.find(RowAggregator).props().expandRow.onRowExpand = jest.fn(); + wrapper.find('tr').simulate('click'); + }); + + it('should call expandRow.onRowExpand correctly', () => { + expect(wrapper.find(RowAggregator).props().expandRow.onRowExpand) + .toHaveBeenCalledTimes(0); + }); + }); + }); + + describe('if props.expandRow.renderer is defined', () => { + const expandRow = { renderer: jest.fn() }; + beforeEach(() => { + wrapper = mount( + + + + ); + wrapper.find(RowAggregator).props().expandRow.onRowExpand = jest.fn(); + wrapper.find('tr').simulate('click'); + }); + + it('should call expandRow.onRowExpand correctly', () => { + expect(wrapper.find(RowAggregator).props().expandRow.onRowExpand).toHaveBeenCalledTimes(1); + }); + }); + + describe('if props.attrs.onClick and props.expandRow.renderer both are defined', () => { + const attrs = { onClick: jest.fn() }; + const expandRow = { renderer: jest.fn() }; + + beforeEach(() => { + wrapper = mount( + + + + ); + wrapper.find(RowAggregator).props().expandRow.onRowExpand = jest.fn(); + wrapper.find('tr').simulate('click'); + }); + + it('should call attrs.onClick and expandRow.onRowExpand correctly', () => { + expect(attrs.onClick).toHaveBeenCalledTimes(1); + expect(wrapper.find(RowAggregator).props().expandRow.onRowExpand).toHaveBeenCalledTimes(1); + }); + }); + describe('if props.attrs.onClick and props.selectRow.clickToSelect both are defined', () => { const attrs = { onClick: jest.fn() }; const selectRow = { mode: 'radio', clickToSelect: true }; @@ -151,14 +279,14 @@ describe('Row Aggregator', () => { beforeEach(() => { wrapper = mount( - + ); wrapper.find(RowAggregator).props().selectRow.onRowSelect = jest.fn(); wrapper.find('tr').simulate('click'); }); - it('should add onClick prop to Row Component', () => { + it('should call attrs.onClick and selectRow.onRowSelect correctly', () => { expect(attrs.onClick).toHaveBeenCalledTimes(1); expect(wrapper.find(RowAggregator).props().selectRow.onRowSelect).toHaveBeenCalledTimes(1); }); diff --git a/packages/react-bootstrap-table2/test/test-helpers/mock/body-resolved-props.js b/packages/react-bootstrap-table2/test/test-helpers/mock/body-resolved-props.js index 0eafb00..4d1d6eb 100644 --- a/packages/react-bootstrap-table2/test/test-helpers/mock/body-resolved-props.js +++ b/packages/react-bootstrap-table2/test/test-helpers/mock/body-resolved-props.js @@ -3,7 +3,14 @@ import Const from '../../../src/const'; const { ROW_SELECT_DISABLED, UNABLE_TO_CELL_EDIT } = Const; export const rowSelectionResolvedProps = { - mode: ROW_SELECT_DISABLED + mode: ROW_SELECT_DISABLED, + selected: [], + hideSelectColumn: true +}; + +export const expandRowResolvedProps = { + renderer: undefined, + expanded: [] }; export const cellEditResolvedProps = { @@ -12,5 +19,6 @@ export const cellEditResolvedProps = { export default { cellEdit: cellEditResolvedProps, + expandRow: expandRowResolvedProps, selectRow: rowSelectionResolvedProps }; diff --git a/packages/react-bootstrap-table2/test/test-helpers/mock/header-resolved-props.js b/packages/react-bootstrap-table2/test/test-helpers/mock/header-resolved-props.js index bcc8a2b..1b3ca1b 100644 --- a/packages/react-bootstrap-table2/test/test-helpers/mock/header-resolved-props.js +++ b/packages/react-bootstrap-table2/test/test-helpers/mock/header-resolved-props.js @@ -3,9 +3,17 @@ import Const from '../../../src/const'; const { ROW_SELECT_DISABLED } = Const; export const rowSelectionResolvedProps = { - mode: ROW_SELECT_DISABLED + mode: ROW_SELECT_DISABLED, + selected: [], + hideSelectColumn: true +}; + +export const expandRowResolvedProps = { + renderer: undefined, + expanded: [] }; export default { - selectRow: rowSelectionResolvedProps + selectRow: rowSelectionResolvedProps, + expandRow: expandRowResolvedProps }; From 994ed2e39505df59dd28da9264c9b308b2375fd5 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 19 Aug 2018 13:07:10 +0800 Subject: [PATCH 11/38] no more expand row props resolver --- .../src/bootstrap-table.js | 7 ++++--- .../src/props-resolver/expand-row-resolver.js | 17 ----------------- .../src/props-resolver/index.js | 4 +--- 3 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 387ae02..c0bbb7c 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -55,7 +55,8 @@ class BootstrapTable extends PropsBaseResolver(Component) { rowClasses, wrapperClasses, rowEvents, - selectRow + selectRow, + expandRow } = this.props; const tableWrapperClass = cs('react-bootstrap-table', wrapperClasses); @@ -68,7 +69,6 @@ class BootstrapTable extends PropsBaseResolver(Component) { }, classes); const tableCaption = (caption && { caption }); - const expandRow = this.resolveExpandRowProps(); return (
@@ -195,7 +195,8 @@ BootstrapTable.defaultProps = { }, expandRow: { renderer: undefined, - expanded: [] + expanded: [], + nonExpandable: [] } }; diff --git a/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js deleted file mode 100644 index 015e321..0000000 --- a/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js +++ /dev/null @@ -1,17 +0,0 @@ -export default ExtendBase => - class ExpandRowResolver extends ExtendBase { - resolveExpandRowProps() { - const { expandRow, expanded, onRowExpand, onAllRowExpand, isAnyExpands } = this.props; - if (expandRow) { - return { - ...expandRow, - expanded, - onRowExpand, - onAllRowExpand, - isAnyExpands, - nonExpandable: expandRow.nonExpandable || [] - }; - } - return null; - } - }; diff --git a/packages/react-bootstrap-table2/src/props-resolver/index.js b/packages/react-bootstrap-table2/src/props-resolver/index.js index 105c3ea..0c3c92d 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/index.js +++ b/packages/react-bootstrap-table2/src/props-resolver/index.js @@ -1,9 +1,7 @@ import ColumnResolver from './column-resolver'; -import ExpandRowResolver from './expand-row-resolver'; export default ExtendBase => - class TableResolver extends - ExpandRowResolver(ColumnResolver(ExtendBase)) { + class TableResolver extends ColumnResolver(ExtendBase) { validateProps() { const { keyField } = this.props; if (!keyField) { From 2879cf891eeacbe97a9c8f43bcc4695f6c0e56f2 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 20 Aug 2018 20:31:22 +0800 Subject: [PATCH 12/38] refactoring cell edit consumer --- .../react-bootstrap-table2-editor/index.js | 10 +-- .../src/cell-binder.js | 38 +++++++++++ .../src/context.js | 15 ++--- .../src/editing-cell-binder.js | 43 ++++++++++++ .../src/row-binder.js | 35 ++++++++++ packages/react-bootstrap-table2/src/body.js | 65 +++++++++---------- .../src/bootstrap-table.js | 9 ++- .../src/contexts/index.js | 24 +------ .../src/row-aggregator.js | 18 ++--- .../src/row-expand/row-binder.js | 3 +- .../src/row-selection/row-binder.js | 2 +- packages/react-bootstrap-table2/src/row.js | 42 ++---------- 12 files changed, 181 insertions(+), 123 deletions(-) create mode 100644 packages/react-bootstrap-table2-editor/src/cell-binder.js create mode 100644 packages/react-bootstrap-table2-editor/src/editing-cell-binder.js create mode 100644 packages/react-bootstrap-table2-editor/src/row-binder.js diff --git a/packages/react-bootstrap-table2-editor/index.js b/packages/react-bootstrap-table2-editor/index.js index 0bc57b5..a4b4e65 100644 --- a/packages/react-bootstrap-table2-editor/index.js +++ b/packages/react-bootstrap-table2-editor/index.js @@ -1,16 +1,18 @@ import createContext from './src/context'; -import editingCellFactory from './src/editing-cell'; +import bindCellLevelCellEdit from './src/cell-binder'; +import bindRowLevelCellEdit from './src/row-binder'; +import createEditingCell from './src/editing-cell-binder'; import { EDITTYPE, - CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK } from './src/const'; export default (options = {}) => ({ createContext, - editingCellFactory, - CLICK_TO_CELL_EDIT, + createEditingCell, + bindCellLevelCellEdit, + bindRowLevelCellEdit, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK, options diff --git a/packages/react-bootstrap-table2-editor/src/cell-binder.js b/packages/react-bootstrap-table2-editor/src/cell-binder.js new file mode 100644 index 0000000..6ef110a --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/cell-binder.js @@ -0,0 +1,38 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT } from './const'; + +import { Consumer } from './context'; + +export default (Component, keyField, _) => { + const renderWithCellEdit = (props, cellEdit) => { + const content = _.get(props.row, props.column.dataField); + const editableRow = props.editable; + + let editable = _.isDefined(props.column.editable) ? props.column.editable : true; + if (props.column.dataField === keyField || !editableRow) editable = false; + if (_.isFunction(props.column.editable)) { + editable = props.column.editable( + content, + props.row, + props.rowIndex, + props.columnIndex + ); + } + + return ( + + ); + }; + return props => ( + + { cellEdit => renderWithCellEdit(props, cellEdit) } + + ); +}; diff --git a/packages/react-bootstrap-table2-editor/src/context.js b/packages/react-bootstrap-table2-editor/src/context.js index 5437cca..7011137 100644 --- a/packages/react-bootstrap-table2-editor/src/context.js +++ b/packages/react-bootstrap-table2-editor/src/context.js @@ -4,15 +4,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT } from './const'; +const CellEditContext = React.createContext(); + export default ( _, dataOperator, isRemoteCellEdit, handleCellChange ) => { - let EditingCell; - const CellEditContext = React.createContext(); - class CellEditProvider extends React.Component { static propTypes = { data: PropTypes.array.isRequired, @@ -32,7 +31,6 @@ export default ( constructor(props) { super(props); - EditingCell = props.cellEdit.editingCellFactory(_, props.cellEdit.options.onStartEdit); this.startEditing = this.startEditing.bind(this); this.escapeEditing = this.escapeEditing.bind(this); this.completeEditing = this.completeEditing.bind(this); @@ -102,7 +100,6 @@ export default ( const { cellEdit: { options: { nonEditableRows, errorMessage, ...optionsRest }, - editingCellFactory, createContext, ...cellEditRest } @@ -112,7 +109,6 @@ export default ( ...optionsRest, ...cellEditRest, ...this.state, - EditingCell, nonEditableRows: _.isDefined(nonEditableRows) ? nonEditableRows() : [], onStart: this.startEditing, onEscape: this.escapeEditing, @@ -121,7 +117,7 @@ export default ( return ( { this.props.children } @@ -129,7 +125,8 @@ export default ( } } return { - Provider: CellEditProvider, - Consumer: CellEditContext.Consumer + Provider: CellEditProvider }; }; + +export const Consumer = CellEditContext.Consumer; diff --git a/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js b/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js new file mode 100644 index 0000000..6d3b079 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js @@ -0,0 +1,43 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import { Consumer } from './context'; +import createEditingCell from './editing-cell'; + +export default (_) => { + const renderWithEditingCell = (props, cellEdit) => { + const content = _.get(props.row, props.column.dataField); + let editCellstyle = props.column.editCellStyle || {}; + let editCellclasses = props.column.editCellClasses; + if (_.isFunction(props.column.editCellStyle)) { + editCellstyle = props.column.editCellStyle( + content, + props.row, + props.rowIndex, + props.columnIndex + ); + } + if (_.isFunction(props.column.editCellClasses)) { + editCellclasses = props.column.editCellClasses( + content, + props.row, + props.rowIndex, + props.columnIndex) + ; + } + const EditingCell = createEditingCell(_); + return ( + + ); + }; + + return props => ( + + { cellEdit => renderWithEditingCell(props, cellEdit) } + + ); +}; diff --git a/packages/react-bootstrap-table2-editor/src/row-binder.js b/packages/react-bootstrap-table2-editor/src/row-binder.js new file mode 100644 index 0000000..b2e308c --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/row-binder.js @@ -0,0 +1,35 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import { DELAY_FOR_DBCLICK, DBCLICK_TO_CELL_EDIT } from './const'; +import { Consumer } from './context'; + +export default (Component, selectRowEnabled) => { + const renderWithCellEdit = (props, cellEdit) => { + const key = props.value; + const editableRow = !( + cellEdit.nonEditableRows.length > 0 && + cellEdit.nonEditableRows.indexOf(key) > -1 + ); + + const attrs = {}; + + if (selectRowEnabled && cellEdit.mode === DBCLICK_TO_CELL_EDIT) { + attrs.DELAY_FOR_DBCLICK = DELAY_FOR_DBCLICK; + } + + return ( + + ); + }; + return props => ( + + { cellEdit => renderWithCellEdit(props, cellEdit) } + + ); +}; diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index 4da1be7..03582ce 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import _ from './utils'; import Row from './row'; +import Cell from './cell'; import RowAggregator from './row-aggregator'; import RowSection from './row-section'; import Const from './const'; @@ -38,55 +39,49 @@ const Body = (props) => { content = ; } else { let RowComponent = Row; - const nonEditableRows = cellEdit.nonEditableRows || []; const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED; const expandRowEnabled = !!expandRow.renderer; + const additionalRowProps = {}; if (expandRowEnabled) { RowComponent = bindExpansion(RowAggregator, visibleColumnSize); } + if (selectRowEnabled) { RowComponent = bindSelection(expandRowEnabled ? RowComponent : RowAggregator); } + if (cellEdit.createContext) { + const CellComponent = cellEdit.bindCellLevelCellEdit(Cell, keyField, _); + const EditingCell = cellEdit.createEditingCell(_, cellEdit.options.onStartEdit); + RowComponent = cellEdit.bindRowLevelCellEdit(RowComponent, selectRowEnabled); + additionalRowProps.CellComponent = CellComponent; + additionalRowProps.EditingCellComponent = EditingCell; + } + + if (selectRowEnabled || expandRowEnabled) { + additionalRowProps.expandRow = expandRow; + additionalRowProps.selectRow = selectRow; + } + content = data.map((row, index) => { const key = _.get(row, keyField); - const editable = !(nonEditableRows.length > 0 && nonEditableRows.indexOf(key) > -1); + const baseRowProps = { + key, + row, + columns, + keyField, + cellEdit, + value: key, + rowIndex: index, + attrs: rowEvents || {}, + ...additionalRowProps + }; - const attrs = rowEvents || {}; - const style = _.isFunction(rowStyle) ? rowStyle(row, index) : rowStyle; - const classes = (_.isFunction(rowClasses) ? rowClasses(row, index) : rowClasses); + baseRowProps.style = _.isFunction(rowStyle) ? rowStyle(row, index) : rowStyle; + baseRowProps.className = (_.isFunction(rowClasses) ? rowClasses(row, index) : rowClasses); - // refine later - const result = - selectRowEnabled || expandRowEnabled ? - () : - (); - - return result; + return ; }); } diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index c0bbb7c..769fa78 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -56,7 +56,8 @@ class BootstrapTable extends PropsBaseResolver(Component) { wrapperClasses, rowEvents, selectRow, - expandRow + expandRow, + cellEdit } = this.props; const tableWrapperClass = cs('react-bootstrap-table', wrapperClasses); @@ -92,7 +93,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { isEmpty={ this.isEmpty() } visibleColumnSize={ this.visibleColumnSize() } noDataIndication={ noDataIndication } - cellEdit={ this.props.cellEdit || {} } + cellEdit={ cellEdit } selectRow={ selectRow } expandRow={ expandRow } rowStyle={ rowStyle } @@ -197,6 +198,10 @@ BootstrapTable.defaultProps = { renderer: undefined, expanded: [], nonExpandable: [] + }, + cellEdit: { + mode: null, + nonEditableRows: [] } }; diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 08d600f..3011d6b 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -57,7 +57,6 @@ const withContext = Base => renderBase() { return ( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -67,7 +66,6 @@ const withContext = Base => ref={ n => this.table = n } { ...this.props } { ...sortProps } - { ...cellEditProps } { ...filterProps } { ...searchProps } { ...paginationProps } @@ -79,7 +77,6 @@ const withContext = Base => renderWithSelectionCtx(base, baseProps) { return ( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -94,7 +91,6 @@ const withContext = Base => { base( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -108,7 +104,6 @@ const withContext = Base => renderWithRowExpandCtx(base, baseProps) { return ( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -123,7 +118,6 @@ const withContext = Base => { base( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -137,7 +131,6 @@ const withContext = Base => renderWithPaginationCtx(base) { return ( rootProps, - cellEditProps, filterProps, searchProps, sortProps @@ -152,7 +145,6 @@ const withContext = Base => { paginationProps => base( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -167,7 +159,6 @@ const withContext = Base => renderWithSortCtx(base, baseProps) { return ( rootProps, - cellEditProps, filterProps, searchProps ) => ( @@ -182,7 +173,6 @@ const withContext = Base => { sortProps => base( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -196,7 +186,6 @@ const withContext = Base => renderWithSearchCtx(base, baseProps) { return ( rootProps, - cellEditProps, filterProps ) => ( { searchProps => base( rootProps, - cellEditProps, filterProps, searchProps ) @@ -220,10 +208,7 @@ const withContext = Base => } renderWithFilterCtx(base, baseProps) { - return ( - rootProps, - cellEditProps - ) => ( + return rootProps => ( this.filterContext = n } @@ -233,7 +218,6 @@ const withContext = Base => { filterProps => base( rootProps, - cellEditProps, filterProps ) } @@ -250,11 +234,7 @@ const withContext = Base => cellEdit={ this.props.cellEdit } data={ rootProps.getData() } > - - { - cellEditProps => base(rootProps, cellEditProps) - } - + { base(rootProps) } ); } diff --git a/packages/react-bootstrap-table2/src/row-aggregator.js b/packages/react-bootstrap-table2/src/row-aggregator.js index e72f6a1..5d709c5 100644 --- a/packages/react-bootstrap-table2/src/row-aggregator.js +++ b/packages/react-bootstrap-table2/src/row-aggregator.js @@ -33,11 +33,7 @@ export default class RowAggregator extends React.Component { expanded, expandRow, selectRow, - cellEdit: { - mode, - DBCLICK_TO_CELL_EDIT, - DELAY_FOR_DBCLICK - } + DELAY_FOR_DBCLICK } = this.props; const clickFn = () => { @@ -53,7 +49,7 @@ export default class RowAggregator extends React.Component { } }; - if (mode === DBCLICK_TO_CELL_EDIT && selectRow.clickToEdit) { + if (DELAY_FOR_DBCLICK) { this.clickNum += 1; _.debounce(() => { if (this.clickNum === 1) { @@ -76,21 +72,18 @@ export default class RowAggregator extends React.Component { style, className, attrs, - cellEdit, selectRow, expandRow, expanded, selected, - selectable + selectable, + ...rest } = this.props; const key = _.get(row, keyField); const { hideSelectColumn, clickToSelect } = selectRow; const { showExpandColumn } = expandRow; - const nonEditableRows = cellEdit.nonEditableRows || []; - const editable = !(nonEditableRows.length > 0 && nonEditableRows.indexOf(key) > -1); - const newAttrs = { ...attrs }; if (clickToSelect || !!expandRow.renderer) { newAttrs.onClick = this.createClickEventHandler(attrs.onClick); @@ -103,11 +96,10 @@ export default class RowAggregator extends React.Component { keyField={ keyField } rowIndex={ rowIndex } columns={ columns } - cellEdit={ cellEdit } - editable={ editable } style={ style } className={ className } attrs={ newAttrs } + { ...rest } > { showExpandColumn ? ( diff --git a/packages/react-bootstrap-table2/src/row-expand/row-binder.js b/packages/react-bootstrap-table2/src/row-expand/row-binder.js index 64eda2c..77f5e36 100644 --- a/packages/react-bootstrap-table2/src/row-expand/row-binder.js +++ b/packages/react-bootstrap-table2/src/row-expand/row-binder.js @@ -1,12 +1,11 @@ /* eslint react/prop-types: 0 */ import React from 'react'; -import _ from '../utils'; import ExpandRow from './expand-row'; import ExpansionContext from '../contexts/row-expand-context'; export default (Component, visibleColumnSize) => { const renderWithExpansion = (props, expandRow) => { - const key = _.get(props.row, props.keyField); + const key = props.value; const expanded = expandRow.expanded.includes(key); const expandable = !expandRow.nonExpandable || !expandRow.nonExpandable.includes(key); diff --git a/packages/react-bootstrap-table2/src/row-selection/row-binder.js b/packages/react-bootstrap-table2/src/row-selection/row-binder.js index f0ad227..68c97f5 100644 --- a/packages/react-bootstrap-table2/src/row-selection/row-binder.js +++ b/packages/react-bootstrap-table2/src/row-selection/row-binder.js @@ -6,7 +6,7 @@ import SelectionContext from '../contexts/selection-context'; export default (Component) => { const renderWithSelection = (props, selectRow) => { - const key = _.get(props.row, props.keyField); + const key = props.value; const selected = selectRow.selected.includes(key); const selectable = !selectRow.nonSelectable || !selectRow.nonSelectable.includes(key); diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js index 8de768f..c41da6c 100644 --- a/packages/react-bootstrap-table2/src/row.js +++ b/packages/react-bootstrap-table2/src/row.js @@ -12,25 +12,15 @@ class Row extends eventDelegater(Component) { const { row, columns, - keyField, rowIndex, className, style, attrs, - cellEdit, - editable: editableRow + editable, + editingRowIdx, + editingColIdx } = this.props; - - const { - mode, - onStart, - EditingCell, - ridx: editingRowIdx, - cidx: editingColIdx, - CLICK_TO_CELL_EDIT, - DBCLICK_TO_CELL_EDIT, - ...rest - } = cellEdit; + const CellComponent = this.props.CellComponent || Cell; const trAttrs = this.delegate(attrs); return ( @@ -41,20 +31,8 @@ class Row extends eventDelegater(Component) { if (!column.hidden) { const { dataField } = column; const content = _.get(row, dataField); - let editable = _.isDefined(column.editable) ? column.editable : true; - if (dataField === keyField || !editableRow) editable = false; - if (_.isFunction(column.editable)) { - editable = column.editable(content, row, rowIndex, index); - } if (rowIndex === editingRowIdx && index === editingColIdx) { - let editCellstyle = column.editCellStyle || {}; - let editCellclasses = column.editCellClasses; - if (_.isFunction(column.editCellStyle)) { - editCellstyle = column.editCellStyle(content, row, rowIndex, index); - } - if (_.isFunction(column.editCellClasses)) { - editCellclasses = column.editCellClasses(content, row, rowIndex, index); - } + const EditingCell = this.props.EditingCellComponent; return ( ); } @@ -108,16 +83,13 @@ class Row extends eventDelegater(Component) { if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle; return ( - ); From 73a5c34535a2d4dc91a405ce88e3654ef6086b60 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 20 Aug 2018 20:32:48 +0800 Subject: [PATCH 13/38] add story for dbclick to edit with row selection --- .../dbclick-to-edit-with-selection-table.js | 69 +++++++++++++++++++ .../stories/index.js | 2 + 2 files changed, 71 insertions(+) create mode 100644 packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-with-selection-table.js diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-with-selection-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-with-selection-table.js new file mode 100644 index 0000000..ad5efd9 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-with-selection-table.js @@ -0,0 +1,69 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +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 selectRow = { + mode: 'checkbox', + clickToSelect: true, + clickToEdit: true +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const selectRow = { + mode: 'checkbox', + clickToSelect: true, + clickToEdit: true +}; + + +`; + +export default () => ( +
+

Double click to edit cell

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 7e0aca7..13d95dd 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -102,6 +102,7 @@ import CellEditClassTable from 'examples/cell-edit/cell-edit-class-table'; import AutoSelectTextInput from 'examples/cell-edit/auto-select-text-input-table'; import EditorStyleTable from 'examples/cell-edit/editor-style-table'; import EditorClassTable from 'examples/cell-edit/editor-class-table'; +import DBClickEditWithSelection from 'examples/cell-edit/dbclick-to-edit-with-selection-table'; import DropdownEditorTable from 'examples/cell-edit/dropdown-editor-table'; import TextareaEditorTable from 'examples/cell-edit/textarea-editor-table'; import CheckboxEditorTable from 'examples/cell-edit/checkbox-editor-table'; @@ -290,6 +291,7 @@ storiesOf('Cell Editing', module) .add('Custom Cell Classes', () => ) .add('Custom Editor Classes', () => ) .add('Custom Editor Style', () => ) + .add('DoubleClick to Edit with Selection', () => ) .add('Dropdown Editor', () => ) .add('Textarea Editor', () => ) .add('Checkbox Editor', () => ) From 640ada7659576070d5dca423b0f337e14e0c0f5b Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 20 Aug 2018 22:56:41 +0800 Subject: [PATCH 14/38] patch tests for refactoring cell edit with consumer --- .../test/cell-binder.test.js | 210 +++++++++ .../test/context.test.js | 33 +- .../test/editing-cell-binder.test.js | 147 +++++++ .../test/row-binder.test.js | 138 ++++++ .../react-bootstrap-table2/test/body.test.js | 29 +- .../test/contexts/index.test.js | 5 +- .../test/row-aggregator.test.js | 1 + .../test/row-selection/row-binder.test.js | 89 +++- .../react-bootstrap-table2/test/row.test.js | 413 ++---------------- .../test-helpers/mock/body-resolved-props.js | 5 +- 10 files changed, 638 insertions(+), 432 deletions(-) create mode 100644 packages/react-bootstrap-table2-editor/test/cell-binder.test.js create mode 100644 packages/react-bootstrap-table2-editor/test/editing-cell-binder.test.js create mode 100644 packages/react-bootstrap-table2-editor/test/row-binder.test.js diff --git a/packages/react-bootstrap-table2-editor/test/cell-binder.test.js b/packages/react-bootstrap-table2-editor/test/cell-binder.test.js new file mode 100644 index 0000000..dfe72ee --- /dev/null +++ b/packages/react-bootstrap-table2-editor/test/cell-binder.test.js @@ -0,0 +1,210 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { mount } from 'enzyme'; +import _ from 'react-bootstrap-table-next/src/utils'; +import op from 'react-bootstrap-table-next/src/store/operators'; + +import cellEditFactory from '../index'; +import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT } from '../src/const'; +import createCellEditContext from '../src/context'; +import bindCellEditing from '../src/cell-binder'; + +describe('Cell Binder', () => { + let wrapper; + let cellEdit; + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + let columns; + const rowIndex = 1; + const row = { id: 1, name: 'A' }; + const keyField = 'id'; + const columnIndex = 1; + + const { Provider } = createCellEditContext(_, op, false, jest.fn()); + const BaseComponent = () => null; + const WithCellEditComponent = bindCellEditing( + props => , + keyField, + _ + ); + + beforeEach(() => { + columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + }); + + describe(`if cellEdit.mode is ${CLICK_TO_CELL_EDIT}`, () => { + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + wrapper = mount( + + + + ); + }); + + it('should inject correct props to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('clickToEdit')).toBeTruthy(); + expect(wrapper.find(BaseComponent).prop('dbclickToEdit')).toBeFalsy(); + }); + }); + + describe(`if cellEdit.mode is ${DBCLICK_TO_CELL_EDIT}`, () => { + beforeEach(() => { + cellEdit = cellEditFactory({ mode: DBCLICK_TO_CELL_EDIT }); + wrapper = mount( + + + + ); + }); + + it('should inject correct props to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('clickToEdit')).toBeFalsy(); + expect(wrapper.find(BaseComponent).prop('dbclickToEdit')).toBeTruthy(); + }); + }); + + describe('if column prop is a key column', () => { + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + wrapper = mount( + + + + ); + }); + + it('should inject negative editable prop to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('editable')).toBeFalsy(); + }); + }); + + describe('if editable prop is true(Row Level)', () => { + describe('but column.editable prop is false', () => { + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + columns[1].editable = false; + wrapper = mount( + + + + ); + }); + + it('should inject negative editable prop to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('editable')).toBeFalsy(); + }); + }); + + describe('and column.editable prop is true or not defined', () => { + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + wrapper = mount( + + + + ); + }); + + it('should inject positive editable prop to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('editable')).toBeTruthy(); + }); + }); + }); + + describe('if editable prop is false(Row Level)', () => { + describe('even if column.editable prop is true or not defined', () => { + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + columns[1].editable = true; + wrapper = mount( + + + + ); + }); + + it('should inject negative editable prop to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('editable')).toBeFalsy(); + }); + }); + }); + + describe('if column.editable prop is a function', () => { + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + columns[1].editable = jest.fn().mockReturnValue(false); + wrapper = mount( + + + + ); + }); + + it('should call column.editable function correctly', () => { + expect(columns[1].editable).toHaveBeenCalledTimes(1); + }); + + it('should inject correct editable prop to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('editable')).toBeFalsy(); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-editor/test/context.test.js b/packages/react-bootstrap-table2-editor/test/context.test.js index 89af0ce..03cfd74 100644 --- a/packages/react-bootstrap-table2-editor/test/context.test.js +++ b/packages/react-bootstrap-table2-editor/test/context.test.js @@ -3,14 +3,13 @@ import React from 'react'; import { shallow } from 'enzyme'; import _ from 'react-bootstrap-table-next/src/utils'; import dataOperator from 'react-bootstrap-table-next/src/store/operators'; -import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table'; import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK } from '../src/const'; -import createCellEditContext from '../src/context'; +import createCellEditContext, { Consumer } from '../src/context'; import cellEditFactory from '../index'; describe('CellEditContext', () => { @@ -42,14 +41,7 @@ describe('CellEditContext', () => { const defaultSelectRow = undefined; - const mockBase = jest.fn((props => ( - - ))); + const mockBase = jest.fn((() => null)); const handleCellChange = jest.fn(); @@ -75,11 +67,11 @@ describe('CellEditContext', () => { selectRow={ selectRow } data={ data } > - + { cellEditProps => mockBase(cellEditProps) } - + ); } @@ -94,10 +86,6 @@ describe('CellEditContext', () => { expect(CellEditContext.Provider).toBeDefined(); }); - it('should have correct Consumer property after calling createCellEditContext', () => { - expect(CellEditContext.Consumer).toBeDefined(); - }); - it('should have correct state.ridx', () => { expect(wrapper.state().ridx).toBeNull(); }); @@ -113,14 +101,11 @@ describe('CellEditContext', () => { it('should pass correct cell editing props to children element', () => { expect(wrapper.length).toBe(1); expect(JSON.stringify(mockBase.mock.calls[0])).toEqual(JSON.stringify([{ - cellEdit: { - ...defaultCellEdit, - CLICK_TO_CELL_EDIT, - DBCLICK_TO_CELL_EDIT, - DELAY_FOR_DBCLICK, - ...wrapper.state(), - nonEditableRows: [] - } + ...defaultCellEdit, + DBCLICK_TO_CELL_EDIT, + DELAY_FOR_DBCLICK, + ...wrapper.state(), + nonEditableRows: [] }])); }); }); diff --git a/packages/react-bootstrap-table2-editor/test/editing-cell-binder.test.js b/packages/react-bootstrap-table2-editor/test/editing-cell-binder.test.js new file mode 100644 index 0000000..fa8a392 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/test/editing-cell-binder.test.js @@ -0,0 +1,147 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { mount, shallow } from 'enzyme'; +import _ from 'react-bootstrap-table-next/src/utils'; + +import cellEditFactory from '../index'; +import { CLICK_TO_CELL_EDIT } from '../src/const'; +import createCellEditContext from '../src/context'; +import bindEditingCell from '../src/editing-cell-binder'; + +describe('Cell Binder', () => { + let wrapper; + let cellEdit; + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + let columns; + const rowIndex = 1; + const row = { id: 1, name: 'A' }; + const keyField = 'id'; + const columnIndex = 1; + + const { Provider } = createCellEditContext(_); + const WithCellEditComponent = bindEditingCell(_); + + beforeEach(() => { + columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + }); + + describe('if column.editCellClasses is defined as string', () => { + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + columns[1].editCellClasses = 'test-class-1'; + wrapper = shallow( + + + + ); + wrapper = wrapper.render(); + }); + + it('should inject className target component correctly', () => { + expect(wrapper.hasClass(`${columns[1].editCellClasses}`)).toBeTruthy(); + }); + }); + + describe('if column.editCellStyle is defined as object', () => { + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + columns[1].editCellStyle = { color: 'pink' }; + wrapper = mount( + + + + ); + }); + + it('should inject style target component correctly', () => { + expect(wrapper.find('.react-bootstrap-table-editing-cell').prop('style')).toEqual(columns[1].editCellStyle); + }); + }); + + describe('if column.editCellClasses is defined as function', () => { + const className = 'test-class-1'; + + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + columns[1].editCellClasses = jest.fn().mockReturnValue(className); + wrapper = mount( + + + + ); + }); + + it('should inject empty className and style to target component', () => { + expect(wrapper.find(className)).toBeTruthy(); + }); + + it('should call column.editCellClasses function correctly', () => { + expect(columns[1].editCellClasses).toHaveBeenCalledTimes(1); + expect(columns[1].editCellClasses).toHaveBeenCalledWith( + _.get(row, columns[1].dataField), + row, + rowIndex, + columnIndex + ); + }); + }); + + describe('if column.editCellStyle is defined as function', () => { + const style = { color: 'blue' }; + beforeEach(() => { + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + columns[1].editCellStyle = jest.fn().mockReturnValue(style); + wrapper = mount( + + + + ); + }); + + it('should inject style target component correctly', () => { + expect(wrapper.find('.react-bootstrap-table-editing-cell').prop('style')).toEqual(style); + }); + + it('should call column.editCellStyle function correctly', () => { + expect(columns[1].editCellStyle).toHaveBeenCalledTimes(1); + expect(columns[1].editCellStyle).toHaveBeenCalledWith( + _.get(row, columns[1].dataField), + row, + rowIndex, + columnIndex + ); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-editor/test/row-binder.test.js b/packages/react-bootstrap-table2-editor/test/row-binder.test.js new file mode 100644 index 0000000..97490d6 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/test/row-binder.test.js @@ -0,0 +1,138 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { mount } from 'enzyme'; +import _ from 'react-bootstrap-table-next/src/utils'; +import op from 'react-bootstrap-table-next/src/store/operators'; + +import cellEditFactory from '../index'; +import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK } from '../src/const'; +import createCellEditContext from '../src/context'; +import bindCellEditing from '../src/row-binder'; + +describe('Row Binder', () => { + let wrapper; + let cellEdit; + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + const row = { id: 1, name: 'A' }; + const keyField = 'id'; + const value = _.get(row, keyField); + + const { Provider } = createCellEditContext(_, op, false, jest.fn()); + const BaseComponent = () => null; + + describe('if cellEdit.nonEditableRows is undefined', () => { + beforeEach(() => { + const WithCellEditComponent = bindCellEditing( + props => , + false + ); + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + wrapper = mount( + + + + ); + }); + + it('should inject correct props to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('editingRowIdx')).toBeNull(); + expect(wrapper.find(BaseComponent).prop('editingColIdx')).toBeNull(); + expect(wrapper.find(BaseComponent).prop('editable')).toBeTruthy(); + }); + }); + + describe('if cellEdit.nonEditableRows is defined', () => { + const nonEditableRows = jest.fn().mockReturnValue([value]); + describe('if value prop is match in one of cellEdit.nonEditableRows', () => { + beforeEach(() => { + const WithCellEditComponent = bindCellEditing( + props => , + false + ); + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT, nonEditableRows }); + wrapper = mount( + + + + ); + }); + + it('should inject correct editable prop as false to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('editable')).toBeFalsy(); + }); + }); + + describe('if value prop is not match in one of cellEdit.nonEditableRows', () => { + beforeEach(() => { + const WithCellEditComponent = bindCellEditing( + props => , + false + ); + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT, nonEditableRows }); + wrapper = mount( + + + + ); + }); + + it('should inject correct editable prop as false to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('editable')).toBeTruthy(); + }); + }); + }); + + describe(`if selectRowEnabled argument is true and cellEdit.mode is ${DBCLICK_TO_CELL_EDIT}`, () => { + beforeEach(() => { + const WithCellEditComponent = bindCellEditing( + props => , + true + ); + cellEdit = cellEditFactory({ mode: DBCLICK_TO_CELL_EDIT }); + wrapper = mount( + + + + ); + }); + + it('should inject correct DELAY_FOR_DBCLICK prop to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('DELAY_FOR_DBCLICK')).toEqual(DELAY_FOR_DBCLICK); + }); + }); + + describe('if cellEdit.ridx and cellEdit.cidx are defined', () => { + const ridx = 0; + const cidx = 1; + beforeEach(() => { + const WithCellEditComponent = bindCellEditing( + props => , + false + ); + cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); + wrapper = mount( + + + + ); + wrapper.instance().startEditing(ridx, cidx); + wrapper.update(); + }); + + it('should inject correct editable prop as false to target component', () => { + expect(wrapper.find(BaseComponent)).toHaveLength(1); + expect(wrapper.find(BaseComponent).prop('editingRowIdx')).toEqual(ridx); + expect(wrapper.find(BaseComponent).prop('editingColIdx')).toEqual(cidx); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/body.test.js b/packages/react-bootstrap-table2/test/body.test.js index ba71bda..e449877 100644 --- a/packages/react-bootstrap-table2/test/body.test.js +++ b/packages/react-bootstrap-table2/test/body.test.js @@ -254,11 +254,15 @@ describe('Body', () => { }); }); - describe('when cellEdit.nonEditableRows props is defined', () => { - const nonEditableRows = [data[1].id]; + describe('when cellEdit.createContext props is defined', () => { + const CellComponent = () => null; + const EditingCellComponent = () => null; + const RowComponent = props => ; const cellEdit = { - mode: Const.CLICK_TO_CELL_EDIT, - nonEditableRows + createContext: jest.fn(), + bindCellLevelCellEdit: jest.fn().mockReturnValue(CellComponent), + createEditingCell: jest.fn().mockReturnValue(EditingCellComponent), + bindRowLevelCellEdit: jest.fn().mockReturnValue(RowComponent) }; beforeEach(() => { wrapper = shallow( @@ -272,16 +276,15 @@ describe('Body', () => { ); }); - it('should render Row component with correct editable prop', () => { + it('should render Row Component correctly', () => { expect(wrapper.length).toBe(1); - const rows = wrapper.find(Row); - for (let i = 0; i < rows.length; i += 1) { - if (nonEditableRows.indexOf(rows.get(i).props.row[keyField]) > -1) { - expect(rows.get(i).props.editable).toBeFalsy(); - } else { - expect(rows.get(i).props.editable).toBeTruthy(); - } - } + expect(cellEdit.bindCellLevelCellEdit).toHaveBeenCalledTimes(1); + expect(cellEdit.createEditingCell).toHaveBeenCalledTimes(1); + expect(cellEdit.bindRowLevelCellEdit).toHaveBeenCalledTimes(1); + expect(wrapper.find(RowComponent)).toHaveLength(2); + const aRowElement = wrapper.find(RowComponent).get(0); + expect(aRowElement.props.CellComponent).toBeDefined(); + expect(aRowElement.props.EditingCellComponent).toBeDefined(); }); }); diff --git a/packages/react-bootstrap-table2/test/contexts/index.test.js b/packages/react-bootstrap-table2/test/contexts/index.test.js index 4a2b1de..90e8a69 100644 --- a/packages/react-bootstrap-table2/test/contexts/index.test.js +++ b/packages/react-bootstrap-table2/test/contexts/index.test.js @@ -111,7 +111,10 @@ describe('Context', () => { createContext: jest.fn().mockReturnValue({ Provider: CellEditContext.Provider, Consumer: CellEditContext.Consumer - }) + }), + bindCellLevelCellEdit: jest.fn().mockReturnValue(() => null), + createEditingCell: jest.fn().mockReturnValue(() => null), + bindRowLevelCellEdit: jest.fn().mockReturnValue(() => null) }; wrapper = shallow( { const getBaseProps = () => ({ row, + value: row[keyField], columns, keyField, rowIndex, diff --git a/packages/react-bootstrap-table2/test/row-selection/row-binder.test.js b/packages/react-bootstrap-table2/test/row-selection/row-binder.test.js index 7cc0a4c..0905eee 100644 --- a/packages/react-bootstrap-table2/test/row-selection/row-binder.test.js +++ b/packages/react-bootstrap-table2/test/row-selection/row-binder.test.js @@ -24,13 +24,19 @@ describe('Selection Row Binder', () => { const rowIndex = 1; const row = data[rowIndex]; const keyField = 'id'; + const value = row[keyField]; describe('if current row is selected', () => { beforeEach(() => { selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]] }; wrapper = mount( - + ); }); @@ -46,7 +52,12 @@ describe('Selection Row Binder', () => { selectRow = { mode: 'checkbox', selected: [] }; wrapper = mount( - + ); }); @@ -62,7 +73,12 @@ describe('Selection Row Binder', () => { selectRow = { mode: 'checkbox', nonSelectable: [] }; wrapper = mount( - + ); }); @@ -78,7 +94,12 @@ describe('Selection Row Binder', () => { selectRow = { mode: 'checkbox', nonSelectable: [data[rowIndex][keyField]] }; wrapper = mount( - + ); }); @@ -96,7 +117,12 @@ describe('Selection Row Binder', () => { selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]], style: selectedStyle }; wrapper = mount( - + ); }); @@ -113,6 +139,7 @@ describe('Selection Row Binder', () => { { selectRow.bgColor = 'gray'; wrapper = mount( - + ); }); @@ -155,7 +187,12 @@ describe('Selection Row Binder', () => { selectRow.bgColor = jest.fn().mockReturnValue(color); wrapper = mount( - + ); }); @@ -180,7 +217,12 @@ describe('Selection Row Binder', () => { selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]], style: jest.fn().mockReturnValue(selectedStyle) }; wrapper = mount( - + ); }); @@ -202,6 +244,7 @@ describe('Selection Row Binder', () => { { selectRow.bgColor = 'gray'; wrapper = mount( - + ); }); @@ -244,7 +292,12 @@ describe('Selection Row Binder', () => { selectRow.bgColor = jest.fn().mockReturnValue(color); wrapper = mount( - + ); }); @@ -272,7 +325,12 @@ describe('Selection Row Binder', () => { selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]], classes: selectedClassName }; wrapper = mount( - + ); }); @@ -289,6 +347,7 @@ describe('Selection Row Binder', () => { { selectRow = { mode: 'checkbox', selected: [data[rowIndex][keyField]], classes: jest.fn().mockReturnValue(selectedClassName) }; wrapper = mount( - + ); }); @@ -331,6 +395,7 @@ describe('Selection Row Binder', () => { { }); }); - describe('when cellEdit prop is defined', () => { - let columns; - let cellEdit; - + describe('when CellComponent prop is defined', () => { + const CellComponent = () => null; beforeEach(() => { - columns = defaultColumns; - cellEdit = { - mode: 'click', - CLICK_TO_CELL_EDIT: 'click', - DBCLICK_TO_CELL_EDIT: 'dbclick' - }; wrapper = shallow( - ); + columns={ defaultColumns } + row={ row } + CellComponent={ CellComponent } + />); }); - afterEach(() => { - columns = undefined; - cellEdit = undefined; - }); - - it('Cell component should receive correct editable props', () => { + it('should render CellComponent successfully', () => { expect(wrapper.length).toBe(1); - for (let i = 0; i < columns.length; i += 1) { - const column = columns[i]; - if (column.dataField === keyField) { - expect(wrapper.find(Cell).get(i).props.editable).toBeFalsy(); - } else { - expect(wrapper.find(Cell).get(i).props.editable).toBeTruthy(); - } - } + expect(wrapper.find(CellComponent)).toHaveLength(defaultColumns.length); + }); + }); + + describe('when editingRowIdx and editingColIdx prop is defined', () => { + const editingRowIdx = rowIndex; + const editingColIdx = 1; + const EditingCellComponent = () => null; + beforeEach(() => { + wrapper = shallow( + ); }); - it('Cell component should receive correct clickToEdit props', () => { + it('should render EditingCell component correctly', () => { + const EditingCell = wrapper.find(EditingCellComponent); expect(wrapper.length).toBe(1); - for (let i = 0; i < columns.length; i += 1) { - expect(wrapper.find(Cell).get(i).props.clickToEdit).toBeTruthy(); - } - }); - - it('Cell component should receive correct dbclickToEdit props', () => { - expect(wrapper.length).toBe(1); - for (let i = 0; i < columns.length; i += 1) { - expect(wrapper.find(Cell).get(i).props.dbclickToEdit).toBeFalsy(); - } - }); - - describe('when props.cellEdit.mode is dbclick', () => { - beforeEach(() => { - cellEdit.mode = cellEdit.DBCLICK_TO_CELL_EDIT; - wrapper = shallow( - - ); - }); - - it('Cell component should receive correct clickToEdit props', () => { - expect(wrapper.length).toBe(1); - for (let i = 0; i < columns.length; i += 1) { - expect(wrapper.find(Cell).get(i).props.clickToEdit).toBeFalsy(); - } - }); - - it('Cell component should receive correct dbclickToEdit props', () => { - expect(wrapper.length).toBe(1); - for (let i = 0; i < columns.length; i += 1) { - expect(wrapper.find(Cell).get(i).props.dbclickToEdit).toBeTruthy(); - } - }); - }); - - describe('and column.editable defined false', () => { - const nonEditableColIndex = 1; - beforeEach(() => { - columns[nonEditableColIndex].editable = false; - wrapper = shallow( - - ); - }); - - it('Cell component should receive correct editable props', () => { - expect(wrapper.length).toBe(1); - for (let i = 0; i < columns.length; i += 1) { - const column = columns[i]; - if (i === nonEditableColIndex || column.dataField === keyField) { - expect(wrapper.find(Cell).get(i).props.editable).toBeFalsy(); - } else { - expect(wrapper.find(Cell).get(i).props.editable).toBeTruthy(); - } - } - }); - }); - - describe('and column.editable defined as function', () => { - const nonEditableColIndex = 1; - let editableCallBack; - - afterEach(() => { - editableCallBack.reset(); - }); - - describe('which return false', () => { - beforeEach(() => { - editableCallBack = sinon.stub().returns(false); - columns[nonEditableColIndex].editable = editableCallBack; - wrapper = shallow( - - ); - }); - - it('column.editable callback function should be called once', () => { - expect(editableCallBack.callCount).toBe(1); - }); - - it('Cell component should receive correct editable props', () => { - expect(wrapper.length).toBe(1); - for (let i = 0; i < columns.length; i += 1) { - const column = columns[i]; - if (i === nonEditableColIndex || column.dataField === keyField) { - expect(wrapper.find(Cell).get(i).props.editable).toBeFalsy(); - } else { - expect(wrapper.find(Cell).get(i).props.editable).toBeTruthy(); - } - } - }); - }); - - describe('which return true', () => { - beforeEach(() => { - editableCallBack = sinon.stub().returns(true); - columns[nonEditableColIndex].editable = editableCallBack; - wrapper = shallow( - - ); - }); - - it('column.editable callback function should be called once', () => { - expect(editableCallBack.callCount).toBe(1); - }); - - it('Cell component should receive correct editable props', () => { - expect(wrapper.length).toBe(1); - for (let i = 0; i < columns.length; i += 1) { - const column = columns[i]; - if (column.dataField === keyField) { - expect(wrapper.find(Cell).get(i).props.editable).toBeFalsy(); - } else { - expect(wrapper.find(Cell).get(i).props.editable).toBeTruthy(); - } - } - }); - }); - }); - - // Means user defined cellEdit.nonEditableRows - // and some rows will be treated as noneditable by this rules - describe('when editable prop is false', () => { - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('All the Cell components should be noneditable', () => { - expect(wrapper.length).toBe(1); - for (let i = 0; i < columns.length; i += 1) { - expect(wrapper.find(Cell).get(i).props.editable).toBeFalsy(); - } - }); - }); - - // Means a cell now is undering editing - describe('when cellEdit.ridx and cellEdit.cidx is defined', () => { - const EditingCell = () => null; - describe('and cellEdit.ridx is match to current row index', () => { - const editingColIndex = 1; - beforeEach(() => { - cellEdit.ridx = rowIndex; - cellEdit.cidx = editingColIndex; - cellEdit.onUpdate = sinon.stub(); - cellEdit.onEscape = sinon.stub(); - cellEdit.EditingCell = EditingCell; - 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(complexComponents.at(editingColIndex).type()).toEqual(EditingCell); - }); - - describe('if column.editCellStyle defined as object', () => { - const definedStyleColIndex = editingColIndex; - - beforeEach(() => { - columns[definedStyleColIndex].editCellStyle = { backgroundColor: 'red' }; - wrapper = shallow( - - ); - }); - - it('should also rendering EditingCell with correct style object', () => { - expect(wrapper.find(EditingCell).length).toBe(1); - expect(wrapper.find(EditingCell).props().style) - .toEqual(columns[definedStyleColIndex].editCellStyle); - }); - }); - - describe('if column.editCellStyle defined as function', () => { - const definedStyleColIndex = editingColIndex; - const customStyle = { backgroundColor: 'red' }; - let editCellStyleCallBack; - - beforeEach(() => { - editCellStyleCallBack = sinon.stub().returns(customStyle); - columns[definedStyleColIndex].editCellStyle = editCellStyleCallBack; - wrapper = shallow( - - ); - }); - - it('should calling custom column.editCellStyle callback correctly', () => { - expect(editCellStyleCallBack.callCount).toBe(1); - expect( - editCellStyleCallBack.calledWith( - row[columns[editingColIndex].dataField], row, rowIndex, editingColIndex) - ).toBe(true); - }); - - it('should also rendering EditingCell with correct style object', () => { - expect(wrapper.find(EditingCell).length).toBe(1); - expect(wrapper.find(EditingCell).props().style).toEqual(customStyle); - }); - }); - - describe('if column.editCellClasses defined as string', () => { - const definedStyleColIndex = editingColIndex; - - beforeEach(() => { - columns[definedStyleColIndex].editCellClasses = 'custom-class'; - wrapper = shallow( - - ); - }); - - it('should also rendering EditingCell with correct class', () => { - expect(wrapper.find(EditingCell).length).toBe(1); - expect(wrapper.find(EditingCell).props().className) - .toEqual(columns[definedStyleColIndex].editCellClasses); - }); - }); - - describe('if column.editCellClasses defined as function', () => { - const definedStyleColIndex = editingColIndex; - const customClass = 'custom-class'; - let editCellClassesCallBack; - - beforeEach(() => { - editCellClassesCallBack = sinon.stub().returns(customClass); - columns[definedStyleColIndex].editCellClasses = editCellClassesCallBack; - wrapper = shallow( - - ); - }); - - it('should calling custom column.editCellStyle callback correctly', () => { - expect(editCellClassesCallBack.callCount).toBe(1); - expect( - editCellClassesCallBack.calledWith( - row[columns[editingColIndex].dataField], row, rowIndex, editingColIndex) - ).toBe(true); - }); - - it('should also rendering EditingCell with correct class', () => { - expect(wrapper.find(EditingCell).length).toBe(1); - expect(wrapper.find(EditingCell).props().className).toEqual(customClass); - }); - }); - }); - - describe('and cellEdit.ridx is not match to current row index', () => { - const editingColIndex = 1; - beforeEach(() => { - cellEdit.ridx = 3; - cellEdit.cidx = editingColIndex; - cellEdit.onUpdate = sinon.stub(); - cellEdit.onEscape = sinon.stub(); - wrapper = shallow( - - ); - }); - - it('should not render any EditingCell component', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(EditingCell).length).toBe(0); - expect(wrapper.find(Cell).length).toBe(columns.length); - }); - }); + expect(EditingCell).toHaveLength(1); + expect(EditingCell.prop('row')).toEqual(row); + expect(EditingCell.prop('rowIndex')).toEqual(editingRowIdx); + expect(EditingCell.prop('column')).toEqual(defaultColumns[editingColIdx]); + expect(EditingCell.prop('columnIndex')).toEqual(editingColIdx); }); }); diff --git a/packages/react-bootstrap-table2/test/test-helpers/mock/body-resolved-props.js b/packages/react-bootstrap-table2/test/test-helpers/mock/body-resolved-props.js index 4d1d6eb..faee815 100644 --- a/packages/react-bootstrap-table2/test/test-helpers/mock/body-resolved-props.js +++ b/packages/react-bootstrap-table2/test/test-helpers/mock/body-resolved-props.js @@ -1,6 +1,6 @@ import Const from '../../../src/const'; -const { ROW_SELECT_DISABLED, UNABLE_TO_CELL_EDIT } = Const; +const { ROW_SELECT_DISABLED } = Const; export const rowSelectionResolvedProps = { mode: ROW_SELECT_DISABLED, @@ -14,7 +14,8 @@ export const expandRowResolvedProps = { }; export const cellEditResolvedProps = { - mode: UNABLE_TO_CELL_EDIT + mode: null, + nonEditableRows: [] }; export default { From 8c10867b8cb75914fc677532c1ca54d2bcbdfbdf Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 1 Sep 2018 12:56:57 +0800 Subject: [PATCH 15/38] fix cell level performace, remain select all --- .../react-bootstrap-table2-editor/index.js | 2 - .../src/cell-binder.js | 38 ----- .../src/context.js | 1 - .../src/editing-cell-binder.js | 3 +- .../src/row-binder.js | 5 +- .../examples/basic/large-table.js | 3 +- packages/react-bootstrap-table2/src/body.js | 153 +++++++++--------- .../src/row-aggregator.js | 15 +- .../src/row-selection/row-binder.js | 2 +- .../src/row-selection/selection-cell.js | 8 +- .../src/row-should-updater.js | 25 +++ packages/react-bootstrap-table2/src/row.js | 41 ++++- 12 files changed, 165 insertions(+), 131 deletions(-) delete mode 100644 packages/react-bootstrap-table2-editor/src/cell-binder.js create mode 100644 packages/react-bootstrap-table2/src/row-should-updater.js diff --git a/packages/react-bootstrap-table2-editor/index.js b/packages/react-bootstrap-table2-editor/index.js index a4b4e65..fc8439d 100644 --- a/packages/react-bootstrap-table2-editor/index.js +++ b/packages/react-bootstrap-table2-editor/index.js @@ -1,5 +1,4 @@ import createContext from './src/context'; -import bindCellLevelCellEdit from './src/cell-binder'; import bindRowLevelCellEdit from './src/row-binder'; import createEditingCell from './src/editing-cell-binder'; import { @@ -11,7 +10,6 @@ import { export default (options = {}) => ({ createContext, createEditingCell, - bindCellLevelCellEdit, bindRowLevelCellEdit, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK, diff --git a/packages/react-bootstrap-table2-editor/src/cell-binder.js b/packages/react-bootstrap-table2-editor/src/cell-binder.js deleted file mode 100644 index 6ef110a..0000000 --- a/packages/react-bootstrap-table2-editor/src/cell-binder.js +++ /dev/null @@ -1,38 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from 'react'; -import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT } from './const'; - -import { Consumer } from './context'; - -export default (Component, keyField, _) => { - const renderWithCellEdit = (props, cellEdit) => { - const content = _.get(props.row, props.column.dataField); - const editableRow = props.editable; - - let editable = _.isDefined(props.column.editable) ? props.column.editable : true; - if (props.column.dataField === keyField || !editableRow) editable = false; - if (_.isFunction(props.column.editable)) { - editable = props.column.editable( - content, - props.row, - props.rowIndex, - props.columnIndex - ); - } - - return ( - - ); - }; - return props => ( - - { cellEdit => renderWithCellEdit(props, cellEdit) } - - ); -}; diff --git a/packages/react-bootstrap-table2-editor/src/context.js b/packages/react-bootstrap-table2-editor/src/context.js index 7011137..c3f37c5 100644 --- a/packages/react-bootstrap-table2-editor/src/context.js +++ b/packages/react-bootstrap-table2-editor/src/context.js @@ -100,7 +100,6 @@ export default ( const { cellEdit: { options: { nonEditableRows, errorMessage, ...optionsRest }, - createContext, ...cellEditRest } } = this.props; diff --git a/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js b/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js index 6d3b079..0a9ef77 100644 --- a/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js +++ b/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js @@ -4,6 +4,7 @@ import { Consumer } from './context'; import createEditingCell from './editing-cell'; export default (_) => { + const EditingCell = createEditingCell(_); const renderWithEditingCell = (props, cellEdit) => { const content = _.get(props.row, props.column.dataField); let editCellstyle = props.column.editCellStyle || {}; @@ -24,7 +25,7 @@ export default (_) => { props.columnIndex) ; } - const EditingCell = createEditingCell(_); + return ( { @@ -24,6 +24,9 @@ export default (Component, selectRowEnabled) => { editingRowIdx={ cellEdit.ridx } editingColIdx={ cellEdit.cidx } editable={ editableRow } + onStart={ cellEdit.onStart } + clickToEdit={ cellEdit.mode === CLICK_TO_CELL_EDIT } + dbclickToEdit={ cellEdit.mode === DBCLICK_TO_CELL_EDIT } /> ); }; diff --git a/packages/react-bootstrap-table2-example/examples/basic/large-table.js b/packages/react-bootstrap-table2-example/examples/basic/large-table.js index 671aaa5..f25beae 100644 --- a/packages/react-bootstrap-table2-example/examples/basic/large-table.js +++ b/packages/react-bootstrap-table2-example/examples/basic/large-table.js @@ -4,7 +4,7 @@ import BootstrapTable from 'react-bootstrap-table-next'; import cellEditFactory from 'react-bootstrap-table2-editor'; import { productsGenerator } from 'utils/common'; -const products = productsGenerator(5000); +const products = productsGenerator(5); const columns = [{ dataField: 'id', @@ -23,7 +23,6 @@ export default () => ( keyField="id" data={ products } columns={ columns } - selectRow={ { mode: 'checkbox' } } cellEdit={ cellEditFactory({ mode: 'click' }) } diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index 03582ce..8c83eaa 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -6,89 +6,94 @@ import PropTypes from 'prop-types'; import _ from './utils'; import Row from './row'; -import Cell from './cell'; import RowAggregator from './row-aggregator'; import RowSection from './row-section'; import Const from './const'; import bindSelection from './row-selection/row-binder'; import bindExpansion from './row-expand/row-binder'; -const Body = (props) => { - const { - columns, - data, - keyField, - isEmpty, - noDataIndication, - visibleColumnSize, - cellEdit, - selectRow, - rowStyle, - rowClasses, - rowEvents, - expandRow - } = props; - - let content; - - if (isEmpty) { - const indication = _.isFunction(noDataIndication) ? noDataIndication() : noDataIndication; - if (!indication) { - return null; +class Body extends React.Component { + constructor(props) { + super(props); + if (props.cellEdit.createContext) { + this.EditingCell = props.cellEdit.createEditingCell(_, props.cellEdit.options.onStartEdit); } - content = ; - } else { - let RowComponent = Row; - const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED; - const expandRowEnabled = !!expandRow.renderer; - - const additionalRowProps = {}; - if (expandRowEnabled) { - RowComponent = bindExpansion(RowAggregator, visibleColumnSize); - } - - if (selectRowEnabled) { - RowComponent = bindSelection(expandRowEnabled ? RowComponent : RowAggregator); - } - - if (cellEdit.createContext) { - const CellComponent = cellEdit.bindCellLevelCellEdit(Cell, keyField, _); - const EditingCell = cellEdit.createEditingCell(_, cellEdit.options.onStartEdit); - RowComponent = cellEdit.bindRowLevelCellEdit(RowComponent, selectRowEnabled); - additionalRowProps.CellComponent = CellComponent; - additionalRowProps.EditingCellComponent = EditingCell; - } - - if (selectRowEnabled || expandRowEnabled) { - additionalRowProps.expandRow = expandRow; - additionalRowProps.selectRow = selectRow; - } - - content = data.map((row, index) => { - const key = _.get(row, keyField); - const baseRowProps = { - key, - row, - columns, - keyField, - cellEdit, - value: key, - rowIndex: index, - attrs: rowEvents || {}, - ...additionalRowProps - }; - - baseRowProps.style = _.isFunction(rowStyle) ? rowStyle(row, index) : rowStyle; - baseRowProps.className = (_.isFunction(rowClasses) ? rowClasses(row, index) : rowClasses); - - return ; - }); } - return ( - { content } - ); -}; + render() { + const { + columns, + data, + keyField, + isEmpty, + noDataIndication, + visibleColumnSize, + cellEdit, + selectRow, + rowStyle, + rowClasses, + rowEvents, + expandRow + } = this.props; + + let content; + + if (isEmpty) { + const indication = _.isFunction(noDataIndication) ? noDataIndication() : noDataIndication; + if (!indication) { + return null; + } + content = ; + } else { + let RowComponent = Row; + const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED; + const expandRowEnabled = !!expandRow.renderer; + + const additionalRowProps = {}; + if (expandRowEnabled) { + RowComponent = bindExpansion(RowAggregator, visibleColumnSize); + } + + if (selectRowEnabled) { + RowComponent = bindSelection(expandRowEnabled ? RowComponent : RowAggregator); + } + + if (cellEdit.createContext) { + RowComponent = cellEdit.bindRowLevelCellEdit(RowComponent, selectRowEnabled, keyField, _); + additionalRowProps.EditingCellComponent = this.EditingCell; + } + + if (selectRowEnabled || expandRowEnabled) { + additionalRowProps.expandRow = expandRow; + additionalRowProps.selectRow = selectRow; + } + + content = data.map((row, index) => { + const key = _.get(row, keyField); + const baseRowProps = { + key, + row, + columns, + keyField, + cellEdit, + value: key, + rowIndex: index, + attrs: rowEvents || {}, + ...additionalRowProps + }; + + baseRowProps.style = _.isFunction(rowStyle) ? rowStyle(row, index) : rowStyle; + baseRowProps.className = (_.isFunction(rowClasses) ? rowClasses(row, index) : rowClasses); + + return ; + }); + } + + return ( + { content } + ); + } +} Body.propTypes = { keyField: PropTypes.string.isRequired, diff --git a/packages/react-bootstrap-table2/src/row-aggregator.js b/packages/react-bootstrap-table2/src/row-aggregator.js index 5d709c5..f9438b3 100644 --- a/packages/react-bootstrap-table2/src/row-aggregator.js +++ b/packages/react-bootstrap-table2/src/row-aggregator.js @@ -6,8 +6,9 @@ import _ from './utils'; import Row from './row'; import ExpandCell from './row-expand/expand-cell'; import SelectionCell from './row-selection/selection-cell'; +import shouldRowUpdater from './row-should-updater'; -export default class RowAggregator extends React.Component { +export default class RowAggregator extends shouldRowUpdater(React.Component) { static propTypes = { attrs: PropTypes.object } @@ -21,6 +22,17 @@ export default class RowAggregator extends React.Component { this.createClickEventHandler = this.createClickEventHandler.bind(this); } + shouldComponentUpdate(nextProps) { + const shouldUpdate = + this.props.selected !== nextProps.selected || + this.props.expanded !== nextProps.expanded || + this.props.selectable !== nextProps.selectable || + this.shouldUpdateByWhenEditing(nextProps) || + this.shouldUpdatedByNormalProps(nextProps); + + return shouldUpdate; + } + createClickEventHandler(cb) { return (e) => { const { @@ -91,6 +103,7 @@ export default class RowAggregator extends React.Component { return ( { ...style, ...selectedStyle }; - className = cs(className, selectedClasses); + className = cs(className, selectedClasses) || undefined; if (selectRow.bgColor) { style = style || {}; diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js index cdd6bbd..8d46da1 100644 --- a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js +++ b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js @@ -25,9 +25,13 @@ export default class SelectionCell extends Component { } shouldComponentUpdate(nextProps) { - const { selected } = this.props; + const shouldUpdate = + this.props.rowIndex !== nextProps.rowIndex || + this.props.selected !== nextProps.selected || + this.props.disabled !== nextProps.disabled || + this.props.rowKey !== nextProps.rowKey; - return nextProps.selected !== selected; + return shouldUpdate; } handleClick(e) { diff --git a/packages/react-bootstrap-table2/src/row-should-updater.js b/packages/react-bootstrap-table2/src/row-should-updater.js new file mode 100644 index 0000000..17c9947 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-should-updater.js @@ -0,0 +1,25 @@ +/* eslint react/prop-types: 0 */ +import _ from './utils'; + +export default ExtendBase => + class RowShouldUpdater extends ExtendBase { + shouldUpdateByWhenEditing(nextProps) { + return ( + nextProps.editingRowIdx === nextProps.rowIndex || + (this.props.editingRowIdx === nextProps.rowIndex && + nextProps.editingRowIdx === null) + ); + } + + shouldUpdatedByNormalProps(nextProps) { + const shouldUpdate = + this.props.rowIndex !== nextProps.rowIndex || + this.props.className !== nextProps.className || + this.props.editable !== nextProps.editable || + this.props.columns.length !== nextProps.columns.length || + !_.isEqual(this.props.row, nextProps.row) || + !_.isEqual(this.props.style, nextProps.style); + + return shouldUpdate; + } + }; diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js index c41da6c..f04caed 100644 --- a/packages/react-bootstrap-table2/src/row.js +++ b/packages/react-bootstrap-table2/src/row.js @@ -6,11 +6,23 @@ import PropTypes from 'prop-types'; import _ from './utils'; import Cell from './cell'; import eventDelegater from './row-event-delegater'; +import shouldRowUpdater from './row-should-updater'; + +class Row extends shouldRowUpdater(eventDelegater(Component)) { + shouldComponentUpdate(nextProps) { + console.log('lol'); + const shouldUpdate = + nextProps.shouldUpdate || + this.shouldUpdateByWhenEditing(nextProps) || + this.shouldUpdatedByNormalProps(nextProps); + + return shouldUpdate; + } -class Row extends eventDelegater(Component) { render() { const { row, + keyField, columns, rowIndex, className, @@ -18,9 +30,11 @@ class Row extends eventDelegater(Component) { attrs, editable, editingRowIdx, - editingColIdx + editingColIdx, + onStart, + clickToEdit, + dbclickToEdit } = this.props; - const CellComponent = this.props.CellComponent || Cell; const trAttrs = this.delegate(attrs); return ( @@ -35,7 +49,7 @@ class Row extends eventDelegater(Component) { const EditingCell = this.props.EditingCellComponent; return ( ); @@ -108,14 +131,16 @@ Row.propTypes = { columns: PropTypes.array.isRequired, style: PropTypes.object, className: PropTypes.string, - attrs: PropTypes.object + attrs: PropTypes.object, + shouldUpdate: PropTypes.bool }; Row.defaultProps = { editable: true, style: {}, className: null, - attrs: {} + attrs: {}, + shouldUpdate: false }; export default Row; From 52fc84899b2fdfe76d7b7b84fa29c1c9792b95db Mon Sep 17 00:00:00 2001 From: AllenFang Date: Wed, 5 Sep 2018 23:20:36 +0800 Subject: [PATCH 16/38] fixed wrong conflict fixs for #514 --- .../react-bootstrap-table2-editor/src/editing-cell-binder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js b/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js index 0a9ef77..b3dc6bd 100644 --- a/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js +++ b/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js @@ -3,8 +3,8 @@ import React from 'react'; import { Consumer } from './context'; import createEditingCell from './editing-cell'; -export default (_) => { - const EditingCell = createEditingCell(_); +export default (_, onStartEdit) => { + const EditingCell = createEditingCell(_, onStartEdit); const renderWithEditingCell = (props, cellEdit) => { const content = _.get(props.row, props.column.dataField); let editCellstyle = props.column.editCellStyle || {}; From ee6cec5a2d1781907f6c258ce923e590e09bb69a Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 17 Sep 2018 23:12:16 +0800 Subject: [PATCH 17/38] refactoring row level components --- .../src/row-aggregator.js | 143 ----------------- .../src/row-selection/row-binder.js | 57 ------- .../selection-header-cell-binder.js | 8 - packages/react-bootstrap-table2/src/row.js | 146 ------------------ .../src/row/aggregate-row.js | 110 +++++++++++++ .../src/row/event-delegater.js | 78 ++++++++++ .../src/row/row-pure-content.js | 110 +++++++++++++ .../src/{ => row}/row-section.js | 0 .../should-updater.js} | 14 +- .../src/row/simple-row.js | 56 +++++++ 10 files changed, 364 insertions(+), 358 deletions(-) delete mode 100644 packages/react-bootstrap-table2/src/row-aggregator.js delete mode 100644 packages/react-bootstrap-table2/src/row-selection/row-binder.js delete mode 100644 packages/react-bootstrap-table2/src/row-selection/selection-header-cell-binder.js delete mode 100644 packages/react-bootstrap-table2/src/row.js create mode 100644 packages/react-bootstrap-table2/src/row/aggregate-row.js create mode 100644 packages/react-bootstrap-table2/src/row/event-delegater.js create mode 100644 packages/react-bootstrap-table2/src/row/row-pure-content.js rename packages/react-bootstrap-table2/src/{ => row}/row-section.js (100%) rename packages/react-bootstrap-table2/src/{row-should-updater.js => row/should-updater.js} (72%) create mode 100644 packages/react-bootstrap-table2/src/row/simple-row.js diff --git a/packages/react-bootstrap-table2/src/row-aggregator.js b/packages/react-bootstrap-table2/src/row-aggregator.js deleted file mode 100644 index f9438b3..0000000 --- a/packages/react-bootstrap-table2/src/row-aggregator.js +++ /dev/null @@ -1,143 +0,0 @@ -/* eslint react/prop-types: 0 */ -/* eslint react/no-array-index-key: 0 */ -import React from 'react'; -import PropTypes from 'prop-types'; -import _ from './utils'; -import Row from './row'; -import ExpandCell from './row-expand/expand-cell'; -import SelectionCell from './row-selection/selection-cell'; -import shouldRowUpdater from './row-should-updater'; - -export default class RowAggregator extends shouldRowUpdater(React.Component) { - static propTypes = { - attrs: PropTypes.object - } - static defaultProps = { - attrs: {} - } - - constructor(props) { - super(props); - this.clickNum = 0; - this.createClickEventHandler = this.createClickEventHandler.bind(this); - } - - shouldComponentUpdate(nextProps) { - const shouldUpdate = - this.props.selected !== nextProps.selected || - this.props.expanded !== nextProps.expanded || - this.props.selectable !== nextProps.selectable || - this.shouldUpdateByWhenEditing(nextProps) || - this.shouldUpdatedByNormalProps(nextProps); - - return shouldUpdate; - } - - createClickEventHandler(cb) { - return (e) => { - const { - row, - selected, - keyField, - selectable, - expandable, - rowIndex, - expanded, - expandRow, - selectRow, - DELAY_FOR_DBCLICK - } = this.props; - - const clickFn = () => { - if (cb) { - cb(e, row, rowIndex); - } - const key = _.get(row, keyField); - if (expandRow && expandable) { - expandRow.onRowExpand(key, !expanded, rowIndex, e); - } - if (selectRow.clickToSelect && selectable) { - selectRow.onRowSelect(key, !selected, rowIndex, e); - } - }; - - if (DELAY_FOR_DBCLICK) { - this.clickNum += 1; - _.debounce(() => { - if (this.clickNum === 1) { - clickFn(); - } - this.clickNum = 0; - }, DELAY_FOR_DBCLICK)(); - } else { - clickFn(); - } - }; - } - - render() { - const { - row, - columns, - keyField, - rowIndex, - style, - className, - attrs, - selectRow, - expandRow, - expanded, - selected, - selectable, - ...rest - } = this.props; - - const key = _.get(row, keyField); - const { hideSelectColumn, clickToSelect } = selectRow; - const { showExpandColumn } = expandRow; - - const newAttrs = { ...attrs }; - if (clickToSelect || !!expandRow.renderer) { - newAttrs.onClick = this.createClickEventHandler(attrs.onClick); - } - - return ( - - { - showExpandColumn ? ( - - ) : null - } - { - !hideSelectColumn - ? ( - - ) - : null - } - - ); - } -} diff --git a/packages/react-bootstrap-table2/src/row-selection/row-binder.js b/packages/react-bootstrap-table2/src/row-selection/row-binder.js deleted file mode 100644 index 27982a0..0000000 --- a/packages/react-bootstrap-table2/src/row-selection/row-binder.js +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from 'react'; -import cs from 'classnames'; -import _ from '../utils'; -import SelectionContext from '../contexts/selection-context'; - -export default (Component) => { - const renderWithSelection = (props, selectRow) => { - const key = props.value; - const selected = selectRow.selected.includes(key); - const selectable = !selectRow.nonSelectable || !selectRow.nonSelectable.includes(key); - - let { - style, - className - } = props; - - if (selected) { - const selectedStyle = _.isFunction(selectRow.style) - ? selectRow.style(props.row, props.rowIndex) - : selectRow.style; - - const selectedClasses = _.isFunction(selectRow.classes) - ? selectRow.classes(props.row, props.rowIndex) - : selectRow.classes; - - style = { - ...style, - ...selectedStyle - }; - className = cs(className, selectedClasses) || undefined; - - if (selectRow.bgColor) { - style = style || {}; - style.backgroundColor = _.isFunction(selectRow.bgColor) - ? selectRow.bgColor(props.row, props.rowIndex) - : selectRow.bgColor; - } - } - - return ( - - ); - }; - return props => ( - - { selectRow => renderWithSelection(props, selectRow) } - - ); -}; diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell-binder.js b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell-binder.js deleted file mode 100644 index de7d3c3..0000000 --- a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell-binder.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import SelectionContext from '../contexts/selection-context'; - -export default Component => () => ( - - { selectRow => } - -); diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js deleted file mode 100644 index f04caed..0000000 --- a/packages/react-bootstrap-table2/src/row.js +++ /dev/null @@ -1,146 +0,0 @@ -/* eslint react/prop-types: 0 */ -/* eslint react/no-array-index-key: 0 */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -import _ from './utils'; -import Cell from './cell'; -import eventDelegater from './row-event-delegater'; -import shouldRowUpdater from './row-should-updater'; - -class Row extends shouldRowUpdater(eventDelegater(Component)) { - shouldComponentUpdate(nextProps) { - console.log('lol'); - const shouldUpdate = - nextProps.shouldUpdate || - this.shouldUpdateByWhenEditing(nextProps) || - this.shouldUpdatedByNormalProps(nextProps); - - return shouldUpdate; - } - - render() { - const { - row, - keyField, - columns, - rowIndex, - className, - style, - attrs, - editable, - editingRowIdx, - editingColIdx, - onStart, - clickToEdit, - dbclickToEdit - } = this.props; - const trAttrs = this.delegate(attrs); - - return ( - - { this.props.children } - { - columns.map((column, index) => { - if (!column.hidden) { - const { dataField } = column; - const content = _.get(row, dataField); - if (rowIndex === editingRowIdx && index === editingColIdx) { - const EditingCell = this.props.EditingCellComponent; - return ( - - ); - } - // render cell - let cellTitle; - let cellStyle = {}; - const cellAttrs = { - ..._.isFunction(column.attrs) - ? column.attrs(content, row, rowIndex, index) - : column.attrs, - ...column.events - }; - - const cellClasses = _.isFunction(column.classes) - ? column.classes(content, row, rowIndex, index) - : column.classes; - - if (column.style) { - cellStyle = _.isFunction(column.style) - ? column.style(content, row, rowIndex, index) - : column.style; - cellStyle = Object.assign({}, cellStyle) || {}; - } - - - if (column.title) { - cellTitle = _.isFunction(column.title) - ? column.title(content, row, rowIndex, index) - : content; - cellAttrs.title = cellTitle; - } - - if (column.align) { - cellStyle.textAlign = - _.isFunction(column.align) - ? column.align(content, row, rowIndex, index) - : column.align; - } - - if (cellClasses) cellAttrs.className = cellClasses; - if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle; - - let editableCell = _.isDefined(column.editable) ? column.editable : true; - if (column.dataField === keyField || !editable) editableCell = false; - if (_.isFunction(column.editable)) { - editableCell = column.editable(content, row, rowIndex, index); - } - - return ( - - ); - } - return false; - }) - } - - ); - } -} - -Row.propTypes = { - row: PropTypes.object.isRequired, - rowIndex: PropTypes.number.isRequired, - columns: PropTypes.array.isRequired, - style: PropTypes.object, - className: PropTypes.string, - attrs: PropTypes.object, - shouldUpdate: PropTypes.bool -}; - -Row.defaultProps = { - editable: true, - style: {}, - className: null, - attrs: {}, - shouldUpdate: false -}; - -export default Row; diff --git a/packages/react-bootstrap-table2/src/row/aggregate-row.js b/packages/react-bootstrap-table2/src/row/aggregate-row.js new file mode 100644 index 0000000..d176743 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row/aggregate-row.js @@ -0,0 +1,110 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import _ from '../utils'; +import ExpandCell from '../row-expand/expand-cell'; +import SelectionCell from '../row-selection/selection-cell'; +import shouldUpdater from './should-updater'; +import eventDelegater from './event-delegater'; +import RowPureContent from './row-pure-content'; + +export default class RowAggregator extends shouldUpdater(eventDelegater(React.Component)) { + static propTypes = { + attrs: PropTypes.object, + style: PropTypes.object + } + + static defaultProps = { + attrs: {}, + style: {} + } + + constructor(props) { + super(props); + this.clickNum = 0; + this.shouldUpdateRowContent = false; + this.createClickEventHandler = this.createClickEventHandler.bind(this); + } + + shouldComponentUpdate(nextProps) { + if ( + this.props.selected !== nextProps.selected || + this.props.expanded !== nextProps.expanded || + this.props.selectable !== nextProps.selectable || + this.shouldUpdateByWhenEditing(nextProps) || + this.shouldUpdatedBySelfProps(nextProps) + ) { + this.shouldUpdateRowContent = this.shouldUpdatedByNormalProps(nextProps); + return true; + } + this.shouldUpdateRowContent = this.shouldUpdatedByNormalProps(nextProps); + + return this.shouldUpdateRowContent; + } + + render() { + const { + row, + columns, + keyField, + rowIndex, + style, + className, + attrs, + selectRow, + expandRow, + expanded, + selected, + selectable, + ...rest + } = this.props; + const key = _.get(row, keyField); + const { hideSelectColumn, clickToSelect } = selectRow; + const { showExpandColumn } = expandRow; + + const newAttrs = this.delegate({ ...attrs }); + if (clickToSelect || !!expandRow.renderer) { + newAttrs.onClick = this.createClickEventHandler(newAttrs.onClick); + } + + return ( + + { + showExpandColumn ? ( + + ) : null + } + { + !hideSelectColumn + ? ( + + ) + : null + } + + + ); + } +} diff --git a/packages/react-bootstrap-table2/src/row/event-delegater.js b/packages/react-bootstrap-table2/src/row/event-delegater.js new file mode 100644 index 0000000..2c6868f --- /dev/null +++ b/packages/react-bootstrap-table2/src/row/event-delegater.js @@ -0,0 +1,78 @@ +import _ from '../utils'; + +const events = [ + 'onClick', + 'onDoubleClick', + 'onMouseEnter', + 'onMouseLeave', + 'onContextMenu' +]; + +export default ExtendBase => + class RowEventDelegater extends ExtendBase { + constructor(props) { + super(props); + this.clickNum = 0; + this.createDefaultEventHandler = this.createDefaultEventHandler.bind(this); + this.createClickEventHandler = this.createClickEventHandler.bind(this); + } + + createClickEventHandler(cb) { + return (e) => { + const { + row, + selected, + keyField, + selectable, + expandable, + rowIndex, + expanded, + expandRow, + selectRow, + DELAY_FOR_DBCLICK + } = this.props; + + const clickFn = () => { + if (cb) { + cb(e, row, rowIndex); + } + const key = _.get(row, keyField); + if (expandRow && expandable) { + expandRow.onRowExpand(key, !expanded, rowIndex, e); + } + if (selectRow.clickToSelect && selectable) { + selectRow.onRowSelect(key, !selected, rowIndex, e); + } + }; + + if (DELAY_FOR_DBCLICK) { + this.clickNum += 1; + _.debounce(() => { + if (this.clickNum === 1) { + clickFn(); + } + this.clickNum = 0; + }, DELAY_FOR_DBCLICK)(); + } else { + clickFn(); + } + }; + } + + createDefaultEventHandler(cb) { + return (e) => { + const { row, rowIndex } = this.props; + cb(e, row, rowIndex); + }; + } + + delegate(attrs = {}) { + const newAttrs = { ...attrs }; + Object.keys(attrs).forEach((attr) => { + if (events.includes(attr)) { + newAttrs[attr] = this.createDefaultEventHandler(attrs[attr]); + } + }); + return newAttrs; + } + }; diff --git a/packages/react-bootstrap-table2/src/row/row-pure-content.js b/packages/react-bootstrap-table2/src/row/row-pure-content.js new file mode 100644 index 0000000..52b77f0 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row/row-pure-content.js @@ -0,0 +1,110 @@ +/* eslint react/prop-types: 0 */ +/* eslint react/no-array-index-key: 0 */ +import React from 'react'; + +import _ from '../utils'; +import Cell from '../cell'; + +export default class RowPureContent extends React.Component { + shouldComponentUpdate(nextProps) { + if (typeof this.props.shouldUpdate !== 'undefined') { + if (nextProps.shouldUpdate === this.props.shouldUpdate) { + return false; + } + } + return true; + } + + render() { + const { + row, + keyField, + columns, + rowIndex, + editable, + editingRowIdx, + editingColIdx, + onStart, + clickToEdit, + dbclickToEdit, + EditingCellComponent + } = this.props; + + return columns.map((column, index) => { + if (!column.hidden) { + const { dataField } = column; + const content = _.get(row, dataField); + if (rowIndex === editingRowIdx && index === editingColIdx) { + return ( + + ); + } + // render cell + let cellTitle; + let cellStyle = {}; + const cellAttrs = { + ..._.isFunction(column.attrs) + ? column.attrs(content, row, rowIndex, index) + : column.attrs, + ...column.events + }; + + const cellClasses = _.isFunction(column.classes) + ? column.classes(content, row, rowIndex, index) + : column.classes; + + if (column.style) { + cellStyle = _.isFunction(column.style) + ? column.style(content, row, rowIndex, index) + : column.style; + cellStyle = Object.assign({}, cellStyle) || {}; + } + + if (column.title) { + cellTitle = _.isFunction(column.title) + ? column.title(content, row, rowIndex, index) + : content; + cellAttrs.title = cellTitle; + } + + if (column.align) { + cellStyle.textAlign = + _.isFunction(column.align) + ? column.align(content, row, rowIndex, index) + : column.align; + } + + if (cellClasses) cellAttrs.className = cellClasses; + if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle; + + let editableCell = _.isDefined(column.editable) ? column.editable : true; + if (column.dataField === keyField || !editable) editableCell = false; + if (_.isFunction(column.editable)) { + editableCell = column.editable(content, row, rowIndex, index); + } + + return ( + + ); + } + return false; + }); + } +} diff --git a/packages/react-bootstrap-table2/src/row-section.js b/packages/react-bootstrap-table2/src/row/row-section.js similarity index 100% rename from packages/react-bootstrap-table2/src/row-section.js rename to packages/react-bootstrap-table2/src/row/row-section.js diff --git a/packages/react-bootstrap-table2/src/row-should-updater.js b/packages/react-bootstrap-table2/src/row/should-updater.js similarity index 72% rename from packages/react-bootstrap-table2/src/row-should-updater.js rename to packages/react-bootstrap-table2/src/row/should-updater.js index 17c9947..294150c 100644 --- a/packages/react-bootstrap-table2/src/row-should-updater.js +++ b/packages/react-bootstrap-table2/src/row/should-updater.js @@ -1,5 +1,5 @@ /* eslint react/prop-types: 0 */ -import _ from './utils'; +import _ from '../utils'; export default ExtendBase => class RowShouldUpdater extends ExtendBase { @@ -11,14 +11,20 @@ export default ExtendBase => ); } + shouldUpdatedBySelfProps(nextProps) { + return ( + this.props.className !== nextProps.className || + !_.isEqual(this.props.style, nextProps.style) || + !_.isEqual(this.props.attrs, nextProps.attrs) + ); + } + shouldUpdatedByNormalProps(nextProps) { const shouldUpdate = this.props.rowIndex !== nextProps.rowIndex || - this.props.className !== nextProps.className || this.props.editable !== nextProps.editable || this.props.columns.length !== nextProps.columns.length || - !_.isEqual(this.props.row, nextProps.row) || - !_.isEqual(this.props.style, nextProps.style); + !_.isEqual(this.props.row, nextProps.row); return shouldUpdate; } diff --git a/packages/react-bootstrap-table2/src/row/simple-row.js b/packages/react-bootstrap-table2/src/row/simple-row.js new file mode 100644 index 0000000..b33e2c6 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row/simple-row.js @@ -0,0 +1,56 @@ +/* eslint react/prop-types: 0 */ +/* eslint react/no-array-index-key: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import RowPureContent from './row-pure-content'; +import eventDelegater from './event-delegater'; +import shouldUpdater from './should-updater'; + +class Row extends shouldUpdater(eventDelegater(Component)) { + constructor(props) { + super(props); + this.shouldUpdateRowContent = false; + } + + shouldComponentUpdate(nextProps) { + this.shouldUpdateRowContent = this.shouldUpdatedByNormalProps(nextProps); + if (this.shouldUpdateRowContent) return true; + + return this.shouldUpdatedBySelfProps(nextProps); + } + + render() { + const { + className, + style, + attrs, + ...rest + } = this.props; + const trAttrs = this.delegate(attrs); + + return ( + + + + ); + } +} + +Row.propTypes = { + row: PropTypes.object.isRequired, + rowIndex: PropTypes.number.isRequired, + columns: PropTypes.array.isRequired, + style: PropTypes.object, + className: PropTypes.string, + attrs: PropTypes.object +}; + +Row.defaultProps = { + editable: true, + style: {}, + className: null, + attrs: {} +}; + +export default Row; From 66329ecdbf3b51410a2b0bfe15ef6297662dc3db Mon Sep 17 00:00:00 2001 From: AllenFang Date: Tue, 18 Sep 2018 23:44:10 +0800 Subject: [PATCH 18/38] implement selection consumers --- packages/react-bootstrap-table2/src/body.js | 10 +-- packages/react-bootstrap-table2/src/header.js | 4 +- .../src/row-selection/row-consumer.js | 63 +++++++++++++++++++ .../selection-header-cell-consumer.js | 8 +++ 4 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 packages/react-bootstrap-table2/src/row-selection/row-consumer.js create mode 100644 packages/react-bootstrap-table2/src/row-selection/selection-header-cell-consumer.js diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index 8c83eaa..3179737 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -5,11 +5,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from './utils'; -import Row from './row'; -import RowAggregator from './row-aggregator'; -import RowSection from './row-section'; +import Row from './row/simple-row'; +import RowAggregator from './row/aggregate-row'; +import RowSection from './row/row-section'; import Const from './const'; -import bindSelection from './row-selection/row-binder'; +import withRowSelection from './row-selection/row-consumer'; import bindExpansion from './row-expand/row-binder'; class Body extends React.Component { @@ -55,7 +55,7 @@ class Body extends React.Component { } if (selectRowEnabled) { - RowComponent = bindSelection(expandRowEnabled ? RowComponent : RowAggregator); + RowComponent = withRowSelection(expandRowEnabled ? RowComponent : RowAggregator); } if (cellEdit.createContext) { diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index 06a38a3..c39ac89 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import HeaderCell from './header-cell'; import SelectionHeaderCell from './row-selection/selection-header-cell'; import ExpandHeaderCell from './row-expand/expand-header-cell'; -import bindSelection from './row-selection/selection-header-cell-binder'; +import withHeaderSelection from './row-selection/selection-header-cell-consumer'; import bindExpansion from './row-expand/expand-header-cell-binder'; const Header = (props) => { @@ -30,7 +30,7 @@ const Header = (props) => { } if (selectRow) { - SelectionHeaderCellComp = bindSelection(SelectionHeaderCell); + SelectionHeaderCellComp = withHeaderSelection(SelectionHeaderCell); } return ( diff --git a/packages/react-bootstrap-table2/src/row-selection/row-consumer.js b/packages/react-bootstrap-table2/src/row-selection/row-consumer.js new file mode 100644 index 0000000..5a51534 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-selection/row-consumer.js @@ -0,0 +1,63 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import cs from 'classnames'; +import _ from '../utils'; +import SelectionContext from '../contexts/selection-context'; + +export default (Component) => { + const renderWithSelection = (props, selectRow) => { + const key = props.value; + const selected = selectRow.selected.includes(key); + const selectable = !selectRow.nonSelectable || !selectRow.nonSelectable.includes(key); + + let { + style, + className + } = props; + + if (selected) { + const selectedStyle = _.isFunction(selectRow.style) + ? selectRow.style(props.row, props.rowIndex) + : selectRow.style; + + const selectedClasses = _.isFunction(selectRow.classes) + ? selectRow.classes(props.row, props.rowIndex) + : selectRow.classes; + + style = { + ...style, + ...selectedStyle + }; + className = cs(className, selectedClasses) || undefined; + + if (selectRow.bgColor) { + style = style || {}; + style.backgroundColor = _.isFunction(selectRow.bgColor) + ? selectRow.bgColor(props.row, props.rowIndex) + : selectRow.bgColor; + } + } + + return ( + + ); + }; + + function withConsumer(props) { + return ( + + { selectRow => renderWithSelection(props, selectRow) } + + ); + } + + withConsumer.displayName = 'WithSelectionRowConsumer'; + return withConsumer; +}; diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell-consumer.js b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell-consumer.js new file mode 100644 index 0000000..de7d3c3 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell-consumer.js @@ -0,0 +1,8 @@ +import React from 'react'; +import SelectionContext from '../contexts/selection-context'; + +export default Component => () => ( + + { selectRow => } + +); From 709d59ce625be4ee0f725a8ac069a9816e35afe0 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Fri, 21 Sep 2018 14:31:06 +0800 Subject: [PATCH 19/38] patch tests for row refactoring --- .../src/row/should-updater.js | 4 +- .../aggregate-row.test.js} | 23 +- .../row-pure-content.test.js} | 273 +++++++----------- .../test/{ => row}/row-section.test.js | 2 +- .../test/row/should-updater.test.js | 148 ++++++++++ .../test/row/simple-row.test.js | 173 +++++++++++ 6 files changed, 444 insertions(+), 179 deletions(-) rename packages/react-bootstrap-table2/test/{row-aggregator.test.js => row/aggregate-row.test.js} (93%) rename packages/react-bootstrap-table2/test/{row.test.js => row/row-pure-content.test.js} (69%) rename packages/react-bootstrap-table2/test/{ => row}/row-section.test.js (92%) create mode 100644 packages/react-bootstrap-table2/test/row/should-updater.test.js create mode 100644 packages/react-bootstrap-table2/test/row/simple-row.test.js diff --git a/packages/react-bootstrap-table2/src/row/should-updater.js b/packages/react-bootstrap-table2/src/row/should-updater.js index 294150c..355c7d7 100644 --- a/packages/react-bootstrap-table2/src/row/should-updater.js +++ b/packages/react-bootstrap-table2/src/row/should-updater.js @@ -23,8 +23,8 @@ export default ExtendBase => const shouldUpdate = this.props.rowIndex !== nextProps.rowIndex || this.props.editable !== nextProps.editable || - this.props.columns.length !== nextProps.columns.length || - !_.isEqual(this.props.row, nextProps.row); + !_.isEqual(this.props.row, nextProps.row || + this.props.columns.length !== nextProps.columns.length); return shouldUpdate; } diff --git a/packages/react-bootstrap-table2/test/row-aggregator.test.js b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js similarity index 93% rename from packages/react-bootstrap-table2/test/row-aggregator.test.js rename to packages/react-bootstrap-table2/test/row/aggregate-row.test.js index e09886a..ea2c617 100644 --- a/packages/react-bootstrap-table2/test/row-aggregator.test.js +++ b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js @@ -1,15 +1,14 @@ import 'jsdom-global/register'; import React from 'react'; import { mount } from 'enzyme'; -import mockBodyResolvedProps from './test-helpers/mock/body-resolved-props'; -import SelectionContext from '../src/contexts/selection-context'; -import ExpansionContext from '../src/contexts/row-expand-context'; -import bindSelection from '../src/row-selection/row-binder'; -import bindExpansion from '../src/row-expand/row-binder'; -import ExpandCell from '../src/row-expand/expand-cell'; -import SelectionCell from '../src/row-selection/selection-cell'; -import RowAggregator from '../src/row-aggregator'; -import Row from '../src/row'; +import mockBodyResolvedProps from '../test-helpers/mock/body-resolved-props'; +import SelectionContext from '../../src/contexts/selection-context'; +import ExpansionContext from '../../src/contexts/row-expand-context'; +import bindSelection from '../../src/row-selection/row-consumer'; +import bindExpansion from '../../src/row-expand/row-binder'; +import ExpandCell from '../../src/row-expand/expand-cell'; +import SelectionCell from '../../src/row-selection/selection-cell'; +import RowAggregator from '../../src/row/aggregate-row'; describe('Row Aggregator', () => { let wrapper; @@ -108,9 +107,9 @@ describe('Row Aggregator', () => { }); it('should add onClick prop to Row Component', () => { - const rowComp = wrapper.find(Row); - expect(rowComp).toHaveLength(1); - expect(rowComp.props().attrs.onClick).toBeDefined(); + const tr = wrapper.find('tr'); + expect(tr).toHaveLength(1); + expect(tr.props().onClick).toBeDefined(); }); }); }); diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row/row-pure-content.test.js similarity index 69% rename from packages/react-bootstrap-table2/test/row.test.js rename to packages/react-bootstrap-table2/test/row/row-pure-content.test.js index bb0fd5b..70982c5 100644 --- a/packages/react-bootstrap-table2/test/row.test.js +++ b/packages/react-bootstrap-table2/test/row/row-pure-content.test.js @@ -1,10 +1,9 @@ import React from 'react'; -import sinon from 'sinon'; import { shallow } from 'enzyme'; -import Cell from '../src/cell'; -import Row from '../src/row'; -import mockBodyResolvedProps from './test-helpers/mock/body-resolved-props'; +import Cell from '../../src/cell'; +import RowPureContent from '../../src/row/row-pure-content'; +import mockBodyResolvedProps from '../test-helpers/mock/body-resolved-props'; let defaultColumns = [{ dataField: 'id', @@ -20,7 +19,7 @@ let defaultColumns = [{ const keyField = 'id'; const rowIndex = 1; -describe('Row', () => { +describe('RowPureContent', () => { let wrapper; const row = { @@ -42,11 +41,55 @@ describe('Row', () => { }]; }); + describe('shouldComponentUpdate', () => { + let props; + let nextProps; + + describe('if nextProps.shouldUpdate is different with this.props.shouldUpdate', () => { + beforeEach(() => { + props = { + keyField, + columns: defaultColumns, + rowIndex: 1, + row, + shouldUpdate: false + }; + wrapper = shallow( + + ); + }); + + it('should return true', () => { + nextProps = { ...props, shouldUpdate: true }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + }); + + describe('if nextProps.shouldUpdate is same with this.props.shouldUpdate', () => { + beforeEach(() => { + props = { + keyField, + columns: defaultColumns, + rowIndex: 1, + row, + shouldUpdate: false + }; + wrapper = shallow( + + ); + }); + + it('should return false', () => { + nextProps = { ...props }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(false); + }); + }); + }); + describe('simplest row', () => { beforeEach(() => { wrapper = shallow( - { }); it('should render successfully', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find('tr').length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).length).toBe(Object.keys(row).length); }); }); - describe('when style prop is defined', () => { - const customStyle = { backgroundColor: 'red' }; - beforeEach(() => { - wrapper = shallow( - ); - }); - - it('should render component with style successfully', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.prop('style')).toEqual(customStyle); - }); - }); - - describe('when className prop is defined', () => { - const className = 'test-class'; - beforeEach(() => { - wrapper = shallow( - ); - }); - - it('should render component with className successfully', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.hasClass(className)).toBe(true); - }); - }); - - describe('when CellComponent prop is defined', () => { - const CellComponent = () => null; - beforeEach(() => { - wrapper = shallow( - ); - }); - - it('should render CellComponent successfully', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(CellComponent)).toHaveLength(defaultColumns.length); - }); - }); - describe('when editingRowIdx and editingColIdx prop is defined', () => { const editingRowIdx = rowIndex; const editingColIdx = 1; const EditingCellComponent = () => null; beforeEach(() => { wrapper = shallow( - { it('should render EditingCell component correctly', () => { const EditingCell = wrapper.find(EditingCellComponent); - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(EditingCell).toHaveLength(1); expect(EditingCell.prop('row')).toEqual(row); expect(EditingCell.prop('rowIndex')).toEqual(editingRowIdx); @@ -147,27 +132,6 @@ describe('Row', () => { }); }); - describe('when attrs prop is defined', () => { - const customClickCallBack = sinon.stub(); - const attrs = { 'data-index': 1, onClick: customClickCallBack }; - beforeEach(() => { - wrapper = shallow( - ); - }); - - it('should render component with correct attributes', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.prop('data-index')).toBe(attrs['data-index']); - expect(wrapper.prop('onClick')).toBeDefined(); - }); - }); - describe('when column.hidden is true', () => { beforeEach(() => { const newColumns = [{ @@ -182,8 +146,8 @@ describe('Row', () => { text: 'Price' }]; wrapper = shallow( - { beforeEach(() => { columns[columnIndex].style = { backgroundColor: 'red' }; wrapper = shallow( - { }); it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props.style).toEqual(columns[columnIndex].style); }); }); @@ -228,11 +191,10 @@ describe('Row', () => { let styleCallBack; beforeEach(() => { - styleCallBack = sinon.stub().returns(returnStyle); + styleCallBack = jest.fn().mockReturnValue(returnStyle); columns[columnIndex].style = styleCallBack; wrapper = shallow( - { ); }); - afterEach(() => { styleCallBack.reset(); }); + afterEach(() => { styleCallBack.mockClear(); }); it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props.style).toEqual(returnStyle); }); it('should call custom style function correctly', () => { - expect(styleCallBack.callCount).toBe(1); - expect( - styleCallBack.calledWith(row[columns[columnIndex].dataField], row, rowIndex, columnIndex) - ).toBe(true); + expect(styleCallBack).toHaveBeenCalledTimes(1); + expect(styleCallBack).toHaveBeenCalledWith( + row[columns[columnIndex].dataField], row, rowIndex, columnIndex); }); }); }); @@ -269,8 +230,7 @@ describe('Row', () => { beforeEach(() => { columns[columnIndex].classes = 'td-test-class'; wrapper = shallow( - { }); it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props.className) .toEqual(columns[columnIndex].classes); }); @@ -291,11 +251,10 @@ describe('Row', () => { let classesCallBack; beforeEach(() => { - classesCallBack = sinon.stub().returns(returnClasses); + classesCallBack = jest.fn().mockReturnValue(returnClasses); columns[columnIndex].classes = classesCallBack; wrapper = shallow( - { ); }); - afterEach(() => { classesCallBack.reset(); }); + afterEach(() => { classesCallBack.mockClear(); }); it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props.className).toEqual(returnClasses); }); it('should call custom classes function correctly', () => { - expect(classesCallBack.callCount).toBe(1); - expect( - classesCallBack.calledWith( - row[columns[columnIndex].dataField], row, rowIndex, columnIndex) - ).toBe(true); + expect(classesCallBack).toHaveBeenCalledTimes(1); + expect(classesCallBack).toHaveBeenCalledWith( + row[columns[columnIndex].dataField], row, rowIndex, columnIndex); }); }); }); @@ -333,8 +290,7 @@ describe('Row', () => { beforeEach(() => { columns[columnIndex].title = true; wrapper = shallow( - { }); it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props.title) .toEqual(row[columns[columnIndex].dataField]); }); @@ -355,11 +311,10 @@ describe('Row', () => { let titleCallBack; beforeEach(() => { - titleCallBack = sinon.stub().returns(returnTitle); + titleCallBack = jest.fn().mockReturnValue(returnTitle); columns[columnIndex].title = titleCallBack; wrapper = shallow( - { ); }); - afterEach(() => { titleCallBack.reset(); }); + afterEach(() => { titleCallBack.mockClear(); }); it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props.title).toEqual(returnTitle); }); it('should call custom title function correctly', () => { - expect(titleCallBack.callCount).toBe(1); - expect( - titleCallBack.calledWith( - row[columns[columnIndex].dataField], row, rowIndex, columnIndex) - ).toBe(true); + expect(titleCallBack).toHaveBeenCalledTimes(1); + expect(titleCallBack).toHaveBeenCalledWith( + row[columns[columnIndex].dataField], row, rowIndex, columnIndex); }); }); }); @@ -392,12 +345,11 @@ describe('Row', () => { beforeEach(() => { columns = [...defaultColumns]; columns[columnIndex].events = { - onClick: sinon.stub() + onClick: jest.fn() }; wrapper = shallow( - { }); it('should attachs DOM event successfully', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props.onClick).toBeDefined(); }); }); @@ -424,8 +376,7 @@ describe('Row', () => { beforeEach(() => { columns[columnIndex].align = 'right'; wrapper = shallow( - { }); it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props.style.textAlign) .toEqual(columns[columnIndex].align); }); @@ -446,10 +397,10 @@ describe('Row', () => { let alignCallBack; beforeEach(() => { - alignCallBack = sinon.stub().returns(returnAlign); + alignCallBack = jest.fn().mockReturnValue(returnAlign); columns[columnIndex].align = alignCallBack; wrapper = shallow( - { ); }); - afterEach(() => { alignCallBack.reset(); }); + afterEach(() => { alignCallBack.mockClear(); }); it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props.style.textAlign).toEqual(returnAlign); }); it('should call custom align function correctly', () => { - expect(alignCallBack.callCount).toBe(1); - expect( - alignCallBack.calledWith(row[columns[columnIndex].dataField], row, rowIndex, columnIndex) - ).toBe(true); + expect(alignCallBack).toHaveBeenCalledTimes(1); + expect(alignCallBack).toHaveBeenCalledWith( + row[columns[columnIndex].dataField], row, rowIndex, columnIndex); }); }); }); @@ -497,8 +447,7 @@ describe('Row', () => { }; wrapper = shallow( - { /> ); - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props['data-test']) .toEqual(columns[columnIndex].attrs['data-test']); expect(wrapper.find(Cell).get(columnIndex).props.title) @@ -523,8 +472,7 @@ describe('Row', () => { columns[columnIndex].attrs = { title: 'title' }; wrapper = shallow( - { columns[columnIndex].attrs = { className: 'attrs-class' }; wrapper = shallow( - { columns[columnIndex].attrs = { style: { backgroundColor: 'attrs-style-test' } }; wrapper = shallow( - { columns[columnIndex].attrs = { style: { textAlign: 'right' } }; wrapper = shallow( - { }; beforeEach(() => { - attrsCallBack = sinon.stub().returns(customAttrs); + attrsCallBack = jest.fn().mockReturnValue(customAttrs); columns[columnIndex].attrs = attrsCallBack; wrapper = shallow( - { ); }); + afterEach(() => { attrsCallBack.mockClear(); }); + it('should render style.attrs correctly', () => { - expect(wrapper.length).toBe(1); + expect(wrapper.length).toBe(defaultColumns.length); expect(wrapper.find(Cell).get(columnIndex).props['data-test']) .toEqual(customAttrs['data-test']); expect(wrapper.find(Cell).get(columnIndex).props.title) @@ -628,10 +574,9 @@ describe('Row', () => { }); it('should call custom attrs function correctly', () => { - expect(attrsCallBack.callCount).toBe(1); - expect( - attrsCallBack.calledWith(row[columns[columnIndex].dataField], row, rowIndex, columnIndex) - ).toBe(true); + expect(attrsCallBack).toHaveBeenCalledTimes(1); + expect(attrsCallBack).toHaveBeenCalledWith( + row[columns[columnIndex].dataField], row, rowIndex, columnIndex); }); }); }); diff --git a/packages/react-bootstrap-table2/test/row-section.test.js b/packages/react-bootstrap-table2/test/row/row-section.test.js similarity index 92% rename from packages/react-bootstrap-table2/test/row-section.test.js rename to packages/react-bootstrap-table2/test/row/row-section.test.js index 640d11b..15d76b6 100644 --- a/packages/react-bootstrap-table2/test/row-section.test.js +++ b/packages/react-bootstrap-table2/test/row/row-section.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import RowSection from '../src/row-section'; +import RowSection from '../../src/row/row-section'; describe('Row', () => { const colSpan = 3; diff --git a/packages/react-bootstrap-table2/test/row/should-updater.test.js b/packages/react-bootstrap-table2/test/row/should-updater.test.js new file mode 100644 index 0000000..04a114c --- /dev/null +++ b/packages/react-bootstrap-table2/test/row/should-updater.test.js @@ -0,0 +1,148 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import shouldUpdater from '../../src/row/should-updater'; + +describe('Row shouldUpdater', () => { + let wrapper; + let props; + let nextProps; + + class DummyComponent extends shouldUpdater(React.Component) { + render() { return null; } + } + + describe('shouldUpdateByWhenEditing', () => { + describe('when nextProps.editingRowIdx eq props.rowIndex and it\' not null', () => { + beforeEach(() => { + props = { + editingRowIdx: null, + rowIndex: 0 + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, editingRowIdx: 0 }; + expect(wrapper.instance().shouldUpdateByWhenEditing(nextProps)).toBeTruthy(); + }); + }); + + describe('when props.editingRowIdx eq props.rowIndex but nextProps.editingRowIdx is null', () => { + beforeEach(() => { + props = { + editingRowIdx: 0, + rowIndex: 0 + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, editingRowIdx: null }; + expect(wrapper.instance().shouldUpdateByWhenEditing(nextProps)).toBeTruthy(); + }); + }); + }); + + describe('shouldUpdatedBySelfProps', () => { + describe('when nextProps.className is not eq props.className', () => { + beforeEach(() => { + props = { + className: '' + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, className: 'test' }; + expect(wrapper.instance().shouldUpdatedBySelfProps(nextProps)).toBeTruthy(); + }); + }); + + describe('when nextProps.style is not eq props.style', () => { + beforeEach(() => { + props = { + style: null + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, style: { color: 'red' } }; + expect(wrapper.instance().shouldUpdatedBySelfProps(nextProps)).toBeTruthy(); + }); + }); + + describe('when nextProps.attrs is not eq props.attrs', () => { + beforeEach(() => { + props = { + attrs: null + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, attrs: { onClick: jest.fn() } }; + expect(wrapper.instance().shouldUpdatedBySelfProps(nextProps)).toBeTruthy(); + }); + }); + }); + + describe('shouldUpdatedByNormalProps', () => { + describe('when nextProps.rowIndex is not eq props.rowIndex', () => { + beforeEach(() => { + props = { + rowIndex: 0 + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, rowIndex: 1 }; + expect(wrapper.instance().shouldUpdatedByNormalProps(nextProps)).toBeTruthy(); + }); + }); + + describe('when nextProps.editable is not eq props.editable', () => { + beforeEach(() => { + props = { + editable: false + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, editable: true }; + expect(wrapper.instance().shouldUpdatedByNormalProps(nextProps)).toBeTruthy(); + }); + }); + + describe('when nextProps.columns.length is not eq props.columns.length', () => { + beforeEach(() => { + props = { + columns: [{ dataField: 'price', text: 'Price' }] + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, columns: [...props.columns, { dataField: 'name', text: 'Name' }] }; + expect(wrapper.instance().shouldUpdatedByNormalProps(nextProps)).toBeTruthy(); + }); + }); + + describe('when nextProps.row is not eq props.row', () => { + beforeEach(() => { + props = { + row: { id: 1, name: 'test' } + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, row: { id: 1, name: 'test', price: 123 } }; + expect(wrapper.instance().shouldUpdatedByNormalProps(nextProps)).toBeTruthy(); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/row/simple-row.test.js b/packages/react-bootstrap-table2/test/row/simple-row.test.js new file mode 100644 index 0000000..744f3cb --- /dev/null +++ b/packages/react-bootstrap-table2/test/row/simple-row.test.js @@ -0,0 +1,173 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import RowPureContent from '../../src/row/row-pure-content'; +import SimpleRow from '../../src/row/simple-row'; + +let defaultColumns = [{ + dataField: 'id', + text: 'ID' +}, { + dataField: 'name', + text: 'Name' +}, { + dataField: 'price', + text: 'Price' +}]; + +const keyField = 'id'; +const rowIndex = 1; + +describe('SimpleRow', () => { + let wrapper; + + const row = { + id: 1, + name: 'A', + price: 1000 + }; + + beforeEach(() => { + defaultColumns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }, { + dataField: 'price', + text: 'Price' + }]; + }); + + describe('simplest row', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find(RowPureContent)).toHaveLength(1); + }); + }); + + describe('shouldComponentUpdate', () => { + let props; + let nextProps; + describe('if shouldUpdatedByNormalProps return true', () => { + beforeEach(() => { + props = { + keyField, + columns: defaultColumns, + rowIndex: 1, + row, + editable: true + }; + wrapper = shallow( + + ); + }); + + it('should return true', () => { + nextProps = { ...props, rowIndex: 2 }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + + it('should set this.shouldUpdateRowContent as true', () => { + nextProps = { ...props, rowIndex: 2 }; + wrapper.instance().shouldComponentUpdate(nextProps); + expect(wrapper.instance().shouldUpdateRowContent).toBe(true); + }); + }); + + describe('if shouldUpdatedByNormalProps return false', () => { + beforeEach(() => { + props = { + keyField, + columns: defaultColumns, + rowIndex: 1, + row, + editable: true + }; + wrapper = shallow( + + ); + }); + + it('should return value which depends on the result of shouldUpdatedBySelfProps', () => { + nextProps = { ...props, className: 'test' }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + + it('should always set this.shouldUpdateRowContent as false', () => { + nextProps = { ...props, className: 'test' }; + wrapper.instance().shouldComponentUpdate(nextProps); + expect(wrapper.instance().shouldUpdateRowContent).toBe(false); + }); + }); + }); + + describe('when style prop is defined', () => { + const customStyle = { backgroundColor: 'red' }; + beforeEach(() => { + wrapper = shallow( + ); + }); + + it('should render component with style successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.prop('style')).toEqual(customStyle); + }); + }); + + describe('when className prop is defined', () => { + const className = 'test-class'; + beforeEach(() => { + wrapper = shallow( + ); + }); + + it('should render component with className successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass(className)).toBe(true); + }); + }); + + describe('when attrs prop is defined', () => { + const customClickCallBack = sinon.stub(); + const attrs = { 'data-index': 1, onClick: customClickCallBack }; + beforeEach(() => { + wrapper = shallow( + ); + }); + + it('should render component with correct attributes', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.prop('data-index')).toBe(attrs['data-index']); + expect(wrapper.prop('onClick')).toBeDefined(); + }); + }); +}); From fa13550d8c184a4daff696c5789824829caebc4b Mon Sep 17 00:00:00 2001 From: AllenFang Date: Fri, 21 Sep 2018 15:13:27 +0800 Subject: [PATCH 20/38] patch test for row selection consumer refactoring --- .../react-bootstrap-table2/test/body.test.js | 11 +-- .../test/contexts/index.test.js | 1 + ...ow-binder.test.js => row-consumer.test.js} | 6 +- .../test/row-selection/selection-cell.test.js | 83 ++++++++++++++++--- 4 files changed, 80 insertions(+), 21 deletions(-) rename packages/react-bootstrap-table2/test/row-selection/{row-binder.test.js => row-consumer.test.js} (98%) diff --git a/packages/react-bootstrap-table2/test/body.test.js b/packages/react-bootstrap-table2/test/body.test.js index e449877..c7d8894 100644 --- a/packages/react-bootstrap-table2/test/body.test.js +++ b/packages/react-bootstrap-table2/test/body.test.js @@ -4,10 +4,10 @@ import sinon from 'sinon'; import { shallow, mount } from 'enzyme'; import Body from '../src/body'; -import Row from '../src/row'; -import RowAggregator from '../src/row-aggregator'; +import Row from '../src/row/simple-row'; +import RowAggregator from '../src/row/aggregate-row'; import Const from '../src/const'; -import RowSection from '../src/row-section'; +import RowSection from '../src/row/row-section'; import SelectionContext from '../src/contexts/selection-context'; import ExpansionContext from '../src/contexts/row-expand-context'; import mockBodyResolvedProps from './test-helpers/mock/body-resolved-props'; @@ -255,12 +255,11 @@ describe('Body', () => { }); describe('when cellEdit.createContext props is defined', () => { - const CellComponent = () => null; const EditingCellComponent = () => null; const RowComponent = props => ; const cellEdit = { + options: { onStartEdit: jest.fn() }, createContext: jest.fn(), - bindCellLevelCellEdit: jest.fn().mockReturnValue(CellComponent), createEditingCell: jest.fn().mockReturnValue(EditingCellComponent), bindRowLevelCellEdit: jest.fn().mockReturnValue(RowComponent) }; @@ -278,12 +277,10 @@ describe('Body', () => { it('should render Row Component correctly', () => { expect(wrapper.length).toBe(1); - expect(cellEdit.bindCellLevelCellEdit).toHaveBeenCalledTimes(1); expect(cellEdit.createEditingCell).toHaveBeenCalledTimes(1); expect(cellEdit.bindRowLevelCellEdit).toHaveBeenCalledTimes(1); expect(wrapper.find(RowComponent)).toHaveLength(2); const aRowElement = wrapper.find(RowComponent).get(0); - expect(aRowElement.props.CellComponent).toBeDefined(); expect(aRowElement.props.EditingCellComponent).toBeDefined(); }); }); diff --git a/packages/react-bootstrap-table2/test/contexts/index.test.js b/packages/react-bootstrap-table2/test/contexts/index.test.js index 90e8a69..52ae08b 100644 --- a/packages/react-bootstrap-table2/test/contexts/index.test.js +++ b/packages/react-bootstrap-table2/test/contexts/index.test.js @@ -112,6 +112,7 @@ describe('Context', () => { Provider: CellEditContext.Provider, Consumer: CellEditContext.Consumer }), + options: {}, bindCellLevelCellEdit: jest.fn().mockReturnValue(() => null), createEditingCell: jest.fn().mockReturnValue(() => null), bindRowLevelCellEdit: jest.fn().mockReturnValue(() => null) diff --git a/packages/react-bootstrap-table2/test/row-selection/row-binder.test.js b/packages/react-bootstrap-table2/test/row-selection/row-consumer.test.js similarity index 98% rename from packages/react-bootstrap-table2/test/row-selection/row-binder.test.js rename to packages/react-bootstrap-table2/test/row-selection/row-consumer.test.js index 0905eee..53cc804 100644 --- a/packages/react-bootstrap-table2/test/row-selection/row-binder.test.js +++ b/packages/react-bootstrap-table2/test/row-selection/row-consumer.test.js @@ -3,13 +3,13 @@ import React from 'react'; import { mount } from 'enzyme'; import SelectionContext from '../../src/contexts/selection-context'; -import bindSelection from '../../src/row-selection/row-binder'; +import withSelectionConsumer from '../../src/row-selection/row-consumer'; -describe('Selection Row Binder', () => { +describe('withSelectionConsumer', () => { let wrapper; let selectRow; const BaseComponent = () => null; - const WithSelectionComponent = bindSelection(props => ); + const WithSelectionComponent = withSelectionConsumer(props => ); const data = [{ id: 1, 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 index 311c361..7de9a40 100644 --- a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js +++ b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js @@ -14,24 +14,85 @@ describe('', () => { let wrapper; describe('shouldComponentUpdate', () => { - const selected = true; + let props; + let nextProps; - describe('when selected prop has not been changed', () => { - it('should not update component', () => { - const nextProps = { selected }; + describe('when selected prop has been changed', () => { + beforeEach(() => { + props = { + selected: false, + mode, + rowIndex, + disabled: false, + rowKey: 1 + }; + wrapper = shallow( + + ); + }); - wrapper = shallow(); - - expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(false); + it('should return true', () => { + nextProps = { ...props, selected: true }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); }); }); - describe('when selected prop has been changed', () => { - it('should update component', () => { - const nextProps = { selected: !selected }; + describe('when rowIndex prop has been changed', () => { + beforeEach(() => { + props = { + selected: false, + mode, + rowIndex, + disabled: false, + rowKey: 1 + }; + wrapper = shallow( + + ); + }); - wrapper = shallow(); + it('should return true', () => { + nextProps = { ...props, rowIndex: 2 }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + }); + describe('when disabled prop has been changed', () => { + beforeEach(() => { + props = { + selected: false, + mode, + rowIndex, + disabled: false, + rowKey: 1 + }; + wrapper = shallow( + + ); + }); + + it('should return true', () => { + nextProps = { ...props, disabled: true }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + }); + + describe('when rowKey prop has been changed', () => { + beforeEach(() => { + props = { + selected: false, + mode, + rowIndex, + disabled: false, + rowKey: 1 + }; + wrapper = shallow( + + ); + }); + + it('should return true', () => { + nextProps = { ...props, rowKey: '1' }; expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); }); }); From 1e76ca9bdbffde2e1027a127e5d308c0e21f634d Mon Sep 17 00:00:00 2001 From: AllenFang Date: Fri, 21 Sep 2018 16:27:10 +0800 Subject: [PATCH 21/38] fix row-pure-content will not update in some case --- packages/react-bootstrap-table2/src/row/row-pure-content.js | 6 ++---- packages/react-bootstrap-table2/src/row/should-updater.js | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/react-bootstrap-table2/src/row/row-pure-content.js b/packages/react-bootstrap-table2/src/row/row-pure-content.js index 52b77f0..cd1cbfd 100644 --- a/packages/react-bootstrap-table2/src/row/row-pure-content.js +++ b/packages/react-bootstrap-table2/src/row/row-pure-content.js @@ -7,10 +7,8 @@ import Cell from '../cell'; export default class RowPureContent extends React.Component { shouldComponentUpdate(nextProps) { - if (typeof this.props.shouldUpdate !== 'undefined') { - if (nextProps.shouldUpdate === this.props.shouldUpdate) { - return false; - } + if (typeof nextProps.shouldUpdate !== 'undefined') { + return nextProps.shouldUpdate; } return true; } diff --git a/packages/react-bootstrap-table2/src/row/should-updater.js b/packages/react-bootstrap-table2/src/row/should-updater.js index 355c7d7..f99ff08 100644 --- a/packages/react-bootstrap-table2/src/row/should-updater.js +++ b/packages/react-bootstrap-table2/src/row/should-updater.js @@ -23,8 +23,8 @@ export default ExtendBase => const shouldUpdate = this.props.rowIndex !== nextProps.rowIndex || this.props.editable !== nextProps.editable || - !_.isEqual(this.props.row, nextProps.row || - this.props.columns.length !== nextProps.columns.length); + !_.isEqual(this.props.row, nextProps.row) || + this.props.columns.length !== nextProps.columns.length; return shouldUpdate; } From 8499991c41497408ede95d4da62bbea5966daeb0 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 24 Sep 2018 15:24:07 +0800 Subject: [PATCH 22/38] fix cell edit broken --- packages/react-bootstrap-table2/src/row/simple-row.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-bootstrap-table2/src/row/simple-row.js b/packages/react-bootstrap-table2/src/row/simple-row.js index b33e2c6..02e4bbb 100644 --- a/packages/react-bootstrap-table2/src/row/simple-row.js +++ b/packages/react-bootstrap-table2/src/row/simple-row.js @@ -14,7 +14,9 @@ class Row extends shouldUpdater(eventDelegater(Component)) { } shouldComponentUpdate(nextProps) { - this.shouldUpdateRowContent = this.shouldUpdatedByNormalProps(nextProps); + this.shouldUpdateRowContent = false; + this.shouldUpdateRowContent = + this.shouldUpdateByWhenEditing(nextProps) || this.shouldUpdatedByNormalProps(nextProps); if (this.shouldUpdateRowContent) return true; return this.shouldUpdatedBySelfProps(nextProps); From 9567c7829d661021e9d5c181c267e08d6753e5bf Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 24 Sep 2018 16:32:56 +0800 Subject: [PATCH 23/38] refactoring shouldComponentUpdate for aggreate and simple row for cell editing --- .../src/contexts/selection-context.js | 2 +- .../src/row/aggregate-row.js | 5 ++-- .../src/row/event-delegater.js | 2 +- .../src/row/should-updater.js | 8 ++++++- .../src/row/simple-row.js | 3 +-- .../test/row/should-updater.test.js | 24 +++++++++++++++---- 6 files changed, 32 insertions(+), 12 deletions(-) diff --git a/packages/react-bootstrap-table2/src/contexts/selection-context.js b/packages/react-bootstrap-table2/src/contexts/selection-context.js index 964b704..ce8d354 100644 --- a/packages/react-bootstrap-table2/src/contexts/selection-context.js +++ b/packages/react-bootstrap-table2/src/contexts/selection-context.js @@ -22,7 +22,7 @@ class SelectionProvider extends React.Component { } } - state = { selected: (this.props.selectRow && this.props.selectRow.selected) || [] }; + state = { selected: this.props.selectRow.selected || [] }; componentWillReceiveProps(nextProps) { if (nextProps.selectRow) { diff --git a/packages/react-bootstrap-table2/src/row/aggregate-row.js b/packages/react-bootstrap-table2/src/row/aggregate-row.js index d176743..1b3df9d 100644 --- a/packages/react-bootstrap-table2/src/row/aggregate-row.js +++ b/packages/react-bootstrap-table2/src/row/aggregate-row.js @@ -31,13 +31,12 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co this.props.selected !== nextProps.selected || this.props.expanded !== nextProps.expanded || this.props.selectable !== nextProps.selectable || - this.shouldUpdateByWhenEditing(nextProps) || this.shouldUpdatedBySelfProps(nextProps) ) { - this.shouldUpdateRowContent = this.shouldUpdatedByNormalProps(nextProps); + this.shouldUpdateRowContent = this.shouldUpdateChild(nextProps); return true; } - this.shouldUpdateRowContent = this.shouldUpdatedByNormalProps(nextProps); + this.shouldUpdateRowContent = this.shouldUpdateChild(nextProps); return this.shouldUpdateRowContent; } diff --git a/packages/react-bootstrap-table2/src/row/event-delegater.js b/packages/react-bootstrap-table2/src/row/event-delegater.js index 2c6868f..46d2285 100644 --- a/packages/react-bootstrap-table2/src/row/event-delegater.js +++ b/packages/react-bootstrap-table2/src/row/event-delegater.js @@ -45,7 +45,7 @@ export default ExtendBase => } }; - if (DELAY_FOR_DBCLICK) { + if (DELAY_FOR_DBCLICK && selectRow.clickToEdit) { this.clickNum += 1; _.debounce(() => { if (this.clickNum === 1) { diff --git a/packages/react-bootstrap-table2/src/row/should-updater.js b/packages/react-bootstrap-table2/src/row/should-updater.js index f99ff08..7756b73 100644 --- a/packages/react-bootstrap-table2/src/row/should-updater.js +++ b/packages/react-bootstrap-table2/src/row/should-updater.js @@ -3,7 +3,8 @@ import _ from '../utils'; export default ExtendBase => class RowShouldUpdater extends ExtendBase { - shouldUpdateByWhenEditing(nextProps) { + shouldUpdateByCellEditing(nextProps) { + if (!(this.props.clickToEdit || this.props.dbclickToEdit)) return false; return ( nextProps.editingRowIdx === nextProps.rowIndex || (this.props.editingRowIdx === nextProps.rowIndex && @@ -28,4 +29,9 @@ export default ExtendBase => return shouldUpdate; } + + shouldUpdateChild(nextProps) { + return this.shouldUpdateByCellEditing(nextProps) || + this.shouldUpdatedByNormalProps(nextProps); + } }; diff --git a/packages/react-bootstrap-table2/src/row/simple-row.js b/packages/react-bootstrap-table2/src/row/simple-row.js index 02e4bbb..ab5d573 100644 --- a/packages/react-bootstrap-table2/src/row/simple-row.js +++ b/packages/react-bootstrap-table2/src/row/simple-row.js @@ -15,8 +15,7 @@ class Row extends shouldUpdater(eventDelegater(Component)) { shouldComponentUpdate(nextProps) { this.shouldUpdateRowContent = false; - this.shouldUpdateRowContent = - this.shouldUpdateByWhenEditing(nextProps) || this.shouldUpdatedByNormalProps(nextProps); + this.shouldUpdateRowContent = this.shouldUpdateChild(nextProps); if (this.shouldUpdateRowContent) return true; return this.shouldUpdatedBySelfProps(nextProps); diff --git a/packages/react-bootstrap-table2/test/row/should-updater.test.js b/packages/react-bootstrap-table2/test/row/should-updater.test.js index 04a114c..ab1ca05 100644 --- a/packages/react-bootstrap-table2/test/row/should-updater.test.js +++ b/packages/react-bootstrap-table2/test/row/should-updater.test.js @@ -12,8 +12,8 @@ describe('Row shouldUpdater', () => { render() { return null; } } - describe('shouldUpdateByWhenEditing', () => { - describe('when nextProps.editingRowIdx eq props.rowIndex and it\' not null', () => { + describe('shouldUpdateByCellEditing', () => { + describe('when nextProps.clickToEdit and nexrProps.dbclickToEdit both are negative', () => { beforeEach(() => { props = { editingRowIdx: null, @@ -22,15 +22,31 @@ describe('Row shouldUpdater', () => { wrapper = shallow(); }); + it('should always return false', () => { + nextProps = { ...props, editingRowIdx: 0 }; + expect(wrapper.instance().shouldUpdateByCellEditing(nextProps)).toBeFalsy(); + }); + }); + describe('when nextProps.editingRowIdx eq props.rowIndex and it\' not null', () => { + beforeEach(() => { + props = { + clickToEdit: true, + editingRowIdx: null, + rowIndex: 0 + }; + wrapper = shallow(); + }); + it('should return true', () => { nextProps = { ...props, editingRowIdx: 0 }; - expect(wrapper.instance().shouldUpdateByWhenEditing(nextProps)).toBeTruthy(); + expect(wrapper.instance().shouldUpdateByCellEditing(nextProps)).toBeTruthy(); }); }); describe('when props.editingRowIdx eq props.rowIndex but nextProps.editingRowIdx is null', () => { beforeEach(() => { props = { + clickToEdit: true, editingRowIdx: 0, rowIndex: 0 }; @@ -39,7 +55,7 @@ describe('Row shouldUpdater', () => { it('should return true', () => { nextProps = { ...props, editingRowIdx: null }; - expect(wrapper.instance().shouldUpdateByWhenEditing(nextProps)).toBeTruthy(); + expect(wrapper.instance().shouldUpdateByCellEditing(nextProps)).toBeTruthy(); }); }); }); From 95623bbb5f11dc97d12e4b417cfe41c25945a5b1 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 24 Sep 2018 16:34:21 +0800 Subject: [PATCH 24/38] fix selection header checkbox will be checked when table data is empty --- packages/react-bootstrap-table2/src/store/selection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-bootstrap-table2/src/store/selection.js b/packages/react-bootstrap-table2/src/store/selection.js index 135651d..af6a8c1 100644 --- a/packages/react-bootstrap-table2/src/store/selection.js +++ b/packages/react-bootstrap-table2/src/store/selection.js @@ -6,7 +6,7 @@ export const getSelectionSummary = ( keyField, selected = [] ) => { - let allRowsSelected = true; + let allRowsSelected = data.length > 0; let allRowsNotSelected = true; const rowKeys = data.map(d => d[keyField]); From 6735536fd8f21c3814ab3c99f812c159092e92d2 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 24 Sep 2018 16:35:22 +0800 Subject: [PATCH 25/38] refactoring cell edit consumer --- .../react-bootstrap-table2-editor/index.js | 4 +- .../src/{row-binder.js => row-consumer.js} | 15 +- .../test/cell-binder.test.js | 210 ------------------ ...ow-binder.test.js => row-consumer.test.js} | 16 +- packages/react-bootstrap-table2/src/body.js | 2 +- .../react-bootstrap-table2/test/body.test.js | 4 +- .../test/contexts/index.test.js | 3 +- 7 files changed, 24 insertions(+), 230 deletions(-) rename packages/react-bootstrap-table2-editor/src/{row-binder.js => row-consumer.js} (80%) delete mode 100644 packages/react-bootstrap-table2-editor/test/cell-binder.test.js rename packages/react-bootstrap-table2-editor/test/{row-binder.test.js => row-consumer.test.js} (91%) diff --git a/packages/react-bootstrap-table2-editor/index.js b/packages/react-bootstrap-table2-editor/index.js index fc8439d..04d5840 100644 --- a/packages/react-bootstrap-table2-editor/index.js +++ b/packages/react-bootstrap-table2-editor/index.js @@ -1,5 +1,5 @@ import createContext from './src/context'; -import bindRowLevelCellEdit from './src/row-binder'; +import withRowLevelCellEdit from './src/row-consumer'; import createEditingCell from './src/editing-cell-binder'; import { EDITTYPE, @@ -10,7 +10,7 @@ import { export default (options = {}) => ({ createContext, createEditingCell, - bindRowLevelCellEdit, + withRowLevelCellEdit, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK, options diff --git a/packages/react-bootstrap-table2-editor/src/row-binder.js b/packages/react-bootstrap-table2-editor/src/row-consumer.js similarity index 80% rename from packages/react-bootstrap-table2-editor/src/row-binder.js rename to packages/react-bootstrap-table2-editor/src/row-consumer.js index bd7b687..7fe6648 100644 --- a/packages/react-bootstrap-table2-editor/src/row-binder.js +++ b/packages/react-bootstrap-table2-editor/src/row-consumer.js @@ -30,9 +30,14 @@ export default (Component, selectRowEnabled) => { /> ); }; - return props => ( - - { cellEdit => renderWithCellEdit(props, cellEdit) } - - ); + function withConsumer(props) { + return ( + + { cellEdit => renderWithCellEdit(props, cellEdit) } + + ); + } + + withConsumer.displayName = 'WithCellEditingRowConsumer'; + return withConsumer; }; diff --git a/packages/react-bootstrap-table2-editor/test/cell-binder.test.js b/packages/react-bootstrap-table2-editor/test/cell-binder.test.js deleted file mode 100644 index dfe72ee..0000000 --- a/packages/react-bootstrap-table2-editor/test/cell-binder.test.js +++ /dev/null @@ -1,210 +0,0 @@ -import 'jsdom-global/register'; -import React from 'react'; -import { mount } from 'enzyme'; -import _ from 'react-bootstrap-table-next/src/utils'; -import op from 'react-bootstrap-table-next/src/store/operators'; - -import cellEditFactory from '../index'; -import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT } from '../src/const'; -import createCellEditContext from '../src/context'; -import bindCellEditing from '../src/cell-binder'; - -describe('Cell Binder', () => { - let wrapper; - let cellEdit; - const data = [{ - id: 1, - name: 'A' - }, { - id: 2, - name: 'B' - }]; - let columns; - const rowIndex = 1; - const row = { id: 1, name: 'A' }; - const keyField = 'id'; - const columnIndex = 1; - - const { Provider } = createCellEditContext(_, op, false, jest.fn()); - const BaseComponent = () => null; - const WithCellEditComponent = bindCellEditing( - props => , - keyField, - _ - ); - - beforeEach(() => { - columns = [{ - dataField: 'id', - text: 'ID' - }, { - dataField: 'name', - text: 'Name' - }]; - }); - - describe(`if cellEdit.mode is ${CLICK_TO_CELL_EDIT}`, () => { - beforeEach(() => { - cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); - wrapper = mount( - - - - ); - }); - - it('should inject correct props to target component', () => { - expect(wrapper.find(BaseComponent)).toHaveLength(1); - expect(wrapper.find(BaseComponent).prop('clickToEdit')).toBeTruthy(); - expect(wrapper.find(BaseComponent).prop('dbclickToEdit')).toBeFalsy(); - }); - }); - - describe(`if cellEdit.mode is ${DBCLICK_TO_CELL_EDIT}`, () => { - beforeEach(() => { - cellEdit = cellEditFactory({ mode: DBCLICK_TO_CELL_EDIT }); - wrapper = mount( - - - - ); - }); - - it('should inject correct props to target component', () => { - expect(wrapper.find(BaseComponent)).toHaveLength(1); - expect(wrapper.find(BaseComponent).prop('clickToEdit')).toBeFalsy(); - expect(wrapper.find(BaseComponent).prop('dbclickToEdit')).toBeTruthy(); - }); - }); - - describe('if column prop is a key column', () => { - beforeEach(() => { - cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); - wrapper = mount( - - - - ); - }); - - it('should inject negative editable prop to target component', () => { - expect(wrapper.find(BaseComponent)).toHaveLength(1); - expect(wrapper.find(BaseComponent).prop('editable')).toBeFalsy(); - }); - }); - - describe('if editable prop is true(Row Level)', () => { - describe('but column.editable prop is false', () => { - beforeEach(() => { - cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); - columns[1].editable = false; - wrapper = mount( - - - - ); - }); - - it('should inject negative editable prop to target component', () => { - expect(wrapper.find(BaseComponent)).toHaveLength(1); - expect(wrapper.find(BaseComponent).prop('editable')).toBeFalsy(); - }); - }); - - describe('and column.editable prop is true or not defined', () => { - beforeEach(() => { - cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); - wrapper = mount( - - - - ); - }); - - it('should inject positive editable prop to target component', () => { - expect(wrapper.find(BaseComponent)).toHaveLength(1); - expect(wrapper.find(BaseComponent).prop('editable')).toBeTruthy(); - }); - }); - }); - - describe('if editable prop is false(Row Level)', () => { - describe('even if column.editable prop is true or not defined', () => { - beforeEach(() => { - cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); - columns[1].editable = true; - wrapper = mount( - - - - ); - }); - - it('should inject negative editable prop to target component', () => { - expect(wrapper.find(BaseComponent)).toHaveLength(1); - expect(wrapper.find(BaseComponent).prop('editable')).toBeFalsy(); - }); - }); - }); - - describe('if column.editable prop is a function', () => { - beforeEach(() => { - cellEdit = cellEditFactory({ mode: CLICK_TO_CELL_EDIT }); - columns[1].editable = jest.fn().mockReturnValue(false); - wrapper = mount( - - - - ); - }); - - it('should call column.editable function correctly', () => { - expect(columns[1].editable).toHaveBeenCalledTimes(1); - }); - - it('should inject correct editable prop to target component', () => { - expect(wrapper.find(BaseComponent)).toHaveLength(1); - expect(wrapper.find(BaseComponent).prop('editable')).toBeFalsy(); - }); - }); -}); diff --git a/packages/react-bootstrap-table2-editor/test/row-binder.test.js b/packages/react-bootstrap-table2-editor/test/row-consumer.test.js similarity index 91% rename from packages/react-bootstrap-table2-editor/test/row-binder.test.js rename to packages/react-bootstrap-table2-editor/test/row-consumer.test.js index 97490d6..2d8c93e 100644 --- a/packages/react-bootstrap-table2-editor/test/row-binder.test.js +++ b/packages/react-bootstrap-table2-editor/test/row-consumer.test.js @@ -4,12 +4,12 @@ import { mount } from 'enzyme'; import _ from 'react-bootstrap-table-next/src/utils'; import op from 'react-bootstrap-table-next/src/store/operators'; -import cellEditFactory from '../index'; +import cellEditFactory from '..'; import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK } from '../src/const'; import createCellEditContext from '../src/context'; -import bindCellEditing from '../src/row-binder'; +import withRowLevelCellEdit from '../src/row-consumer'; -describe('Row Binder', () => { +describe('Row Consumer', () => { let wrapper; let cellEdit; const data = [{ @@ -28,7 +28,7 @@ describe('Row Binder', () => { describe('if cellEdit.nonEditableRows is undefined', () => { beforeEach(() => { - const WithCellEditComponent = bindCellEditing( + const WithCellEditComponent = withRowLevelCellEdit( props => , false ); @@ -52,7 +52,7 @@ describe('Row Binder', () => { const nonEditableRows = jest.fn().mockReturnValue([value]); describe('if value prop is match in one of cellEdit.nonEditableRows', () => { beforeEach(() => { - const WithCellEditComponent = bindCellEditing( + const WithCellEditComponent = withRowLevelCellEdit( props => , false ); @@ -72,7 +72,7 @@ describe('Row Binder', () => { describe('if value prop is not match in one of cellEdit.nonEditableRows', () => { beforeEach(() => { - const WithCellEditComponent = bindCellEditing( + const WithCellEditComponent = withRowLevelCellEdit( props => , false ); @@ -93,7 +93,7 @@ describe('Row Binder', () => { describe(`if selectRowEnabled argument is true and cellEdit.mode is ${DBCLICK_TO_CELL_EDIT}`, () => { beforeEach(() => { - const WithCellEditComponent = bindCellEditing( + const WithCellEditComponent = withRowLevelCellEdit( props => , true ); @@ -115,7 +115,7 @@ describe('Row Binder', () => { const ridx = 0; const cidx = 1; beforeEach(() => { - const WithCellEditComponent = bindCellEditing( + const WithCellEditComponent = withRowLevelCellEdit( props => , false ); diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index 3179737..20620e5 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -59,7 +59,7 @@ class Body extends React.Component { } if (cellEdit.createContext) { - RowComponent = cellEdit.bindRowLevelCellEdit(RowComponent, selectRowEnabled, keyField, _); + RowComponent = cellEdit.withRowLevelCellEdit(RowComponent, selectRowEnabled, keyField, _); additionalRowProps.EditingCellComponent = this.EditingCell; } diff --git a/packages/react-bootstrap-table2/test/body.test.js b/packages/react-bootstrap-table2/test/body.test.js index c7d8894..764af39 100644 --- a/packages/react-bootstrap-table2/test/body.test.js +++ b/packages/react-bootstrap-table2/test/body.test.js @@ -261,7 +261,7 @@ describe('Body', () => { options: { onStartEdit: jest.fn() }, createContext: jest.fn(), createEditingCell: jest.fn().mockReturnValue(EditingCellComponent), - bindRowLevelCellEdit: jest.fn().mockReturnValue(RowComponent) + withRowLevelCellEdit: jest.fn().mockReturnValue(RowComponent) }; beforeEach(() => { wrapper = shallow( @@ -278,7 +278,7 @@ describe('Body', () => { it('should render Row Component correctly', () => { expect(wrapper.length).toBe(1); expect(cellEdit.createEditingCell).toHaveBeenCalledTimes(1); - expect(cellEdit.bindRowLevelCellEdit).toHaveBeenCalledTimes(1); + expect(cellEdit.withRowLevelCellEdit).toHaveBeenCalledTimes(1); expect(wrapper.find(RowComponent)).toHaveLength(2); const aRowElement = wrapper.find(RowComponent).get(0); expect(aRowElement.props.EditingCellComponent).toBeDefined(); diff --git a/packages/react-bootstrap-table2/test/contexts/index.test.js b/packages/react-bootstrap-table2/test/contexts/index.test.js index 52ae08b..76caecb 100644 --- a/packages/react-bootstrap-table2/test/contexts/index.test.js +++ b/packages/react-bootstrap-table2/test/contexts/index.test.js @@ -113,9 +113,8 @@ describe('Context', () => { Consumer: CellEditContext.Consumer }), options: {}, - bindCellLevelCellEdit: jest.fn().mockReturnValue(() => null), createEditingCell: jest.fn().mockReturnValue(() => null), - bindRowLevelCellEdit: jest.fn().mockReturnValue(() => null) + withRowLevelCellEdit: jest.fn().mockReturnValue(() => null) }; wrapper = shallow( Date: Fri, 28 Sep 2018 00:10:13 +0800 Subject: [PATCH 26/38] implement selectRow.clickToExpand --- .../examples/row-selection/selection-with-expansion.js | 8 ++++++-- .../react-bootstrap-table2/src/row/event-delegater.js | 10 ++++++++-- .../test/row/aggregate-row.test.js | 6 +++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/selection-with-expansion.js b/packages/react-bootstrap-table2-example/examples/row-selection/selection-with-expansion.js index e1cd4a3..e163fed 100644 --- a/packages/react-bootstrap-table2-example/examples/row-selection/selection-with-expansion.js +++ b/packages/react-bootstrap-table2-example/examples/row-selection/selection-with-expansion.js @@ -18,7 +18,9 @@ const columns = [{ }]; const selectRow = { - mode: 'checkbox' + mode: 'checkbox', + clickToSelect: true, + clickToExpand: true }; const expandRow = { @@ -48,7 +50,8 @@ const columns = [{ const selectRow = { mode: 'checkbox', - clickToSelect: true + clickToSelect: true, + clickToExpand: true }; const expandRow = { @@ -67,6 +70,7 @@ const expandRow = { data={ products } columns={ columns } selectRow={ selectRow } + expandRow={ expandRow } /> `; diff --git a/packages/react-bootstrap-table2/src/row/event-delegater.js b/packages/react-bootstrap-table2/src/row/event-delegater.js index 46d2285..e666f7e 100644 --- a/packages/react-bootstrap-table2/src/row/event-delegater.js +++ b/packages/react-bootstrap-table2/src/row/event-delegater.js @@ -1,4 +1,5 @@ import _ from '../utils'; +import Const from '../const'; const events = [ 'onClick', @@ -38,14 +39,19 @@ export default ExtendBase => } const key = _.get(row, keyField); if (expandRow && expandable) { - expandRow.onRowExpand(key, !expanded, rowIndex, e); + if ( + (selectRow.mode !== Const.ROW_SELECT_DISABLED && selectRow.clickToExpand) || + selectRow.mode === Const.ROW_SELECT_DISABLED + ) { + expandRow.onRowExpand(key, !expanded, rowIndex, e); + } } if (selectRow.clickToSelect && selectable) { selectRow.onRowSelect(key, !selected, rowIndex, e); } }; - if (DELAY_FOR_DBCLICK && selectRow.clickToEdit) { + if (DELAY_FOR_DBCLICK) { this.clickNum += 1; _.debounce(() => { if (this.clickNum === 1) { diff --git a/packages/react-bootstrap-table2/test/row/aggregate-row.test.js b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js index ea2c617..6bda105 100644 --- a/packages/react-bootstrap-table2/test/row/aggregate-row.test.js +++ b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js @@ -215,7 +215,7 @@ describe('Row Aggregator', () => { }); }); - describe('if props.expandRow.renderer is defined', () => { + describe('if props.expandRow is not defined', () => { describe('but expandable props is false', () => { const expandRow = { renderer: jest.fn(), nonExpandable: [row[keyField]] }; beforeEach(() => { @@ -235,7 +235,7 @@ describe('Row Aggregator', () => { }); }); - describe('if props.expandRow.renderer is defined', () => { + describe('if props.expandRow is defined', () => { const expandRow = { renderer: jest.fn() }; beforeEach(() => { wrapper = mount( @@ -252,7 +252,7 @@ describe('Row Aggregator', () => { }); }); - describe('if props.attrs.onClick and props.expandRow.renderer both are defined', () => { + describe('if props.attrs.onClick and props.expandRow both are defined', () => { const attrs = { onClick: jest.fn() }; const expandRow = { renderer: jest.fn() }; From d45345ed1012971694d8bc69ecfe63f01c762024 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 30 Sep 2018 17:04:53 +0800 Subject: [PATCH 27/38] binder -> consumer --- packages/react-bootstrap-table2-editor/index.js | 2 +- .../{editing-cell-binder.js => editing-cell-consumer.js} | 0 ...ng-cell-binder.test.js => editing-cell-consumer.test.js} | 6 +++--- packages/react-bootstrap-table2/src/body.js | 4 ++-- packages/react-bootstrap-table2/src/header.js | 4 ++-- ...header-cell-binder.js => expand-header-cell-consumer.js} | 0 .../src/row-expand/{row-binder.js => row-consumer.js} | 0 .../react-bootstrap-table2/test/row/aggregate-row.test.js | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) rename packages/react-bootstrap-table2-editor/src/{editing-cell-binder.js => editing-cell-consumer.js} (100%) rename packages/react-bootstrap-table2-editor/test/{editing-cell-binder.test.js => editing-cell-consumer.test.js} (96%) rename packages/react-bootstrap-table2/src/row-expand/{expand-header-cell-binder.js => expand-header-cell-consumer.js} (100%) rename packages/react-bootstrap-table2/src/row-expand/{row-binder.js => row-consumer.js} (100%) diff --git a/packages/react-bootstrap-table2-editor/index.js b/packages/react-bootstrap-table2-editor/index.js index 04d5840..6a7af65 100644 --- a/packages/react-bootstrap-table2-editor/index.js +++ b/packages/react-bootstrap-table2-editor/index.js @@ -1,6 +1,6 @@ import createContext from './src/context'; import withRowLevelCellEdit from './src/row-consumer'; -import createEditingCell from './src/editing-cell-binder'; +import createEditingCell from './src/editing-cell-consumer'; import { EDITTYPE, DBCLICK_TO_CELL_EDIT, diff --git a/packages/react-bootstrap-table2-editor/src/editing-cell-binder.js b/packages/react-bootstrap-table2-editor/src/editing-cell-consumer.js similarity index 100% rename from packages/react-bootstrap-table2-editor/src/editing-cell-binder.js rename to packages/react-bootstrap-table2-editor/src/editing-cell-consumer.js diff --git a/packages/react-bootstrap-table2-editor/test/editing-cell-binder.test.js b/packages/react-bootstrap-table2-editor/test/editing-cell-consumer.test.js similarity index 96% rename from packages/react-bootstrap-table2-editor/test/editing-cell-binder.test.js rename to packages/react-bootstrap-table2-editor/test/editing-cell-consumer.test.js index fa8a392..bca2edb 100644 --- a/packages/react-bootstrap-table2-editor/test/editing-cell-binder.test.js +++ b/packages/react-bootstrap-table2-editor/test/editing-cell-consumer.test.js @@ -3,12 +3,12 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import _ from 'react-bootstrap-table-next/src/utils'; -import cellEditFactory from '../index'; +import cellEditFactory from '..'; import { CLICK_TO_CELL_EDIT } from '../src/const'; import createCellEditContext from '../src/context'; -import bindEditingCell from '../src/editing-cell-binder'; +import bindEditingCell from '../src/editing-cell-consumer'; -describe('Cell Binder', () => { +describe('Editing Cell Consumer', () => { let wrapper; let cellEdit; const data = [{ diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index 20620e5..7bcd373 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -10,7 +10,7 @@ import RowAggregator from './row/aggregate-row'; import RowSection from './row/row-section'; import Const from './const'; import withRowSelection from './row-selection/row-consumer'; -import bindExpansion from './row-expand/row-binder'; +import withRowExpansion from './row-expand/row-consumer'; class Body extends React.Component { constructor(props) { @@ -51,7 +51,7 @@ class Body extends React.Component { const additionalRowProps = {}; if (expandRowEnabled) { - RowComponent = bindExpansion(RowAggregator, visibleColumnSize); + RowComponent = withRowExpansion(RowAggregator, visibleColumnSize); } if (selectRowEnabled) { diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index c39ac89..3adf213 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -6,7 +6,7 @@ import HeaderCell from './header-cell'; import SelectionHeaderCell from './row-selection/selection-header-cell'; import ExpandHeaderCell from './row-expand/expand-header-cell'; import withHeaderSelection from './row-selection/selection-header-cell-consumer'; -import bindExpansion from './row-expand/expand-header-cell-binder'; +import withHeaderExpansion from './row-expand/expand-header-cell-consumer'; const Header = (props) => { const { @@ -26,7 +26,7 @@ const Header = (props) => { let ExpansionHeaderCellComp = () => null; if (expandRow.showExpandColumn) { - ExpansionHeaderCellComp = bindExpansion(ExpandHeaderCell); + ExpansionHeaderCellComp = withHeaderExpansion(ExpandHeaderCell); } if (selectRow) { diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-header-cell-binder.js b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell-consumer.js similarity index 100% rename from packages/react-bootstrap-table2/src/row-expand/expand-header-cell-binder.js rename to packages/react-bootstrap-table2/src/row-expand/expand-header-cell-consumer.js diff --git a/packages/react-bootstrap-table2/src/row-expand/row-binder.js b/packages/react-bootstrap-table2/src/row-expand/row-consumer.js similarity index 100% rename from packages/react-bootstrap-table2/src/row-expand/row-binder.js rename to packages/react-bootstrap-table2/src/row-expand/row-consumer.js diff --git a/packages/react-bootstrap-table2/test/row/aggregate-row.test.js b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js index 6bda105..76aa625 100644 --- a/packages/react-bootstrap-table2/test/row/aggregate-row.test.js +++ b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js @@ -5,7 +5,7 @@ import mockBodyResolvedProps from '../test-helpers/mock/body-resolved-props'; import SelectionContext from '../../src/contexts/selection-context'; import ExpansionContext from '../../src/contexts/row-expand-context'; import bindSelection from '../../src/row-selection/row-consumer'; -import bindExpansion from '../../src/row-expand/row-binder'; +import bindExpansion from '../../src/row-expand/row-consumer'; import ExpandCell from '../../src/row-expand/expand-cell'; import SelectionCell from '../../src/row-selection/selection-cell'; import RowAggregator from '../../src/row/aggregate-row'; From 185c184f0187802853b95afedc77e6692345260b Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 30 Sep 2018 23:58:41 +0800 Subject: [PATCH 28/38] upgrade react --- package.json | 4 ++-- yarn.lock | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 45bef02..f4fc487 100644 --- a/package.json +++ b/package.json @@ -82,8 +82,8 @@ "dependencies": { "classnames": "2.2.5", "prop-types": "15.5.10", - "react": "16.3.2", - "react-dom": "16.3.2", + "react": "16.4.0", + "react-dom": "16.4.0", "underscore": "1.9.1" }, "jest": { diff --git a/yarn.lock b/yarn.lock index 99271d4..b20bdb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6958,9 +6958,9 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@16.3.2: - version "16.3.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df" +react-dom@16.4.0: + version "16.4.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.0.tgz#099f067dd5827ce36a29eaf9a6cdc7cbf6216b1e" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -6996,9 +6996,9 @@ react-test-renderer@~16.3.0-0: prop-types "^15.6.0" react-is "^16.3.2" -react@16.3.2: - version "16.3.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9" +react@16.4.0: + version "16.4.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.0.tgz#402c2db83335336fba1962c08b98c6272617d585" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" From dd5429438294be3d4e406c813e01e354c0607266 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Wed, 10 Oct 2018 23:54:11 +0800 Subject: [PATCH 29/38] refine large table example --- .../examples/basic/large-table.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/react-bootstrap-table2-example/examples/basic/large-table.js b/packages/react-bootstrap-table2-example/examples/basic/large-table.js index f25beae..03f7d37 100644 --- a/packages/react-bootstrap-table2-example/examples/basic/large-table.js +++ b/packages/react-bootstrap-table2-example/examples/basic/large-table.js @@ -1,10 +1,9 @@ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; -import cellEditFactory from 'react-bootstrap-table2-editor'; import { productsGenerator } from 'utils/common'; -const products = productsGenerator(5); +const products = productsGenerator(3000); const columns = [{ dataField: 'id', @@ -17,15 +16,25 @@ const columns = [{ text: 'Product Price' }]; +const expandRow = { + showExpandColumn: true, + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ) +}; + export default () => (
); From 15731932cf99aa14b66f316cc258d244c5a1663a Mon Sep 17 00:00:00 2001 From: AllenFang Date: Thu, 11 Oct 2018 00:08:13 +0800 Subject: [PATCH 30/38] fix #546 --- packages/react-bootstrap-table2/src/bootstrap-table.js | 1 + packages/react-bootstrap-table2/src/row-expand/expand-cell.js | 2 +- packages/react-bootstrap-table2/src/row/event-delegater.js | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 769fa78..1a1d16d 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -156,6 +156,7 @@ BootstrapTable.propTypes = { nonExpandable: PropTypes.array, showExpandColumn: PropTypes.bool, onlyOneExpanding: PropTypes.bool, + expandByColumnOnly: PropTypes.bool, expandColumnRenderer: PropTypes.func, expandHeaderColumnRenderer: PropTypes.func }), diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-cell.js b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js index 3df5b1c..e970a13 100644 --- a/packages/react-bootstrap-table2/src/row-expand/expand-cell.js +++ b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js @@ -23,7 +23,7 @@ export default class ExpandCell extends Component { handleClick(e) { const { rowKey, expanded, onRowExpand, rowIndex } = this.props; - onRowExpand(rowKey, expanded, rowIndex, e); + onRowExpand(rowKey, !expanded, rowIndex, e); } render() { diff --git a/packages/react-bootstrap-table2/src/row/event-delegater.js b/packages/react-bootstrap-table2/src/row/event-delegater.js index e666f7e..0a17aa2 100644 --- a/packages/react-bootstrap-table2/src/row/event-delegater.js +++ b/packages/react-bootstrap-table2/src/row/event-delegater.js @@ -32,13 +32,12 @@ export default ExtendBase => selectRow, DELAY_FOR_DBCLICK } = this.props; - const clickFn = () => { if (cb) { cb(e, row, rowIndex); } const key = _.get(row, keyField); - if (expandRow && expandable) { + if (expandRow && expandable && !expandRow.expandByColumnOnly) { if ( (selectRow.mode !== Const.ROW_SELECT_DISABLED && selectRow.clickToExpand) || selectRow.mode === Const.ROW_SELECT_DISABLED From ef2f828572cf5fcdbdb9df3c851358b787e1a865 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Thu, 11 Oct 2018 00:08:31 +0800 Subject: [PATCH 31/38] patch docs and story for #546 --- docs/row-expand.md | 12 +++ .../row-expand/expand-by-column-only.js | 76 +++++++++++++++++++ .../stories/index.js | 2 + 3 files changed, 90 insertions(+) create mode 100644 packages/react-bootstrap-table2-example/examples/row-expand/expand-by-column-only.js diff --git a/docs/row-expand.md b/docs/row-expand.md index 5f6b47a..8afb1aa 100644 --- a/docs/row-expand.md +++ b/docs/row-expand.md @@ -14,6 +14,7 @@ * [onExpandAll](#onExpandAll) * [showExpandColumn](#showExpandColumn) * [onlyOneExpanding](#onlyOneExpanding) +* [expandByColumnOnly](#expandByColumnOnly) * [expandColumnRenderer](#expandColumnRenderer) * [expandHeaderColumnRenderer](#expandHeaderColumnRenderer) @@ -138,3 +139,14 @@ const expandRow = { onlyOneExpanding: true }; ``` + +### expandRow.expandByColumnOnly - [Bool] +Default is `false`. If you want to restrict user to expand/collapse row via clicking the expand column only, you can enable it. + +```js +const expandRow = { + renderer: (row) => ..., + showExpandColumn: true, + expandByColumnOnly: true +}; +``` diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/expand-by-column-only.js b/packages/react-bootstrap-table2-example/examples/row-expand/expand-by-column-only.js new file mode 100644 index 0000000..bc8f5be --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-expand/expand-by-column-only.js @@ -0,0 +1,76 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsExpandRowsGenerator } from 'utils/common'; + +const products = productsExpandRowsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true, + expandByColumnOnly: true +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ \`This Expand row is belong to rowKey $\{row.id}\` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true +}; + + +`; + +export default () => ( +
+

Only able to expand row via clicking expand column(indicator)

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 13d95dd..3dbcede 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -132,6 +132,7 @@ import BasicRowExpand from 'examples/row-expand'; import RowExpandManagement from 'examples/row-expand/expand-management'; import NonExpandableRows from 'examples/row-expand/non-expandable-rows'; import ExpandColumn from 'examples/row-expand/expand-column'; +import OnlyExpandByColumn from 'examples/row-expand/expand-by-column-only.js'; import ExpandOnlyOne from 'examples/row-expand/expand-only-one'; import CustomExpandColumn from 'examples/row-expand/custom-expand-column'; import ExpandHooks from 'examples/row-expand/expand-hooks'; @@ -323,6 +324,7 @@ storiesOf('Row Expand', module) .add('Expand Management', () => ) .add('Non Expandabled Rows', () => ) .add('Expand Indicator', () => ) + .add('Only Expand by Indicator', () => ) .add('Expand Only One Row at The Same Time', () => ) .add('Custom Expand Indicator', () => ) .add('Expand Hooks', () => ); From e77cbdb2df42ec61e751ec3b49fce336a4fb8832 Mon Sep 17 00:00:00 2001 From: Benjamin Cavy Date: Sun, 14 Oct 2018 07:32:36 +0200 Subject: [PATCH 32/38] Add MULTISELECT filter type in filter doc (#606) --- packages/react-bootstrap-table2-filter/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-bootstrap-table2-filter/README.md b/packages/react-bootstrap-table2-filter/README.md index 05c7c9e..8369efd 100644 --- a/packages/react-bootstrap-table2-filter/README.md +++ b/packages/react-bootstrap-table2-filter/README.md @@ -288,3 +288,4 @@ Following properties is valid in `FILTER_TYPES`: * SELECT * NUMBER * DATE +* MULTISELECT From 828844a1e914f8ed0084a0f07f02050e3355f85c Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 14 Oct 2018 14:40:53 +0800 Subject: [PATCH 33/38] fix #598 --- packages/react-bootstrap-table2/src/body.js | 7 +++++-- .../src/bootstrap-table.js | 3 +++ packages/react-bootstrap-table2/src/cell.js | 3 ++- .../src/row-expand/expand-cell.js | 19 ++++++++++++++++--- .../src/row-selection/selection-cell.js | 10 ++++++++-- .../src/row/aggregate-row.js | 8 ++++++++ .../src/row/row-pure-content.js | 10 +++++++++- .../src/row/simple-row.js | 17 ++++++++++++----- 8 files changed, 63 insertions(+), 14 deletions(-) diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index 7bcd373..a32a82e 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -5,7 +5,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from './utils'; -import Row from './row/simple-row'; +import SimpleRow from './row/simple-row'; import RowAggregator from './row/aggregate-row'; import RowSection from './row/row-section'; import Const from './const'; @@ -24,6 +24,7 @@ class Body extends React.Component { const { columns, data, + tabIndexCell, keyField, isEmpty, noDataIndication, @@ -45,7 +46,7 @@ class Body extends React.Component { } content = ; } else { - let RowComponent = Row; + let RowComponent = SimpleRow; const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED; const expandRowEnabled = !!expandRow.renderer; @@ -73,11 +74,13 @@ class Body extends React.Component { const baseRowProps = { key, row, + tabIndexCell, columns, keyField, cellEdit, value: key, rowIndex: index, + visibleColumnSize, attrs: rowEvents || {}, ...additionalRowProps }; diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 1a1d16d..053cac1 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -43,6 +43,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { data, columns, keyField, + tabIndexCell, id, classes, striped, @@ -89,6 +90,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { + { expandColumnRenderer ? expandColumnRenderer({ expanded diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js index 8d46da1..3d6f023 100644 --- a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js +++ b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js @@ -15,6 +15,7 @@ export default class SelectionCell extends Component { onRowSelect: PropTypes.func, disabled: PropTypes.bool, rowIndex: PropTypes.number, + tabIndex: PropTypes.number, clickToSelect: PropTypes.bool, selectionRenderer: PropTypes.func } @@ -29,7 +30,8 @@ export default class SelectionCell extends Component { this.props.rowIndex !== nextProps.rowIndex || this.props.selected !== nextProps.selected || this.props.disabled !== nextProps.disabled || - this.props.rowKey !== nextProps.rowKey; + this.props.rowKey !== nextProps.rowKey || + this.props.tabIndex !== nextProps.tabIndex; return shouldUpdate; } @@ -60,14 +62,18 @@ export default class SelectionCell extends Component { mode: inputType, selected, disabled, + tabIndex, selectionRenderer } = this.props; + const attrs = {}; + if (tabIndex !== -1) attrs.tabIndex = tabIndex; + return ( { ({ bootstrap4 }) => ( - + { selectionRenderer ? selectionRenderer({ mode: inputType, diff --git a/packages/react-bootstrap-table2/src/row/aggregate-row.js b/packages/react-bootstrap-table2/src/row/aggregate-row.js index 1b3df9d..32c997a 100644 --- a/packages/react-bootstrap-table2/src/row/aggregate-row.js +++ b/packages/react-bootstrap-table2/src/row/aggregate-row.js @@ -1,4 +1,5 @@ /* eslint react/prop-types: 0 */ +/* eslint no-plusplus: 0 */ import React from 'react'; import PropTypes from 'prop-types'; import _ from '../utils'; @@ -55,6 +56,8 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co expanded, selected, selectable, + visibleColumnSize, + tabIndexCell, ...rest } = this.props; const key = _.get(row, keyField); @@ -66,6 +69,8 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co newAttrs.onClick = this.createClickEventHandler(newAttrs.onClick); } + let tabIndexStart = (rowIndex * visibleColumnSize) + 1; + return ( ) : null } @@ -91,6 +97,7 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co rowIndex={ rowIndex } selected={ selected } disabled={ !selectable } + tabIndex={ tabIndexCell ? tabIndexStart++ : -1 } /> ) : null @@ -101,6 +108,7 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co keyField={ keyField } rowIndex={ rowIndex } shouldUpdate={ this.shouldUpdateRowContent } + tabIndexStart={ tabIndexCell ? tabIndexStart : -1 } { ...rest } /> diff --git a/packages/react-bootstrap-table2/src/row/row-pure-content.js b/packages/react-bootstrap-table2/src/row/row-pure-content.js index cd1cbfd..5b498ff 100644 --- a/packages/react-bootstrap-table2/src/row/row-pure-content.js +++ b/packages/react-bootstrap-table2/src/row/row-pure-content.js @@ -1,5 +1,6 @@ /* eslint react/prop-types: 0 */ /* eslint react/no-array-index-key: 0 */ +/* eslint no-plusplus: 0 */ import React from 'react'; import _ from '../utils'; @@ -25,9 +26,12 @@ export default class RowPureContent extends React.Component { onStart, clickToEdit, dbclickToEdit, - EditingCellComponent + EditingCellComponent, + tabIndexStart } = this.props; + let tabIndex = tabIndexStart; + return columns.map((column, index) => { if (!column.hidden) { const { dataField } = column; @@ -87,6 +91,10 @@ export default class RowPureContent extends React.Component { editableCell = column.editable(content, row, rowIndex, index); } + if (tabIndexStart !== -1) { + cellAttrs.tabIndex = tabIndex++; + } + return ( - + ); } } -Row.propTypes = { +SimpleRow.propTypes = { row: PropTypes.object.isRequired, rowIndex: PropTypes.number.isRequired, columns: PropTypes.array.isRequired, @@ -47,11 +54,11 @@ Row.propTypes = { attrs: PropTypes.object }; -Row.defaultProps = { +SimpleRow.defaultProps = { editable: true, style: {}, className: null, attrs: {} }; -export default Row; +export default SimpleRow; From d0fb46e39f26509a3feff505dbee4e34d3ab0b9b Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 14 Oct 2018 14:41:14 +0800 Subject: [PATCH 34/38] patch test for #598 --- .../react-bootstrap-table2/test/cell.test.js | 20 +++++++++ .../test/row-selection/selection-cell.test.js | 21 +++++++++ .../test/row/row-pure-content.test.js | 41 ++++++++++++++++++ .../test/row/simple-row.test.js | 43 +++++++++++++++++++ 4 files changed, 125 insertions(+) diff --git a/packages/react-bootstrap-table2/test/cell.test.js b/packages/react-bootstrap-table2/test/cell.test.js index e2e42ab..1187c13 100644 --- a/packages/react-bootstrap-table2/test/cell.test.js +++ b/packages/react-bootstrap-table2/test/cell.test.js @@ -198,6 +198,26 @@ describe('Cell', () => { }); }); + describe('when props.tabIndex is change', () => { + const column = { dataField: 'name', text: 'Product Name' }; + beforeEach(() => { + props = { + row, + columnIndex: 1, + rowIndex: 1, + tabIndex: 5, + column + }; + wrapper = shallow( + ); + }); + + it('should return true', () => { + nextProps = { ...props, tabIndex: 2 }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + }); + describe('if column.isDummyField is true', () => { describe('when content is change', () => { const column = { dataField: '', text: 'Product Name', isDummyField: true }; 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 index 7de9a40..fb6f798 100644 --- a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js +++ b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js @@ -57,6 +57,27 @@ describe('', () => { }); }); + describe('when tabIndex prop has been changed', () => { + beforeEach(() => { + props = { + selected: false, + mode, + rowIndex, + disabled: false, + tabIndex: 0, + rowKey: 1 + }; + wrapper = shallow( + + ); + }); + + it('should return true', () => { + nextProps = { ...props, tabIndex: 2 }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + }); + describe('when disabled prop has been changed', () => { beforeEach(() => { props = { diff --git a/packages/react-bootstrap-table2/test/row/row-pure-content.test.js b/packages/react-bootstrap-table2/test/row/row-pure-content.test.js index 70982c5..3000f93 100644 --- a/packages/react-bootstrap-table2/test/row/row-pure-content.test.js +++ b/packages/react-bootstrap-table2/test/row/row-pure-content.test.js @@ -104,6 +104,47 @@ describe('RowPureContent', () => { }); }); + describe('when tabIndexStart prop is -1', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should not render tabIndex prop on Cell', () => { + wrapper.find(Cell).forEach((cell) => { + expect(cell.prop('tabIndex')).toBeUndefined(); + }); + }); + }); + + describe('when tabIndexStart prop is not -1', () => { + const tabIndexStart = 4; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render correct tabIndex prop on Cell', () => { + wrapper.find(Cell).forEach((cell, i) => { + expect(cell.prop('tabIndex')).toEqual(tabIndexStart + i); + }); + }); + }); + describe('when editingRowIdx and editingColIdx prop is defined', () => { const editingRowIdx = rowIndex; const editingColIdx = 1; diff --git a/packages/react-bootstrap-table2/test/row/simple-row.test.js b/packages/react-bootstrap-table2/test/row/simple-row.test.js index 744f3cb..3d0f6c4 100644 --- a/packages/react-bootstrap-table2/test/row/simple-row.test.js +++ b/packages/react-bootstrap-table2/test/row/simple-row.test.js @@ -57,6 +57,49 @@ describe('SimpleRow', () => { expect(wrapper.length).toBe(1); expect(wrapper.find(RowPureContent)).toHaveLength(1); }); + + describe('when tabIndexCell prop is enable', () => { + const visibleColumnSize = 3; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render correct tabIndexStart', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find(RowPureContent)).toHaveLength(1); + expect(wrapper.find(RowPureContent).prop('tabIndexStart')).toBe((rowIndex * visibleColumnSize) + 1); + }); + }); + + describe('when tabIndexCell prop is disable', () => { + const visibleColumnSize = 3; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should always render tabIndexStart as -1', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find(RowPureContent)).toHaveLength(1); + expect(wrapper.find(RowPureContent).prop('tabIndexStart')).toBe(-1); + }); + }); }); describe('shouldComponentUpdate', () => { From 166affc4c19641dedf61bc6f690b6148839ee1c5 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 14 Oct 2018 14:41:29 +0800 Subject: [PATCH 35/38] add story for #598 --- .../examples/basic/tabindex-column.js | 54 +++++++++++++++++++ .../stories/index.js | 4 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 packages/react-bootstrap-table2-example/examples/basic/tabindex-column.js diff --git a/packages/react-bootstrap-table2-example/examples/basic/tabindex-column.js b/packages/react-bootstrap-table2-example/examples/basic/tabindex-column.js new file mode 100644 index 0000000..144cdce --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/basic/tabindex-column.js @@ -0,0 +1,54 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +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 sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 3dbcede..519542e 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -14,6 +14,7 @@ import CustomizedIdClassesTable from 'examples/basic/customized-id-classes'; import CaptionTable from 'examples/basic/caption-table'; import LargeTable from 'examples/basic/large-table'; import ExposedAPITable from 'examples/basic/exposed-function'; +import TabIndexCellTable from 'examples/basic/tabindex-column'; // bootstrap 4 import Bootstrap4DefaultSortTable from 'examples/bootstrap4/sort'; @@ -195,7 +196,8 @@ storiesOf('Basic Table', module) .add('Customized id and class table', () => ) .add('Table with caption', () => ) .add('Large Table', () => ) - .add('Exposed API', () => ); + .add('Exposed API', () => ) + .add('Enable tabIndex on Cell', () => ); storiesOf('Bootstrap 4', module) .addDecorator(bootstrapStyle(BOOTSTRAP_VERSION.FOUR)) From 81a6428a0379213c19fd604420d07ae1eaf2e969 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 14 Oct 2018 14:43:14 +0800 Subject: [PATCH 36/38] patch docs for #598 --- docs/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/README.md b/docs/README.md index b5bd0bd..56f1fa3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,6 +17,7 @@ * [hover](#hover) * [condensed](#condensed) * [id](#id) +* [tabIndexCell](#tabIndexCell) * [classes](#classes) * [wrapperClasses](#wrapperClasses) * [headerClasses](#headerClasses) @@ -112,6 +113,10 @@ Same as bootstrap `.table-condensed` class for making a table more compact by cu ###
id - [String] Customize id on `table` element. + +### tabIndexCell - [Bool] +Enable the `tabIndex` attribute on `` element. + ### classes - [String] Customize class on `table` element. From 19be67c9146167ea8955134011cbc31ac82c36e6 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 14 Oct 2018 15:07:30 +0800 Subject: [PATCH 37/38] fix #599 --- packages/react-bootstrap-table2/src/contexts/index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 3011d6b..dc2f6a7 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -54,6 +54,16 @@ const withContext = Base => } } + componentWillReceiveProps(nextProps) { + if (!nextProps.pagination && this.props.pagination) { + this.PaginationContext = null; + } + if (nextProps.pagination && !this.props.pagination) { + this.PaginationContext = nextProps.pagination.createContext( + this.isRemotePagination, this.handleRemotePageChange); + } + } + renderBase() { return ( rootProps, From 01ec19344d888c7d16ef549da436b35f6ceba0ad Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 14 Oct 2018 16:04:04 +0800 Subject: [PATCH 38/38] patch docs and proptype for selectRow.clickToExpand --- docs/row-selection.md | 11 +++++++++++ .../react-bootstrap-table2/src/bootstrap-table.js | 1 + 2 files changed, 12 insertions(+) diff --git a/docs/row-selection.md b/docs/row-selection.md index 9ed4e9f..9a09361 100644 --- a/docs/row-selection.md +++ b/docs/row-selection.md @@ -12,6 +12,7 @@ * [bgColor](#bgColor) * [nonSelectable)](#nonSelectable) * [clickToSelect)](#clickToSelect) +* [clickToExpand)](#clickToExpand) * [clickToEdit](#clickToEdit) * [onSelect](#onSelect) * [onSelectAll](#onSelectAll) @@ -148,6 +149,16 @@ const selectRow = { > Note: When you also enable [cellEdit](./cell-edit.md), the `selectRow.clickToSelect` will deactivate the functionality of cell editing > If you want to click on row to select row and edit cell simultaneously, you are suppose to enable [`selectRow.clickToEdit`](#clickToEdit) +### selectRow.clickToExpand - [Bool] +Default is false, enable it will let user able to expand and select row when user clicking on the row. + +```js +const selectRow = { + mode: 'checkbox', + clickToExpand: true +}; +``` + ### selectRow.clickToEdit - [Bool] Able to click to edit cell and select row diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 053cac1..2bf37a3 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -139,6 +139,7 @@ BootstrapTable.propTypes = { Const.ROW_SELECT_DISABLED ]).isRequired, clickToSelect: PropTypes.bool, + clickToExpand: PropTypes.bool, clickToEdit: PropTypes.bool, hideSelectAll: PropTypes.bool, onSelect: PropTypes.func,