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. 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/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/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..f4fc487 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", @@ -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/packages/react-bootstrap-table2-editor/index.js b/packages/react-bootstrap-table2-editor/index.js index 0bc57b5..6a7af65 100644 --- a/packages/react-bootstrap-table2-editor/index.js +++ b/packages/react-bootstrap-table2-editor/index.js @@ -1,16 +1,16 @@ import createContext from './src/context'; -import editingCellFactory from './src/editing-cell'; +import withRowLevelCellEdit from './src/row-consumer'; +import createEditingCell from './src/editing-cell-consumer'; 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, + withRowLevelCellEdit, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK, options diff --git a/packages/react-bootstrap-table2-editor/src/context.js b/packages/react-bootstrap-table2-editor/src/context.js index 5437cca..c3f37c5 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,8 +100,6 @@ export default ( const { cellEdit: { options: { nonEditableRows, errorMessage, ...optionsRest }, - editingCellFactory, - createContext, ...cellEditRest } } = this.props; @@ -112,7 +108,6 @@ export default ( ...optionsRest, ...cellEditRest, ...this.state, - EditingCell, nonEditableRows: _.isDefined(nonEditableRows) ? nonEditableRows() : [], onStart: this.startEditing, onEscape: this.escapeEditing, @@ -121,7 +116,7 @@ export default ( return ( { this.props.children } @@ -129,7 +124,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-consumer.js b/packages/react-bootstrap-table2-editor/src/editing-cell-consumer.js new file mode 100644 index 0000000..b3dc6bd --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/editing-cell-consumer.js @@ -0,0 +1,44 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import { Consumer } from './context'; +import createEditingCell from './editing-cell'; + +export default (_, onStartEdit) => { + const EditingCell = createEditingCell(_, onStartEdit); + 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) + ; + } + + return ( + + ); + }; + + return props => ( + + { cellEdit => renderWithEditingCell(props, cellEdit) } + + ); +}; diff --git a/packages/react-bootstrap-table2-editor/src/row-consumer.js b/packages/react-bootstrap-table2-editor/src/row-consumer.js new file mode 100644 index 0000000..7fe6648 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/row-consumer.js @@ -0,0 +1,43 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import { DELAY_FOR_DBCLICK, DBCLICK_TO_CELL_EDIT, CLICK_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 ( + + ); + }; + function withConsumer(props) { + return ( + + { cellEdit => renderWithCellEdit(props, cellEdit) } + + ); + } + + withConsumer.displayName = 'WithCellEditingRowConsumer'; + return withConsumer; +}; 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-consumer.test.js b/packages/react-bootstrap-table2-editor/test/editing-cell-consumer.test.js new file mode 100644 index 0000000..bca2edb --- /dev/null +++ b/packages/react-bootstrap-table2-editor/test/editing-cell-consumer.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 '..'; +import { CLICK_TO_CELL_EDIT } from '../src/const'; +import createCellEditContext from '../src/context'; +import bindEditingCell from '../src/editing-cell-consumer'; + +describe('Editing Cell Consumer', () => { + 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-consumer.test.js b/packages/react-bootstrap-table2-editor/test/row-consumer.test.js new file mode 100644 index 0000000..2d8c93e --- /dev/null +++ b/packages/react-bootstrap-table2-editor/test/row-consumer.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 '..'; +import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK } from '../src/const'; +import createCellEditContext from '../src/context'; +import withRowLevelCellEdit from '../src/row-consumer'; + +describe('Row Consumer', () => { + 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 = withRowLevelCellEdit( + 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 = withRowLevelCellEdit( + 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 = withRowLevelCellEdit( + 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 = withRowLevelCellEdit( + 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 = withRowLevelCellEdit( + 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-example/examples/basic/large-table.js b/packages/react-bootstrap-table2-example/examples/basic/large-table.js index 671aaa5..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(5000); +const products = productsGenerator(3000); const columns = [{ dataField: 'id', @@ -17,16 +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 () => (
); 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/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/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/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..e163fed --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-selection/selection-with-expansion.js @@ -0,0 +1,88 @@ +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', + clickToSelect: true, + clickToExpand: 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

+
+ ) +}; + +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, + clickToExpand: 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/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 }
diff --git a/packages/react-bootstrap-table2-example/examples/welcome.js b/packages/react-bootstrap-table2-example/examples/welcome.js index 41f3547..ca0dade 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,14 +23,21 @@ export default class Welcome extends React.Component { return (
-

react-bootstrap-table2

+
+ + { + +

+ {PROJECT_NAME} +

+
{ this.el = el; } } />
- -
@@ -95,19 +84,19 @@ class BootstrapTable extends PropsBaseResolver(Component) { onSort={ this.props.onSort } onFilter={ this.props.onFilter } onExternalFilter={ this.props.onExternalFilter } - selectRow={ headerCellSelectionInfo } + selectRow={ selectRow } expandRow={ expandRow } /> } if (props.selectRow) { - this.SelectionContext = createSelectionContext(dataOperator); + this.SelectionContext = SelectionContext; } if (props.expandRow) { - this.RowExpandContext = createRowExpandContext(dataOperator); + this.RowExpandContext = RowExpandContext; } if (props.cellEdit && props.cellEdit.createContext) { @@ -54,27 +54,31 @@ 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, - cellEditProps, filterProps, searchProps, sortProps, paginationProps, - expandProps, - selectionProps ) => ( this.table = n } { ...this.props } - { ...selectionProps } { ...sortProps } - { ...cellEditProps } { ...filterProps } { ...searchProps } { ...paginationProps } - { ...expandProps } data={ rootProps.getData(filterProps, searchProps, sortProps, paginationProps) } /> ); @@ -83,12 +87,10 @@ const withContext = Base => renderWithSelectionCtx(base, baseProps) { return ( rootProps, - cellEditProps, filterProps, searchProps, sortProps, - paginationProps, - expandProps + paginationProps ) => ( selectRow={ this.props.selectRow } data={ rootProps.getData(filterProps, searchProps, sortProps, paginationProps) } > - - { - selectionProps => base( - rootProps, - cellEditProps, - filterProps, - searchProps, - sortProps, - paginationProps, - expandProps, - selectionProps - ) - } - + { + base( + rootProps, + filterProps, + searchProps, + sortProps, + paginationProps + ) + } ); } @@ -117,7 +114,6 @@ const withContext = Base => renderWithRowExpandCtx(base, baseProps) { return ( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -129,19 +125,15 @@ 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, + filterProps, + searchProps, + sortProps, + paginationProps + ) + } ); } @@ -149,7 +141,6 @@ const withContext = Base => renderWithPaginationCtx(base) { return ( rootProps, - cellEditProps, filterProps, searchProps, sortProps @@ -164,7 +155,6 @@ const withContext = Base => { paginationProps => base( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -179,7 +169,6 @@ const withContext = Base => renderWithSortCtx(base, baseProps) { return ( rootProps, - cellEditProps, filterProps, searchProps ) => ( @@ -194,7 +183,6 @@ const withContext = Base => { sortProps => base( rootProps, - cellEditProps, filterProps, searchProps, sortProps, @@ -208,7 +196,6 @@ const withContext = Base => renderWithSearchCtx(base, baseProps) { return ( rootProps, - cellEditProps, filterProps ) => ( { searchProps => base( rootProps, - cellEditProps, filterProps, searchProps ) @@ -232,10 +218,7 @@ const withContext = Base => } renderWithFilterCtx(base, baseProps) { - return ( - rootProps, - cellEditProps - ) => ( + return rootProps => ( this.filterContext = n } @@ -245,7 +228,6 @@ const withContext = Base => { filterProps => base( rootProps, - cellEditProps, filterProps ) } @@ -262,11 +244,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/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/contexts/selection-context.js b/packages/react-bootstrap-table2/src/contexts/selection-context.js index 12a7af8..ce8d354 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.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..3adf213 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -1,15 +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 withHeaderSelection from './row-selection/selection-header-cell-consumer'; +import withHeaderExpansion from './row-expand/expand-header-cell-consumer'; const Header = (props) => { - const { ROW_SELECT_DISABLED } = Const; - const { className, columns, @@ -23,20 +22,24 @@ const Header = (props) => { bootstrap4 } = props; + let SelectionHeaderCellComp = () => null; + let ExpansionHeaderCellComp = () => null; + + if (expandRow.showExpandColumn) { + ExpansionHeaderCellComp = withHeaderExpansion(ExpandHeaderCell); + } + + if (selectRow) { + SelectionHeaderCellComp = withHeaderSelection(SelectionHeaderCell); + } + return ( + { - (expandRow && expandRow.showExpandColumn) - ? : null - } - { - (selectRow.mode !== ROW_SELECT_DISABLED && !selectRow.hideSelectColumn) - ? : null + !selectRow.hideSelectColumn ? + : null } { columns.map((column, i) => { 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 2154ce3..0c3c92d 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/index.js +++ b/packages/react-bootstrap-table2/src/props-resolver/index.js @@ -1,11 +1,7 @@ import ColumnResolver from './column-resolver'; -import ExpandRowResolver from './expand-row-resolver'; -import Const from '../const'; -import _ from '../utils'; export default ExtendBase => - class TableResolver extends - ExpandRowResolver(ColumnResolver(ExtendBase)) { + class TableResolver extends ColumnResolver(ExtendBase) { validateProps() { const { keyField } = this.props; if (!keyField) { @@ -19,63 +15,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-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-expand/expand-cell.js b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js index 3df5b1c..4e626ee 100644 --- a/packages/react-bootstrap-table2/src/row-expand/expand-cell.js +++ b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js @@ -12,7 +12,8 @@ export default class ExpandCell extends Component { expanded: PropTypes.bool.isRequired, onRowExpand: PropTypes.func.isRequired, expandColumnRenderer: PropTypes.func, - rowIndex: PropTypes.number + rowIndex: PropTypes.number, + tabIndex: PropTypes.number } constructor() { @@ -20,17 +21,29 @@ export default class ExpandCell extends Component { this.handleClick = this.handleClick.bind(this); } + shouldComponentUpdate(nextProps) { + const shouldUpdate = + this.props.rowIndex !== nextProps.rowIndex || + this.props.expanded !== nextProps.expanded || + this.props.rowKey !== nextProps.rowKey || + this.props.tabIndex !== nextProps.tabIndex; + + return shouldUpdate; + } + handleClick(e) { const { rowKey, expanded, onRowExpand, rowIndex } = this.props; - onRowExpand(rowKey, expanded, rowIndex, e); + onRowExpand(rowKey, !expanded, rowIndex, e); } render() { - const { expanded, expandColumnRenderer } = this.props; + const { expanded, expandColumnRenderer, tabIndex } = this.props; + const attrs = {}; + if (tabIndex !== -1) attrs.tabIndex = tabIndex; return ( - + { expandColumnRenderer ? expandColumnRenderer({ expanded diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-header-cell-consumer.js b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell-consumer.js new file mode 100644 index 0000000..4460b44 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell-consumer.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-consumer.js b/packages/react-bootstrap-table2/src/row-expand/row-consumer.js new file mode 100644 index 0000000..77f5e36 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-expand/row-consumer.js @@ -0,0 +1,34 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import ExpandRow from './expand-row'; +import ExpansionContext from '../contexts/row-expand-context'; + +export default (Component, visibleColumnSize) => { + const renderWithExpansion = (props, expandRow) => { + const key = props.value; + + 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) } + + ); +}; 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-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js index cdd6bbd..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 } @@ -25,9 +26,14 @@ 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 || + this.props.tabIndex !== nextProps.tabIndex; - return nextProps.selected !== selected; + return shouldUpdate; } handleClick(e) { @@ -56,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-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 => } + +); diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js deleted file mode 100644 index e3254b5..0000000 --- a/packages/react-bootstrap-table2/src/row.js +++ /dev/null @@ -1,183 +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 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() { - const { - row, - columns, - keyField, - rowIndex, - className, - style, - attrs, - cellEdit, - selected, - selectRow, - expanded, - expandRow, - selectable, - editable: editableRow - } = this.props; - - const { - mode, - onStart, - EditingCell, - ridx: editingRowIdx, - cidx: editingColIdx, - CLICK_TO_CELL_EDIT, - 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 - } - { - columns.map((column, index) => { - 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); - } - 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; - - 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 -}; - -Row.defaultProps = { - editable: true, - style: {}, - className: null, - attrs: {} -}; - -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..32c997a --- /dev/null +++ b/packages/react-bootstrap-table2/src/row/aggregate-row.js @@ -0,0 +1,117 @@ +/* eslint react/prop-types: 0 */ +/* eslint no-plusplus: 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.shouldUpdatedBySelfProps(nextProps) + ) { + this.shouldUpdateRowContent = this.shouldUpdateChild(nextProps); + return true; + } + this.shouldUpdateRowContent = this.shouldUpdateChild(nextProps); + + return this.shouldUpdateRowContent; + } + + render() { + const { + row, + columns, + keyField, + rowIndex, + style, + className, + attrs, + selectRow, + expandRow, + expanded, + selected, + selectable, + visibleColumnSize, + tabIndexCell, + ...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); + } + + let tabIndexStart = (rowIndex * visibleColumnSize) + 1; + + 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..0a17aa2 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row/event-delegater.js @@ -0,0 +1,83 @@ +import _ from '../utils'; +import Const from '../const'; + +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.expandByColumnOnly) { + 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) { + 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..5b498ff --- /dev/null +++ b/packages/react-bootstrap-table2/src/row/row-pure-content.js @@ -0,0 +1,116 @@ +/* eslint react/prop-types: 0 */ +/* eslint react/no-array-index-key: 0 */ +/* eslint no-plusplus: 0 */ +import React from 'react'; + +import _ from '../utils'; +import Cell from '../cell'; + +export default class RowPureContent extends React.Component { + shouldComponentUpdate(nextProps) { + if (typeof nextProps.shouldUpdate !== 'undefined') { + return nextProps.shouldUpdate; + } + return true; + } + + render() { + const { + row, + keyField, + columns, + rowIndex, + editable, + editingRowIdx, + editingColIdx, + onStart, + clickToEdit, + dbclickToEdit, + EditingCellComponent, + tabIndexStart + } = this.props; + + let tabIndex = tabIndexStart; + + 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); + } + + if (tabIndexStart !== -1) { + cellAttrs.tabIndex = tabIndex++; + } + + 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 new file mode 100644 index 0000000..7756b73 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row/should-updater.js @@ -0,0 +1,37 @@ +/* eslint react/prop-types: 0 */ +import _ from '../utils'; + +export default ExtendBase => + class RowShouldUpdater extends ExtendBase { + shouldUpdateByCellEditing(nextProps) { + if (!(this.props.clickToEdit || this.props.dbclickToEdit)) return false; + return ( + nextProps.editingRowIdx === nextProps.rowIndex || + (this.props.editingRowIdx === nextProps.rowIndex && + nextProps.editingRowIdx === null) + ); + } + + 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.editable !== nextProps.editable || + !_.isEqual(this.props.row, nextProps.row) || + this.props.columns.length !== nextProps.columns.length; + + 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 new file mode 100644 index 0000000..83aff66 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row/simple-row.js @@ -0,0 +1,64 @@ +/* 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 SimpleRow extends shouldUpdater(eventDelegater(Component)) { + constructor(props) { + super(props); + this.shouldUpdateRowContent = false; + } + + shouldComponentUpdate(nextProps) { + this.shouldUpdateRowContent = false; + this.shouldUpdateRowContent = this.shouldUpdateChild(nextProps); + if (this.shouldUpdateRowContent) return true; + + return this.shouldUpdatedBySelfProps(nextProps); + } + + render() { + const { + className, + style, + attrs, + visibleColumnSize, + tabIndexCell, + ...rest + } = this.props; + const trAttrs = this.delegate(attrs); + const tabIndexStart = (this.props.rowIndex * visibleColumnSize) + 1; + + return ( + + + + ); + } +} + +SimpleRow.propTypes = { + row: PropTypes.object.isRequired, + rowIndex: PropTypes.number.isRequired, + columns: PropTypes.array.isRequired, + style: PropTypes.object, + className: PropTypes.string, + attrs: PropTypes.object +}; + +SimpleRow.defaultProps = { + editable: true, + style: {}, + className: null, + attrs: {} +}; + +export default SimpleRow; 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]); diff --git a/packages/react-bootstrap-table2/test/body.test.js b/packages/react-bootstrap-table2/test/body.test.js index ea624e5..764af39 100644 --- a/packages/react-bootstrap-table2/test/body.test.js +++ b/packages/react-bootstrap-table2/test/body.test.js @@ -1,11 +1,15 @@ +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 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'; describe('Body', () => { @@ -169,92 +173,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 +228,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', () => { @@ -361,11 +254,14 @@ describe('Body', () => { }); }); - describe('when cellEdit.nonEditableRows props is defined', () => { - const nonEditableRows = [data[1].id]; + describe('when cellEdit.createContext props is defined', () => { + const EditingCellComponent = () => null; + const RowComponent = props => ; const cellEdit = { - mode: Const.CLICK_TO_CELL_EDIT, - nonEditableRows + options: { onStartEdit: jest.fn() }, + createContext: jest.fn(), + createEditingCell: jest.fn().mockReturnValue(EditingCellComponent), + withRowLevelCellEdit: jest.fn().mockReturnValue(RowComponent) }; beforeEach(() => { wrapper = shallow( @@ -379,259 +275,82 @@ 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.createEditingCell).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(); }); }); - describe('when selectRow.mode is checkbox or radio (row was selectable)', () => { + describe('when selectRow.mode is ROW_SELECT_DISABLED or expandRow.renderer is undefined', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('shouldn\'t render RowAggregator component', () => { + expect(wrapper.find(RowAggregator)).toHaveLength(0); + }); + }); + + describe('when selectRow.mode is defined correctly', () => { const selectRow = { mode: 'checkbox' }; - const selectedRowKey = data[0][keyField]; - const selectedRowKeys = [selectedRowKey]; beforeEach(() => { - wrapper = shallow( - + wrapper = mount( + + + ); }); - 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); - } - }); + it('should render RowAggregator component correctly', () => { + const rowAggregator = wrapper.find(RowAggregator); - 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(); - }); + 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 selectRow.mode is ROW_SELECT_DISABLED (row was un-selectable)', () => { + describe('when expandRow.renderer is defined correctly', () => { + const expandRow = { renderer: jest.fn() }; + beforeEach(() => { - wrapper = shallow( - + wrapper = mount( + + + ); }); - it('prop selected should be null', () => { - expect(wrapper.find(Row).get(0).props.selected).toBeNull(); + 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/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/contexts/index.test.js b/packages/react-bootstrap-table2/test/contexts/index.test.js index 4a2b1de..76caecb 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 - }) + }), + options: {}, + createEditingCell: jest.fn().mockReturnValue(() => null), + withRowLevelCellEdit: jest.fn().mockReturnValue(() => null) }; wrapper = shallow( { 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..ecb2318 100644 --- a/packages/react-bootstrap-table2/test/header.test.js +++ b/packages/react-bootstrap-table2/test/header.test.js @@ -1,8 +1,12 @@ +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 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'; @@ -17,6 +21,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 +103,18 @@ describe('Header', () => { describe('when selectRow.mode is radio (single selection)', () => { beforeEach(() => { const selectRow = { mode: 'radio' }; - wrapper = shallow( -
+ > +
+ ); }); @@ -105,12 +125,18 @@ describe('Header', () => { describe('when selectRow.hideSelectColumn is true', () => { beforeEach(() => { const selectRow = { mode: 'radio', hideSelectColumn: true }; - wrapper = shallow( -
+ > +
+ ); }); @@ -146,12 +172,18 @@ describe('Header', () => { describe('when selectRow.mode is checkbox (multiple selection)', () => { beforeEach(() => { const selectRow = { mode: 'checkbox' }; - wrapper = shallow( -
+ > +
+ ); }); @@ -162,12 +194,18 @@ describe('Header', () => { describe('when selectRow.hideSelectColumn is true', () => { beforeEach(() => { const selectRow = { mode: 'checkbox', hideSelectColumn: true }; - wrapper = shallow( -
+ > +
+ ); }); @@ -177,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/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-selection/row-consumer.test.js b/packages/react-bootstrap-table2/test/row-selection/row-consumer.test.js new file mode 100644 index 0000000..53cc804 --- /dev/null +++ b/packages/react-bootstrap-table2/test/row-selection/row-consumer.test.js @@ -0,0 +1,414 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { mount } from 'enzyme'; + +import SelectionContext from '../../src/contexts/selection-context'; +import withSelectionConsumer from '../../src/row-selection/row-consumer'; + +describe('withSelectionConsumer', () => { + let wrapper; + let selectRow; + const BaseComponent = () => null; + const WithSelectionComponent = withSelectionConsumer(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'; + const value = row[keyField]; + + 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-selection/selection-cell.test.js b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js index 311c361..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 @@ -14,24 +14,106 @@ 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 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 = { + 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); }); }); diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row.test.js deleted file mode 100644 index 621f140..0000000 --- a/packages/react-bootstrap-table2/test/row.test.js +++ /dev/null @@ -1,1327 +0,0 @@ -import React from 'react'; -import sinon from 'sinon'; -import { shallow } from 'enzyme'; - -import Cell from '../src/cell'; -import Row from '../src/row'; -import Const from '../src/const'; -import SelectionCell from '../src//row-selection/selection-cell'; -import mockBodyResolvedProps from './test-helpers/mock/body-resolved-props'; - -let defaultColumns = [{ - dataField: 'id', - text: 'ID' -}, { - dataField: 'name', - text: 'Name' -}, { - dataField: 'price', - text: 'Price' -}]; - -const keyField = 'id'; -const rowIndex = 1; - -describe('Row', () => { - 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('tr').length).toBe(1); - 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 cellEdit prop is defined', () => { - let columns; - let cellEdit; - - beforeEach(() => { - columns = defaultColumns; - cellEdit = { - mode: 'click', - CLICK_TO_CELL_EDIT: 'click', - DBCLICK_TO_CELL_EDIT: 'dbclick' - }; - wrapper = shallow( - - ); - }); - - afterEach(() => { - columns = undefined; - cellEdit = undefined; - }); - - 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(); - } - } - }); - - 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).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); - }); - }); - }); - }); - - 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 = [{ - dataField: 'id', - text: 'ID', - hidden: true - }, { - dataField: 'name', - text: 'Name' - }, { - dataField: 'price', - text: 'Price' - }]; - wrapper = shallow( - ); - }); - - it('should not render column with hidden value true', () => { - expect(wrapper.find(Cell).length).toBe(2); - }); - }); - - 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; - - beforeEach(() => { - columns = [...defaultColumns]; - }); - - describe('when style is an object', () => { - beforeEach(() => { - columns[columnIndex].style = { backgroundColor: 'red' }; - wrapper = shallow( - - ); - }); - - it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(Cell).get(columnIndex).props.style).toEqual(columns[columnIndex].style); - }); - }); - - describe('when style is a function', () => { - const returnStyle = { backgroundColor: 'red' }; - let styleCallBack; - - beforeEach(() => { - styleCallBack = sinon.stub().returns(returnStyle); - columns[columnIndex].style = styleCallBack; - wrapper = shallow( - - ); - }); - - afterEach(() => { styleCallBack.reset(); }); - - it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); - 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); - }); - }); - }); - - describe('when column.classes prop is defined', () => { - let columns; - const columnIndex = 1; - - beforeEach(() => { - columns = [...defaultColumns]; - }); - - describe('when classes is an object', () => { - beforeEach(() => { - columns[columnIndex].classes = 'td-test-class'; - wrapper = shallow( - - ); - }); - - it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(Cell).get(columnIndex).props.className) - .toEqual(columns[columnIndex].classes); - }); - }); - - describe('when classes is a function', () => { - const returnClasses = 'td-test-class'; - let classesCallBack; - - beforeEach(() => { - classesCallBack = sinon.stub().returns(returnClasses); - columns[columnIndex].classes = classesCallBack; - wrapper = shallow( - - ); - }); - - afterEach(() => { classesCallBack.reset(); }); - - it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); - 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); - }); - }); - }); - - describe('when column.title prop is defined', () => { - let columns; - const columnIndex = 1; - - beforeEach(() => { - columns = [...defaultColumns]; - }); - - describe('when title is an string', () => { - beforeEach(() => { - columns[columnIndex].title = true; - wrapper = shallow( - - ); - }); - - it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(Cell).get(columnIndex).props.title) - .toEqual(row[columns[columnIndex].dataField]); - }); - }); - - describe('when title is a function', () => { - const returnTitle = 'test title'; - let titleCallBack; - - beforeEach(() => { - titleCallBack = sinon.stub().returns(returnTitle); - columns[columnIndex].title = titleCallBack; - wrapper = shallow( - - ); - }); - - afterEach(() => { titleCallBack.reset(); }); - - it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); - 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); - }); - }); - }); - - describe('when column.events prop is defined', () => { - let columns; - const columnIndex = 1; - - beforeEach(() => { - columns = [...defaultColumns]; - columns[columnIndex].events = { - onClick: sinon.stub() - }; - - wrapper = shallow( - - ); - }); - - it('should attachs DOM event successfully', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(Cell).get(columnIndex).props.onClick).toBeDefined(); - }); - }); - - describe('when column.align prop is defined', () => { - let columns; - const columnIndex = 1; - - beforeEach(() => { - columns = [...defaultColumns]; - }); - - describe('when align is a string', () => { - beforeEach(() => { - columns[columnIndex].align = 'right'; - wrapper = shallow( - - ); - }); - - it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(Cell).get(columnIndex).props.style.textAlign) - .toEqual(columns[columnIndex].align); - }); - }); - - describe('when align is a function', () => { - const returnAlign = 'right'; - let alignCallBack; - - beforeEach(() => { - alignCallBack = sinon.stub().returns(returnAlign); - columns[columnIndex].align = alignCallBack; - wrapper = shallow( - - ); - }); - - afterEach(() => { alignCallBack.reset(); }); - - it('should render Cell correctly', () => { - expect(wrapper.length).toBe(1); - 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); - }); - }); - }); - - describe('when column.attrs prop is defined', () => { - let columns; - const columnIndex = 1; - - beforeEach(() => { - columns = [...defaultColumns]; - }); - - describe('when attrs is an object', () => { - it('should render Cell correctly', () => { - columns[columnIndex].attrs = { - 'data-test': 'test', - title: 'title', - className: 'attrs-class', - style: { - backgroundColor: 'attrs-style-test', - display: 'none', - textAlign: 'right' - } - }; - - wrapper = shallow( - - ); - - expect(wrapper.length).toBe(1); - expect(wrapper.find(Cell).get(columnIndex).props['data-test']) - .toEqual(columns[columnIndex].attrs['data-test']); - expect(wrapper.find(Cell).get(columnIndex).props.title) - .toEqual(columns[columnIndex].attrs.title); - expect(wrapper.find(Cell).get(columnIndex).props.className) - .toEqual(columns[columnIndex].attrs.className); - expect(wrapper.find(Cell).get(columnIndex).props.style) - .toEqual(columns[columnIndex].attrs.style); - }); - - describe('when column.title prop is defined', () => { - it('attrs.title should be overwrited', () => { - columns[columnIndex].title = true; - columns[columnIndex].attrs = { title: 'title' }; - - wrapper = shallow( - - ); - - expect(wrapper.find(Cell).get(columnIndex).props.title) - .toEqual(row[columns[columnIndex].dataField]); - }); - }); - - describe('when column.classes prop is defined', () => { - it('attrs.className should be overwrited', () => { - columns[columnIndex].classes = 'td-test-class'; - columns[columnIndex].attrs = { className: 'attrs-class' }; - - wrapper = shallow( - - ); - - expect(wrapper.find(Cell).get(columnIndex).props.className) - .toEqual(columns[columnIndex].classes); - }); - }); - - describe('when column.style prop is defined', () => { - it('attrs.style should be overwrited', () => { - columns[columnIndex].style = { backgroundColor: 'red' }; - columns[columnIndex].attrs = { style: { backgroundColor: 'attrs-style-test' } }; - - wrapper = shallow( - - ); - - expect(wrapper.find(Cell).get(columnIndex).props.style) - .toEqual(columns[columnIndex].style); - }); - }); - - describe('when column.align prop is defined', () => { - it('attrs.style.textAlign should be overwrited', () => { - columns[columnIndex].align = 'center'; - columns[columnIndex].attrs = { style: { textAlign: 'right' } }; - - wrapper = shallow( - - ); - - expect(wrapper.find(Cell).get(columnIndex).props.style.textAlign) - .toEqual(columns[columnIndex].align); - }); - }); - }); - - describe('when attrs is custom function', () => { - let attrsCallBack; - const customAttrs = { - 'data-test': 'test', - title: 'title' - }; - - beforeEach(() => { - attrsCallBack = sinon.stub().returns(customAttrs); - columns[columnIndex].attrs = attrsCallBack; - wrapper = shallow( - - ); - }); - - it('should render style.attrs correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(Cell).get(columnIndex).props['data-test']) - .toEqual(customAttrs['data-test']); - expect(wrapper.find(Cell).get(columnIndex).props.title) - .toEqual(customAttrs.title); - }); - - it('should call custom attrs function correctly', () => { - expect(attrsCallBack.callCount).toBe(1); - expect( - attrsCallBack.calledWith(row[columns[columnIndex].dataField], row, rowIndex, columnIndex) - ).toBe(true); - }); - }); - }); -}); diff --git a/packages/react-bootstrap-table2/test/row/aggregate-row.test.js b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js new file mode 100644 index 0000000..76aa625 --- /dev/null +++ b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js @@ -0,0 +1,295 @@ +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-consumer'; +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'; + +describe('Row Aggregator', () => { + let wrapper; + let rowAggregator; + const RowAggregatorWithSelection = bindSelection(RowAggregator); + const RowAggregatorWithExpansion = bindExpansion(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, + value: row[keyField], + columns, + keyField, + rowIndex, + ...mockBodyResolvedProps + }); + + describe('when selectRow is enable', () => { + 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 tr = wrapper.find('tr'); + expect(tr).toHaveLength(1); + expect(tr.props().onClick).toBeDefined(); + }); + }); + }); + + 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() }; + + beforeEach(() => { + const selectRow = { mode: 'radio' }; + wrapper = mount( + + + + ); + wrapper.find('tr').simulate('click'); + }); + + it('should call attrs.onClick correctly', () => { + expect(attrs.onClick).toHaveBeenCalledTimes(1); + }); + }); + + 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 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 is not 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 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 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 }; + + beforeEach(() => { + wrapper = mount( + + + + ); + wrapper.find(RowAggregator).props().selectRow.onRowSelect = jest.fn(); + wrapper.find('tr').simulate('click'); + }); + + 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/row/row-pure-content.test.js b/packages/react-bootstrap-table2/test/row/row-pure-content.test.js new file mode 100644 index 0000000..3000f93 --- /dev/null +++ b/packages/react-bootstrap-table2/test/row/row-pure-content.test.js @@ -0,0 +1,624 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +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', + text: 'ID' +}, { + dataField: 'name', + text: 'Name' +}, { + dataField: 'price', + text: 'Price' +}]; + +const keyField = 'id'; +const rowIndex = 1; + +describe('RowPureContent', () => { + 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('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(defaultColumns.length); + expect(wrapper.find(Cell).length).toBe(Object.keys(row).length); + }); + }); + + 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; + const EditingCellComponent = () => null; + beforeEach(() => { + wrapper = shallow( + ); + }); + + it('should render EditingCell component correctly', () => { + const EditingCell = wrapper.find(EditingCellComponent); + expect(wrapper.length).toBe(defaultColumns.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); + }); + }); + + describe('when column.hidden is true', () => { + beforeEach(() => { + const newColumns = [{ + dataField: 'id', + text: 'ID', + hidden: true + }, { + dataField: 'name', + text: 'Name' + }, { + dataField: 'price', + text: 'Price' + }]; + wrapper = shallow( + ); + }); + + it('should not render column with hidden value true', () => { + expect(wrapper.find(Cell).length).toBe(2); + }); + }); + + describe('when column.style prop is defined', () => { + let columns; + const columnIndex = 1; + + beforeEach(() => { + columns = [...defaultColumns]; + }); + + describe('when style is an object', () => { + beforeEach(() => { + columns[columnIndex].style = { backgroundColor: 'red' }; + wrapper = shallow( + + ); + }); + + it('should render Cell correctly', () => { + expect(wrapper.length).toBe(defaultColumns.length); + expect(wrapper.find(Cell).get(columnIndex).props.style).toEqual(columns[columnIndex].style); + }); + }); + + describe('when style is a function', () => { + const returnStyle = { backgroundColor: 'red' }; + let styleCallBack; + + beforeEach(() => { + styleCallBack = jest.fn().mockReturnValue(returnStyle); + columns[columnIndex].style = styleCallBack; + wrapper = shallow( + + ); + }); + + afterEach(() => { styleCallBack.mockClear(); }); + + it('should render Cell correctly', () => { + 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).toHaveBeenCalledTimes(1); + expect(styleCallBack).toHaveBeenCalledWith( + row[columns[columnIndex].dataField], row, rowIndex, columnIndex); + }); + }); + }); + + describe('when column.classes prop is defined', () => { + let columns; + const columnIndex = 1; + + beforeEach(() => { + columns = [...defaultColumns]; + }); + + describe('when classes is an object', () => { + beforeEach(() => { + columns[columnIndex].classes = 'td-test-class'; + wrapper = shallow( + + ); + }); + + it('should render Cell correctly', () => { + expect(wrapper.length).toBe(defaultColumns.length); + expect(wrapper.find(Cell).get(columnIndex).props.className) + .toEqual(columns[columnIndex].classes); + }); + }); + + describe('when classes is a function', () => { + const returnClasses = 'td-test-class'; + let classesCallBack; + + beforeEach(() => { + classesCallBack = jest.fn().mockReturnValue(returnClasses); + columns[columnIndex].classes = classesCallBack; + wrapper = shallow( + + ); + }); + + afterEach(() => { classesCallBack.mockClear(); }); + + it('should render Cell correctly', () => { + 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).toHaveBeenCalledTimes(1); + expect(classesCallBack).toHaveBeenCalledWith( + row[columns[columnIndex].dataField], row, rowIndex, columnIndex); + }); + }); + }); + + describe('when column.title prop is defined', () => { + let columns; + const columnIndex = 1; + + beforeEach(() => { + columns = [...defaultColumns]; + }); + + describe('when title is an string', () => { + beforeEach(() => { + columns[columnIndex].title = true; + wrapper = shallow( + + ); + }); + + it('should render Cell correctly', () => { + expect(wrapper.length).toBe(defaultColumns.length); + expect(wrapper.find(Cell).get(columnIndex).props.title) + .toEqual(row[columns[columnIndex].dataField]); + }); + }); + + describe('when title is a function', () => { + const returnTitle = 'test title'; + let titleCallBack; + + beforeEach(() => { + titleCallBack = jest.fn().mockReturnValue(returnTitle); + columns[columnIndex].title = titleCallBack; + wrapper = shallow( + + ); + }); + + afterEach(() => { titleCallBack.mockClear(); }); + + it('should render Cell correctly', () => { + 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).toHaveBeenCalledTimes(1); + expect(titleCallBack).toHaveBeenCalledWith( + row[columns[columnIndex].dataField], row, rowIndex, columnIndex); + }); + }); + }); + + describe('when column.events prop is defined', () => { + let columns; + const columnIndex = 1; + + beforeEach(() => { + columns = [...defaultColumns]; + columns[columnIndex].events = { + onClick: jest.fn() + }; + + wrapper = shallow( + + ); + }); + + it('should attachs DOM event successfully', () => { + expect(wrapper.length).toBe(defaultColumns.length); + expect(wrapper.find(Cell).get(columnIndex).props.onClick).toBeDefined(); + }); + }); + + describe('when column.align prop is defined', () => { + let columns; + const columnIndex = 1; + + beforeEach(() => { + columns = [...defaultColumns]; + }); + + describe('when align is a string', () => { + beforeEach(() => { + columns[columnIndex].align = 'right'; + wrapper = shallow( + + ); + }); + + it('should render Cell correctly', () => { + expect(wrapper.length).toBe(defaultColumns.length); + expect(wrapper.find(Cell).get(columnIndex).props.style.textAlign) + .toEqual(columns[columnIndex].align); + }); + }); + + describe('when align is a function', () => { + const returnAlign = 'right'; + let alignCallBack; + + beforeEach(() => { + alignCallBack = jest.fn().mockReturnValue(returnAlign); + columns[columnIndex].align = alignCallBack; + wrapper = shallow( + + ); + }); + + afterEach(() => { alignCallBack.mockClear(); }); + + it('should render Cell correctly', () => { + 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).toHaveBeenCalledTimes(1); + expect(alignCallBack).toHaveBeenCalledWith( + row[columns[columnIndex].dataField], row, rowIndex, columnIndex); + }); + }); + }); + + describe('when column.attrs prop is defined', () => { + let columns; + const columnIndex = 1; + + beforeEach(() => { + columns = [...defaultColumns]; + }); + + describe('when attrs is an object', () => { + it('should render Cell correctly', () => { + columns[columnIndex].attrs = { + 'data-test': 'test', + title: 'title', + className: 'attrs-class', + style: { + backgroundColor: 'attrs-style-test', + display: 'none', + textAlign: 'right' + } + }; + + wrapper = shallow( + + ); + + 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) + .toEqual(columns[columnIndex].attrs.title); + expect(wrapper.find(Cell).get(columnIndex).props.className) + .toEqual(columns[columnIndex].attrs.className); + expect(wrapper.find(Cell).get(columnIndex).props.style) + .toEqual(columns[columnIndex].attrs.style); + }); + + describe('when column.title prop is defined', () => { + it('attrs.title should be overwrited', () => { + columns[columnIndex].title = true; + columns[columnIndex].attrs = { title: 'title' }; + + wrapper = shallow( + + ); + + expect(wrapper.find(Cell).get(columnIndex).props.title) + .toEqual(row[columns[columnIndex].dataField]); + }); + }); + + describe('when column.classes prop is defined', () => { + it('attrs.className should be overwrited', () => { + columns[columnIndex].classes = 'td-test-class'; + columns[columnIndex].attrs = { className: 'attrs-class' }; + + wrapper = shallow( + + ); + + expect(wrapper.find(Cell).get(columnIndex).props.className) + .toEqual(columns[columnIndex].classes); + }); + }); + + describe('when column.style prop is defined', () => { + it('attrs.style should be overwrited', () => { + columns[columnIndex].style = { backgroundColor: 'red' }; + columns[columnIndex].attrs = { style: { backgroundColor: 'attrs-style-test' } }; + + wrapper = shallow( + + ); + + expect(wrapper.find(Cell).get(columnIndex).props.style) + .toEqual(columns[columnIndex].style); + }); + }); + + describe('when column.align prop is defined', () => { + it('attrs.style.textAlign should be overwrited', () => { + columns[columnIndex].align = 'center'; + columns[columnIndex].attrs = { style: { textAlign: 'right' } }; + + wrapper = shallow( + + ); + + expect(wrapper.find(Cell).get(columnIndex).props.style.textAlign) + .toEqual(columns[columnIndex].align); + }); + }); + }); + + describe('when attrs is custom function', () => { + let attrsCallBack; + const customAttrs = { + 'data-test': 'test', + title: 'title' + }; + + beforeEach(() => { + attrsCallBack = jest.fn().mockReturnValue(customAttrs); + columns[columnIndex].attrs = attrsCallBack; + wrapper = shallow( + + ); + }); + + afterEach(() => { attrsCallBack.mockClear(); }); + + it('should render style.attrs correctly', () => { + 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) + .toEqual(customAttrs.title); + }); + + it('should call custom attrs function correctly', () => { + 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..ab1ca05 --- /dev/null +++ b/packages/react-bootstrap-table2/test/row/should-updater.test.js @@ -0,0 +1,164 @@ +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('shouldUpdateByCellEditing', () => { + describe('when nextProps.clickToEdit and nexrProps.dbclickToEdit both are negative', () => { + beforeEach(() => { + props = { + editingRowIdx: null, + rowIndex: 0 + }; + 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().shouldUpdateByCellEditing(nextProps)).toBeTruthy(); + }); + }); + + describe('when props.editingRowIdx eq props.rowIndex but nextProps.editingRowIdx is null', () => { + beforeEach(() => { + props = { + clickToEdit: true, + editingRowIdx: 0, + rowIndex: 0 + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, editingRowIdx: null }; + expect(wrapper.instance().shouldUpdateByCellEditing(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..3d0f6c4 --- /dev/null +++ b/packages/react-bootstrap-table2/test/row/simple-row.test.js @@ -0,0 +1,216 @@ +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('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', () => { + 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(); + }); + }); +}); 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..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,16 +1,25 @@ 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 + mode: ROW_SELECT_DISABLED, + selected: [], + hideSelectColumn: true +}; + +export const expandRowResolvedProps = { + renderer: undefined, + expanded: [] }; export const cellEditResolvedProps = { - mode: UNABLE_TO_CELL_EDIT + mode: null, + nonEditableRows: [] }; 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 }; diff --git a/yarn.lock b/yarn.lock index b44934f..b20bdb4 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" @@ -6920,15 +6958,19 @@ 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" 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,17 +6987,18 @@ 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" - 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"