diff --git a/README.md b/README.md
index 7d90d42..39f14b1 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ Rebuilt [react-bootstrap-table](https://github.com/AllenFang/react-bootstrap-tab
* [`react-bootstrap-table2-editor`](https://www.npmjs.com/package/react-bootstrap-table2-editor)
* [`react-bootstrap-table2-paginator`](https://www.npmjs.com/package/react-bootstrap-table2-paginator)
* [`react-bootstrap-table2-overlay`](https://www.npmjs.com/package/react-bootstrap-table2-overlay)
+* [`react-bootstrap-table2-toolkit`](https://www.npmjs.com/package/react-bootstrap-table2-toolkit)
This can help your application with less bundled size and also help us have clean design to avoid handling to much logic in kernal module(SRP).
diff --git a/docs/README.md b/docs/README.md
index 14782fe..b5bd0bd 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -9,6 +9,7 @@
#### Optional
* [remote](#remote)
+* [bootstrap4](#bootstrap4)
* [loading](#loading)
* [caption](#caption)
* [striped](#striped)
@@ -21,6 +22,7 @@
* [headerClasses](#headerClasses)
* [cellEdit](#cellEdit)
* [selectRow](#selectRow)
+* [expandRow](#expandRow)
* [rowStyle](#rowStyle)
* [rowClasses](#rowClasses)
* [rowEvents](#rowEvents)
@@ -66,6 +68,9 @@ remote={ { pagination: true, filter: false, sort: false } }
There is a special case for remote pagination, even you only specified the pagination need to handle as remote, `react-bootstrap-table2` will handle all the table changes(filter, sort etc) as remote mode, because `react-bootstrap-table2` only know the data of current page, but filtering, searching or sort need to work on overall data.
+### bootstrap4 - [Bool]
+`true` to indicate your bootstrap version is 4. Default version is 3.
+
### loading - [Bool]
Telling if table is loading or not, for example: waiting data loading, filtering etc. It's **only** valid when [`remote`](#remote) is enabled.
When `loading` is `true`, `react-bootstrap-table2` will attend to render a overlay on table via [`overlay`](#overlay) prop, if [`overlay`](#overlay) prop is not given, `react-bootstrap-table2` will ignore the overlay rendering.
@@ -122,6 +127,9 @@ Makes table cells editable, please see [cellEdit definition](./cell-edit.md) for
### selectRow - [Object]
Makes table rows selectable, please see [selectRow definition](./row-selection.md) for more detail.
+### expandRow - [Object]
+Makes table rows expandable, please see [expandRow definition](./row-expand.md) for more detail.
+
### rowStyle = [Object | Function]
Custom the style of table rows:
diff --git a/docs/columns.md b/docs/columns.md
index 4b78e36..330b7ad 100644
--- a/docs/columns.md
+++ b/docs/columns.md
@@ -38,6 +38,10 @@ Available properties in a column object:
* [editorRenderer](#editorRenderer)
* [filter](#filter)
* [filterValue](#filterValue)
+* [csvType](#csvType)
+* [csvFormatter](#csvFormatter)
+* [csvText](#csvText)
+* [csvExport](#csvExport)
Following is a most simplest and basic usage:
@@ -91,6 +95,10 @@ dataField: 'address.city'
* `rowIndex`
* [`formatExtraData`](#formatExtraData)
+> Attention:
+> Don't use any state data or any external data in formatter function, please pass them via [`formatExtraData`](#formatExtraData).
+> In addition, please make formatter function as pure function as possible as you can.
+
## column.headerFormatter - [Function]
`headerFormatter` allow you to customize the header column and only accept a callback function which take three arguments and a JSX/String are expected for return.
@@ -685,4 +693,17 @@ A final `String` value you want to be filtered.
filter: textFilter(),
filterValue: (cell, row) => owners[cell]
}
-```
\ No newline at end of file
+```
+
+## column.csvType - [Object]
+Default is `String`. Currently, the available value is `String` and `Number`. If `Number` assigned, the cell value will not wrapped with double quote.
+
+## column.csvFormatter - [Function]
+
+This is same as [`column.formatter`](#formatter). But `csvFormatter` only for CSV export and called when export CSV.
+
+## column.csvText - [String]
+Custom the CSV header cell, Default is [`column.text`](#text).
+
+## column.csvExport - [Bool]
+Default is `true`, `false` will hide this column when export CSV.
diff --git a/docs/migration.md b/docs/migration.md
index 60edbd2..ffe7861 100644
--- a/docs/migration.md
+++ b/docs/migration.md
@@ -22,6 +22,8 @@ Currently, **I still can't implement all the mainly features in legacy `react-bo
* Pagination Addons
* [`react-bootstrap-table2-overlay`](https://www.npmjs.com/package/react-bootstrap-table2-overlay)
* Overlay/Loading Addons
+* [`react-bootstrap-table2-toolkit`](https://www.npmjs.com/package/react-bootstrap-table2-toolkit)
+ * Table Toolkits, like search, csv etc.
This can help your application with less bundled size and also help `react-bootstrap-table2` have clean design to avoid handling to much logic in kernel module(SRP). Hence, which means you probably need to install above addons when you need specific features.
@@ -88,7 +90,7 @@ Please see [available filter configuration](https://react-bootstrap-table.github
- [x] Custom Select Filter
- [X] Number Filter
- [X] Date Filter
-- [ ] Array Filter
+- [X] Array Filter
- [X] Programmatically Filter
Remember to install [`react-bootstrap-table2-filter`](https://www.npmjs.com/package/react-bootstrap-table2-filter) firstly.
@@ -113,6 +115,34 @@ Remember to install [`react-bootstrap-table2-paginator`](https://www.npmjs.com/p
No big changes for pagination, but still can't custom the pagination list, button and sizePerPage dropdown.
+## Table Search
+The usage of search functionality is a little bit different from legacy search. The mainly different thing is developer have to render the search input field, we do believe it will be very flexible for all the developers who want to custom the search position or search field itself.
+
+- [x] Custom search component and position
+- [x] Custom search value
+- [ ] Clear search
+- [ ] Multiple search
+- [ ] Strict search
+
+## Row Expand
+- [x] Expand Row Events
+- [x] Expand Row Indicator
+- [x] Expand Row Management
+- [x] Custom Expand Row Indicators
+- [ ] Compatiable with Row Selection
+- [ ] Expand Column position
+- [ ] Expand Column Style/Class
+
+## Export CSV
+Export CSV functionality is like search, which is one of functionality in the `react-bootstrap-table2-toolkit`. All of the legacy functions we already implemented.
+
## Remote
-> It's totally different in `react-bootstrap-table2`. Please [see](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-remote.html).
\ No newline at end of file
+> It's totally different in `react-bootstrap-table2`. Please [see](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-remote.html).
+
+
+## Row insert/Delete
+Not support yet
+
+## Keyboard Navigation
+Not support yet
diff --git a/docs/row-expand.md b/docs/row-expand.md
new file mode 100644
index 0000000..1aa958a
--- /dev/null
+++ b/docs/row-expand.md
@@ -0,0 +1,129 @@
+
+# Row expand
+`react-bootstrap-table2` supports the row expand feature. By passing prop `expandRow` to enable this functionality.
+
+> Default is click to expand/collapse a row. In addition, we don't support any way to chagne this mechanism!
+
+## Required
+* [renderer (**required**)](#renderer)
+
+## Optional
+* [expanded](#expanded)
+* [nonExpandable](#nonExpandable)
+* [onExpand](#onExpand)
+* [onExpandAll](#onExpandAll)
+* [showExpandColumn](#showExpandColumn)
+* [expandColumnRenderer](#expandColumnRenderer)
+* [expandHeaderColumnRenderer](#expandHeaderColumnRenderer)
+
+### expandRow.renderer - [Function]
+
+Specify the content of expand row, `react-bootstrap-table2` will pass a row object as argument and expect return a react element.
+
+#### values
+* **row**
+
+#### examples
+
+```js
+const expandRow = {
+ renderer: row => (
+
{ sourceCode }
@@ -77,10 +100,25 @@ class Container extends React.Component {
this.handleTableChange = this.handleTableChange.bind(this);
}
- handleTableChange = (type, { page, sizePerPage, filters }) => {
+ handleTableChange = (type, { page, sizePerPage, filters, sortField, sortOrder, cellEdit }) => {
const currentIndex = (page - 1) * sizePerPage;
setTimeout(() => {
- const result = products.filter((row) => {
+ // Handle cell editing
+ if (type === 'cellEdit') {
+ const { rowId, dataField, newValue } = cellEdit;
+ products = products.map((row) => {
+ if (row.id === rowId) {
+ const newRow = { ...row };
+ newRow[dataField] = newValue;
+ return newRow;
+ }
+ return row;
+ });
+ }
+ let result = products;
+
+ // Handle column filters
+ result = result.filter((row) => {
let valid = true;
for (const dataField in filters) {
const { filterVal, filterType, comparator } = filters[dataField];
@@ -96,6 +134,26 @@ class Container extends React.Component {
}
return valid;
});
+ // Handle column sort
+ if (sortOrder === 'asc') {
+ result = result.sort((a, b) => {
+ if (a[sortField] > b[sortField]) {
+ return 1;
+ } else if (b[sortField] > a[sortField]) {
+ return -1;
+ }
+ return 0;
+ });
+ } else {
+ result = result.sort((a, b) => {
+ if (a[sortField] > b[sortField]) {
+ return -1;
+ } else if (b[sortField] > a[sortField]) {
+ return 1;
+ }
+ return 0;
+ });
+ }
this.setState(() => ({
page,
data: result.slice(currentIndex, currentIndex + sizePerPage),
@@ -120,18 +178,29 @@ class Container extends React.Component {
}
`;
+const defaultSorted = [{
+ dataField: 'name',
+ order: 'desc'
+}];
+
+const cellEditProps = {
+ mode: 'click'
+};
+
const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => (
When remote.pagination is enabled, the filtering,
sorting and searching will also change to remote mode automatically
{ sourceCode }
@@ -157,10 +226,24 @@ class Container extends React.Component {
this.handleTableChange = this.handleTableChange.bind(this);
}
- handleTableChange = (type, { page, sizePerPage, filters }) => {
+ handleTableChange = (type, { page, sizePerPage, filters, sortField, sortOrder, cellEdit }) => {
const currentIndex = (page - 1) * sizePerPage;
setTimeout(() => {
- const result = products.filter((row) => {
+ // Handle cell editing
+ if (type === 'cellEdit') {
+ const { rowId, dataField, newValue } = cellEdit;
+ products = products.map((row) => {
+ if (row.id === rowId) {
+ const newRow = { ...row };
+ newRow[dataField] = newValue;
+ return newRow;
+ }
+ return row;
+ });
+ }
+ let result = products;
+ // Handle column filters
+ result = result.filter((row) => {
let valid = true;
for (const dataField in filters) {
const { filterVal, filterType, comparator } = filters[dataField];
@@ -176,6 +259,26 @@ class Container extends React.Component {
}
return valid;
});
+ // Handle column sort
+ if (sortOrder === 'asc') {
+ result = result.sort((a, b) => {
+ if (a[sortField] > b[sortField]) {
+ return 1;
+ } else if (b[sortField] > a[sortField]) {
+ return -1;
+ }
+ return 0;
+ });
+ } else {
+ result = result.sort((a, b) => {
+ if (a[sortField] > b[sortField]) {
+ return -1;
+ } else if (b[sortField] > a[sortField]) {
+ return 1;
+ }
+ return 0;
+ });
+ }
this.setState(() => ({
page,
data: result.slice(currentIndex, currentIndex + sizePerPage),
diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js
new file mode 100644
index 0000000..ad4d5a1
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js
@@ -0,0 +1,175 @@
+/* eslint guard-for-in: 0 */
+/* eslint no-restricted-syntax: 0 */
+import React from 'react';
+import PropTypes from 'prop-types';
+import BootstrapTable from 'react-bootstrap-table-next';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const { SearchBar } = Search;
+const products = productsGenerator(17);
+
+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';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID',
+}, {
+ dataField: 'name',
+ text: 'Product Name',
+ filter: textFilter()
+}, {
+ dataField: 'price',
+ text: 'Product Price',
+ filter: textFilter()
+}];
+
+const RemoteFilter = props => (
+
+
+ {
+ toolkitprops => [
+ ,
+
+ ]
+ }
+
+ { sourceCode }
+
+);
+
+class Container extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ data: products
+ };
+ }
+
+ handleTableChange = (type, { filters }) => {
+ setTimeout(() => {
+ const result = products.filter((row) => {
+ let valid = true;
+ for (const dataField in filters) {
+ const { filterVal, filterType, comparator } = filters[dataField];
+
+ if (filterType === 'TEXT') {
+ if (comparator === Comparator.LIKE) {
+ valid = row[dataField].toString().indexOf(filterVal) > -1;
+ } else {
+ valid = row[dataField] === filterVal;
+ }
+ }
+ if (!valid) break;
+ }
+ return valid;
+ });
+ this.setState(() => ({
+ data: result
+ }));
+ }, 2000);
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+`;
+
+const RemoteFilter = props => (
+
+
+ {
+ toolkitprops => [
+ ,
+
+ ]
+ }
+
+ { sourceCode }
+
+);
+
+RemoteFilter.propTypes = {
+ data: PropTypes.array.isRequired,
+ onTableChange: PropTypes.func.isRequired
+};
+
+class Container extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ data: products
+ };
+ }
+
+ handleTableChange = (type, { searchText }) => {
+ setTimeout(() => {
+ const result = products.filter((row) => {
+ for (let cidx = 0; cidx < columns.length; cidx += 1) {
+ const column = columns[cidx];
+ let targetValue = row[column.dataField];
+ if (targetValue !== null && typeof targetValue !== 'undefined') {
+ targetValue = targetValue.toString().toLowerCase();
+ if (targetValue.indexOf(searchText) > -1) {
+ return true;
+ }
+ }
+ }
+ return false;
+ });
+ this.setState(() => ({
+ data: result
+ }));
+ }, 2000);
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default Container;
diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/custom-expand-column.js b/packages/react-bootstrap-table2-example/examples/row-expand/custom-expand-column.js
new file mode 100644
index 0000000..067046d
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/row-expand/custom-expand-column.js
@@ -0,0 +1,107 @@
+/* eslint react/prop-types: 0 */
+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,
+ expandHeaderColumnRenderer: ({ isAnyExpands }) => {
+ if (isAnyExpands) {
+ return
- ;
+ }
+ return
+ ;
+ },
+ expandColumnRenderer: ({ expanded }) => {
+ if (expanded) {
+ return (
+
-
+ );
+ }
+ return (
+
...
+ );
+ }
+};
+
+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,
+ expandHeaderColumnRenderer: ({ isAnyExpands }) => {
+ if (isAnyExpands) {
+ return
- ;
+ }
+ return
+ ;
+ },
+ expandColumnRenderer: ({ expanded }) => {
+ if (expanded) {
+ return (
+
-
+ );
+ }
+ return (
+
...
+ );
+ }
+};
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/expand-column.js b/packages/react-bootstrap-table2-example/examples/row-expand/expand-column.js
new file mode 100644
index 0000000..edfff16
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/row-expand/expand-column.js
@@ -0,0 +1,74 @@
+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
+};
+
+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 () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/expand-hooks.js b/packages/react-bootstrap-table2-example/examples/row-expand/expand-hooks.js
new file mode 100644
index 0000000..96fcbfe
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/row-expand/expand-hooks.js
@@ -0,0 +1,97 @@
+/* eslint no-console: 0 */
+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,
+ onExpand: (row, isExpand, rowIndex, e) => {
+ console.log(row.id);
+ console.log(isExpand);
+ console.log(rowIndex);
+ console.log(e);
+ },
+ onExpandAll: (isExpandAll, rows, e) => {
+ console.log(isExpandAll);
+ console.log(rows);
+ console.log(e);
+ }
+};
+
+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,
+ onExpand: (row, isExpand, rowIndex, e) => {
+ console.log(row.id);
+ console.log(isExpand);
+ console.log(rowIndex);
+ console.log(e);
+ },
+ onExpandAll: (isExpandAll, rows, e) => {
+ console.log(isExpandAll);
+ console.log(rows);
+ console.log(e);
+ }
+};
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/expand-management.js b/packages/react-bootstrap-table2-example/examples/row-expand/expand-management.js
new file mode 100644
index 0000000..83b1588
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/row-expand/expand-management.js
@@ -0,0 +1,138 @@
+/* eslint no-unused-vars: 0 */
+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 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'
+}];
+
+class RowExpandManagment extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { expanded: [0, 1] };
+ }
+
+ handleBtnClick = () => {
+ if (!this.state.expanded.includes(2)) {
+ this.setState(() => ({
+ expanded: [...this.state.expanded, 2]
+ }));
+ } else {
+ this.setState(() => ({
+ expanded: this.state.expanded.filter(x => x !== 2)
+ }));
+ }
+ }
+
+ handleOnExpand = (row, isExpand, rowIndex, e) => {
+ if (isExpand) {
+ this.setState(() => ({
+ expanded: [...this.state.expanded, row.id]
+ }));
+ } else {
+ this.setState(() => ({
+ expanded: this.state.expanded.filter(x => x !== row.id)
+ }));
+ }
+ }
+
+ render() {
+ 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
+
+ ),
+ expanded: this.state.expanded,
+ onExpand: this.handleOnExpand
+ };
+ return (
+
+ Expand/Collapse 3rd row
+
+ { sourceCode }
+
+ );
+ }
+}
+`;
+
+export default class RowExpandManagment extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = { expanded: [0, 1] };
+ }
+
+ handleBtnClick = () => {
+ if (!this.state.expanded.includes(2)) {
+ this.setState(() => ({
+ expanded: [...this.state.expanded, 2]
+ }));
+ } else {
+ this.setState(() => ({
+ expanded: this.state.expanded.filter(x => x !== 2)
+ }));
+ }
+ }
+
+ handleOnExpand = (row, isExpand, rowIndex, e) => {
+ if (isExpand) {
+ this.setState(() => ({
+ expanded: [...this.state.expanded, row.id]
+ }));
+ } else {
+ this.setState(() => ({
+ expanded: this.state.expanded.filter(x => x !== row.id)
+ }));
+ }
+ }
+
+ render() {
+ 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
+
+ ),
+ expanded: this.state.expanded,
+ onExpand: this.handleOnExpand
+ };
+ return (
+
+ Expand/Collapse 3rd row
+
+ { sourceCode }
+
+ );
+ }
+}
diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/index.js b/packages/react-bootstrap-table2-example/examples/row-expand/index.js
new file mode 100644
index 0000000..0a08904
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/row-expand/index.js
@@ -0,0 +1,72 @@
+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
+
+ )
+};
+
+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
+
+ )
+};
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/non-expandable-rows.js b/packages/react-bootstrap-table2-example/examples/row-expand/non-expandable-rows.js
new file mode 100644
index 0000000..6d1ef32
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/row-expand/non-expandable-rows.js
@@ -0,0 +1,75 @@
+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
+
+ ),
+ nonExpandable: [1, 3]
+};
+
+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
+
+ ),
+ nonExpandable: [1, 3]
+};
+
+
+`;
+
+export default () => (
+
+
The second and fourth row is not expandable
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js b/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js
new file mode 100644
index 0000000..fcb4d19
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js
@@ -0,0 +1,102 @@
+/* eslint react/prop-types: 0 */
+/* eslint no-unused-vars: 0 */
+import React from 'react';
+
+import BootstrapTable from 'react-bootstrap-table-next';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+import Code from 'components/common/code-block';
+import { jobsGenerator1 } from 'utils/common';
+
+const { SearchBar } = Search;
+const products = jobsGenerator1(5);
+
+const owners = ['Allen', 'Bob', 'Cat'];
+const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money'];
+
+const columns = [{
+ dataField: 'id',
+ text: 'Job ID',
+ searchable: false,
+ hidden: true
+}, {
+ dataField: 'owner',
+ text: 'Job Owner',
+ formatter: (cell, row) => owners[cell],
+ filterValue: (cell, row) => owners[cell] // we will search the value after filterValue called
+}, {
+ dataField: 'type',
+ text: 'Job Type',
+ formatter: (cell, row) => types[cell],
+ filterValue: (cell, row) => types[cell] // we will search the value after filterValue called
+}];
+
+const sourceCode = `\
+import BootstrapTable from 'react-bootstrap-table-next';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+
+const { SearchBar } = Search;
+const owners = ['Allen', 'Bob', 'Cat'];
+const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money'];
+
+const columns = [{
+ dataField: 'id',
+ text: 'Job ID',
+ searchable: false,
+ hidden: true
+}, {
+ dataField: 'owner',
+ text: 'Job Owner',
+ formatter: (cell, row) => owners[cell],
+ filterValue: (cell, row) => owners[cell] // we will search the value after filterValue called
+}, {
+ dataField: 'type',
+ text: 'Job Type',
+ formatter: (cell, row) => types[cell],
+ filterValue: (cell, row) => types[cell] // we will search the value after filterValue called
+}];
+
+
+ {
+ props => (
+
+
Try to Search Bob, Cat or Allen instead of 0, 1 or 2
+
+
+
+
+ )
+ }
+
+`;
+
+export default () => (
+
+
+ {
+ props => (
+
+
Try to Search Bob, Cat or Allen instead of 0, 1 or 2
+
+
+
+
+ )
+ }
+
+
{ sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js b/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js
new file mode 100644
index 0000000..3e146db
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js
@@ -0,0 +1,95 @@
+/* eslint react/prop-types: 0 */
+import React from 'react';
+
+import BootstrapTable from 'react-bootstrap-table-next';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const { SearchBar } = Search;
+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';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+
+const { SearchBar } = Search;
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+
+ {
+ props => (
+
+
Input something at below input field:
+
+
+
+
+ )
+ }
+
+`;
+
+export default () => (
+
+
+ {
+ props => (
+
+
Input something at below input field:
+
+
+
+
+ )
+ }
+
+
{ sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js b/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js
new file mode 100644
index 0000000..78bf504
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js
@@ -0,0 +1,116 @@
+/* eslint react/prop-types: 0 */
+/* eslint no-return-assign: 0 */
+import React from 'react';
+
+import BootstrapTable from 'react-bootstrap-table-next';
+import ToolkitProvider from 'react-bootstrap-table2-toolkit';
+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';
+import ToolkitProvider from 'react-bootstrap-table2-toolkit';
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const MySearch = (props) => {
+ let input;
+ const handleClick = () => {
+ props.onSearch(input.value);
+ };
+ return (
+
+ input = n }
+ type="text"
+ />
+ Click to Search!!
+
+ );
+};
+
+
+ {
+ props => (
+
+
+
+
+
+ )
+ }
+
+`;
+
+const MySearch = (props) => {
+ let input;
+ const handleClick = () => {
+ props.onSearch(input.value);
+ };
+ return (
+
+ input = n }
+ type="text"
+ />
+ Click to Search!!
+
+ );
+};
+
+export default () => (
+
+
+ {
+ props => (
+
+
+
+
+
+ )
+ }
+
+
{ sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/search/index.js b/packages/react-bootstrap-table2-example/examples/search/index.js
new file mode 100644
index 0000000..3a8effc
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/search/index.js
@@ -0,0 +1,83 @@
+/* eslint react/prop-types: 0 */
+import React from 'react';
+
+import BootstrapTable from 'react-bootstrap-table-next';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const { SearchBar } = Search;
+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';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+
+const { SearchBar } = Search;
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+
+ {
+ props => (
+
+
Input something at below input field:
+
+
+
+
+ )
+ }
+
+`;
+
+export default () => (
+
+
+ {
+ props => (
+
+
Input something at below input field:
+
+
+
+
+ )
+ }
+
+
{ sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/search/search-formatted.js b/packages/react-bootstrap-table2-example/examples/search/search-formatted.js
new file mode 100644
index 0000000..71575ff
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/search/search-formatted.js
@@ -0,0 +1,85 @@
+/* eslint react/prop-types: 0 */
+import React from 'react';
+
+import BootstrapTable from 'react-bootstrap-table-next';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const { SearchBar } = Search;
+const products = productsGenerator();
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price',
+ formatter: cell => `USD ${cell}`
+}];
+
+const sourceCode = `\
+import BootstrapTable from 'react-bootstrap-table-next';
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+
+const { SearchBar } = Search;
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price',
+ formatter: cell => \`USD \${cell}\` // we will search the data after formatted
+}];
+
+
+ {
+ props => (
+
+
Try to Search USD at below input field:
+
+
+
+
+ )
+ }
+
+`;
+
+export default () => (
+
+
+ {
+ props => (
+
+
Try to Search USD at below input field:
+
+
+
+
+ )
+ }
+
+
{ sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/package.json b/packages/react-bootstrap-table2-example/package.json
index eef8377..f92fe0b 100644
--- a/packages/react-bootstrap-table2-example/package.json
+++ b/packages/react-bootstrap-table2-example/package.json
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table2-example",
- "version": "0.1.11",
+ "version": "1.0.1",
"description": "",
"main": "index.js",
"private": true,
@@ -14,8 +14,8 @@
"license": "ISC",
"peerDependencies": {
"prop-types": "^15.0.0",
- "react": "^15.0.0",
- "react-dom": "^15.0.0"
+ "react": "^16.3.0",
+ "react-dom": "^116.3.0"
},
"dependencies": {
"bootstrap": "^3.3.7"
diff --git a/packages/react-bootstrap-table2-example/src/utils/common.js b/packages/react-bootstrap-table2-example/src/utils/common.js
index b8f954e..dbf7b7e 100644
--- a/packages/react-bootstrap-table2-example/src/utils/common.js
+++ b/packages/react-bootstrap-table2-example/src/utils/common.js
@@ -41,6 +41,14 @@ export const jobsGenerator = (quantity = 5) =>
type: jobType[Math.floor((Math.random() * 4) + 1)]
}));
+export const jobsGenerator1 = (quantity = 5) =>
+ Array.from({ length: quantity }, (value, index) => ({
+ id: index,
+ name: `Job name ${index}`,
+ owner: Math.floor((Math.random() * 2) + 1),
+ type: Math.floor((Math.random() * 4) + 1)
+ }));
+
export const todosGenerator = (quantity = 5) =>
Array.from({ length: quantity }, (value, index) => ({
id: index,
@@ -60,3 +68,17 @@ export const stockGenerator = (quantity = 5) =>
}));
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+export const productsExpandRowsGenerator = (quantity = 5, callback) => {
+ if (callback) return Array.from({ length: quantity }, callback);
+
+ // if no given callback, retrun default product format.
+ return (
+ Array.from({ length: quantity }, (value, index) => ({
+ id: index,
+ name: `Item name ${index}`,
+ price: 2100 + index,
+ expand: productsQualityGenerator(index)
+ }))
+ );
+};
diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js
index 77dc4c9..24db59c 100644
--- a/packages/react-bootstrap-table2-example/stories/index.js
+++ b/packages/react-bootstrap-table2-example/stories/index.js
@@ -11,6 +11,7 @@ import StripHoverCondensedTable from 'examples/basic/striped-hover-condensed-tab
import NoDataTable from 'examples/basic/no-data-table';
import CustomizedIdClassesTable from 'examples/basic/customized-id-classes';
import CaptionTable from 'examples/basic/caption-table';
+import LargeTable from 'examples/basic/large-table';
// work on columns
import NestedDataTable from 'examples/columns/nested-data-table';
@@ -46,6 +47,9 @@ import SelectFilter from 'examples/column-filter/select-filter';
import SelectFilterWithDefaultValue from 'examples/column-filter/select-filter-default-value';
import SelectFilterComparator from 'examples/column-filter/select-filter-like-comparator';
import CustomSelectFilter from 'examples/column-filter/custom-select-filter';
+import MultiSelectFilter from 'examples/column-filter/multi-select-filter';
+import MultiSelectFilterDefaultValue from 'examples/column-filter/multi-select-filter-default-value';
+import CustomMultiSelectFilter from 'examples/column-filter/custom-multi-select-filter';
import NumberFilter from 'examples/column-filter/number-filter';
import NumberFilterWithDefaultValue from 'examples/column-filter/number-filter-default-value';
import CustomNumberFilter from 'examples/column-filter/custom-number-filter';
@@ -56,8 +60,10 @@ import ProgrammaticallyTextFilter from 'examples/column-filter/programmatically-
import ProgrammaticallySelectFilter from 'examples/column-filter/programmatically-select-filter';
import ProgrammaticallyNumberFilter from 'examples/column-filter/programmatically-number-filter';
import ProgrammaticallyDateFilter from 'examples/column-filter/programmatically-date-filter';
+import ProgrammaticallyMultiSelectFilter from 'examples/column-filter/programmatically-multi-select-filter';
import CustomFilter from 'examples/column-filter/custom-filter';
import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter';
+import ClearAllFilters from 'examples/column-filter/clear-all-filters';
// work on rows
import RowStyleTable from 'examples/rows/row-style';
@@ -108,11 +114,35 @@ import SelectionBgColorTable from 'examples/row-selection/selection-bgcolor';
import SelectionHooks from 'examples/row-selection/selection-hooks';
import HideSelectionColumnTable from 'examples/row-selection/hide-selection-column';
+// work on row expand
+import BasicRowExpand from 'examples/row-expand';
+import RowExpandManagement from 'examples/row-expand/expand-management';
+import NonExpandableRows from 'examples/row-expand/non-expandable-rows';
+import ExpandColumn from 'examples/row-expand/expand-column';
+import CustomExpandColumn from 'examples/row-expand/custom-expand-column';
+import ExpandHooks from 'examples/row-expand/expand-hooks';
+
// pagination
import PaginationTable from 'examples/pagination';
import PaginationHooksTable from 'examples/pagination/pagination-hooks';
import CustomPaginationTable from 'examples/pagination/custom-pagination';
+// search
+import SearchTable from 'examples/search';
+import DefaultCustomSearch from 'examples/search/default-custom-search';
+import FullyCustomSearch from 'examples/search/fully-custom-search';
+import SearchFormattedData from 'examples/search/search-formatted';
+import CustomSearchValue from 'examples/search/custom-search-value';
+
+// CSV
+import ExportCSV from 'examples/csv';
+import CSVFormatter from 'examples/csv/csv-column-formatter';
+import CustomCSVHeader from 'examples/csv/custom-csv-header';
+import HideCSVColumn from 'examples/csv/hide-column';
+import CSVColumnType from 'examples/csv/csv-column-type';
+import CustomCSVButton from 'examples/csv/custom-csv-button';
+import CustomCSV from 'examples/csv/custom-csv';
+
// loading overlay
import EmptyTableOverlay from 'examples/loading-overlay/empty-table-overlay';
import TableOverlay from 'examples/loading-overlay/table-overlay';
@@ -121,6 +151,7 @@ import TableOverlay from 'examples/loading-overlay/table-overlay';
import RemoteSort from 'examples/remote/remote-sort';
import RemoteFilter from 'examples/remote/remote-filter';
import RemotePaginationTable from 'examples/remote/remote-pagination';
+import RemoteSearch from 'examples/remote/remote-search';
import RemoteCellEdit from 'examples/remote/remote-celledit';
import RemoteAll from 'examples/remote/remote-all';
@@ -144,7 +175,8 @@ storiesOf('Basic Table', module)
.add('borderless table', () =>
)
.add('Indication For Empty Table', () =>
)
.add('Customized id and class table', () =>
)
- .add('Table with caption', () =>
);
+ .add('Table with caption', () =>
)
+ .add('Large Table', () =>
);
storiesOf('Work on Columns', module)
.add('Display Nested Data', () =>
)
@@ -178,6 +210,8 @@ storiesOf('Column Filter', module)
.add('Select Filter', () =>
)
.add('Select Filter with Default Value', () =>
)
.add('Select Filter with Comparator', () =>
)
+ .add('MultiSelect Filter', () =>
)
+ .add('MultiSelect Filter with Default Value', () =>
)
.add('Number Filter', () =>
)
.add('Number Filter with Default Value', () =>
)
.add('Date Filter', () =>
)
@@ -186,13 +220,16 @@ storiesOf('Column Filter', module)
.add('Custom Select Filter', () =>
)
.add('Custom Number Filter', () =>
)
.add('Custom Date Filter', () =>
)
+ .add('Custom MultiSelect Filter', () =>
)
.add('Custom Filter Value', () =>
)
.add('Programmatically Text Filter', () =>
)
.add('Programmatically Select Filter', () =>
)
.add('Programmatically Number Filter', () =>
)
.add('Programmatically Date Filter', () =>
)
+ .add('Programmatically Multi Select Filter', () =>
)
.add('Custom Filter', () =>
)
- .add('Advance Custom Filter', () =>
);
+ .add('Advance Custom Filter', () =>
)
+ .add('Clear All Filters', () =>
);
storiesOf('Work on Rows', module)
.add('Customize Row Style', () =>
)
@@ -243,11 +280,35 @@ storiesOf('Row Selection', module)
.add('Selection Hooks', () =>
)
.add('Hide Selection Column', () =>
);
+storiesOf('Row Expand', module)
+ .add('Basic Row Expand', () =>
)
+ .add('Expand Management', () =>
)
+ .add('Non Expandabled Rows', () =>
)
+ .add('Expand Indicator', () =>
)
+ .add('Custom Expand Indicator', () =>
)
+ .add('Expand Hooks', () =>
);
+
storiesOf('Pagination', module)
.add('Basic Pagination Table', () =>
)
.add('Pagination Hooks', () =>
)
.add('Custom Pagination', () =>
);
+storiesOf('Table Search', module)
+ .add('Basic Search Table', () =>
)
+ .add('Default Custom Search', () =>
)
+ .add('Fully Custom Search', () =>
)
+ .add('Search Fromatted Value', () =>
)
+ .add('Custom Search Value', () =>
);
+
+storiesOf('Export CSV', module)
+ .add('Basic Export CSV', () =>
)
+ .add('Format CSV Column', () =>
)
+ .add('Custom CSV Header', () =>
)
+ .add('Hide CSV Column', () =>
)
+ .add('CSV Column Type', () =>
)
+ .add('Custom CSV Button', () =>
)
+ .add('Custom CSV', () =>
);
+
storiesOf('EmptyTableOverlay', module)
.add('Empty Table Overlay', () =>
)
.add('Table Overlay', () =>
);
@@ -256,5 +317,6 @@ storiesOf('Remote', module)
.add('Remote Sort', () =>
)
.add('Remote Filter', () =>
)
.add('Remote Pagination', () =>
)
+ .add('Remote Search', () =>
)
.add('Remote Cell Editing', () =>
)
.add('Remote All', () =>
);
diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/search/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/search/_index.scss
new file mode 100644
index 0000000..8c47ed2
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/stories/stylesheet/search/_index.scss
@@ -0,0 +1,3 @@
+.custome-search-field {
+ background-color: #c8e6c9;
+}
\ No newline at end of file
diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss
index c38e02c..14959a6 100644
--- a/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss
+++ b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss
@@ -10,4 +10,5 @@
@import "row-selection/index";
@import "rows/index";
@import "sort/index";
+@import "search/index";
@import "loading-overlay/index";
diff --git a/packages/react-bootstrap-table2-filter/README.md b/packages/react-bootstrap-table2-filter/README.md
index 0af54da..05c7c9e 100644
--- a/packages/react-bootstrap-table2-filter/README.md
+++ b/packages/react-bootstrap-table2-filter/README.md
@@ -18,6 +18,7 @@ You can get all types of filters via import and these filters are a factory func
* TextFilter
* SelectFilter
+* MultiSelectFilter
* NumberFilter
* DateFilter
* CustomFilter
@@ -114,6 +115,52 @@ const qualityFilter = selectFilter({
// omit...
```
+## MultiSelect Filter
+
+A quick example:
+
+```js
+import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter';
+
+// omit...
+const selectOptions = {
+ 0: 'good',
+ 1: 'Bad',
+ 2: 'unknown'
+};
+
+const columns = [
+ ..., {
+ dataField: 'quality',
+ text: 'Product Quailty',
+ formatter: cell => selectOptions[cell],
+ filter: multiSelectFilter({
+ options: selectOptions
+ })
+}];
+
+
+```
+
+Following is an example for custom select filter:
+
+```js
+import filterFactory, { multiSelectFilter, Comparator } from 'react-bootstrap-table2-filter';
+// omit...
+
+const qualityFilter = multiSelectFilter({
+ options: selectOptions,
+ placeholder: 'My Custom PlaceHolder', // custom the input placeholder
+ className: 'my-custom-text-filter', // custom classname on input
+ defaultValue: '2', // default filtering value
+ comparator: Comparator.LIKE, // default is Comparator.EQ
+ style: { ... }, // your custom styles on input
+ withoutEmptyOption: true // hide the default select option
+});
+
+// omit...
+```
+
## Number Filter
```js
@@ -131,7 +178,7 @@ const columns = [..., {
Numner filter is same as other filter, you can custom the number filter via `numberFilter` factory function:
```js
-import filterFactory, { selectFilter, Comparator } from 'react-bootstrap-table2-filter';
+import filterFactory, { selectFilter, Comparator, numberFilter } from 'react-bootstrap-table2-filter';
// omit...
const numberFilter = numberFilter({
@@ -167,7 +214,7 @@ const columns = [..., {
```
-> **Notes:** date filter accept a Javascript Date object in your raw data.
+> **Notes:** date filter accept a Javascript Date object in your raw data and you have to use `column.formatter` to make it as your prefer string result
Date filter is same as other filter, you can custom the date filter via `dateFilter` factory function:
@@ -240,4 +287,4 @@ Following properties is valid in `FILTER_TYPES`:
* TEXT
* SELECT
* NUMBER
-* DATE
\ No newline at end of file
+* DATE
diff --git a/packages/react-bootstrap-table2-filter/index.js b/packages/react-bootstrap-table2-filter/index.js
index fe60268..af67dfc 100644
--- a/packages/react-bootstrap-table2-filter/index.js
+++ b/packages/react-bootstrap-table2-filter/index.js
@@ -1,13 +1,14 @@
import TextFilter from './src/components/text';
import SelectFilter from './src/components/select';
+import MultiSelectFilter from './src/components/multiselect';
import NumberFilter from './src/components/number';
import DateFilter from './src/components/date';
-import wrapperFactory from './src/wrapper';
+import createContext from './src/context';
import * as Comparison from './src/comparison';
import { FILTER_TYPE } from './src/const';
export default (options = {}) => ({
- wrapperFactory,
+ createContext,
options
});
@@ -25,6 +26,11 @@ export const selectFilter = (props = {}) => ({
props
});
+export const multiSelectFilter = (props = {}) => ({
+ Filter: MultiSelectFilter,
+ props
+});
+
export const numberFilter = (props = {}) => ({
Filter: NumberFilter,
props
diff --git a/packages/react-bootstrap-table2-filter/package.json b/packages/react-bootstrap-table2-filter/package.json
index e153df0..48ef849 100644
--- a/packages/react-bootstrap-table2-filter/package.json
+++ b/packages/react-bootstrap-table2-filter/package.json
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table2-filter",
- "version": "0.3.0",
+ "version": "1.0.0",
"description": "it's a column filter addon for react-bootstrap-table2",
"main": "./lib/index.js",
"repository": {
@@ -38,7 +38,7 @@
],
"peerDependencies": {
"prop-types": "^15.0.0",
- "react": "^16.0.0",
- "react-dom": "^16.0.0"
+ "react": "^16.3.0",
+ "react-dom": "^16.3.0"
}
}
diff --git a/packages/react-bootstrap-table2-filter/src/components/date.js b/packages/react-bootstrap-table2-filter/src/components/date.js
index 02468aa..be6ecd5 100644
--- a/packages/react-bootstrap-table2-filter/src/components/date.js
+++ b/packages/react-bootstrap-table2-filter/src/components/date.js
@@ -36,7 +36,7 @@ class DateFilter extends Component {
const comparator = this.dateFilterComparator.value;
const date = this.inputDate.value;
if (comparator && date) {
- this.applyFilter(date, comparator);
+ this.applyFilter(date, comparator, true);
}
// export onFilter function to allow users to access
@@ -92,14 +92,18 @@ class DateFilter extends Component {
return defaultDate;
}
- applyFilter(value, comparator) {
- if (!comparator || !value) {
- return;
- }
+ applyFilter(value, comparator, isInitial) {
+ // if (!comparator || !value) {
+ // return;
+ // }
const { column, onFilter, delay } = this.props;
const execute = () => {
- const date = typeof value !== 'object' ? new Date(value) : value;
- onFilter(column, FILTER_TYPE.DATE)({ date, comparator });
+ // Incoming value should always be a string, and the defaultDate
+ // above is implemented as an empty string, so we can just check for that.
+ // instead of parsing an invalid Date. The filter function will interpret
+ // null as an empty date field
+ const date = value === '' ? null : new Date(value);
+ onFilter(column, FILTER_TYPE.DATE, isInitial)({ date, comparator });
};
if (delay) {
this.timeout = setTimeout(() => { execute(); }, delay);
diff --git a/packages/react-bootstrap-table2-filter/src/components/multiselect.js b/packages/react-bootstrap-table2-filter/src/components/multiselect.js
new file mode 100644
index 0000000..5f4623b
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/src/components/multiselect.js
@@ -0,0 +1,152 @@
+/* eslint react/require-default-props: 0 */
+/* eslint no-return-assign: 0 */
+/* eslint no-param-reassign: 0 */
+/* eslint react/no-unused-prop-types: 0 */
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { LIKE, EQ } from '../comparison';
+import { FILTER_TYPE } from '../const';
+
+
+function optionsEquals(currOpts, prevOpts) {
+ const keys = Object.keys(currOpts);
+ for (let i = 0; i < keys.length; i += 1) {
+ if (currOpts[keys[i]] !== prevOpts[keys[i]]) {
+ return false;
+ }
+ }
+ return Object.keys(currOpts).length === Object.keys(prevOpts).length;
+}
+
+const getSelections = container =>
+ Array.from(container.selectedOptions).map(item => item.value);
+
+class MultiSelectFilter extends Component {
+ constructor(props) {
+ super(props);
+ this.filter = this.filter.bind(this);
+ this.applyFilter = this.applyFilter.bind(this);
+ const isSelected = props.defaultValue.map(item => props.options[item]).length > 0;
+ this.state = { isSelected };
+ }
+
+ componentDidMount() {
+ const { getFilter } = this.props;
+
+ const value = getSelections(this.selectInput);
+ if (value && value.length > 0) {
+ this.applyFilter(value);
+ }
+
+ // export onFilter function to allow users to access
+ if (getFilter) {
+ getFilter((filterVal) => {
+ this.selectInput.value = filterVal;
+ this.applyFilter(filterVal);
+ });
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ let needFilter = false;
+ if (this.props.defaultValue !== prevProps.defaultValue) {
+ needFilter = true;
+ } else if (!optionsEquals(this.props.options, prevProps.options)) {
+ needFilter = true;
+ }
+ if (needFilter) {
+ this.applyFilter(this.selectInput.value);
+ }
+ }
+
+ getOptions() {
+ const optionTags = [];
+ const { options, placeholder, column, withoutEmptyOption } = this.props;
+ if (!withoutEmptyOption) {
+ optionTags.push((
+
{ placeholder || `Select ${column.text}...` }
+ ));
+ }
+ Object.keys(options).forEach(key =>
+ optionTags.push(
{ options[key] } )
+ );
+ return optionTags;
+ }
+
+ cleanFiltered() {
+ const value = (this.props.defaultValue !== undefined) ? this.props.defaultValue : [];
+ this.selectInput.value = value;
+ this.applyFilter(value);
+ }
+
+ applyFilter(value) {
+ if (value.length === 1 && value[0] === '') {
+ value = [];
+ }
+ this.setState(() => ({ isSelected: value.length > 0 }));
+ this.props.onFilter(this.props.column, FILTER_TYPE.MULTISELECT)(value);
+ }
+
+ filter(e) {
+ const value = getSelections(e.target);
+ this.applyFilter(value);
+ }
+
+ render() {
+ const {
+ style,
+ className,
+ defaultValue,
+ onFilter,
+ column,
+ options,
+ comparator,
+ withoutEmptyOption,
+ caseSensitive,
+ getFilter,
+ ...rest
+ } = this.props;
+
+ const selectClass =
+ `filter select-filter form-control ${className} ${this.state.isSelected ? '' : 'placeholder-selected'}`;
+
+ return (
+
this.selectInput = n }
+ style={ style }
+ multiple
+ className={ selectClass }
+ onChange={ this.filter }
+ onClick={ e => e.stopPropagation() }
+ defaultValue={ defaultValue !== undefined ? defaultValue : '' }
+ >
+ { this.getOptions() }
+
+ );
+ }
+}
+
+MultiSelectFilter.propTypes = {
+ onFilter: PropTypes.func.isRequired,
+ column: PropTypes.object.isRequired,
+ options: PropTypes.object.isRequired,
+ comparator: PropTypes.oneOf([LIKE, EQ]),
+ placeholder: PropTypes.string,
+ style: PropTypes.object,
+ className: PropTypes.string,
+ withoutEmptyOption: PropTypes.bool,
+ defaultValue: PropTypes.array,
+ caseSensitive: PropTypes.bool,
+ getFilter: PropTypes.func
+};
+
+MultiSelectFilter.defaultProps = {
+ defaultValue: [],
+ className: '',
+ withoutEmptyOption: false,
+ comparator: EQ,
+ caseSensitive: true
+};
+
+export default MultiSelectFilter;
diff --git a/packages/react-bootstrap-table2-filter/src/components/number.js b/packages/react-bootstrap-table2-filter/src/components/number.js
index 960aa5d..447eed2 100644
--- a/packages/react-bootstrap-table2-filter/src/components/number.js
+++ b/packages/react-bootstrap-table2-filter/src/components/number.js
@@ -36,7 +36,7 @@ class NumberFilter extends Component {
const comparator = this.numberFilterComparator.value;
const number = this.numberFilter.value;
if (comparator && number) {
- onFilter(column, FILTER_TYPE.NUMBER)({ number, comparator });
+ onFilter(column, FILTER_TYPE.NUMBER, true)({ number, comparator });
}
// export onFilter function to allow users to access
diff --git a/packages/react-bootstrap-table2-filter/src/components/select.js b/packages/react-bootstrap-table2-filter/src/components/select.js
index 7c4d285..e505138 100644
--- a/packages/react-bootstrap-table2-filter/src/components/select.js
+++ b/packages/react-bootstrap-table2-filter/src/components/select.js
@@ -29,7 +29,7 @@ class SelectFilter extends Component {
const value = this.selectInput.value;
if (value && value !== '') {
- onFilter(column, FILTER_TYPE.SELECT)(value);
+ onFilter(column, FILTER_TYPE.SELECT, true)(value);
}
// export onFilter function to allow users to access
diff --git a/packages/react-bootstrap-table2-filter/src/components/text.js b/packages/react-bootstrap-table2-filter/src/components/text.js
index 3af8ae2..a17c3ea 100644
--- a/packages/react-bootstrap-table2-filter/src/components/text.js
+++ b/packages/react-bootstrap-table2-filter/src/components/text.js
@@ -23,7 +23,7 @@ class TextFilter extends Component {
const defaultValue = this.input.value;
if (defaultValue) {
- onFilter(this.props.column, FILTER_TYPE.TEXT)(defaultValue);
+ onFilter(this.props.column, FILTER_TYPE.TEXT, true)(defaultValue);
}
// export onFilter function to allow users to access
diff --git a/packages/react-bootstrap-table2-filter/src/const.js b/packages/react-bootstrap-table2-filter/src/const.js
index ccb4d78..e685640 100644
--- a/packages/react-bootstrap-table2-filter/src/const.js
+++ b/packages/react-bootstrap-table2-filter/src/const.js
@@ -1,6 +1,7 @@
export const FILTER_TYPE = {
TEXT: 'TEXT',
SELECT: 'SELECT',
+ MULTISELECT: 'MULTISELECT',
NUMBER: 'NUMBER',
DATE: 'DATE'
};
diff --git a/packages/react-bootstrap-table2-filter/src/context.js b/packages/react-bootstrap-table2-filter/src/context.js
new file mode 100644
index 0000000..790ce00
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/src/context.js
@@ -0,0 +1,99 @@
+/* eslint react/prop-types: 0 */
+/* eslint react/require-default-props: 0 */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { filters } from './filter';
+import { LIKE, EQ } from './comparison';
+import { FILTER_TYPE } from './const';
+
+export default (
+ _,
+ isRemoteFiltering,
+ handleFilterChange
+) => {
+ const FilterContext = React.createContext();
+
+ class FilterProvider extends React.Component {
+ static propTypes = {
+ data: PropTypes.array.isRequired,
+ columns: PropTypes.array.isRequired
+ }
+
+ constructor(props) {
+ super(props);
+ this.currFilters = {};
+ this.onFilter = this.onFilter.bind(this);
+ this.onExternalFilter = this.onExternalFilter.bind(this);
+ }
+
+ componentDidMount() {
+ if (isRemoteFiltering() && Object.keys(this.currFilters).length > 0) {
+ handleFilterChange(this.currFilters);
+ }
+ }
+
+ onFilter(column, filterType, initialize = false) {
+ return (filterVal) => {
+ // watch out here if migration to context API, #334
+ const currFilters = Object.assign({}, this.currFilters);
+ const { dataField, filter } = column;
+
+ const needClearFilters =
+ !_.isDefined(filterVal) ||
+ filterVal === '' ||
+ filterVal.length === 0;
+
+ if (needClearFilters) {
+ delete currFilters[dataField];
+ } else {
+ // select default comparator is EQ, others are LIKE
+ const {
+ comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE),
+ caseSensitive = false
+ } = filter.props;
+ currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive };
+ }
+
+ this.currFilters = currFilters;
+
+ if (isRemoteFiltering()) {
+ if (!initialize) {
+ handleFilterChange(this.currFilters);
+ }
+ return;
+ }
+
+ this.forceUpdate();
+ };
+ }
+
+ onExternalFilter(column, filterType) {
+ return (value) => {
+ this.onFilter(column, filterType)(value);
+ };
+ }
+
+ render() {
+ let { data } = this.props;
+ if (!isRemoteFiltering()) {
+ data = filters(data, this.props.columns, _)(this.currFilters);
+ }
+ return (
+
+ { this.props.children }
+
+ );
+ }
+ }
+
+ return {
+ Provider: FilterProvider,
+ Consumer: FilterContext.Consumer
+ };
+};
diff --git a/packages/react-bootstrap-table2-filter/src/filter.js b/packages/react-bootstrap-table2-filter/src/filter.js
index a65d52a..07454c4 100644
--- a/packages/react-bootstrap-table2-filter/src/filter.js
+++ b/packages/react-bootstrap-table2-filter/src/filter.js
@@ -187,6 +187,26 @@ export const filterByDate = _ => (
});
};
+export const filterByArray = _ => (
+ data,
+ dataField,
+ { filterVal, comparator }
+) => {
+ if (filterVal.length === 0) return data;
+ const refinedFilterVal = filterVal
+ .filter(x => _.isDefined(x))
+ .map(x => x.toString());
+ return data.filter((row) => {
+ const cell = _.get(row, dataField);
+ let cellStr = _.isDefined(cell) ? cell.toString() : '';
+ if (comparator === EQ) {
+ return refinedFilterVal.indexOf(cellStr) !== -1;
+ }
+ cellStr = cellStr.toLocaleUpperCase();
+ return refinedFilterVal.some(item => cellStr.indexOf(item.toLocaleUpperCase()) !== -1);
+ });
+};
+
export const filterFactory = _ => (filterType) => {
let filterFn;
switch (filterType) {
@@ -194,6 +214,9 @@ export const filterFactory = _ => (filterType) => {
case FILTER_TYPE.SELECT:
filterFn = filterByText(_);
break;
+ case FILTER_TYPE.MULTISELECT:
+ filterFn = filterByArray(_);
+ break;
case FILTER_TYPE.NUMBER:
filterFn = filterByNumber(_);
break;
@@ -206,9 +229,9 @@ export const filterFactory = _ => (filterType) => {
return filterFn;
};
-export const filters = (store, columns, _) => (currFilters) => {
+export const filters = (data, columns, _) => (currFilters) => {
const factory = filterFactory(_);
- let result = store.getAllData();
+ let result = data;
let filterFn;
Object.keys(currFilters).forEach((dataField) => {
const filterObj = currFilters[dataField];
diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js
deleted file mode 100644
index c2b24e8..0000000
--- a/packages/react-bootstrap-table2-filter/src/wrapper.js
+++ /dev/null
@@ -1,99 +0,0 @@
-/* eslint no-param-reassign: 0 */
-
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { filters } from './filter';
-import { LIKE, EQ } from './comparison';
-import { FILTER_TYPE } from './const';
-
-export default (Base, {
- _,
- remoteResolver
-}) =>
- class FilterWrapper extends remoteResolver(Component) {
- static propTypes = {
- store: PropTypes.object.isRequired,
- columns: PropTypes.array.isRequired
- }
-
- constructor(props) {
- super(props);
- this.state = { currFilters: {}, isDataChanged: props.isDataChanged || false };
- this.onFilter = this.onFilter.bind(this);
- this.onExternalFilter = this.onExternalFilter.bind(this);
- }
-
- componentWillReceiveProps({ isDataChanged, store, columns }) {
- // consider to use lodash.isEqual
- const isRemoteFilter = this.isRemoteFiltering() || this.isRemotePagination();
- if (isRemoteFilter ||
- JSON.stringify(this.state.currFilters) !== JSON.stringify(store.filters)) {
- // I think this condition only isRemoteFilter is enough
- store.filteredData = store.getAllData();
- this.setState(() => ({ isDataChanged: true, currFilters: store.filters }));
- } else {
- if (Object.keys(this.state.currFilters).length > 0) {
- store.filteredData = filters(store, columns, _)(this.state.currFilters);
- }
- this.setState(() => ({ isDataChanged }));
- }
- }
-
- /**
- * filter the table like below:
- * onFilter(column, filterType)(filterVal)
- * @param {Object} column
- * @param {String} filterType
- * @param {String} filterVal - user input for filtering.
- */
- onFilter(column, filterType) {
- return (filterVal) => {
- const { store, columns } = this.props;
- // watch out here if migration to context API, #334
- const currFilters = Object.assign({}, store.filters);
- const { dataField, filter } = column;
-
- if (!_.isDefined(filterVal) || filterVal === '') {
- delete currFilters[dataField];
- } else {
- // select default comparator is EQ, others are LIKE
- const {
- comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE),
- caseSensitive = false
- } = filter.props;
- currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive };
- }
-
- store.filters = currFilters;
-
- if (this.isRemoteFiltering() || this.isRemotePagination()) {
- this.handleRemoteFilterChange();
- // when remote filtering is enable, dont set currFilters state
- // in the componentWillReceiveProps,
- // it's the key point that we can know the filter is changed
- return;
- }
-
- store.filteredData = filters(store, columns, _)(currFilters);
- this.setState(() => ({ currFilters, isDataChanged: true }));
- };
- }
-
- onExternalFilter(column, filterType) {
- return (value) => {
- this.onFilter(column, filterType)(value);
- };
- }
-
- render() {
- return (
-
- );
- }
- };
diff --git a/packages/react-bootstrap-table2-filter/test/components/date.test.js b/packages/react-bootstrap-table2-filter/test/components/date.test.js
index 366a675..9fb3776 100644
--- a/packages/react-bootstrap-table2-filter/test/components/date.test.js
+++ b/packages/react-bootstrap-table2-filter/test/components/date.test.js
@@ -124,7 +124,7 @@ describe('Date Filter', () => {
it('should do onFilter correctly when exported function was executed', () => {
expect(onFilter).toHaveBeenCalledTimes(1);
- expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE);
+ expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE, undefined);
expect(onFilterFirstReturn).toHaveBeenCalledTimes(1);
expect(onFilterFirstReturn).toHaveBeenCalledWith({ comparator, date });
});
@@ -148,7 +148,7 @@ describe('Date Filter', () => {
it('should calling onFilter on componentDidMount', () => {
expect(onFilter).toHaveBeenCalledTimes(1);
- expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE);
+ expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE, true);
expect(onFilterFirstReturn).toHaveBeenCalledTimes(1);
// expect(onFilterFirstReturn).toHaveBeenCalledWith({ comparator, date });
});
diff --git a/packages/react-bootstrap-table2-filter/test/components/multiselect.test.js b/packages/react-bootstrap-table2-filter/test/components/multiselect.test.js
new file mode 100644
index 0000000..a4e214a
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/test/components/multiselect.test.js
@@ -0,0 +1,354 @@
+import 'jsdom-global/register';
+import React from 'react';
+import sinon from 'sinon';
+import { mount } from 'enzyme';
+import MultiSelectFilter from '../../src/components/multiselect';
+import { FILTER_TYPE } from '../../src/const';
+
+
+describe('Multi Select Filter', () => {
+ let wrapper;
+ let instance;
+
+ // onFilter(x)(y) = filter result
+ const onFilter = sinon.stub();
+ const onFilterFirstReturn = sinon.stub();
+
+ const column = {
+ dataField: 'quality',
+ text: 'Product Quality'
+ };
+
+ const options = {
+ 0: 'Bad',
+ 1: 'Good',
+ 2: 'Unknown'
+ };
+
+ afterEach(() => {
+ onFilter.reset();
+ onFilterFirstReturn.reset();
+
+ onFilter.returns(onFilterFirstReturn);
+ });
+
+ describe('initialization', () => {
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ });
+
+ it('should have correct state', () => {
+ expect(instance.state.isSelected).toBeFalsy();
+ });
+
+ it('should rendering component successfully', () => {
+ expect(wrapper).toHaveLength(1);
+ expect(wrapper.find('select')).toHaveLength(1);
+ expect(wrapper.find('.select-filter')).toHaveLength(1);
+ expect(wrapper.find('.placeholder-selected')).toHaveLength(1);
+ });
+
+ it('should rendering select options correctly', () => {
+ const select = wrapper.find('select');
+ expect(select.find('option')).toHaveLength(Object.keys(options).length + 1);
+ expect(select.childAt(0).text()).toEqual(`Select ${column.text}...`);
+
+ Object.keys(options).forEach((key, i) => {
+ expect(select.childAt(i + 1).prop('value')).toEqual(key);
+ expect(select.childAt(i + 1).text()).toEqual(options[key]);
+ });
+ });
+ });
+
+ describe('when defaultValue is defined', () => {
+ let defaultValue;
+
+ describe('and it is valid', () => {
+ beforeEach(() => {
+ defaultValue = ['0'];
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ });
+
+ it('should have correct state', () => {
+ expect(instance.state.isSelected).toBeTruthy();
+ });
+
+ it('should rendering component successfully', () => {
+ expect(wrapper).toHaveLength(1);
+ expect(wrapper.find('.placeholder-selected')).toHaveLength(0);
+ });
+
+ it('should calling onFilter on componentDidMount', () => {
+ expect(onFilter.calledOnce).toBeTruthy();
+ expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
+ expect(onFilterFirstReturn.calledOnce).toBeTruthy();
+ expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy();
+ });
+ });
+ });
+
+ describe('when props.getFilter is defined', () => {
+ let programmaticallyFilter;
+
+ const filterValue = ['foo'];
+
+ const getFilter = (filter) => {
+ programmaticallyFilter = filter;
+ };
+
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+
+ programmaticallyFilter(filterValue);
+ });
+
+ it('should do onFilter correctly when exported function was executed', () => {
+ expect(onFilter.calledOnce).toBeTruthy();
+ expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
+ expect(onFilterFirstReturn.calledOnce).toBeTruthy();
+ expect(onFilterFirstReturn.calledWith(filterValue)).toBeTruthy();
+ });
+
+ it('should setState correctly when exported function was executed', () => {
+ expect(instance.state.isSelected).toBeTruthy();
+ });
+ });
+
+ describe('when placeholder is defined', () => {
+ const placeholder = 'test';
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ });
+
+ it('should rendering component successfully', () => {
+ expect(wrapper).toHaveLength(1);
+ const select = wrapper.find('select');
+ expect(select.childAt(0).text()).toEqual(placeholder);
+ });
+ });
+
+ describe('when style is defined', () => {
+ const style = { backgroundColor: 'red' };
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ });
+
+ it('should rendering component successfully', () => {
+ expect(wrapper).toHaveLength(1);
+ expect(wrapper.find('select').prop('style')).toEqual(style);
+ });
+ });
+
+ describe('when withoutEmptyOption is defined', () => {
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ });
+
+ it('should rendering select without default empty option', () => {
+ const select = wrapper.find('select');
+ expect(select.find('option')).toHaveLength(Object.keys(options).length);
+ });
+ });
+
+ describe('componentDidUpdate', () => {
+ let prevProps;
+
+ describe('when props.defaultValue is diff from prevProps.defaultValue', () => {
+ const defaultValue = ['0'];
+
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ prevProps = {
+ column,
+ options,
+ defaultValue: ['1']
+ };
+ instance = wrapper.instance();
+ instance.componentDidUpdate(prevProps);
+ });
+
+ it('should update', () => {
+ expect(onFilter.callCount).toBe(2);
+ expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
+ expect(onFilterFirstReturn.callCount).toBe(2);
+ expect(onFilterFirstReturn.calledWith(instance.props.defaultValue)).toBeTruthy();
+ });
+ });
+
+ describe('when props.options is diff from prevProps.options', () => {
+ const defaultValue = ['0'];
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ prevProps = {
+ column,
+ options
+ };
+ instance = wrapper.instance();
+ instance.componentDidUpdate(prevProps);
+ });
+
+ it('should update', () => {
+ expect(onFilter.callCount).toBe(2);
+ expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
+ expect(onFilterFirstReturn.callCount).toBe(2);
+ expect(onFilterFirstReturn.calledWith(instance.props.defaultValue)).toBeTruthy();
+ });
+ });
+ });
+
+ describe('cleanFiltered', () => {
+ describe('when props.defaultValue is defined', () => {
+ const defaultValue = ['0'];
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ instance.cleanFiltered();
+ });
+
+ it('should setting state correctly', () => {
+ expect(instance.state.isSelected).toBeTruthy();
+ });
+
+ it('should calling onFilter correctly', () => {
+ expect(onFilter.callCount).toBe(2);
+ expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
+ expect(onFilterFirstReturn.callCount).toBe(2);
+ expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy();
+ });
+ });
+
+ describe('when props.defaultValue is not defined', () => {
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ instance.cleanFiltered();
+ });
+
+ it('should setting state correctly', () => {
+ expect(instance.state.isSelected).toBeFalsy();
+ });
+
+ it('should calling onFilter correctly', () => {
+ expect(onFilter.callCount).toBe(1);
+ expect(onFilterFirstReturn.callCount).toBe(1);
+ });
+ });
+ });
+
+ describe('applyFilter', () => {
+ const values = ['2'];
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ instance.applyFilter(values);
+ });
+
+ it('should setting state correctly', () => {
+ expect(instance.state.isSelected).toBeTruthy();
+ });
+
+ it('should calling onFilter correctly', () => {
+ expect(onFilter.calledOnce).toBeTruthy();
+ expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
+ expect(onFilterFirstReturn.calledOnce).toBeTruthy();
+ expect(onFilterFirstReturn.calledWith(values)).toBeTruthy();
+ });
+ });
+
+ describe('filter', () => {
+ const event = { target: { selectedOptions: [{ value: 'tester' }] } };
+
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ instance = wrapper.instance();
+ instance.filter(event);
+ });
+
+ it('should setting state correctly', () => {
+ expect(instance.state.isSelected).toBeTruthy();
+ });
+
+ it('should calling onFilter correctly', () => {
+ expect(onFilter.calledOnce).toBeTruthy();
+ expect(onFilter.calledWith(column, FILTER_TYPE.MULTISELECT)).toBeTruthy();
+ expect(onFilterFirstReturn.calledOnce).toBeTruthy();
+ expect(onFilterFirstReturn.calledWith(
+ event.target.selectedOptions.map(item => item.value))).toBeTruthy();
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2-filter/test/components/number.test.js b/packages/react-bootstrap-table2-filter/test/components/number.test.js
index 3586ec4..1556dac 100644
--- a/packages/react-bootstrap-table2-filter/test/components/number.test.js
+++ b/packages/react-bootstrap-table2-filter/test/components/number.test.js
@@ -147,7 +147,7 @@ describe('Number Filter', () => {
it('should calling onFilter on componentDidMount', () => {
expect(onFilter.calledOnce).toBeTruthy();
- expect(onFilter.calledWith(column, FILTER_TYPE.NUMBER)).toBeTruthy();
+ expect(onFilter.calledWith(column, FILTER_TYPE.NUMBER, true)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith({ number: `${number}`, comparator })).toBeTruthy();
});
diff --git a/packages/react-bootstrap-table2-filter/test/components/select.test.js b/packages/react-bootstrap-table2-filter/test/components/select.test.js
index 85f9686..80d30d7 100644
--- a/packages/react-bootstrap-table2-filter/test/components/select.test.js
+++ b/packages/react-bootstrap-table2-filter/test/components/select.test.js
@@ -91,7 +91,7 @@ describe('Select Filter', () => {
it('should calling onFilter on componentDidMount', () => {
expect(onFilter.calledOnce).toBeTruthy();
- expect(onFilter.calledWith(column, FILTER_TYPE.SELECT)).toBeTruthy();
+ expect(onFilter.calledWith(column, FILTER_TYPE.SELECT, true)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy();
});
diff --git a/packages/react-bootstrap-table2-filter/test/components/text.test.js b/packages/react-bootstrap-table2-filter/test/components/text.test.js
index 5a8d297..fbc849f 100644
--- a/packages/react-bootstrap-table2-filter/test/components/text.test.js
+++ b/packages/react-bootstrap-table2-filter/test/components/text.test.js
@@ -65,7 +65,7 @@ describe('Text Filter', () => {
it('should calling onFilter on componentDidMount', () => {
expect(onFilter.calledOnce).toBeTruthy();
- expect(onFilter.calledWith(column, FILTER_TYPE.TEXT)).toBeTruthy();
+ expect(onFilter.calledWith(column, FILTER_TYPE.TEXT, true)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy();
});
diff --git a/packages/react-bootstrap-table2-filter/test/context.test.js b/packages/react-bootstrap-table2-filter/test/context.test.js
new file mode 100644
index 0000000..f4b97ca
--- /dev/null
+++ b/packages/react-bootstrap-table2-filter/test/context.test.js
@@ -0,0 +1,253 @@
+import 'jsdom-global/register';
+import React from 'react';
+import { shallow } from 'enzyme';
+import _ from 'react-bootstrap-table-next/src/utils';
+import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table';
+
+import {
+ FILTER_TYPE
+} from '../src/const';
+import createFilterContext from '../src/context';
+import { textFilter } from '../index';
+
+describe('FilterContext', () => {
+ let wrapper;
+ let FilterContext;
+
+ const data = [{
+ id: 1,
+ name: 'A'
+ }, {
+ id: 2,
+ name: 'B'
+ }];
+
+ const columns = [{
+ dataField: 'id',
+ text: 'ID',
+ filter: textFilter()
+ }, {
+ dataField: 'name',
+ text: 'Name',
+ filter: textFilter()
+ }];
+
+ const mockBase = jest.fn((props => (
+
+ )));
+
+ const handleFilterChange = jest.fn();
+
+ function shallowContext(
+ enableRemote = false
+ ) {
+ mockBase.mockReset();
+ handleFilterChange.mockReset();
+ FilterContext = createFilterContext(
+ _,
+ jest.fn().mockReturnValue(enableRemote),
+ handleFilterChange
+ );
+
+ return (
+
+
+ {
+ filterProps => mockBase(filterProps)
+ }
+
+
+ );
+ }
+
+ describe('default render', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ });
+
+ it('should have correct Provider property after calling createFilterContext', () => {
+ expect(FilterContext.Provider).toBeDefined();
+ });
+
+ it('should have correct Consumer property after calling createFilterContext', () => {
+ expect(FilterContext.Consumer).toBeDefined();
+ });
+
+ it('should have correct currFilters', () => {
+ expect(wrapper.instance().currFilters).toEqual({});
+ });
+
+ it('should pass correct cell editing props to children element', () => {
+ expect(wrapper.length).toBe(1);
+ expect(mockBase).toHaveBeenCalledWith({
+ data,
+ onFilter: wrapper.instance().onFilter,
+ onExternalFilter: wrapper.instance().onExternalFilter
+ });
+ });
+ });
+
+ describe('when remote filter is enable', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext(true));
+ wrapper.render();
+ wrapper.instance().currFilters = { price: { filterVal: 20, filterType: FILTER_TYPE.TEXT } };
+ });
+
+ it('should pass original data without internal filtering', () => {
+ expect(wrapper.length).toBe(1);
+ expect(mockBase).toHaveBeenCalledWith({
+ data,
+ onFilter: wrapper.instance().onFilter,
+ onExternalFilter: wrapper.instance().onExternalFilter
+ });
+ });
+ });
+
+ describe('componentDidMount', () => {
+ describe('when remote filter is disabled', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ wrapper.instance().componentDidMount();
+ });
+
+ it('should not call handleFilterChange', () => {
+ expect(handleFilterChange).toHaveBeenCalledTimes(0);
+ });
+ });
+
+ describe('when remote filter is enable but currFilters is empty', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext(true));
+ wrapper.render();
+ wrapper.instance().componentDidMount();
+ });
+
+ it('should not call handleFilterChange', () => {
+ expect(handleFilterChange).toHaveBeenCalledTimes(0);
+ });
+ });
+
+ describe('when remote filter is enable and currFilters is not empty', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext(true));
+ wrapper.instance().currFilters.price = { filterVal: 40, filterType: FILTER_TYPE.TEXT };
+ });
+
+ it('should not call handleFilterChange', () => {
+ wrapper.instance().componentDidMount();
+ expect(handleFilterChange).toHaveBeenCalledTimes(1);
+ expect(handleFilterChange).toHaveBeenCalledWith(wrapper.instance().currFilters);
+ });
+ });
+ });
+
+ describe('onFilter', () => {
+ let instance;
+ describe('when filterVal is empty or undefined', () => {
+ const filterVals = ['', undefined, []];
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ instance = wrapper.instance();
+ });
+
+ it('should correct currFilters', () => {
+ filterVals.forEach((filterVal) => {
+ instance.onFilter(columns[1], FILTER_TYPE.TEXT)(filterVal);
+ expect(Object.keys(instance.currFilters)).toHaveLength(0);
+ });
+ });
+ });
+
+ describe('when filterVal is existing', () => {
+ const filterVal = '3';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ instance = wrapper.instance();
+ });
+
+ it('should correct currFilters', () => {
+ instance.onFilter(columns[1], FILTER_TYPE.TEXT)(filterVal);
+ expect(Object.keys(instance.currFilters)).toHaveLength(1);
+ });
+ });
+
+ describe('when remote filter is enabled', () => {
+ const filterVal = '3';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext(true));
+ wrapper.render();
+ instance = wrapper.instance();
+ instance.onFilter(columns[1], FILTER_TYPE.TEXT)(filterVal);
+ });
+
+ it('should correct currFilters', () => {
+ expect(Object.keys(instance.currFilters)).toHaveLength(1);
+ });
+
+ it('should calling handleFilterChange correctly', () => {
+ expect(handleFilterChange).toHaveBeenCalledTimes(1);
+ expect(handleFilterChange).toHaveBeenCalledWith(instance.currFilters);
+ });
+ });
+
+ describe('when remote filter is enabled but initialize argument is true', () => {
+ const filterVal = '3';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext(true));
+ wrapper.render();
+ instance = wrapper.instance();
+ instance.onFilter(columns[1], FILTER_TYPE.TEXT, true)(filterVal);
+ });
+
+ it('should correct currFilters', () => {
+ expect(Object.keys(instance.currFilters)).toHaveLength(1);
+ });
+
+ it('should not call handleFilterChange correctly', () => {
+ expect(handleFilterChange).toHaveBeenCalledTimes(0);
+ });
+ });
+
+ describe('combination', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ instance = wrapper.instance();
+ });
+
+ it('should set correct currFilters', () => {
+ instance.onFilter(columns[0], FILTER_TYPE.TEXT)('3');
+ expect(Object.keys(instance.currFilters)).toHaveLength(1);
+
+ instance.onFilter(columns[0], FILTER_TYPE.TEXT)('2');
+ expect(Object.keys(instance.currFilters)).toHaveLength(1);
+
+ instance.onFilter(columns[1], FILTER_TYPE.TEXT)('2');
+ expect(Object.keys(instance.currFilters)).toHaveLength(2);
+
+ instance.onFilter(columns[1], FILTER_TYPE.TEXT)('');
+ expect(Object.keys(instance.currFilters)).toHaveLength(1);
+
+ instance.onFilter(columns[0], FILTER_TYPE.TEXT)('');
+ expect(Object.keys(instance.currFilters)).toHaveLength(0);
+ });
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2-filter/test/filter.test.js b/packages/react-bootstrap-table2-filter/test/filter.test.js
index 25f629d..32956e4 100644
--- a/packages/react-bootstrap-table2-filter/test/filter.test.js
+++ b/packages/react-bootstrap-table2-filter/test/filter.test.js
@@ -1,5 +1,4 @@
import _ from 'react-bootstrap-table-next/src/utils';
-import Store from 'react-bootstrap-table-next/src/store';
import { filters } from '../src/filter';
import { FILTER_TYPE } from '../src/const';
@@ -16,14 +15,10 @@ for (let i = 0; i < 20; i += 1) {
}
describe('filter', () => {
- let store;
- let filterFn;
let currFilters;
let columns;
beforeEach(() => {
- store = new Store('id');
- store.data = data;
currFilters = {};
columns = [{
dataField: 'id',
@@ -41,10 +36,6 @@ describe('filter', () => {
});
describe('filterByText', () => {
- beforeEach(() => {
- filterFn = filters(store, columns, _);
- });
-
describe('when filter value is not a String', () => {
it('should transform to string and do the filter', () => {
currFilters.name = {
@@ -52,7 +43,7 @@ describe('filter', () => {
filterType: FILTER_TYPE.TEXT
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toBeDefined();
expect(result).toHaveLength(2);
});
@@ -65,7 +56,7 @@ describe('filter', () => {
filterType: FILTER_TYPE.TEXT
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toBeDefined();
expect(result).toHaveLength(2);
});
@@ -79,7 +70,7 @@ describe('filter', () => {
filterType: FILTER_TYPE.TEXT
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toBeDefined();
expect(result).toHaveLength(0);
});
@@ -93,7 +84,7 @@ describe('filter', () => {
comparator: EQ
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toBeDefined();
expect(result).toHaveLength(1);
});
@@ -102,7 +93,6 @@ describe('filter', () => {
describe('column.filterValue is defined', () => {
beforeEach(() => {
columns[1].filterValue = jest.fn();
- filterFn = filters(store, columns, _);
});
it('should calling custom filterValue callback correctly', () => {
@@ -111,7 +101,7 @@ describe('filter', () => {
filterType: FILTER_TYPE.TEXT
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toBeDefined();
expect(columns[1].filterValue).toHaveBeenCalledTimes(data.length);
// const calls = columns[1].filterValue.mock.calls;
@@ -123,11 +113,52 @@ describe('filter', () => {
});
});
- describe('filterByNumber', () => {
- beforeEach(() => {
- filterFn = filters(store, columns, _);
+ describe('filterByArray', () => {
+ describe('when filter value is empty array', () => {
+ it('should return original data', () => {
+ currFilters.name = {
+ filterVal: [],
+ filterType: FILTER_TYPE.MULTISELECT
+ };
+
+ const result = filters(data, columns, _)(currFilters);
+ expect(result).toBeDefined();
+ expect(result).toHaveLength(data.length);
+ });
});
+ describe('when filter value is not an empty array', () => {
+ describe(`and comparator is ${EQ}`, () => {
+ it('should return data correctly', () => {
+ currFilters.price = {
+ filterVal: [201, 203],
+ filterType: FILTER_TYPE.MULTISELECT,
+ comparator: EQ
+ };
+
+ const result = filters(data, columns, _)(currFilters);
+ expect(result).toBeDefined();
+ expect(result).toHaveLength(2);
+ });
+ });
+
+ describe(`and comparator is ${LIKE}`, () => {
+ it('should return data correctly', () => {
+ currFilters.name = {
+ filterVal: ['name 3', '5'],
+ filterType: FILTER_TYPE.MULTISELECT,
+ comparator: LIKE
+ };
+
+ const result = filters(data, columns, _)(currFilters);
+ expect(result).toBeDefined();
+ expect(result).toHaveLength(3);
+ });
+ });
+ });
+ });
+
+ describe('filterByNumber', () => {
describe('when currFilters.filterVal.comparator is empty', () => {
it('should returning correct result', () => {
currFilters.price = {
@@ -135,11 +166,11 @@ describe('filter', () => {
filterType: FILTER_TYPE.NUMBER
};
- let result = filterFn(currFilters);
+ let result = filters(data, columns, _)(currFilters);
expect(result).toHaveLength(data.length);
currFilters.price.filterVal.comparator = undefined;
- result = filterFn(currFilters);
+ result = filters(result, columns, _)(currFilters);
expect(result).toHaveLength(data.length);
});
});
@@ -151,7 +182,7 @@ describe('filter', () => {
filterType: FILTER_TYPE.NUMBER
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toHaveLength(data.length);
});
});
@@ -163,11 +194,11 @@ describe('filter', () => {
filterType: FILTER_TYPE.NUMBER
};
- let result = filterFn(currFilters);
+ let result = filters(data, columns, _)(currFilters);
expect(result).toHaveLength(1);
currFilters.price.filterVal.number = '0';
- result = filterFn(currFilters);
+ result = filters(result, columns, _)(currFilters);
expect(result).toHaveLength(0);
});
});
@@ -179,7 +210,7 @@ describe('filter', () => {
filterType: FILTER_TYPE.NUMBER
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toHaveLength(16);
});
});
@@ -191,7 +222,7 @@ describe('filter', () => {
filterType: FILTER_TYPE.NUMBER
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toHaveLength(17);
});
});
@@ -203,7 +234,7 @@ describe('filter', () => {
filterType: FILTER_TYPE.NUMBER
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toHaveLength(3);
});
});
@@ -215,7 +246,7 @@ describe('filter', () => {
filterType: FILTER_TYPE.NUMBER
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toHaveLength(4);
});
});
@@ -227,15 +258,16 @@ describe('filter', () => {
filterType: FILTER_TYPE.NUMBER
};
- const result = filterFn(currFilters);
+ const result = filters(data, columns, _)(currFilters);
expect(result).toHaveLength(19);
});
});
});
describe('filterByDate', () => {
+ let filterFn;
beforeEach(() => {
- filterFn = filters(store, columns, _);
+ filterFn = filters(data, columns, _);
});
describe('when currFilters.filterVal.comparator is empty', () => {
diff --git a/packages/react-bootstrap-table2-filter/test/wrapper.test.js b/packages/react-bootstrap-table2-filter/test/wrapper.test.js
deleted file mode 100644
index 1442658..0000000
--- a/packages/react-bootstrap-table2-filter/test/wrapper.test.js
+++ /dev/null
@@ -1,252 +0,0 @@
-import React from 'react';
-import sinon from 'sinon';
-import { shallow } from 'enzyme';
-
-import _ from 'react-bootstrap-table-next/src/utils';
-import remoteResolver from 'react-bootstrap-table-next/src/props-resolver/remote-resolver';
-import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table';
-import Store from 'react-bootstrap-table-next/src/store';
-import filter, { textFilter } from '..';
-import wrapperFactory from '../src/wrapper';
-import { FILTER_TYPE } from '../src/const';
-
-const data = [];
-for (let i = 0; i < 20; i += 1) {
- data.push({
- id: i,
- name: `itme name ${i}`,
- price: 200 + i
- });
-}
-
-describe('Wrapper', () => {
- let wrapper;
- let instance;
- const onTableChangeCB = sinon.stub();
-
- afterEach(() => {
- onTableChangeCB.reset();
- });
-
- const createTableProps = (props) => {
- const tableProps = {
- keyField: 'id',
- columns: [{
- dataField: 'id',
- text: 'ID'
- }, {
- dataField: 'name',
- text: 'Name',
- filter: textFilter()
- }, {
- dataField: 'price',
- text: 'Price',
- filter: textFilter()
- }],
- data,
- filter: filter(),
- _,
- store: new Store('id'),
- onTableChange: onTableChangeCB,
- ...props
- };
- tableProps.store.data = data;
- return tableProps;
- };
-
- const FilterWrapper = wrapperFactory(BootstrapTable, {
- _,
- remoteResolver
- });
-
- const createFilterWrapper = (props, renderFragment = true) => {
- wrapper = shallow(
);
- instance = wrapper.instance();
- if (renderFragment) {
- const fragment = instance.render();
- wrapper = shallow(
{ fragment }
);
- }
- };
-
- describe('default filter wrapper', () => {
- const props = createTableProps();
-
- beforeEach(() => {
- createFilterWrapper(props);
- });
-
- it('should rendering correctly', () => {
- expect(wrapper.length).toBe(1);
- });
-
- it('should initializing state correctly', () => {
- expect(instance.state.isDataChanged).toBeFalsy();
- expect(instance.state.currFilters).toEqual({});
- });
-
- it('should rendering BootstraTable correctly', () => {
- const table = wrapper.find(BootstrapTable);
- expect(table.length).toBe(1);
- expect(table.prop('onFilter')).toBeDefined();
- expect(table.prop('isDataChanged')).toEqual(instance.state.isDataChanged);
- });
- });
-
- describe('componentWillReceiveProps', () => {
- let nextProps;
-
- describe('when props.store.filters is same as current state.currFilters', () => {
- beforeEach(() => {
- nextProps = createTableProps();
- instance.componentWillReceiveProps(nextProps);
- });
-
- it('should setting isDataChanged as false (Temporary solution)', () => {
- expect(instance.state.isDataChanged).toBeFalsy();
- });
- });
-
- describe('when props.isDataChanged is true', () => {
- beforeEach(() => {
- nextProps = createTableProps({ isDataChanged: true });
- instance.componentWillReceiveProps(nextProps);
- });
-
- it('should setting isDataChanged as true', () => {
- expect(instance.state.isDataChanged).toBeTruthy();
- });
- });
-
- describe('when props.store.filters is different from current state.currFilters', () => {
- const nextData = [];
-
- beforeEach(() => {
- nextProps = createTableProps();
- nextProps.store.filters = { price: { filterVal: 20, filterType: FILTER_TYPE.TEXT } };
- nextProps.store.setAllData(nextData);
- instance.componentWillReceiveProps(nextProps);
- });
-
- it('should setting states correctly', () => {
- expect(nextProps.store.filteredData).toEqual(nextData);
- expect(instance.state.isDataChanged).toBeTruthy();
- expect(instance.state.currFilters).toBe(nextProps.store.filters);
- });
- });
-
- describe('when remote filter is enabled', () => {
- let props;
- const nextData = [];
-
- beforeEach(() => {
- props = createTableProps({ remote: { filter: true } });
- createFilterWrapper(props);
- nextProps = createTableProps({ remote: { filter: true } });
- nextProps.store.setAllData(nextData);
- instance.componentWillReceiveProps(nextProps);
- });
-
- it('should setting states correctly', () => {
- expect(nextProps.store.filteredData).toEqual(nextData);
- expect(instance.state.isDataChanged).toBeTruthy();
- expect(instance.state.currFilters).toBe(nextProps.store.filters);
- });
- });
- });
-
- describe('onFilter', () => {
- let props;
-
- beforeEach(() => {
- props = createTableProps();
- createFilterWrapper(props);
- });
-
- describe('when filterVal is empty or undefined', () => {
- const filterVals = ['', undefined];
-
- it('should setting store object correctly', () => {
- filterVals.forEach((filterVal) => {
- instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
- expect(props.store.filtering).toBeFalsy();
- });
- });
-
- it('should setting state correctly', () => {
- filterVals.forEach((filterVal) => {
- instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
- expect(instance.state.isDataChanged).toBeTruthy();
- expect(Object.keys(instance.state.currFilters)).toHaveLength(0);
- });
- });
- });
-
- describe('when filterVal is existing', () => {
- const filterVal = '3';
-
- it('should setting store object correctly', () => {
- instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
- expect(props.store.filters).toEqual(instance.state.currFilters);
- });
-
- it('should setting state correctly', () => {
- instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
- expect(instance.state.isDataChanged).toBeTruthy();
- expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
- });
- });
-
- describe('when remote filter is enabled', () => {
- const filterVal = '3';
-
- beforeEach(() => {
- props = createTableProps();
- props.remote = { filter: true };
- createFilterWrapper(props);
- instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
- });
-
- it('should not setting store object correctly', () => {
- expect(props.store.filters).not.toEqual(instance.state.currFilters);
- });
-
- it('should not setting state', () => {
- expect(instance.state.isDataChanged).toBeFalsy();
- expect(Object.keys(instance.state.currFilters)).toHaveLength(0);
- });
-
- it('should calling props.onRemoteFilterChange correctly', () => {
- expect(onTableChangeCB.calledOnce).toBeTruthy();
- });
- });
-
- describe('combination', () => {
- it('should setting store object correctly', () => {
- instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)('3');
- expect(props.store.filters).toEqual(instance.state.currFilters);
- expect(instance.state.isDataChanged).toBeTruthy();
- expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
-
- instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)('2');
- expect(props.store.filters).toEqual(instance.state.currFilters);
- expect(instance.state.isDataChanged).toBeTruthy();
- expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
-
- instance.onFilter(props.columns[2], FILTER_TYPE.TEXT)('2');
- expect(props.store.filters).toEqual(instance.state.currFilters);
- expect(instance.state.isDataChanged).toBeTruthy();
- expect(Object.keys(instance.state.currFilters)).toHaveLength(2);
-
- instance.onFilter(props.columns[2], FILTER_TYPE.TEXT)('');
- expect(props.store.filters).toEqual(instance.state.currFilters);
- expect(instance.state.isDataChanged).toBeTruthy();
- expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
-
- instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)('');
- expect(props.store.filters).toEqual(instance.state.currFilters);
- expect(instance.state.isDataChanged).toBeTruthy();
- expect(Object.keys(instance.state.currFilters)).toHaveLength(0);
- });
- });
- });
-});
diff --git a/packages/react-bootstrap-table2-overlay/package.json b/packages/react-bootstrap-table2-overlay/package.json
index cc8a25d..d0ff1e6 100644
--- a/packages/react-bootstrap-table2-overlay/package.json
+++ b/packages/react-bootstrap-table2-overlay/package.json
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table2-overlay",
- "version": "0.1.2",
+ "version": "1.0.0",
"description": "it's a loading overlay addons for react-bootstrap-table2",
"main": "./lib/index.js",
"repository": {
@@ -41,7 +41,7 @@
},
"peerDependencies": {
"prop-types": "^15.0.0",
- "react": "^16.0.0",
- "react-dom": "^16.0.0"
+ "react": "^16.3.0",
+ "react-dom": "^16.3.0"
}
}
diff --git a/packages/react-bootstrap-table2-paginator/index.js b/packages/react-bootstrap-table2-paginator/index.js
index edd0067..8750183 100644
--- a/packages/react-bootstrap-table2-paginator/index.js
+++ b/packages/react-bootstrap-table2-paginator/index.js
@@ -1,6 +1,6 @@
-import wrapperFactory from './src/wrapper';
+import createContext from './src/context';
export default (options = {}) => ({
- wrapperFactory,
+ createContext,
options
});
diff --git a/packages/react-bootstrap-table2-paginator/package.json b/packages/react-bootstrap-table2-paginator/package.json
index 6beb303..664aaa6 100644
--- a/packages/react-bootstrap-table2-paginator/package.json
+++ b/packages/react-bootstrap-table2-paginator/package.json
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table2-paginator",
- "version": "0.1.6",
+ "version": "1.0.1",
"description": "it's the pagination addon for react-bootstrap-table2",
"main": "./lib/index.js",
"repository": {
@@ -38,7 +38,7 @@
],
"peerDependencies": {
"prop-types": "^15.0.0",
- "react": "^16.0.0",
- "react-dom": "^16.0.0"
+ "react": "^16.3.0",
+ "react-dom": "^16.3.0"
}
}
diff --git a/packages/react-bootstrap-table2-paginator/src/bootstrap.js b/packages/react-bootstrap-table2-paginator/src/bootstrap.js
new file mode 100644
index 0000000..1ee0db1
--- /dev/null
+++ b/packages/react-bootstrap-table2-paginator/src/bootstrap.js
@@ -0,0 +1,6 @@
+import React from 'react';
+
+// consider to have a common lib?1
+export const BootstrapContext = React.createContext({
+ bootstrap4: false
+});
diff --git a/packages/react-bootstrap-table2-paginator/src/context.js b/packages/react-bootstrap-table2-paginator/src/context.js
new file mode 100644
index 0000000..10d72ff
--- /dev/null
+++ b/packages/react-bootstrap-table2-paginator/src/context.js
@@ -0,0 +1,188 @@
+/* eslint react/prop-types: 0 */
+/* eslint react/require-default-props: 0 */
+/* eslint no-lonely-if: 0 */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Const from './const';
+import { BootstrapContext } from './bootstrap';
+import Pagination from './pagination';
+import { getByCurrPage, alignPage } from './page';
+
+export default (
+ isRemotePagination,
+ handleRemotePageChange
+) => {
+ const PaginationContext = React.createContext();
+
+ class PaginationProvider extends React.Component {
+ static propTypes = {
+ data: PropTypes.array.isRequired
+ }
+
+ constructor(props) {
+ super(props);
+ this.handleChangePage = this.handleChangePage.bind(this);
+ this.handleChangeSizePerPage = this.handleChangeSizePerPage.bind(this);
+
+ let currPage;
+ let currSizePerPage;
+ const { options } = props.pagination;
+ const sizePerPageList = options.sizePerPageList || Const.SIZE_PER_PAGE_LIST;
+
+ // initialize current page
+ if (typeof options.page !== 'undefined') {
+ currPage = options.page;
+ } else if (typeof options.pageStartIndex !== 'undefined') {
+ currPage = options.pageStartIndex;
+ } else {
+ currPage = Const.PAGE_START_INDEX;
+ }
+
+ // initialize current sizePerPage
+ if (typeof options.sizePerPage !== 'undefined') {
+ currSizePerPage = options.sizePerPage;
+ } else if (typeof sizePerPageList[0] === 'object') {
+ currSizePerPage = sizePerPageList[0].value;
+ } else {
+ currSizePerPage = sizePerPageList[0];
+ }
+
+ this.currPage = currPage;
+ this.currSizePerPage = currSizePerPage;
+ }
+
+ componentWillReceiveProps(nextProps) {
+ let needNewState = false;
+ let { currPage, currSizePerPage } = this;
+ const { page, sizePerPage, onPageChange } = nextProps.pagination.options;
+
+ const pageStartIndex = typeof nextProps.pagination.options.pageStartIndex !== 'undefined' ?
+ nextProps.pagination.options.pageStartIndex : Const.PAGE_START_INDEX;
+
+ if (typeof page !== 'undefined' && currPage !== page) { // user defined page
+ currPage = page;
+ needNewState = true;
+ } else {
+ // user should align the page when the page is not fit to the data size when remote enable
+ if (!isRemotePagination()) {
+ const newPage = alignPage(nextProps.data, currPage, currSizePerPage, pageStartIndex);
+ if (currPage !== newPage) {
+ currPage = newPage;
+ needNewState = true;
+ }
+ }
+ }
+
+ if (typeof sizePerPage !== 'undefined' && currSizePerPage !== sizePerPage) {
+ currSizePerPage = sizePerPage;
+ needNewState = true;
+ }
+
+ if (needNewState) {
+ if (onPageChange) {
+ onPageChange(currPage, currSizePerPage);
+ }
+ this.currPage = currPage;
+ this.currSizePerPage = currSizePerPage;
+ }
+ }
+
+ handleChangePage(currPage) {
+ const { currSizePerPage } = this;
+ const { pagination: { options } } = this.props;
+
+ if (options.onPageChange) {
+ options.onPageChange(currPage, currSizePerPage);
+ }
+
+ this.currPage = currPage;
+
+ if (isRemotePagination()) {
+ handleRemotePageChange(currPage, currSizePerPage);
+ return;
+ }
+ this.forceUpdate();
+ }
+
+ handleChangeSizePerPage(currSizePerPage, currPage) {
+ const { pagination: { options } } = this.props;
+
+ if (options.onSizePerPageChange) {
+ options.onSizePerPageChange(currSizePerPage, currPage);
+ }
+
+ this.currPage = currPage;
+ this.currSizePerPage = currSizePerPage;
+
+ if (isRemotePagination()) {
+ handleRemotePageChange(currPage, currSizePerPage);
+ return;
+ }
+ this.forceUpdate();
+ }
+
+ render() {
+ let { data } = this.props;
+ const { pagination: { options }, bootstrap4 } = this.props;
+ const { currPage, currSizePerPage } = this;
+ const withFirstAndLast = typeof options.withFirstAndLast === 'undefined' ?
+ Const.With_FIRST_AND_LAST : options.withFirstAndLast;
+ const alwaysShowAllBtns = typeof options.alwaysShowAllBtns === 'undefined' ?
+ Const.SHOW_ALL_PAGE_BTNS : options.alwaysShowAllBtns;
+ const hideSizePerPage = typeof options.hideSizePerPage === 'undefined' ?
+ Const.HIDE_SIZE_PER_PAGE : options.hideSizePerPage;
+ const hidePageListOnlyOnePage = typeof options.hidePageListOnlyOnePage === 'undefined' ?
+ Const.HIDE_PAGE_LIST_ONLY_ONE_PAGE : options.hidePageListOnlyOnePage;
+ const pageStartIndex = typeof options.pageStartIndex === 'undefined' ?
+ Const.PAGE_START_INDEX : options.pageStartIndex;
+
+ data = isRemotePagination() ?
+ data :
+ getByCurrPage(
+ data,
+ currPage,
+ currSizePerPage,
+ pageStartIndex
+ );
+
+ return (
+
+ { this.props.children }
+
+
+
+
+ );
+ }
+ }
+
+ return {
+ Provider: PaginationProvider,
+ Consumer: PaginationContext.Consumer
+ };
+};
diff --git a/packages/react-bootstrap-table2-paginator/src/page-resolver.js b/packages/react-bootstrap-table2-paginator/src/page-resolver.js
index 8cfeb10..5146faf 100644
--- a/packages/react-bootstrap-table2-paginator/src/page-resolver.js
+++ b/packages/react-bootstrap-table2-paginator/src/page-resolver.js
@@ -40,8 +40,8 @@ export default ExtendBase =>
let from = ((currPage - pageStartIndex) * currSizePerPage);
from = dataSize === 0 ? 0 : from + 1;
- let to = Math.min((currSizePerPage * (currPage + offset) - 1), dataSize);
- if (to >= dataSize) to -= 1;
+ let to = Math.min(currSizePerPage * (currPage + offset), dataSize);
+ if (to > dataSize) to = dataSize;
return [from, to];
}
diff --git a/packages/react-bootstrap-table2-paginator/src/page.js b/packages/react-bootstrap-table2-paginator/src/page.js
index 3a9b863..40d2a8a 100644
--- a/packages/react-bootstrap-table2-paginator/src/page.js
+++ b/packages/react-bootstrap-table2-paginator/src/page.js
@@ -1,5 +1,3 @@
-/* eslint no-param-reassign: 0 */
-
const getNormalizedPage = (
page,
pageStartIndex
@@ -19,25 +17,36 @@ const startIndex = (
sizePerPage,
) => end - (sizePerPage - 1);
-export const alignPage = (store, pageStartIndex, sizePerPage) => {
- const end = endIndex(store.page, sizePerPage, pageStartIndex);
- const dataSize = store.data.length;
+export const alignPage = (
+ data,
+ page,
+ sizePerPage,
+ pageStartIndex
+) => {
+ const end = endIndex(page, sizePerPage, pageStartIndex);
+ const dataSize = data.length;
if (end - 1 > dataSize) {
return pageStartIndex;
}
- return store.page;
+ return page;
};
-export const getByCurrPage = (store, pageStartIndex) => {
- const dataSize = store.data.length;
+export const getByCurrPage = (
+ data,
+ page,
+ sizePerPage,
+ pageStartIndex
+) => {
+ const dataSize = data.length;
if (!dataSize) return [];
- const end = endIndex(store.page, store.sizePerPage, pageStartIndex);
- const start = startIndex(end, store.sizePerPage);
+
+ const end = endIndex(page, sizePerPage, pageStartIndex);
+ const start = startIndex(end, sizePerPage);
const result = [];
for (let i = start; i <= end; i += 1) {
- result.push(store.data[i]);
+ result.push(data[i]);
if (i + 1 === dataSize) break;
}
return result;
diff --git a/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js b/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js
index 5ae4298..2d8fcc4 100644
--- a/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js
+++ b/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js
@@ -1,6 +1,7 @@
import React from 'react';
import cs from 'classnames';
import PropTypes from 'prop-types';
+import { BootstrapContext } from './bootstrap';
import SizePerPageOption from './size-per-page-option';
const sizePerPageDefaultClass = 'react-bs-table-sizePerPage-dropdown';
@@ -20,44 +21,60 @@ const SizePerPageDropDown = (props) => {
} = props;
const dropDownStyle = { visibility: hidden ? 'hidden' : 'visible' };
+ const openClass = open ? 'open show' : '';
const dropdownClasses = cs(
- open ? 'open show' : '',
+ openClass,
sizePerPageDefaultClass,
variation,
className,
);
return (
-
-
- { currSizePerPage }
-
- { ' ' }
-
-
-
-
- {
- options.map(option => (
-
- ))
- }
-
-
+
+ {
+ ({ bootstrap4 }) => (
+
+
+ { currSizePerPage }
+ { ' ' }
+ {
+ bootstrap4 ? null : (
+
+
+
+ )
+ }
+
+
+ {
+ options.map(option => (
+
+ ))
+ }
+
+
+ )
+ }
+
);
};
diff --git a/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js b/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js
index eba6ca6..a01a360 100644
--- a/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js
+++ b/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js
@@ -5,9 +5,28 @@ import PropTypes from 'prop-types';
const SizePerPageOption = ({
text,
page,
- onSizePerPageChange
-}) => (
-
+ onSizePerPageChange,
+ bootstrap4
+}) => (bootstrap4 ? (
+ {
+ e.preventDefault();
+ onSizePerPageChange(page);
+ } }
+ >
+ { text }
+
+) : (
+
-);
+));
SizePerPageOption.propTypes = {
text: PropTypes.string.isRequired,
page: PropTypes.number.isRequired,
- onSizePerPageChange: PropTypes.func.isRequired
+ onSizePerPageChange: PropTypes.func.isRequired,
+ bootstrap4: PropTypes.bool
+};
+
+SizePerPageOption.defaultProps = {
+ bootstrap4: false
};
export default SizePerPageOption;
diff --git a/packages/react-bootstrap-table2-paginator/src/wrapper.js b/packages/react-bootstrap-table2-paginator/src/wrapper.js
deleted file mode 100644
index 784687a..0000000
--- a/packages/react-bootstrap-table2-paginator/src/wrapper.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/* eslint react/prop-types: 0 */
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-
-import Const from './const';
-import Pagination from './pagination';
-import { getByCurrPage, alignPage } from './page';
-
-export default (Base, {
- remoteResolver
-}) =>
- class PaginationWrapper extends remoteResolver(Component) {
- static propTypes = {
- store: PropTypes.object.isRequired
- }
-
- constructor(props) {
- super(props);
- this.handleChangePage = this.handleChangePage.bind(this);
- this.handleChangeSizePerPage = this.handleChangeSizePerPage.bind(this);
-
- let currPage;
- let currSizePerPage;
- const { options } = props.pagination;
- const sizePerPageList = options.sizePerPageList || Const.SIZE_PER_PAGE_LIST;
-
- // initialize current page
- if (typeof options.page !== 'undefined') {
- currPage = options.page;
- } else if (typeof options.pageStartIndex !== 'undefined') {
- currPage = options.pageStartIndex;
- } else {
- currPage = Const.PAGE_START_INDEX;
- }
-
- // initialize current sizePerPage
- if (typeof options.sizePerPage !== 'undefined') {
- currSizePerPage = options.sizePerPage;
- } else if (typeof sizePerPageList[0] === 'object') {
- currSizePerPage = sizePerPageList[0].value;
- } else {
- currSizePerPage = sizePerPageList[0];
- }
-
- this.state = { currPage, currSizePerPage };
- this.saveToStore(currPage, currSizePerPage);
- }
-
- componentWillReceiveProps(nextProps) {
- let needNewState = false;
- let { currPage, currSizePerPage } = this.state;
- const { page, sizePerPage, onPageChange } = nextProps.pagination.options;
-
- const pageStartIndex = typeof nextProps.pagination.options.pageStartIndex !== 'undefined' ?
- nextProps.pagination.options.pageStartIndex : Const.PAGE_START_INDEX;
-
- if (typeof page !== 'undefined' && currPage !== page) { // user defined page
- currPage = page;
- needNewState = true;
- } else if (nextProps.isDataChanged) {
- currPage = alignPage(this.props.store, pageStartIndex, currSizePerPage);
- needNewState = true;
- }
-
- if (typeof currPage === 'undefined') {
- currPage = pageStartIndex;
- }
-
- if (typeof sizePerPage !== 'undefined') {
- currSizePerPage = sizePerPage;
- needNewState = true;
- }
-
- this.saveToStore(currPage, currSizePerPage);
-
- if (needNewState) {
- if (onPageChange) {
- onPageChange(currPage, currSizePerPage);
- }
- this.setState(() => ({ currPage, currSizePerPage }));
- }
- }
-
- saveToStore(page, sizePerPage) {
- this.props.store.page = page;
- this.props.store.sizePerPage = sizePerPage;
- }
-
- handleChangePage(currPage) {
- const { currSizePerPage } = this.state;
- const { pagination: { options } } = this.props;
- this.saveToStore(currPage, currSizePerPage);
-
- if (options.onPageChange) {
- options.onPageChange(currPage, currSizePerPage);
- }
- if (this.isRemotePagination()) {
- this.handleRemotePageChange();
- return;
- }
- this.setState(() => ({ currPage }));
- }
-
- handleChangeSizePerPage(currSizePerPage, currPage) {
- const { pagination: { options } } = this.props;
- this.saveToStore(currPage, currSizePerPage);
-
- if (options.onSizePerPageChange) {
- options.onSizePerPageChange(currSizePerPage, currPage);
- }
- if (this.isRemotePagination()) {
- this.handleRemotePageChange();
- return;
- }
- this.setState(() => ({
- currPage,
- currSizePerPage
- }));
- }
-
- render() {
- const { pagination: { options }, store } = this.props;
- const { currPage, currSizePerPage } = this.state;
- const withFirstAndLast = typeof options.withFirstAndLast === 'undefined' ?
- Const.With_FIRST_AND_LAST : options.withFirstAndLast;
- const alwaysShowAllBtns = typeof options.alwaysShowAllBtns === 'undefined' ?
- Const.SHOW_ALL_PAGE_BTNS : options.alwaysShowAllBtns;
- const hideSizePerPage = typeof options.hideSizePerPage === 'undefined' ?
- Const.HIDE_SIZE_PER_PAGE : options.hideSizePerPage;
- const hidePageListOnlyOnePage = typeof options.hidePageListOnlyOnePage === 'undefined' ?
- Const.HIDE_PAGE_LIST_ONLY_ONE_PAGE : options.hidePageListOnlyOnePage;
- const pageStartIndex = typeof options.pageStartIndex === 'undefined' ?
- Const.PAGE_START_INDEX : options.pageStartIndex;
-
- const data = this.isRemotePagination() ?
- this.props.data :
- getByCurrPage(store, pageStartIndex);
-
- return [
-
,
-
- ];
- }
- };
diff --git a/packages/react-bootstrap-table2-paginator/test/context.test.js b/packages/react-bootstrap-table2-paginator/test/context.test.js
new file mode 100644
index 0000000..4b9c30d
--- /dev/null
+++ b/packages/react-bootstrap-table2-paginator/test/context.test.js
@@ -0,0 +1,773 @@
+import 'jsdom-global/register';
+import React from 'react';
+import { shallow } from 'enzyme';
+import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table';
+
+import Pagination from '../src/pagination';
+import Const from '../src/const';
+import createPaginationContext from '../src/context';
+import paginationFactory from '../index';
+
+const data = [];
+for (let i = 0; i < 100; i += 1) {
+ data.push({
+ id: i,
+ name: `itme name ${i}`
+ });
+}
+
+describe('PaginationContext', () => {
+ let wrapper;
+ let PaginationContext;
+
+ const columns = [{
+ dataField: 'id',
+ text: 'ID'
+ }, {
+ dataField: 'name',
+ text: 'Name'
+ }];
+
+ const defaultPagination = { options: {} };
+
+ const mockBase = jest.fn((props => (
+
+ )));
+
+ const handleRemotePaginationChange = jest.fn();
+
+ function shallowContext(
+ customPagination = defaultPagination,
+ enableRemote = false
+ ) {
+ mockBase.mockReset();
+ handleRemotePaginationChange.mockReset();
+ PaginationContext = createPaginationContext(
+ jest.fn().mockReturnValue(enableRemote),
+ handleRemotePaginationChange
+ );
+
+ return (
+
+
+ {
+ paginationProps => mockBase(paginationProps)
+ }
+
+
+ );
+ }
+
+ describe('default render', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ });
+
+ it('should have correct Provider property after calling createPaginationContext', () => {
+ expect(PaginationContext.Provider).toBeDefined();
+ });
+
+ it('should have correct Consumer property after calling createPaginationContext', () => {
+ expect(PaginationContext.Consumer).toBeDefined();
+ });
+
+ it('should have correct currPage', () => {
+ expect(wrapper.instance().currPage).toEqual(Const.PAGE_START_INDEX);
+ });
+
+ it('should have correct currSizePerPage', () => {
+ expect(wrapper.instance().currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[0]);
+ });
+
+ it('should render Pagination component correctly', () => {
+ expect(wrapper.length).toBe(1);
+ const instance = wrapper.instance();
+ const pagination = wrapper.find(Pagination);
+ expect(pagination).toHaveLength(1);
+ expect(pagination.prop('dataSize')).toEqual(data.length);
+ expect(pagination.prop('currPage')).toEqual(instance.currPage);
+ expect(pagination.prop('currSizePerPage')).toEqual(instance.currSizePerPage);
+ expect(pagination.prop('onPageChange')).toEqual(instance.handleChangePage);
+ expect(pagination.prop('onSizePerPageChange')).toEqual(instance.handleChangeSizePerPage);
+ expect(pagination.prop('sizePerPageList')).toEqual(Const.SIZE_PER_PAGE_LIST);
+ expect(pagination.prop('paginationSize')).toEqual(Const.PAGINATION_SIZE);
+ expect(pagination.prop('pageStartIndex')).toEqual(Const.PAGE_START_INDEX);
+ expect(pagination.prop('withFirstAndLast')).toEqual(Const.With_FIRST_AND_LAST);
+ expect(pagination.prop('alwaysShowAllBtns')).toEqual(Const.SHOW_ALL_PAGE_BTNS);
+ expect(pagination.prop('firstPageText')).toEqual(Const.FIRST_PAGE_TEXT);
+ expect(pagination.prop('prePageText')).toEqual(Const.PRE_PAGE_TEXT);
+ expect(pagination.prop('nextPageText')).toEqual(Const.NEXT_PAGE_TEXT);
+ expect(pagination.prop('lastPageText')).toEqual(Const.LAST_PAGE_TEXT);
+ expect(pagination.prop('firstPageTitle')).toEqual(Const.FIRST_PAGE_TITLE);
+ expect(pagination.prop('prePageTitle')).toEqual(Const.PRE_PAGE_TITLE);
+ expect(pagination.prop('nextPageTitle')).toEqual(Const.NEXT_PAGE_TITLE);
+ expect(pagination.prop('lastPageTitle')).toEqual(Const.LAST_PAGE_TITLE);
+ expect(pagination.prop('hideSizePerPage')).toEqual(Const.HIDE_SIZE_PER_PAGE);
+ expect(pagination.prop('hideSizePerPage')).toEqual(Const.HIDE_SIZE_PER_PAGE);
+ expect(pagination.prop('paginationTotalRenderer')).toBeNull();
+ });
+
+ it('should pass correct cell editing props to children element', () => {
+ expect(mockBase.mock.calls[0][0].data).toHaveLength(Const.SIZE_PER_PAGE_LIST[0]);
+ });
+ });
+
+ describe('componentWillReceiveProps', () => {
+ let instance;
+ let nextProps;
+
+ describe('when nextProps.pagination.options.page is existing', () => {
+ const onPageChange = jest.fn();
+ afterEach(() => {
+ onPageChange.mockReset();
+ });
+
+ describe('and if it is different with currPage', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ instance = wrapper.instance();
+ wrapper.render();
+ nextProps = {
+ data,
+ pagination: {
+ options: {
+ page: 2,
+ onPageChange
+ }
+ }
+ };
+ instance.componentWillReceiveProps(nextProps);
+ });
+
+ it('should call options.onPageChange', () => {
+ expect(onPageChange).toHaveBeenCalledTimes(1);
+ expect(onPageChange).toHaveBeenCalledWith(
+ instance.currPage,
+ instance.currSizePerPage
+ );
+ });
+
+ it('should set correct currPage', () => {
+ expect(instance.currPage).toEqual(nextProps.pagination.options.page);
+ });
+ });
+
+ describe('and if it is same as currPage', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ instance = wrapper.instance();
+ wrapper.render();
+ nextProps = {
+ data,
+ pagination: {
+ options: {
+ page: 1,
+ onPageChange
+ }
+ }
+ };
+ instance.componentWillReceiveProps(nextProps);
+ });
+
+ it('shouldn\'t call options.onPageChange', () => {
+ expect(onPageChange).toHaveBeenCalledTimes(0);
+ });
+
+ it('should have correct currPage', () => {
+ expect(instance.currPage).toEqual(nextProps.pagination.options.page);
+ });
+ });
+ });
+
+ describe('when nextProps.pagination.options.page is not existing', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ page: 3
+ }));
+ instance = wrapper.instance();
+ wrapper.render();
+ nextProps = { data, pagination: defaultPagination };
+ instance.componentWillReceiveProps(nextProps);
+ });
+
+ it('should not set currPage', () => {
+ expect(instance.currPage).toEqual(3);
+ });
+ });
+
+ describe('when nextProps.pagination.options.sizePerPage is existing', () => {
+ const onPageChange = jest.fn();
+ afterEach(() => {
+ onPageChange.mockReset();
+ });
+
+ describe('and if it is different with currSizePerPage', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ instance = wrapper.instance();
+ wrapper.render();
+ nextProps = {
+ data,
+ pagination: {
+ options: {
+ sizePerPage: Const.SIZE_PER_PAGE_LIST[2],
+ onPageChange
+ }
+ }
+ };
+ instance.componentWillReceiveProps(nextProps);
+ });
+
+ it('should call options.onPageChange', () => {
+ expect(onPageChange).toHaveBeenCalledTimes(1);
+ expect(onPageChange).toHaveBeenCalledWith(
+ instance.currPage,
+ instance.currSizePerPage
+ );
+ });
+
+ it('should set correct currSizePerPage', () => {
+ expect(instance.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage);
+ });
+ });
+
+ describe('and if it is same as currSizePerPage', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ instance = wrapper.instance();
+ wrapper.render();
+ nextProps = {
+ data,
+ pagination: {
+ options: {
+ sizePerPage: Const.SIZE_PER_PAGE_LIST[0],
+ onPageChange
+ }
+ }
+ };
+ instance.componentWillReceiveProps(nextProps);
+ });
+
+ it('shouldn\'t call options.onPageChange', () => {
+ expect(onPageChange).toHaveBeenCalledTimes(0);
+ });
+
+ it('should have correct currSizePerPage', () => {
+ expect(instance.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage);
+ });
+ });
+ });
+
+ describe('when nextProps.pagination.options.sizePerPage is not existing', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ sizePerPage: Const.SIZE_PER_PAGE_LIST[2]
+ }));
+ instance = wrapper.instance();
+ wrapper.render();
+ nextProps = { data, pagination: defaultPagination };
+ instance.componentWillReceiveProps(nextProps);
+ });
+
+ it('should not set currPage', () => {
+ expect(instance.currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[2]);
+ });
+ });
+ });
+
+ describe('handleChangePage', () => {
+ let instance;
+ const newPage = 3;
+
+ describe('should update component correctly', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ instance = wrapper.instance();
+ jest.spyOn(instance, 'forceUpdate');
+ instance.handleChangePage(newPage);
+ });
+
+ it('', () => {
+ expect(instance.currPage).toEqual(newPage);
+ expect(instance.forceUpdate).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('if options.onPageChange is defined', () => {
+ const onPageChange = jest.fn();
+ beforeEach(() => {
+ onPageChange.mockClear();
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ onPageChange
+ }));
+ instance = wrapper.instance();
+ jest.spyOn(instance, 'forceUpdate');
+ instance.handleChangePage(newPage);
+ });
+
+ it('should still update component correctly', () => {
+ expect(instance.currPage).toEqual(newPage);
+ expect(instance.forceUpdate).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call options.onPageChange correctly', () => {
+ expect(onPageChange).toHaveBeenCalledTimes(1);
+ expect(onPageChange).toHaveBeenCalledWith(newPage, instance.currSizePerPage);
+ });
+ });
+
+ describe('if remote pagination is enable', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination
+ }, true));
+ instance = wrapper.instance();
+ jest.spyOn(instance, 'forceUpdate');
+ instance.handleChangePage(newPage);
+ });
+
+ it('should still update component correctly', () => {
+ expect(instance.currPage).toEqual(newPage);
+ expect(instance.forceUpdate).toHaveBeenCalledTimes(0);
+ });
+
+ it('should call handleRemotePageChange correctly', () => {
+ expect(handleRemotePaginationChange).toHaveBeenCalledTimes(1);
+ expect(handleRemotePaginationChange)
+ .toHaveBeenCalledWith(newPage, instance.currSizePerPage);
+ });
+ });
+ });
+
+ describe('handleChangeSizePerPage', () => {
+ let instance;
+ const newPage = 2;
+ const newSizePerPage = 15;
+
+ describe('should update component correctly', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ instance = wrapper.instance();
+ jest.spyOn(instance, 'forceUpdate');
+ instance.handleChangeSizePerPage(newSizePerPage, newPage);
+ });
+
+ it('', () => {
+ expect(instance.currPage).toEqual(newPage);
+ expect(instance.currSizePerPage).toEqual(newSizePerPage);
+ expect(instance.forceUpdate).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('if options.onSizePerPageChange is defined', () => {
+ const onSizePerPageChange = jest.fn();
+ beforeEach(() => {
+ onSizePerPageChange.mockClear();
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ onSizePerPageChange
+ }));
+ instance = wrapper.instance();
+ jest.spyOn(instance, 'forceUpdate');
+ instance.handleChangeSizePerPage(newSizePerPage, newPage);
+ });
+
+ it('should still update component correctly', () => {
+ expect(instance.currPage).toEqual(newPage);
+ expect(instance.currSizePerPage).toEqual(newSizePerPage);
+ expect(instance.forceUpdate).toHaveBeenCalledTimes(1);
+ });
+
+ it('should call options.onSizePerPageChange correctly', () => {
+ expect(onSizePerPageChange).toHaveBeenCalledTimes(1);
+ expect(onSizePerPageChange).toHaveBeenCalledWith(newSizePerPage, newPage);
+ });
+ });
+
+ describe('if remote pagination is enable', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination
+ }, true));
+ instance = wrapper.instance();
+ jest.spyOn(instance, 'forceUpdate');
+ instance.handleChangeSizePerPage(newSizePerPage, newPage);
+ });
+
+ it('should still update component correctly', () => {
+ expect(instance.currPage).toEqual(newPage);
+ expect(instance.currSizePerPage).toEqual(newSizePerPage);
+ expect(instance.forceUpdate).toHaveBeenCalledTimes(0);
+ });
+
+ it('should call handleRemotePageChange correctly', () => {
+ expect(handleRemotePaginationChange).toHaveBeenCalledTimes(1);
+ expect(handleRemotePaginationChange)
+ .toHaveBeenCalledWith(newPage, newSizePerPage);
+ });
+ });
+ });
+
+ describe('when options.page is defined', () => {
+ const page = 3;
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ page
+ }));
+ wrapper.render();
+ });
+
+ it('should set correct currPage', () => {
+ expect(wrapper.instance().currPage).toEqual(page);
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('currPage')).toEqual(page);
+ });
+ });
+
+ describe('when options.sizePerPage is defined', () => {
+ const sizePerPage = Const.SIZE_PER_PAGE_LIST[2];
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ sizePerPage
+ }));
+ wrapper.render();
+ });
+
+ it('should set correct currSizePerPage', () => {
+ expect(wrapper.instance().currSizePerPage).toEqual(sizePerPage);
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('currSizePerPage')).toEqual(sizePerPage);
+ });
+ });
+
+ describe('when options.totalSize is defined', () => {
+ const totalSize = 100;
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ totalSize
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('dataSize')).toEqual(totalSize);
+ });
+ });
+
+ describe('when options.showTotal is defined', () => {
+ const showTotal = true;
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ showTotal
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('showTotal')).toEqual(showTotal);
+ });
+ });
+
+ describe('when options.pageStartIndex is defined', () => {
+ const pageStartIndex = -1;
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ pageStartIndex
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('pageStartIndex')).toEqual(pageStartIndex);
+ });
+ });
+
+ describe('when options.sizePerPageList is defined', () => {
+ const sizePerPageList = [10, 40];
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ sizePerPageList
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('sizePerPageList')).toEqual(sizePerPageList);
+ });
+ });
+
+ describe('when options.paginationSize is defined', () => {
+ const paginationSize = 10;
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ paginationSize
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('paginationSize')).toEqual(paginationSize);
+ });
+ });
+
+ describe('when options.withFirstAndLast is defined', () => {
+ const withFirstAndLast = false;
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ withFirstAndLast
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('withFirstAndLast')).toEqual(withFirstAndLast);
+ });
+ });
+
+ describe('when options.alwaysShowAllBtns is defined', () => {
+ const alwaysShowAllBtns = true;
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ alwaysShowAllBtns
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('alwaysShowAllBtns')).toEqual(alwaysShowAllBtns);
+ });
+ });
+
+ describe('when options.firstPageText is defined', () => {
+ const firstPageText = '1st';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ firstPageText
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('firstPageText')).toEqual(firstPageText);
+ });
+ });
+
+ describe('when options.prePageText is defined', () => {
+ const prePageText = 'PRE';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ prePageText
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('prePageText')).toEqual(prePageText);
+ });
+ });
+
+ describe('when options.nextPageText is defined', () => {
+ const nextPageText = 'NEXT';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ nextPageText
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('nextPageText')).toEqual(nextPageText);
+ });
+ });
+
+ describe('when options.lastPageText is defined', () => {
+ const lastPageText = 'LAST';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ lastPageText
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('lastPageText')).toEqual(lastPageText);
+ });
+ });
+
+ describe('when options.firstPageTitle is defined', () => {
+ const firstPageTitle = '1st';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ firstPageTitle
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('firstPageTitle')).toEqual(firstPageTitle);
+ });
+ });
+
+ describe('when options.prePageTitle is defined', () => {
+ const prePageTitle = 'PRE';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ prePageTitle
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('prePageTitle')).toEqual(prePageTitle);
+ });
+ });
+
+ describe('when options.nextPageTitle is defined', () => {
+ const nextPageTitle = 'NEXT';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ nextPageTitle
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('nextPageTitle')).toEqual(nextPageTitle);
+ });
+ });
+
+ describe('when options.lastPageTitle is defined', () => {
+ const lastPageTitle = 'nth';
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ lastPageTitle
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('lastPageTitle')).toEqual(lastPageTitle);
+ });
+ });
+
+ describe('when options.hideSizePerPage is defined', () => {
+ const hideSizePerPage = true;
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ hideSizePerPage
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('hideSizePerPage')).toEqual(hideSizePerPage);
+ });
+ });
+
+ describe('when options.hidePageListOnlyOnePage is defined', () => {
+ const hidePageListOnlyOnePage = true;
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultPagination,
+ hidePageListOnlyOnePage
+ }));
+ wrapper.render();
+ });
+
+ it('should rendering Pagination correctly', () => {
+ const pagination = wrapper.find(Pagination);
+ expect(pagination.length).toBe(1);
+ expect(pagination.prop('hidePageListOnlyOnePage')).toEqual(hidePageListOnlyOnePage);
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js b/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js
index 1eaf996..7dbc198 100644
--- a/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js
+++ b/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js
@@ -119,7 +119,34 @@ describe('PageResolver', () => {
it('should return correct array with from and to value', () => {
const instance = wrapper.instance();
- expect(instance.calculateFromTo()).toEqual([1, props.currSizePerPage - 1]);
+ expect(instance.calculateFromTo()).toEqual([1, props.currSizePerPage]);
+ });
+
+ describe('if data is empty', () => {
+ beforeEach(() => {
+ props.dataSize = 87;
+ props.currPage = 9;
+ const mockElement = React.createElement(MockComponent, props, null);
+ wrapper = shallow(mockElement);
+ });
+
+ it('should return correct array with from and to value', () => {
+ const instance = wrapper.instance();
+ expect(instance.calculateFromTo()).toEqual([81, props.dataSize]);
+ });
+ });
+
+ describe('if current page is last page', () => {
+ beforeEach(() => {
+ props.dataSize = 0;
+ const mockElement = React.createElement(MockComponent, props, null);
+ wrapper = shallow(mockElement);
+ });
+
+ it('should return correct array with from and to value', () => {
+ const instance = wrapper.instance();
+ expect(instance.calculateFromTo()).toEqual([0, 0]);
+ });
});
});
diff --git a/packages/react-bootstrap-table2-paginator/test/page.test.js b/packages/react-bootstrap-table2-paginator/test/page.test.js
index bd63574..28ab1e1 100644
--- a/packages/react-bootstrap-table2-paginator/test/page.test.js
+++ b/packages/react-bootstrap-table2-paginator/test/page.test.js
@@ -1,9 +1,8 @@
-import Store from 'react-bootstrap-table-next/src/store';
+
import { getByCurrPage, alignPage } from '../src/page';
describe('Page Functions', () => {
let data;
- let store;
const params = [
// [page, sizePerPage, pageStartIndex]
[1, 10, 1],
@@ -23,27 +22,21 @@ describe('Page Functions', () => {
for (let i = 0; i < 100; i += 1) {
data.push({ id: i, name: `test_name${i}` });
}
- store = new Store('id');
- store.data = data;
});
it('should always return correct data', () => {
params.forEach(([page, sizePerPage, pageStartIndex]) => {
- store.page = page;
- store.sizePerPage = sizePerPage;
- const rows = getByCurrPage(store, pageStartIndex);
+ const rows = getByCurrPage(data, page, sizePerPage, pageStartIndex);
expect(rows).toBeDefined();
expect(Array.isArray(rows)).toBeTruthy();
expect(rows.every(row => !!row)).toBeTruthy();
});
});
- it('should return empty array when store.data is empty', () => {
- store.data = [];
+ it('should return empty array when data is empty', () => {
+ data = [];
params.forEach(([page, sizePerPage, pageStartIndex]) => {
- store.page = page;
- store.sizePerPage = sizePerPage;
- const rows = getByCurrPage(store, pageStartIndex);
+ const rows = getByCurrPage(data, page, sizePerPage, pageStartIndex);
expect(rows).toHaveLength(0);
});
});
@@ -52,19 +45,17 @@ describe('Page Functions', () => {
describe('alignPage', () => {
const pageStartIndex = 1;
const sizePerPage = 10;
+ const page = 2;
describe('if the length of store.data is less than the end page index', () => {
beforeEach(() => {
data = [];
for (let i = 0; i < 15; i += 1) {
data.push({ id: i, name: `test_name${i}` });
}
- store = new Store('id');
- store.data = data;
- store.page = 2;
});
it('should return pageStartIndex argument', () => {
- expect(alignPage(store, pageStartIndex, sizePerPage)).toEqual(pageStartIndex);
+ expect(alignPage(data, page, sizePerPage, pageStartIndex)).toEqual(pageStartIndex);
});
});
@@ -74,13 +65,10 @@ describe('Page Functions', () => {
for (let i = 0; i < 30; i += 1) {
data.push({ id: i, name: `test_name${i}` });
}
- store = new Store('id');
- store.data = data;
- store.page = 2;
});
it('should return current page', () => {
- expect(alignPage(store, pageStartIndex, sizePerPage)).toEqual(store.page);
+ expect(alignPage(data, page, sizePerPage, pageStartIndex)).toEqual(page);
});
});
});
diff --git a/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js b/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js
index 21e11cc..fdf55bd 100644
--- a/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js
+++ b/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js
@@ -5,6 +5,12 @@ import { shallow } from 'enzyme';
import SizePerPageOption from '../src/size-per-page-option';
import SizePerPageDropDown from '../src/size-per-page-dropdown';
+const shallowWithContext = (elem, context = {}) => {
+ const wrapper = shallow(elem);
+ const Children = wrapper.props().children(context);
+ return shallow(Children);
+};
+
describe('SizePerPageDropDown', () => {
let wrapper;
const currSizePerPage = '25';
@@ -28,8 +34,9 @@ describe('SizePerPageDropDown', () => {
describe('default SizePerPageDropDown component', () => {
beforeEach(() => {
- wrapper = shallow(
-
+ wrapper = shallowWithContext(
+
,
+ { bootstrap4: false }
);
});
@@ -47,6 +54,7 @@ describe('SizePerPageDropDown', () => {
const option = options[i];
expect(sizePerPage.prop('text')).toEqual(option.text);
expect(sizePerPage.prop('page')).toEqual(option.page);
+ expect(sizePerPage.prop('bootstrap4')).toBeFalsy();
expect(sizePerPage.prop('onSizePerPageChange')).toEqual(onSizePerPageChange);
});
});
@@ -61,10 +69,52 @@ describe('SizePerPageDropDown', () => {
});
});
+ describe('when bootstrap4 context is true', () => {
+ beforeEach(() => {
+ wrapper = shallowWithContext(
+
,
+ { bootstrap4: true }
+ );
+ });
+
+ it('should rendering SizePerPageDropDown correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('button').length).toBe(1);
+ expect(wrapper.find('button').text()).toEqual(`${currSizePerPage} `);
+ });
+
+ it('should rendering SizePerPageOption successfully', () => {
+ expect(wrapper.find('ul.dropdown-menu').length).toBe(1);
+ const sizePerPageOptions = wrapper.find(SizePerPageOption);
+ expect(sizePerPageOptions.length).toBe(options.length);
+ sizePerPageOptions.forEach((sizePerPage, i) => {
+ const option = options[i];
+ expect(sizePerPage.prop('text')).toEqual(option.text);
+ expect(sizePerPage.prop('page')).toEqual(option.page);
+ expect(sizePerPage.prop('bootstrap4')).toBeTruthy();
+ expect(sizePerPage.prop('onSizePerPageChange')).toEqual(onSizePerPageChange);
+ });
+ });
+
+ it('no need to render caret', () => {
+ expect(wrapper.find('.caret')).toHaveLength(0);
+ });
+
+ it('default variation is dropdown', () => {
+ expect(wrapper.hasClass('dropdown')).toBeTruthy();
+ });
+
+ it('default dropdown is not open', () => {
+ expect(wrapper.hasClass('open show')).toBeFalsy();
+ expect(wrapper.find('[aria-expanded=false]').length).toBe(1);
+ });
+ });
+
describe('when open prop is true', () => {
beforeEach(() => {
- wrapper = shallow(
-
+ wrapper = shallowWithContext(
+
,
+ { bootstrap4: false }
);
});
@@ -76,8 +126,9 @@ describe('SizePerPageDropDown', () => {
describe('when hidden prop is true', () => {
beforeEach(() => {
- wrapper = shallow(
-
+ wrapper = shallowWithContext(
+
,
+ { bootstrap4: false }
);
});
@@ -89,8 +140,9 @@ describe('SizePerPageDropDown', () => {
describe('when btnContextual prop is defined', () => {
const contextual = 'btn-warning';
beforeEach(() => {
- wrapper = shallow(
-
+ wrapper = shallowWithContext(
+
,
+ { bootstrap4: false }
);
});
@@ -102,8 +154,9 @@ describe('SizePerPageDropDown', () => {
describe('when variation prop is defined', () => {
const variation = 'dropup';
beforeEach(() => {
- wrapper = shallow(
-
+ wrapper = shallowWithContext(
+
,
+ { bootstrap4: false }
);
});
@@ -115,8 +168,9 @@ describe('SizePerPageDropDown', () => {
describe('when className prop is defined', () => {
const className = 'custom-class';
beforeEach(() => {
- wrapper = shallow(
-
+ wrapper = shallowWithContext(
+
,
+ { bootstrap4: false }
);
});
diff --git a/packages/react-bootstrap-table2-paginator/test/size-per-page-option.test.js b/packages/react-bootstrap-table2-paginator/test/size-per-page-option.test.js
index 8e4daeb..f24abd2 100644
--- a/packages/react-bootstrap-table2-paginator/test/size-per-page-option.test.js
+++ b/packages/react-bootstrap-table2-paginator/test/size-per-page-option.test.js
@@ -11,29 +11,64 @@ describe('SizePerPageOption', () => {
const onSizePerPageChange = sinon.stub();
beforeEach(() => {
- const props = { text, page, onSizePerPageChange };
- wrapper = shallow(
-
- );
+ onSizePerPageChange.reset();
});
- it('should render SizePerPageOption correctly', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('.dropdown-item').length).toBe(1);
- expect(wrapper.find(`[data-page=${page}]`).length).toBe(1);
- expect(wrapper.text()).toEqual(text);
- });
-
- describe('when MouseDown event happen', () => {
- const preventDefault = sinon.stub();
+ describe('when bootstrap4 prop is true', () => {
beforeEach(() => {
- wrapper.find('a').simulate('mousedown', { preventDefault });
+ const props = { text, page, onSizePerPageChange };
+ wrapper = shallow(
+
+ );
});
- it('should calling props.onSizePerPageChange correctly', () => {
- expect(preventDefault.calledOnce).toBeTruthy();
- expect(onSizePerPageChange.calledOnce).toBeTruthy();
- expect(onSizePerPageChange.calledWith(page)).toBeTruthy();
+ it('should render SizePerPageOption correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('li.dropdown-item').length).toBe(1);
+ expect(wrapper.find(`[data-page=${page}]`).length).toBe(1);
+ expect(wrapper.text()).toEqual(text);
+ });
+
+ describe('when MouseDown event happen', () => {
+ const preventDefault = sinon.stub();
+ beforeEach(() => {
+ wrapper.find('a').simulate('mousedown', { preventDefault });
+ });
+
+ it('should calling props.onSizePerPageChange correctly', () => {
+ expect(preventDefault.calledOnce).toBeTruthy();
+ expect(onSizePerPageChange.calledOnce).toBeTruthy();
+ expect(onSizePerPageChange.calledWith(page)).toBeTruthy();
+ });
+ });
+ });
+
+ describe('when bootstrap4 prop is true', () => {
+ beforeEach(() => {
+ const props = { text, page, onSizePerPageChange };
+ wrapper = shallow(
+
+ );
+ });
+
+ it('should render SizePerPageOption correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('a.dropdown-item').length).toBe(1);
+ expect(wrapper.find(`[data-page=${page}]`).length).toBe(1);
+ expect(wrapper.text()).toEqual(text);
+ });
+
+ describe('when MouseDown event happen', () => {
+ const preventDefault = sinon.stub();
+ beforeEach(() => {
+ wrapper.find('a').simulate('mousedown', { preventDefault });
+ });
+
+ it('should calling props.onSizePerPageChange correctly', () => {
+ expect(preventDefault.calledOnce).toBeTruthy();
+ expect(onSizePerPageChange.calledOnce).toBeTruthy();
+ expect(onSizePerPageChange.calledWith(page)).toBeTruthy();
+ });
});
});
});
diff --git a/packages/react-bootstrap-table2-paginator/test/wrapper.test.js b/packages/react-bootstrap-table2-paginator/test/wrapper.test.js
deleted file mode 100644
index f636fe3..0000000
--- a/packages/react-bootstrap-table2-paginator/test/wrapper.test.js
+++ /dev/null
@@ -1,570 +0,0 @@
-import React from 'react';
-import sinon from 'sinon';
-import { shallow } from 'enzyme';
-
-
-import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table';
-import remoteResolver from 'react-bootstrap-table-next/src/props-resolver/remote-resolver';
-import Store from 'react-bootstrap-table-next/src/store';
-import paginator from '..';
-import wrapperFactory from '../src/wrapper';
-import Pagination from '../src/pagination';
-import Const from '../src/const';
-
-const data = [];
-for (let i = 0; i < 100; i += 1) {
- data.push({
- id: i,
- name: `item name ${i}`
- });
-}
-
-describe('Wrapper', () => {
- let wrapper;
- let instance;
- const onTableChangeCB = sinon.stub();
-
- afterEach(() => {
- onTableChangeCB.reset();
- });
-
- const createTableProps = (props = {}) => {
- const tableProps = {
- keyField: 'id',
- columns: [{
- dataField: 'id',
- text: 'ID'
- }, {
- dataField: 'name',
- text: 'Name'
- }],
- data,
- pagination: paginator(props.options),
- store: new Store('id'),
- onTableChange: onTableChangeCB
- };
- tableProps.store.data = data;
- return tableProps;
- };
-
- const PaginationWrapper = wrapperFactory(BootstrapTable, {
- remoteResolver
- });
-
- const createPaginationWrapper = (props, renderFragment = true) => {
- wrapper = shallow(
);
- instance = wrapper.instance();
- if (renderFragment) {
- const fragment = instance.render();
- wrapper = shallow(
{ fragment }
);
- }
- };
-
- describe('default pagination', () => {
- const props = createTableProps();
-
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should render correctly', () => {
- expect(wrapper.length).toBe(1);
- });
-
- it('should initialize state correctly', () => {
- expect(instance.state.currPage).toBeDefined();
- expect(instance.state.currPage).toEqual(Const.PAGE_START_INDEX);
- expect(instance.state.currSizePerPage).toBeDefined();
- expect(instance.state.currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[0]);
- });
-
- it('should save page and sizePerPage to the store correctly', () => {
- expect(props.store.page).toBe(instance.state.currPage);
- expect(props.store.sizePerPage).toBe(instance.state.currSizePerPage);
- });
-
- it('should render BootstrapTable correctly', () => {
- const table = wrapper.find(BootstrapTable);
- expect(table.length).toBe(1);
- expect(table.prop('data').length).toEqual(instance.state.currSizePerPage);
- });
-
- it('should render Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('dataSize')).toEqual(props.store.data.length);
- expect(pagination.prop('currPage')).toEqual(instance.state.currPage);
- expect(pagination.prop('currSizePerPage')).toEqual(instance.state.currSizePerPage);
- expect(pagination.prop('onPageChange')).toEqual(instance.handleChangePage);
- expect(pagination.prop('onSizePerPageChange')).toEqual(instance.handleChangeSizePerPage);
- expect(pagination.prop('sizePerPageList')).toEqual(Const.SIZE_PER_PAGE_LIST);
- expect(pagination.prop('paginationSize')).toEqual(Const.PAGINATION_SIZE);
- expect(pagination.prop('pageStartIndex')).toEqual(Const.PAGE_START_INDEX);
- expect(pagination.prop('withFirstAndLast')).toEqual(Const.With_FIRST_AND_LAST);
- expect(pagination.prop('alwaysShowAllBtns')).toEqual(Const.SHOW_ALL_PAGE_BTNS);
- expect(pagination.prop('firstPageText')).toEqual(Const.FIRST_PAGE_TEXT);
- expect(pagination.prop('prePageText')).toEqual(Const.PRE_PAGE_TEXT);
- expect(pagination.prop('nextPageText')).toEqual(Const.NEXT_PAGE_TEXT);
- expect(pagination.prop('lastPageText')).toEqual(Const.LAST_PAGE_TEXT);
- expect(pagination.prop('firstPageTitle')).toEqual(Const.FIRST_PAGE_TITLE);
- expect(pagination.prop('prePageTitle')).toEqual(Const.PRE_PAGE_TITLE);
- expect(pagination.prop('nextPageTitle')).toEqual(Const.NEXT_PAGE_TITLE);
- expect(pagination.prop('lastPageTitle')).toEqual(Const.LAST_PAGE_TITLE);
- expect(pagination.prop('hideSizePerPage')).toEqual(Const.HIDE_SIZE_PER_PAGE);
- expect(pagination.prop('showTotal')).toBeFalsy();
- });
-
- describe('componentWillReceiveProps', () => {
- let nextProps;
- beforeEach(() => {
- nextProps = createTableProps();
- });
-
- describe('when options.page is existing', () => {
- beforeEach(() => {
- nextProps.pagination.options.page = 2;
- instance.componentWillReceiveProps(nextProps);
- });
-
- it('should setting currPage state correctly', () => {
- expect(instance.state.currPage).toEqual(nextProps.pagination.options.page);
- });
-
- it('should saving store.page correctly', () => {
- expect(props.store.page).toEqual(instance.state.currPage);
- });
- });
-
- it('should not setting currPage state if options.page not existing', () => {
- const { currPage } = instance.state;
- instance.componentWillReceiveProps(nextProps);
- expect(instance.state.currPage).toBe(currPage);
- });
-
- describe('when options.sizePerPage is existing', () => {
- beforeEach(() => {
- nextProps.pagination.options.sizePerPage = 20;
- instance.componentWillReceiveProps(nextProps);
- });
-
- it('should setting currSizePerPage state correctly', () => {
- expect(instance.state.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage);
- });
-
- it('should saving store.sizePerPage correctly', () => {
- expect(props.store.sizePerPage).toEqual(instance.state.currSizePerPage);
- });
- });
-
- it('should not setting currSizePerPage state if options.sizePerPage not existing', () => {
- const { currSizePerPage } = instance.state;
- instance.componentWillReceiveProps(nextProps);
- expect(instance.state.currSizePerPage).toBe(currSizePerPage);
- });
-
- describe('when nextProps.isDataChanged is true', () => {
- beforeEach(() => {
- nextProps.isDataChanged = true;
- instance.componentWillReceiveProps(nextProps);
- });
-
- it('should setting currPage state correctly', () => {
- expect(instance.state.currPage).toBe(Const.PAGE_START_INDEX);
- });
-
- it('should saving store.page correctly', () => {
- expect(props.store.page).toEqual(instance.state.currPage);
- });
- });
- });
- });
-
- describe('when options.page is defined', () => {
- const page = 3;
- const props = createTableProps({ options: { page } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should setting correct state.currPage', () => {
- expect(instance.state.currPage).toEqual(page);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('currPage')).toEqual(page);
- });
- });
-
- describe('when options.sizePerPage is defined', () => {
- const sizePerPage = 30;
- const props = createTableProps({ options: { sizePerPage } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should setting correct state.currPage', () => {
- expect(instance.state.currSizePerPage).toEqual(sizePerPage);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('currSizePerPage')).toEqual(sizePerPage);
- });
- });
-
- describe('when options.totalSize is defined', () => {
- const totalSize = 100;
- const props = createTableProps({ options: { totalSize } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('dataSize')).toEqual(totalSize);
- });
- });
-
- describe('when options.showTotal is defined', () => {
- const props = createTableProps({ options: { showTotal: true } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should render Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('showTotal')).toBeTruthy();
- });
- });
-
- describe('when options.pageStartIndex is defined', () => {
- const pageStartIndex = -1;
- const props = createTableProps({ options: { pageStartIndex } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should setting correct state.currPage', () => {
- expect(instance.state.currPage).toEqual(pageStartIndex);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('pageStartIndex')).toEqual(pageStartIndex);
- });
- });
-
- describe('when options.sizePerPageList is defined', () => {
- const sizePerPageList = [10, 40];
- const props = createTableProps({ options: { sizePerPageList } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('sizePerPageList')).toEqual(sizePerPageList);
- });
- });
-
- describe('when options.paginationSize is defined', () => {
- const paginationSize = 10;
- const props = createTableProps({ options: { paginationSize } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('paginationSize')).toEqual(paginationSize);
- });
- });
-
- describe('when options.withFirstAndLast is defined', () => {
- const withFirstAndLast = false;
- const props = createTableProps({ options: { withFirstAndLast } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('withFirstAndLast')).toEqual(withFirstAndLast);
- });
- });
-
- describe('when options.alwaysShowAllBtns is defined', () => {
- const alwaysShowAllBtns = true;
- const props = createTableProps({ options: { alwaysShowAllBtns } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('alwaysShowAllBtns')).toEqual(alwaysShowAllBtns);
- });
- });
-
- describe('when options.firstPageText is defined', () => {
- const firstPageText = '1st';
- const props = createTableProps({ options: { firstPageText } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('firstPageText')).toEqual(firstPageText);
- });
- });
-
- describe('when options.prePageText is defined', () => {
- const prePageText = 'PRE';
- const props = createTableProps({ options: { prePageText } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('prePageText')).toEqual(prePageText);
- });
- });
-
- describe('when options.nextPageText is defined', () => {
- const nextPageText = 'NEXT';
- const props = createTableProps({ options: { nextPageText } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('nextPageText')).toEqual(nextPageText);
- });
- });
-
- describe('when options.lastPageText is defined', () => {
- const lastPageText = 'nth';
- const props = createTableProps({ options: { lastPageText } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('lastPageText')).toEqual(lastPageText);
- });
- });
-
- describe('when options.firstPageTitle is defined', () => {
- const firstPageTitle = '1st';
- const props = createTableProps({ options: { firstPageTitle } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('firstPageTitle')).toEqual(firstPageTitle);
- });
- });
-
- describe('when options.prePageTitle is defined', () => {
- const prePageTitle = 'PRE';
- const props = createTableProps({ options: { prePageTitle } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('prePageTitle')).toEqual(prePageTitle);
- });
- });
-
- describe('when options.nextPageTitle is defined', () => {
- const nextPageTitle = 'NEXT';
- const props = createTableProps({ options: { nextPageTitle } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('nextPageTitle')).toEqual(nextPageTitle);
- });
- });
-
- describe('when options.lastPageTitle is defined', () => {
- const lastPageTitle = 'nth';
- const props = createTableProps({ options: { lastPageTitle } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('lastPageTitle')).toEqual(lastPageTitle);
- });
- });
-
- describe('when options.hideSizePerPage is defined', () => {
- const hideSizePerPage = true;
- const props = createTableProps({ options: { hideSizePerPage } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('hideSizePerPage')).toEqual(hideSizePerPage);
- });
- });
-
- describe('when options.hidePageListOnlyOnePage is defined', () => {
- const hidePageListOnlyOnePage = true;
- const props = createTableProps({ options: { hidePageListOnlyOnePage } });
- beforeEach(() => {
- createPaginationWrapper(props);
- });
-
- it('should rendering Pagination correctly', () => {
- const pagination = wrapper.find(Pagination);
- expect(wrapper.length).toBe(1);
- expect(pagination.length).toBe(1);
- expect(pagination.prop('hidePageListOnlyOnePage')).toEqual(hidePageListOnlyOnePage);
- });
- });
-
- describe('handleChangePage', () => {
- const newPage = 3;
- const props = createTableProps({ options: { onPageChange: sinon.stub() } });
- beforeEach(() => {
- createPaginationWrapper(props, false);
- instance.handleChangePage(newPage);
- });
-
- afterEach(() => {
- props.pagination.options.onPageChange.reset();
- });
-
- it('should setting state.currPage correctly', () => {
- expect(instance.state.currPage).toEqual(newPage);
- });
-
- it('should calling options.onPageChange correctly when it is defined', () => {
- const { onPageChange } = props.pagination.options;
- expect(onPageChange.calledOnce).toBeTruthy();
- expect(onPageChange.calledWith(newPage, instance.state.currSizePerPage)).toBeTruthy();
- });
-
- it('should saving page and sizePerPage to store correctly', () => {
- expect(props.store.page).toBe(newPage);
- expect(props.store.sizePerPage).toBe(instance.state.currSizePerPage);
- });
-
- describe('when pagination remote is enable', () => {
- beforeEach(() => {
- props.remote = true;
- createPaginationWrapper(props, false);
- onTableChangeCB.reset();
- instance.handleChangePage(newPage);
- });
-
- it('should not setting state.currPage', () => {
- expect(instance.state.currPage).not.toEqual(newPage);
- });
-
- it('should calling props.onRemotePageChange correctly', () => {
- expect(onTableChangeCB.calledOnce).toBeTruthy();
- });
- });
- });
-
- describe('handleChangeSizePerPage', () => {
- const newPage = 2;
- const newSizePerPage = 30;
- const props = createTableProps({ options: { onSizePerPageChange: sinon.stub() } });
- beforeEach(() => {
- createPaginationWrapper(props, false);
- instance.handleChangeSizePerPage(newSizePerPage, newPage);
- });
-
- afterEach(() => {
- props.pagination.options.onSizePerPageChange.reset();
- });
-
- it('should setting state.currPage and state.currSizePerPage correctly', () => {
- expect(instance.state.currPage).toEqual(newPage);
- expect(instance.state.currSizePerPage).toEqual(newSizePerPage);
- });
-
- it('should calling options.onSizePerPageChange correctly when it is defined', () => {
- const { onSizePerPageChange } = props.pagination.options;
- expect(onSizePerPageChange.calledOnce).toBeTruthy();
- expect(onSizePerPageChange.calledWith(newSizePerPage, newPage)).toBeTruthy();
- });
-
- it('should saving page and sizePerPage to store correctly', () => {
- expect(props.store.page).toBe(newPage);
- expect(props.store.sizePerPage).toBe(newSizePerPage);
- });
-
- describe('when pagination remote is enable', () => {
- beforeEach(() => {
- props.remote = true;
- createPaginationWrapper(props, false);
- onTableChangeCB.reset();
- instance.handleChangeSizePerPage(newSizePerPage, newPage);
- });
-
- it('should not setting state.currPage', () => {
- expect(instance.state.currPage).not.toEqual(newPage);
- expect(instance.state.currSizePerPage).not.toEqual(newSizePerPage);
- });
-
- it('should calling props.onRemotePageChange correctly', () => {
- expect(onTableChangeCB.calledOnce).toBeTruthy();
- });
- });
- });
-});
diff --git a/packages/react-bootstrap-table2-toolkit/README.md b/packages/react-bootstrap-table2-toolkit/README.md
new file mode 100644
index 0000000..f26c366
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/README.md
@@ -0,0 +1,126 @@
+# react-bootstrap-table2-toolkit
+
+`react-bootstrap-table2` support some additional features in [`react-bootstrap-table2-toolkit`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/develop/packages/react-bootstrap-table2-toolkit).
+
+In the future, this toolkit will support other feature like row delete, insert etc. Right now we only support Table Search and CSV export.
+
+**[Live Demo For Table Search](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Table%20Search)**
+
+**[API&Props Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html)**
+
+-----
+
+## Install
+
+```sh
+$ npm install react-bootstrap-table2-toolkit --save
+```
+
+## Add CSS
+
+```js
+// es5
+require('react-bootstrap-table2-toolkit/dist/react-bootstrap-table2-toolkit.min.css');
+
+// es6
+import 'react-bootstrap-table2-toolkit/dist/react-bootstrap-table2-toolkit.min.css';
+```
+
+## Table Search
+
+```js
+import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
+
+const { SearchBar } = Search;
+//...
+
+
+ {
+ props => (
+
+
Input something at below input field:
+
+
+
+
+ )
+ }
+
+```
+
+1. You have to enable the search functionality via `search` prop on `ToolkitProvider`.
+
+2. `ToolkitProvider` is a wrapper of react context, you are supposed to wrap the `BootstrapTable` and `SearchBar` as the child of `ToolkitProvider`
+
+3. You should render `SearchBar` with `searchProps` as well. The position of `SearchBar` is depends on you.
+
+### Search Options
+
+#### searchFormatted - [bool]
+If you want to search on the formatted data, you are supposed to enable this props. `react-bootstrap-table2` will check if you define the `column.formatter` when doing search.
+
+```js
+
+ // ...
+
+```
+
+## Export CSV
+There are two step to enable the export CSV functionality:
+
+1. Give `exportCSV` prop as `true` on `ToolkitProvider`.
+2. Render `ExportCSVButton` with `csvProps`. The position of `ExportCSVButton` is depends on you.
+
+```js
+import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit';
+
+const { ExportCSVButton } = CSVExport;
+
+
+ {
+ props => (
+
+ Export CSV!!
+
+
+
+ )
+ }
+
+```
+
+### Export CSV Options
+
+#### fileName - [String]
+Custom the csv file name.
+
+#### separator - [String]
+Custom the csv file separator.
+
+#### ignoreHeader - [bool]
+Default is `false`. Give true to avoid to attach the csv header.
+
+#### noAutoBOM - [bool]
+Default is `true`.
+
+#### exportAll - [bool]
+Default is `true`. `false` will only export current data which display on table.
\ No newline at end of file
diff --git a/packages/react-bootstrap-table2-toolkit/context.js b/packages/react-bootstrap-table2-toolkit/context.js
new file mode 100644
index 0000000..d12a7f0
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/context.js
@@ -0,0 +1,103 @@
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import statelessDrcorator from './statelessOp';
+
+import createContext from './src/search/context';
+
+const ToolkitContext = React.createContext();
+
+class ToolkitProvider extends statelessDrcorator(React.Component) {
+ static propTypes = {
+ keyField: PropTypes.string.isRequired,
+ data: PropTypes.array.isRequired,
+ columns: PropTypes.array.isRequired,
+ children: PropTypes.node.isRequired,
+ bootstrap4: PropTypes.bool,
+ search: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.shape({
+ searchFormatted: PropTypes.bool
+ })
+ ]),
+ exportCSV: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.shape({
+ fileName: PropTypes.string,
+ separator: PropTypes.string,
+ ignoreHeader: PropTypes.bool,
+ noAutoBOM: PropTypes.bool
+ })
+ ])
+ }
+
+ static defaultProps = {
+ search: false,
+ exportCSV: false,
+ bootstrap4: false
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ searchText: ''
+ };
+ this._ = null;
+ this.onSearch = this.onSearch.bind(this);
+ this.setDependencyModules = this.setDependencyModules.bind(this);
+ }
+
+ onSearch(searchText) {
+ if (searchText !== this.state.searchText) {
+ this.setState({ searchText });
+ }
+ }
+
+ /**
+ *
+ * @param {*} _
+ * this function will be called only one time when table render
+ * react-bootstrap-table-next/src/context/index.js will call this cb for passing the _ module
+ * Please consider to extract a common module to handle _ module.
+ * this is just a quick fix
+ */
+ setDependencyModules(_) {
+ this._ = _;
+ }
+
+ render() {
+ const baseProps = {
+ keyField: this.props.keyField,
+ columns: this.props.columns,
+ data: this.props.data,
+ bootstrap4: this.props.bootstrap4,
+ setDependencyModules: this.setDependencyModules,
+ registerExposedAPI: this.registerExposedAPI
+ };
+ if (this.props.search) {
+ baseProps.search = {
+ searchContext: createContext(this.props.search),
+ searchText: this.state.searchText
+ };
+ }
+ return (
+
+ { this.props.children }
+
+ );
+ }
+}
+
+export default {
+ Provider: ToolkitProvider,
+ Consumer: ToolkitContext.Consumer
+};
diff --git a/packages/react-bootstrap-table2-toolkit/index.js b/packages/react-bootstrap-table2-toolkit/index.js
new file mode 100644
index 0000000..f9553ac
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/index.js
@@ -0,0 +1,7 @@
+import Context from './context';
+import ToolkitProvider from './provider';
+
+export default ToolkitProvider;
+export const ToolkitContext = Context;
+export { default as Search } from './src/search';
+export { default as CSVExport } from './src/csv';
diff --git a/packages/react-bootstrap-table2-toolkit/package.json b/packages/react-bootstrap-table2-toolkit/package.json
new file mode 100644
index 0000000..01c3d36
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "react-bootstrap-table2-toolkit",
+ "version": "1.0.1",
+ "description": "The toolkit for react-bootstrap-table2",
+ "main": "./lib/index.js",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/react-bootstrap-table/react-bootstrap-table2.git"
+ },
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "files": [
+ "lib/",
+ "dist/"
+ ],
+ "tags": [
+ "react"
+ ],
+ "author": "AllenFang",
+ "license": "MIT",
+ "keywords": [
+ "react",
+ "bootstrap",
+ "table",
+ "grid",
+ "react-bootstrap-table-addons",
+ "react-component"
+ ],
+ "contributors": [
+ {
+ "name": "Allen Fang",
+ "email": "ayu780129@hotmail.com",
+ "url": "https://github.com/AllenFang"
+ },
+ {
+ "name": "Chun-MingChen",
+ "email": "nick830314@gmail.com",
+ "url": "https://github.com/Chun-MingChen"
+ }
+ ],
+ "peerDependencies": {
+ "prop-types": "^15.0.0",
+ "react": "^16.3.0",
+ "react-dom": "^16.3.0"
+ },
+ "dependencies": {
+ "file-saver": "1.3.8"
+ }
+}
diff --git a/packages/react-bootstrap-table2-toolkit/provider.js b/packages/react-bootstrap-table2-toolkit/provider.js
new file mode 100644
index 0000000..ff8a323
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/provider.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ToolkitContext from './context';
+
+const Toolkitprovider = props => (
+
+
+ {
+ tookKitProps => props.children(tookKitProps)
+ }
+
+
+);
+
+Toolkitprovider.propTypes = {
+ children: PropTypes.func.isRequired
+};
+
+export default Toolkitprovider;
diff --git a/packages/react-bootstrap-table2-toolkit/src/csv/button.js b/packages/react-bootstrap-table2-toolkit/src/csv/button.js
new file mode 100644
index 0000000..d0ee010
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/src/csv/button.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const ExportCSVButton = (props) => {
+ const {
+ onExport,
+ children,
+ ...rest
+ } = props;
+
+ return (
+
+ { children }
+
+ );
+};
+
+ExportCSVButton.propTypes = {
+ children: PropTypes.node.isRequired,
+ onExport: PropTypes.func.isRequired,
+ className: PropTypes.string,
+ style: PropTypes.object
+};
+ExportCSVButton.defaultProps = {
+ className: 'react-bs-table-csv-btn btn btn-default',
+ style: {}
+};
+
+export default ExportCSVButton;
diff --git a/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js b/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js
new file mode 100644
index 0000000..e0e31f1
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js
@@ -0,0 +1,65 @@
+/* eslint no-unneeded-ternary: 0 */
+import FileSaver from 'file-saver';
+
+export const getMetaInfo = columns =>
+ columns
+ .map(column => ({
+ field: column.dataField,
+ type: column.csvType || String,
+ formatter: column.csvFormatter,
+ formatExtraData: column.formatExtraData,
+ header: column.csvText || column.text,
+ export: column.csvExport === false ? false : true,
+ row: Number(column.row) || 0,
+ rowSpan: Number(column.rowSpan) || 1,
+ colSpan: Number(column.colSpan) || 1
+ }))
+ .filter(_ => _.export);
+
+export const transform = (
+ data,
+ meta,
+ getValue,
+ {
+ separator,
+ ignoreHeader
+ }
+) => {
+ const visibleColumns = meta.filter(m => m.export);
+ let content = '';
+ // extract csv header
+ if (!ignoreHeader) {
+ content += visibleColumns.map(m => `"${m.header}"`).join(separator);
+ content += '\n';
+ }
+ // extract csv body
+ if (data.length === 0) return content;
+ content += data
+ .map((row, rowIndex) =>
+ visibleColumns.map((m) => {
+ let cellContent = getValue(row, m.field);
+ if (m.formatter) {
+ cellContent = m.formatter(cellContent, row, rowIndex, m.formatExtraData);
+ }
+ if (m.type === String) {
+ return `"${cellContent}"`;
+ }
+ return cellContent;
+ }).join(separator)).join('\n');
+
+ return content;
+};
+
+export const save = (
+ content,
+ {
+ noAutoBOM,
+ fileName
+ }
+) => {
+ FileSaver.saveAs(
+ new Blob(['\ufeff', content], { type: 'text/plain;charset=utf-8' }),
+ fileName,
+ noAutoBOM
+ );
+};
diff --git a/packages/react-bootstrap-table2-toolkit/src/csv/index.js b/packages/react-bootstrap-table2-toolkit/src/csv/index.js
new file mode 100644
index 0000000..4c60453
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/src/csv/index.js
@@ -0,0 +1,3 @@
+import ExportCSVButton from './button';
+
+export default { ExportCSVButton };
diff --git a/packages/react-bootstrap-table2-toolkit/src/op/csv.js b/packages/react-bootstrap-table2-toolkit/src/op/csv.js
new file mode 100644
index 0000000..8c6ced3
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/src/op/csv.js
@@ -0,0 +1,27 @@
+import { getMetaInfo, transform, save } from '../csv/exporter';
+
+const csvDefaultOptions = {
+ fileName: 'spreadsheet.csv',
+ separator: ',',
+ ignoreHeader: false,
+ noAutoBOM: true,
+ exportAll: true
+};
+
+export default Base =>
+ class CSVOperation extends Base {
+ handleExportCSV = () => {
+ const { columns, exportCSV } = this.props;
+ const meta = getMetaInfo(columns);
+ const options = exportCSV === true ?
+ csvDefaultOptions :
+ {
+ ...csvDefaultOptions,
+ ...exportCSV
+ };
+
+ const data = options.exportAll ? this.props.data : this.getData();
+ const content = transform(data, meta, this._.get, options);
+ save(content, options);
+ }
+ };
diff --git a/packages/react-bootstrap-table2-toolkit/src/op/index.js b/packages/react-bootstrap-table2-toolkit/src/op/index.js
new file mode 100644
index 0000000..847e057
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/src/op/index.js
@@ -0,0 +1,5 @@
+import csvOperation from './csv';
+
+export default {
+ csvOperation
+};
diff --git a/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js b/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js
new file mode 100644
index 0000000..99ee8ee
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js
@@ -0,0 +1,73 @@
+/* eslint no-return-assign: 0 */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const handleDebounce = (func, wait, immediate) => {
+ let timeout;
+
+ return () => {
+ const later = () => {
+ timeout = null;
+
+ if (!immediate) {
+ func.apply(this, arguments);
+ }
+ };
+
+ const callNow = immediate && !timeout;
+
+ clearTimeout(timeout);
+
+ timeout = setTimeout(later, wait || 0);
+
+ if (callNow) {
+ func.appy(this, arguments);
+ }
+ };
+};
+
+const SearchBar = ({
+ delay,
+ onSearch,
+ className,
+ style,
+ placeholder,
+ searchText,
+ ...rest
+}) => {
+ let input;
+ const debounceCallback = handleDebounce(() => {
+ onSearch(input.value);
+ }, delay);
+
+ return (
+
input = n }
+ type="text"
+ style={ style }
+ onKeyUp={ () => debounceCallback() }
+ className={ `form-control ${className}` }
+ placeholder={ placeholder || SearchBar.defaultProps.placeholder }
+ { ...rest }
+ />
+ );
+};
+
+SearchBar.propTypes = {
+ onSearch: PropTypes.func.isRequired,
+ className: PropTypes.string,
+ placeholder: PropTypes.string,
+ style: PropTypes.object,
+ delay: PropTypes.number,
+ searchText: PropTypes.string
+};
+
+SearchBar.defaultProps = {
+ className: '',
+ style: {},
+ placeholder: 'Search',
+ delay: 250,
+ searchText: ''
+};
+
+export default SearchBar;
diff --git a/packages/react-bootstrap-table2-toolkit/src/search/context.js b/packages/react-bootstrap-table2-toolkit/src/search/context.js
new file mode 100644
index 0000000..67935bf
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/src/search/context.js
@@ -0,0 +1,68 @@
+/* eslint react/prop-types: 0 */
+/* eslint react/require-default-props: 0 */
+/* eslint no-continue: 0 */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+export default (options = {
+ searchFormatted: false
+}) => (
+ _,
+ isRemoteSearch,
+ handleRemoteSearchChange
+) => {
+ const SearchContext = React.createContext();
+
+ class SearchProvider extends React.Component {
+ static propTypes = {
+ data: PropTypes.array.isRequired,
+ columns: PropTypes.array.isRequired,
+ searchText: PropTypes.string
+ }
+
+ search() {
+ const { data, columns } = this.props;
+ let { searchText } = this.props;
+
+ if (isRemoteSearch()) {
+ handleRemoteSearchChange(searchText);
+ return data;
+ }
+
+ searchText = searchText.toLowerCase();
+ return data.filter((row, ridx) => {
+ for (let cidx = 0; cidx < columns.length; cidx += 1) {
+ const column = columns[cidx];
+ if (column.searchable === false) continue;
+ let targetValue = _.get(row, column.dataField);
+ if (column.formatter && options.searchFormatted) {
+ targetValue = column.formatter(targetValue, row, ridx, column.formatExtraData);
+ } else if (column.filterValue) {
+ targetValue = column.filterValue(targetValue, row);
+ }
+ if (targetValue !== null && typeof targetValue !== 'undefined') {
+ targetValue = targetValue.toString().toLowerCase();
+ if (targetValue.indexOf(searchText) > -1) {
+ return true;
+ }
+ }
+ }
+ return false;
+ });
+ }
+
+ render() {
+ const data = this.search();
+ return (
+
+ { this.props.children }
+
+ );
+ }
+ }
+
+ return {
+ Provider: SearchProvider,
+ Consumer: SearchContext.Consumer
+ };
+};
diff --git a/packages/react-bootstrap-table2-toolkit/src/search/index.js b/packages/react-bootstrap-table2-toolkit/src/search/index.js
new file mode 100644
index 0000000..c905897
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/src/search/index.js
@@ -0,0 +1,3 @@
+import SearchBar from './SearchBar';
+
+export default { SearchBar };
diff --git a/packages/react-bootstrap-table2-toolkit/statelessOp.js b/packages/react-bootstrap-table2-toolkit/statelessOp.js
new file mode 100644
index 0000000..6c248c1
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/statelessOp.js
@@ -0,0 +1,10 @@
+import Operation from './src/op';
+
+export default Base =>
+ class StatelessOperation extends Operation.csvOperation(Base) {
+ registerExposedAPI = (...exposedFuncs) => {
+ exposedFuncs.forEach((func) => {
+ this[func.name] = func;
+ });
+ }
+ };
diff --git a/packages/react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss b/packages/react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss
new file mode 100644
index 0000000..e69de29
diff --git a/packages/react-bootstrap-table2-toolkit/yarn.lock b/packages/react-bootstrap-table2-toolkit/yarn.lock
new file mode 100644
index 0000000..bcdf61c
--- /dev/null
+++ b/packages/react-bootstrap-table2-toolkit/yarn.lock
@@ -0,0 +1,7 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+file-saver@1.3.8:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8"
diff --git a/packages/react-bootstrap-table2/index.js b/packages/react-bootstrap-table2/index.js
index c134e64..59ad74d 100644
--- a/packages/react-bootstrap-table2/index.js
+++ b/packages/react-bootstrap-table2/index.js
@@ -1,5 +1,4 @@
import BootstrapTable from './src/bootstrap-table';
-import withDataStore from './src/container';
-
-export default withDataStore(BootstrapTable);
+import withContext from './src/contexts';
+export default withContext(BootstrapTable);
diff --git a/packages/react-bootstrap-table2/package.json b/packages/react-bootstrap-table2/package.json
index 802e04d..1ab1ed6 100644
--- a/packages/react-bootstrap-table2/package.json
+++ b/packages/react-bootstrap-table2/package.json
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table-next",
- "version": "0.1.15",
+ "version": "1.1.0",
"description": "Next generation of react-bootstrap-table",
"main": "./lib/index.js",
"repository": {
@@ -41,7 +41,7 @@
"peerDependencies": {
"classnames": "^2.2.5",
"prop-types": "^15.0.0",
- "react": "^16.0.0",
- "react-dom": "^16.0.0"
+ "react": "^16.3.0",
+ "react-dom": "^16.3.0"
}
}
diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js
index b4ca400..7b29989 100644
--- a/packages/react-bootstrap-table2/src/body.js
+++ b/packages/react-bootstrap-table2/src/body.js
@@ -7,6 +7,7 @@ import cs from 'classnames';
import _ from './utils';
import Row from './row';
+import ExpandRow from './row-expand/expand-row';
import RowSection from './row-section';
import Const from './const';
@@ -23,7 +24,8 @@ const Body = (props) => {
selectedRowKeys,
rowStyle,
rowClasses,
- rowEvents
+ rowEvents,
+ expandRow
} = props;
const {
@@ -74,8 +76,10 @@ const Body = (props) => {
}
const selectable = !nonSelectable || !nonSelectable.includes(key);
+ const expandable = expandRow && !expandRow.nonExpandable.includes(key);
+ const expanded = expandRow && expandRow.expanded.includes(key);
- return (
+ const result = [
{
cellEdit={ cellEdit }
editable={ editable }
selectable={ selectable }
+ expandable={ expandable }
selected={ selected }
+ expanded={ expanded }
selectRow={ selectRow }
+ expandRow={ expandRow }
style={ style }
className={ classes }
attrs={ attrs }
/>
- );
+ ];
+
+ if (expanded) {
+ result.push((
+
+ { expandRow.renderer(row) }
+
+ ));
+ }
+
+ return result;
});
}
diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js
index e9ee145..50ec912 100644
--- a/packages/react-bootstrap-table2/src/bootstrap-table.js
+++ b/packages/react-bootstrap-table2/src/bootstrap-table.js
@@ -9,22 +9,21 @@ import Caption from './caption';
import Body from './body';
import PropsBaseResolver from './props-resolver';
import Const from './const';
-import { isSelectedAll } from './store/selection';
+import { getSelectionSummary } from './store/selection';
class BootstrapTable extends PropsBaseResolver(Component) {
constructor(props) {
super(props);
this.validateProps();
-
- this.state = {
- data: props.data
- };
+ if (props.registerExposedAPI) {
+ const getData = () => this.getData();
+ props.registerExposedAPI(getData);
+ }
}
- componentWillReceiveProps(nextProps) {
- this.setState({
- data: nextProps.data
- });
+ // Exposed APIs
+ getData = () => {
+ return this.props.data;
}
render() {
@@ -42,7 +41,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
renderTable() {
const {
- store,
+ data,
columns,
keyField,
id,
@@ -56,7 +55,8 @@ class BootstrapTable extends PropsBaseResolver(Component) {
rowStyle,
rowClasses,
wrapperClasses,
- rowEvents
+ rowEvents,
+ selected
} = this.props;
const tableWrapperClass = cs('react-bootstrap-table', wrapperClasses);
@@ -72,13 +72,16 @@ class BootstrapTable extends PropsBaseResolver(Component) {
onRowSelect: this.props.onRowSelect
});
+ const { allRowsSelected, allRowsNotSelected } = getSelectionSummary(data, keyField, selected);
const headerCellSelectionInfo = this.resolveSelectRowPropsForHeader({
onAllRowsSelect: this.props.onAllRowsSelect,
- selected: store.selected,
- allRowsSelected: isSelectedAll(store)
+ selected,
+ allRowsSelected,
+ allRowsNotSelected
});
const tableCaption = (caption && { caption } );
+ const expandRow = this.resolveExpandRowProps();
return (
@@ -87,15 +90,16 @@ class BootstrapTable extends PropsBaseResolver(Component) {
+
{ typeof content === 'boolean' ? `${content}` : content }
);
diff --git a/packages/react-bootstrap-table2/src/container.js b/packages/react-bootstrap-table2/src/container.js
deleted file mode 100644
index 195ef25..0000000
--- a/packages/react-bootstrap-table2/src/container.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/* eslint no-return-assign: 0 */
-/* eslint react/prop-types: 0 */
-import React, { Component } from 'react';
-import Store from './store';
-import withSort from './sort/wrapper';
-import withSelection from './row-selection/wrapper';
-
-import remoteResolver from './props-resolver/remote-resolver';
-import _ from './utils';
-
-const withDataStore = Base =>
- class BootstrapTableContainer extends remoteResolver(Component) {
- constructor(props) {
- super(props);
- this.store = new Store(props.keyField);
- this.store.data = props.data;
- this.wrapComponents();
- }
-
- componentWillReceiveProps(nextProps) {
- this.store.setAllData(nextProps.data);
- }
-
- wrapComponents() {
- this.BaseComponent = Base;
- const { pagination, columns, filter, selectRow, cellEdit } = this.props;
- if (pagination) {
- const { wrapperFactory } = pagination;
- this.BaseComponent = wrapperFactory(this.BaseComponent, {
- remoteResolver
- });
- }
-
- if (columns.filter(col => col.sort).length > 0) {
- this.BaseComponent = withSort(this.BaseComponent);
- }
-
- if (filter) {
- const { wrapperFactory } = filter;
- this.BaseComponent = wrapperFactory(this.BaseComponent, {
- _,
- remoteResolver
- });
- }
-
- if (cellEdit) {
- const { wrapperFactory } = cellEdit;
- this.BaseComponent = wrapperFactory(this.BaseComponent, {
- _,
- remoteResolver
- });
- }
-
- if (selectRow) {
- this.BaseComponent = withSelection(this.BaseComponent);
- }
- }
-
- render() {
- const baseProps = {
- ...this.props,
- store: this.store
- };
-
- return (
-
- );
- }
- };
-
-export default withDataStore;
diff --git a/packages/react-bootstrap-table2/src/contexts/bootstrap.js b/packages/react-bootstrap-table2/src/contexts/bootstrap.js
new file mode 100644
index 0000000..c719d9b
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/contexts/bootstrap.js
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export const BootstrapContext = React.createContext({
+ bootstrap4: false
+});
diff --git a/packages/react-bootstrap-table2/src/contexts/data-context.js b/packages/react-bootstrap-table2/src/contexts/data-context.js
new file mode 100644
index 0000000..3394c7a
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/contexts/data-context.js
@@ -0,0 +1,44 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+export default () => {
+ const DataContext = React.createContext();
+
+ class DataProvider extends Component {
+ static propTypes = {
+ data: PropTypes.array.isRequired,
+ children: PropTypes.node.isRequired
+ }
+
+ state = { data: this.props.data };
+
+ componentWillReceiveProps(nextProps) {
+ this.setState(() => ({ data: nextProps.data }));
+ }
+
+ getData = (filterProps, searchProps, sortProps, paginationProps) => {
+ if (paginationProps) return paginationProps.data;
+ else if (sortProps) return sortProps.data;
+ else if (searchProps) return searchProps.data;
+ else if (filterProps) return filterProps.data;
+ return this.props.data;
+ }
+
+ render() {
+ return (
+
+ { this.props.children }
+
+ );
+ }
+ }
+ return {
+ Provider: DataProvider,
+ Consumer: DataContext.Consumer
+ };
+};
diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js
new file mode 100644
index 0000000..049c288
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/contexts/index.js
@@ -0,0 +1,322 @@
+/* eslint no-return-assign: 0 */
+/* eslint class-methods-use-this: 0 */
+import React, { Component } from 'react';
+import _ from '../utils';
+import createDataContext from './data-context';
+import createSortContext from './sort-context';
+import createSelectionContext from './selection-context';
+import createRowExpandContext from './row-expand-context';
+import remoteResolver from '../props-resolver/remote-resolver';
+import { BootstrapContext } from './bootstrap';
+import dataOperator from '../store/operators';
+
+const withContext = Base =>
+ class BootstrapTableContainer extends remoteResolver(Component) {
+ constructor(props) {
+ super(props);
+ this.DataContext = createDataContext();
+
+ if (props.columns.filter(col => col.sort).length > 0) {
+ this.SortContext = createSortContext(
+ dataOperator, this.isRemoteSort, this.handleRemoteSortChange);
+ }
+
+ if (props.selectRow) {
+ this.SelectionContext = createSelectionContext(dataOperator);
+ }
+
+ if (props.expandRow) {
+ this.RowExpandContext = createRowExpandContext(dataOperator);
+ }
+
+ if (props.cellEdit && props.cellEdit.createContext) {
+ this.CellEditContext = props.cellEdit.createContext(
+ _, dataOperator, this.isRemoteCellEdit, this.handleRemoteCellChange);
+ }
+
+ if (props.filter) {
+ this.FilterContext = props.filter.createContext(
+ _, this.isRemoteFiltering, this.handleRemoteFilterChange);
+ }
+
+ if (props.pagination) {
+ this.PaginationContext = props.pagination.createContext(
+ this.isRemotePagination, this.handleRemotePageChange);
+ }
+
+ if (props.search && props.search.searchContext) {
+ this.SearchContext = props.search.searchContext(
+ _, this.isRemoteSearch, this.handleRemoteSearchChange);
+ }
+
+ if (props.setDependencyModules) {
+ props.setDependencyModules(_);
+ }
+ }
+
+ renderBase() {
+ return (
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps,
+ sortProps,
+ paginationProps,
+ expandProps,
+ selectionProps
+ ) => (
+
+ );
+ }
+
+ renderWithSelectionCtx(base, baseProps) {
+ return (
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps,
+ sortProps,
+ paginationProps,
+ expandProps
+ ) => (
+
+
+ {
+ selectionProps => base(
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps,
+ sortProps,
+ paginationProps,
+ expandProps,
+ selectionProps
+ )
+ }
+
+
+ );
+ }
+
+ renderWithRowExpandCtx(base, baseProps) {
+ return (
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps,
+ sortProps,
+ paginationProps
+ ) => (
+
+
+ {
+ expandProps => base(
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps,
+ sortProps,
+ paginationProps,
+ expandProps
+ )
+ }
+
+
+ );
+ }
+
+ renderWithPaginationCtx(base) {
+ return (
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps,
+ sortProps
+ ) => (
+ this.paginationContext = n }
+ pagination={ this.props.pagination }
+ data={ rootProps.getData(filterProps, searchProps, sortProps) }
+ bootstrap4={ this.props.bootstrap4 }
+ >
+
+ {
+ paginationProps => base(
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps,
+ sortProps,
+ paginationProps
+ )
+ }
+
+
+ );
+ }
+
+ renderWithSortCtx(base, baseProps) {
+ return (
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps
+ ) => (
+ this.sortContext = n }
+ defaultSorted={ this.props.defaultSorted }
+ defaultSortDirection={ this.props.defaultSortDirection }
+ data={ rootProps.getData(filterProps, searchProps) }
+ >
+
+ {
+ sortProps => base(
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps,
+ sortProps,
+ )
+ }
+
+
+ );
+ }
+
+ renderWithSearchCtx(base, baseProps) {
+ return (
+ rootProps,
+ cellEditProps,
+ filterProps
+ ) => (
+ this.searchContext = n }
+ data={ rootProps.getData(filterProps) }
+ searchText={ this.props.search.searchText }
+ >
+
+ {
+ searchProps => base(
+ rootProps,
+ cellEditProps,
+ filterProps,
+ searchProps
+ )
+ }
+
+
+ );
+ }
+
+ renderWithFilterCtx(base, baseProps) {
+ return (
+ rootProps,
+ cellEditProps
+ ) => (
+ this.filterContext = n }
+ data={ rootProps.getData() }
+ >
+
+ {
+ filterProps => base(
+ rootProps,
+ cellEditProps,
+ filterProps
+ )
+ }
+
+
+ );
+ }
+
+ renderWithCellEditCtx(base, baseProps) {
+ return rootProps => (
+
+
+ {
+ cellEditProps => base(rootProps, cellEditProps)
+ }
+
+
+ );
+ }
+
+ render() {
+ const { keyField, columns, bootstrap4 } = this.props;
+ const baseProps = { keyField, columns };
+
+ let base = this.renderBase();
+
+ if (this.SelectionContext) {
+ base = this.renderWithSelectionCtx(base, baseProps);
+ }
+
+ if (this.RowExpandContext) {
+ base = this.renderWithRowExpandCtx(base, baseProps);
+ }
+
+ if (this.PaginationContext) {
+ base = this.renderWithPaginationCtx(base, baseProps);
+ }
+
+ if (this.SortContext) {
+ base = this.renderWithSortCtx(base, baseProps);
+ }
+
+ if (this.SearchContext) {
+ base = this.renderWithSearchCtx(base, baseProps);
+ }
+
+ if (this.FilterContext) {
+ base = this.renderWithFilterCtx(base, baseProps);
+ }
+
+ if (this.CellEditContext) {
+ base = this.renderWithCellEditCtx(base, baseProps);
+ }
+
+ return (
+
+
+
+ {
+ base
+ }
+
+
+
+ );
+ }
+ };
+
+export default withContext;
diff --git a/packages/react-bootstrap-table2/src/contexts/row-expand-context.js b/packages/react-bootstrap-table2/src/contexts/row-expand-context.js
new file mode 100644
index 0000000..d6b1702
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/contexts/row-expand-context.js
@@ -0,0 +1,91 @@
+/* eslint react/prop-types: 0 */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+export default (
+ dataOperator
+) => {
+ const RowExpandContext = React.createContext();
+
+ 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 || [] };
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.expandRow) {
+ this.setState(() => ({
+ expanded: nextProps.expandRow.expanded || this.state.expanded
+ }));
+ }
+ }
+
+ handleRowExpand = (rowKey, expanded, rowIndex, e) => {
+ const { data, keyField, expandRow: { onExpand } } = this.props;
+
+ let currExpanded = [...this.state.expanded];
+
+ if (expanded) {
+ 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 }
+
+ );
+ }
+ }
+ return {
+ 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
new file mode 100644
index 0000000..a9fb4e8
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/contexts/selection-context.js
@@ -0,0 +1,94 @@
+/* eslint react/prop-types: 0 */
+import React from 'react';
+import PropTypes from 'prop-types';
+import Const from '../const';
+
+export default (
+ dataOperator
+) => {
+ const SelectionContext = React.createContext();
+
+ class SelectionProvider extends React.Component {
+ static propTypes = {
+ children: PropTypes.node.isRequired,
+ data: PropTypes.array.isRequired,
+ keyField: PropTypes.string.isRequired
+ }
+
+ state = { selected: (this.props.selectRow && this.props.selectRow.selected) || [] };
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.selectRow) {
+ this.setState(() => ({
+ selected: nextProps.selectRow.selected || 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, currSelected), e);
+ }
+
+ this.setState(() => ({ selected: currSelected }));
+ }
+
+ render() {
+ return (
+
+ { this.props.children }
+
+ );
+ }
+ }
+ return {
+ Provider: SelectionProvider,
+ Consumer: SelectionContext.Consumer
+ };
+};
diff --git a/packages/react-bootstrap-table2/src/contexts/sort-context.js b/packages/react-bootstrap-table2/src/contexts/sort-context.js
new file mode 100644
index 0000000..033d99a
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/contexts/sort-context.js
@@ -0,0 +1,94 @@
+/* eslint react/require-default-props: 0 */
+import React from 'react';
+import PropTypes from 'prop-types';
+import Const from '../const';
+
+export default (
+ dataOperator,
+ isRemoteSort,
+ handleSortChange
+) => {
+ const SortContext = React.createContext();
+
+ class SortProvider extends React.Component {
+ static propTypes = {
+ data: PropTypes.array.isRequired,
+ columns: PropTypes.array.isRequired,
+ children: PropTypes.node.isRequired,
+ defaultSorted: PropTypes.arrayOf(PropTypes.shape({
+ dataField: PropTypes.string.isRequired,
+ order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired
+ })),
+ defaultSortDirection: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC])
+ }
+
+ constructor(props) {
+ super(props);
+ let sortOrder;
+ let sortColumn;
+ const { columns, defaultSorted, defaultSortDirection } = props;
+
+ if (defaultSorted && defaultSorted.length > 0) {
+ const sortField = defaultSorted[0].dataField;
+ sortOrder = defaultSorted[0].order || defaultSortDirection;
+ const sortColumns = columns.filter(col => col.dataField === sortField);
+ if (sortColumns.length > 0) {
+ sortColumn = sortColumns[0];
+
+ if (sortColumn.onSort) {
+ sortColumn.onSort(sortField, sortOrder);
+ }
+ }
+ }
+ this.state = { sortOrder, sortColumn };
+ }
+
+ componentDidMount() {
+ const { sortOrder, sortColumn } = this.state;
+ if (isRemoteSort() && sortOrder && sortColumn) {
+ handleSortChange(sortColumn.dataField, sortOrder);
+ }
+ }
+
+ handleSort = (column) => {
+ const sortOrder = dataOperator.nextOrder(column, this.state, this.props.defaultSortDirection);
+
+ if (column.onSort) {
+ column.onSort(column.dataField, sortOrder);
+ }
+
+ if (isRemoteSort()) {
+ handleSortChange(column.dataField, sortOrder);
+ }
+ this.setState(() => ({
+ sortOrder,
+ sortColumn: column
+ }));
+ }
+
+ render() {
+ let { data } = this.props;
+ const { sortOrder, sortColumn } = this.state;
+ if (!isRemoteSort() && sortColumn) {
+ data = dataOperator.sort(data, sortOrder, sortColumn);
+ }
+
+ return (
+
+ { this.props.children }
+
+ );
+ }
+ }
+ return {
+ Provider: SortProvider,
+ Consumer: SortContext.Consumer
+ };
+};
diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js
index 0b8ee80..4549343 100644
--- a/packages/react-bootstrap-table2/src/header.js
+++ b/packages/react-bootstrap-table2/src/header.js
@@ -5,6 +5,7 @@ import Const from './const';
import HeaderCell from './header-cell';
import SelectionHeaderCell from './row-selection/selection-header-cell';
+import ExpandHeaderCell from './row-expand/expand-header-cell';
const Header = (props) => {
const { ROW_SELECT_DISABLED } = Const;
@@ -17,12 +18,22 @@ const Header = (props) => {
sortField,
sortOrder,
selectRow,
- onExternalFilter
+ onExternalFilter,
+ expandRow,
+ bootstrap4
} = props;
return (
+ {
+ (expandRow && expandRow.showExpandColumn)
+ ? : null
+ }
{
(selectRow.mode !== ROW_SELECT_DISABLED && !selectRow.hideSelectColumn)
? : null
@@ -36,6 +47,7 @@ const Header = (props) => {
return (
class ColumnResolver extends ExtendBase {
visibleColumnSize(includeSelectColumn = true) {
- const columnLen = this.props.columns.filter(c => !c.hidden).length;
+ let columnLen = this.props.columns.filter(c => !c.hidden).length;
if (!includeSelectColumn) return columnLen;
if (this.props.selectRow && !this.props.selectRow.hideSelectColumn) {
- return columnLen + 1;
+ columnLen += 1;
+ }
+ if (this.props.expandRow && this.props.expandRow.showExpandColumn) {
+ columnLen += 1;
}
return columnLen;
}
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
new file mode 100644
index 0000000..015e321
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js
@@ -0,0 +1,17 @@
+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 4f40d97..2154ce3 100644
--- a/packages/react-bootstrap-table2/src/props-resolver/index.js
+++ b/packages/react-bootstrap-table2/src/props-resolver/index.js
@@ -1,9 +1,11 @@
import ColumnResolver from './column-resolver';
+import ExpandRowResolver from './expand-row-resolver';
import Const from '../const';
import _ from '../utils';
export default ExtendBase =>
- class TableResolver extends ColumnResolver(ExtendBase) {
+ class TableResolver extends
+ ExpandRowResolver(ColumnResolver(ExtendBase)) {
validateProps() {
const { keyField } = this.props;
if (!keyField) {
@@ -51,7 +53,7 @@ export default ExtendBase =>
*/
resolveSelectRowPropsForHeader(options = {}) {
const { selectRow } = this.props;
- const { allRowsSelected, selected = [], ...rest } = options;
+ const { allRowsSelected, allRowsNotSelected, ...rest } = options;
const {
ROW_SELECT_DISABLED, CHECKBOX_STATUS_CHECKED,
CHECKBOX_STATUS_INDETERMINATE, CHECKBOX_STATUS_UNCHECKED
@@ -62,7 +64,7 @@ export default ExtendBase =>
// checkbox status depending on selected rows counts
if (allRowsSelected) checkedStatus = CHECKBOX_STATUS_CHECKED;
- else if (selected.length === 0) checkedStatus = CHECKBOX_STATUS_UNCHECKED;
+ else if (allRowsNotSelected) checkedStatus = CHECKBOX_STATUS_UNCHECKED;
else checkedStatus = CHECKBOX_STATUS_INDETERMINATE;
return {
diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js
index 6a44e57..89af4c6 100644
--- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js
+++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js
@@ -2,45 +2,77 @@ import _ from '../utils';
export default ExtendBase =>
class RemoteResolver extends ExtendBase {
- getNewestState(state = {}) {
- const store = this.store || this.props.store;
+ getNewestState = (state = {}) => {
+ let sortOrder;
+ let sortField;
+ let page;
+ let sizePerPage;
+ let searchText;
+ let filters = {};
+
+ if (this.sortContext) {
+ sortOrder = this.sortContext.state.sortOrder;
+ sortField = this.sortContext.state.sortColumn ?
+ this.sortContext.state.sortColumn.dataField :
+ null;
+ }
+
+ if (this.filterContext) {
+ filters = this.filterContext.currFilters;
+ }
+
+ if (this.paginationContext) {
+ page = this.paginationContext.currPage;
+ sizePerPage = this.paginationContext.currSizePerPage;
+ }
+
+ if (this.searchContext) {
+ searchText = this.props.search.searchText;
+ }
+
return {
- page: store.page,
- sizePerPage: store.sizePerPage,
- filters: store.filters,
- sortField: store.sortField,
- sortOrder: store.sortOrder,
- data: store.getAllData(),
- ...state
+ sortOrder,
+ sortField,
+ filters,
+ page,
+ sizePerPage,
+ searchText,
+ ...state,
+ data: this.props.data
};
}
- isRemotePagination() {
+ isRemoteSearch = () => {
+ const { remote } = this.props;
+ return remote === true || (_.isObject(remote) && remote.search) || this.isRemotePagination();
+ }
+
+ isRemotePagination = () => {
const { remote } = this.props;
return remote === true || (_.isObject(remote) && remote.pagination);
}
- isRemoteFiltering() {
+ isRemoteFiltering = () => {
const { remote } = this.props;
- return remote === true || (_.isObject(remote) && remote.filter);
+ return remote === true || (_.isObject(remote) && remote.filter) || this.isRemotePagination();
}
- isRemoteSort() {
+ isRemoteSort = () => {
const { remote } = this.props;
- return remote === true || (_.isObject(remote) && remote.sort);
+ return remote === true || (_.isObject(remote) && remote.sort) || this.isRemotePagination();
}
- isRemoteCellEdit() {
+ isRemoteCellEdit = () => {
const { remote } = this.props;
return remote === true || (_.isObject(remote) && remote.cellEdit);
}
- handleRemotePageChange() {
- this.props.onTableChange('pagination', this.getNewestState());
+ handleRemotePageChange = (page, sizePerPage) => {
+ this.props.onTableChange('pagination', this.getNewestState({ page, sizePerPage }));
}
- handleRemoteFilterChange() {
- const newState = {};
+ handleRemoteFilterChange = (filters) => {
+ const newState = { filters };
if (this.isRemotePagination()) {
const options = this.props.pagination.options || {};
newState.page = _.isDefined(options.pageStartIndex) ? options.pageStartIndex : 1;
@@ -48,12 +80,16 @@ export default ExtendBase =>
this.props.onTableChange('filter', this.getNewestState(newState));
}
- handleSortChange() {
- this.props.onTableChange('sort', this.getNewestState());
+ handleRemoteSortChange = (sortField, sortOrder) => {
+ this.props.onTableChange('sort', this.getNewestState({ sortField, sortOrder }));
}
- handleCellChange(rowId, dataField, newValue) {
+ handleRemoteCellChange = (rowId, dataField, newValue) => {
const cellEdit = { rowId, dataField, newValue };
this.props.onTableChange('cellEdit', this.getNewestState({ cellEdit }));
}
+
+ handleRemoteSearchChange = (searchText) => {
+ this.props.onTableChange('search', this.getNewestState({ searchText }));
+ }
};
diff --git a/packages/react-bootstrap-table2/src/row-event-delegater.js b/packages/react-bootstrap-table2/src/row-event-delegater.js
index 60d8a62..283ffd6 100644
--- a/packages/react-bootstrap-table2/src/row-event-delegater.js
+++ b/packages/react-bootstrap-table2/src/row-event-delegater.js
@@ -1,4 +1,5 @@
import _ from './utils';
+import Const from './const';
const events = [
'onClick',
@@ -30,11 +31,11 @@ export default ExtendBase =>
selected,
keyField,
selectable,
+ expandable,
rowIndex,
- selectRow: {
- onRowSelect,
- clickToEdit
- },
+ expanded,
+ expandRow,
+ selectRow,
cellEdit: {
mode,
DBCLICK_TO_CELL_EDIT,
@@ -46,13 +47,16 @@ export default ExtendBase =>
if (cb) {
cb(e, row, rowIndex);
}
- if (selectable) {
- const key = _.get(row, keyField);
- onRowSelect(key, !selected, rowIndex, e);
+ 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 && clickToEdit) {
+ if (mode === DBCLICK_TO_CELL_EDIT && selectRow.clickToEdit) {
this.clickNum += 1;
_.debounce(() => {
if (this.clickNum === 1) {
@@ -68,7 +72,8 @@ export default ExtendBase =>
delegate(attrs = {}) {
const newAttrs = {};
- if (this.props.selectRow && this.props.selectRow.clickToSelect) {
+ const { expandRow, selectRow } = this.props;
+ if (expandRow || (selectRow && selectRow.clickToSelect)) {
newAttrs.onClick = this.createClickEventHandler(attrs.onClick);
}
Object.keys(attrs).forEach((attr) => {
diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-cell.js b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js
new file mode 100644
index 0000000..3df5b1c
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js
@@ -0,0 +1,42 @@
+/* eslint
+ react/require-default-props: 0
+ jsx-a11y/no-noninteractive-element-interactions: 0
+*/
+/* eslint no-nested-ternary: 0 */
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+export default class ExpandCell extends Component {
+ static propTypes = {
+ rowKey: PropTypes.any,
+ expanded: PropTypes.bool.isRequired,
+ onRowExpand: PropTypes.func.isRequired,
+ expandColumnRenderer: PropTypes.func,
+ rowIndex: PropTypes.number
+ }
+
+ constructor() {
+ super();
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick(e) {
+ const { rowKey, expanded, onRowExpand, rowIndex } = this.props;
+
+ onRowExpand(rowKey, expanded, rowIndex, e);
+ }
+
+ render() {
+ const { expanded, expandColumnRenderer } = this.props;
+
+ return (
+
+ {
+ expandColumnRenderer ? expandColumnRenderer({
+ expanded
+ }) : (expanded ? '(-)' : '(+)')
+ }
+
+ );
+ }
+}
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
new file mode 100644
index 0000000..65c099b
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js
@@ -0,0 +1,40 @@
+/* eslint react/require-default-props: 0 */
+/* eslint no-nested-ternary: 0 */
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+export default class SelectionHeaderCell extends Component {
+ static propTypes = {
+ anyExpands: PropTypes.bool.isRequired,
+ onAllRowExpand: PropTypes.func.isRequired,
+ renderer: PropTypes.func
+ }
+
+ constructor() {
+ super();
+ this.handleCheckBoxClick = this.handleCheckBoxClick.bind(this);
+ }
+
+ handleCheckBoxClick(e) {
+ const { anyExpands, onAllRowExpand } = this.props;
+
+ onAllRowExpand(e, !anyExpands);
+ }
+
+ render() {
+ const { anyExpands, renderer } = this.props;
+ const attrs = {
+ onClick: this.handleCheckBoxClick
+ };
+
+ return (
+
+ {
+ renderer ?
+ renderer({ isAnyExpands: anyExpands }) :
+ (anyExpands ? '(-)' : '(+)')
+ }
+
+ );
+ }
+}
diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-row.js b/packages/react-bootstrap-table2/src/row-expand/expand-row.js
new file mode 100644
index 0000000..0a89628
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/row-expand/expand-row.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const ExpandRow = ({ children, ...rest }) => (
+
+ { children }
+
+);
+
+ExpandRow.propTypes = {
+ children: PropTypes.node
+};
+
+ExpandRow.defaultProps = {
+ children: null
+};
+
+export default ExpandRow;
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 bec0ef0..cdd6bbd 100644
--- a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js
+++ b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js
@@ -5,6 +5,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Const from '../const';
+import { BootstrapContext } from '../contexts/bootstrap';
export default class SelectionCell extends Component {
static propTypes = {
@@ -59,21 +60,29 @@ export default class SelectionCell extends Component {
} = this.props;
return (
-
+
{
- selectionRenderer ? selectionRenderer({
- mode: inputType,
- checked: selected,
- disabled
- }) : (
-
+ ({ bootstrap4 }) => (
+
+ {
+ selectionRenderer ? selectionRenderer({
+ mode: inputType,
+ checked: selected,
+ disabled
+ }) : (
+ {} }
+ />
+ )
+ }
+
)
}
-
+
);
}
}
diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js
index c92e7c3..3658e0a 100644
--- a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js
+++ b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js
@@ -2,20 +2,24 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Const from '../const';
+import { BootstrapContext } from '../contexts/bootstrap';
-export const CheckBox = ({ checked, indeterminate }) => (
+export const CheckBox = ({ className, checked, indeterminate }) => (
{
if (input) input.indeterminate = indeterminate; // eslint-disable-line no-param-reassign
} }
+ onChange={ () => {} }
/>
);
CheckBox.propTypes = {
checked: PropTypes.bool.isRequired,
- indeterminate: PropTypes.bool.isRequired
+ indeterminate: PropTypes.bool.isRequired,
+ className: PropTypes.string
};
export default class SelectionHeaderCell extends Component {
@@ -46,9 +50,12 @@ export default class SelectionHeaderCell extends Component {
}
handleCheckBoxClick(e) {
- const { onAllRowsSelect } = this.props;
+ const { onAllRowsSelect, checkedStatus } = this.props;
+ const isUnSelect =
+ checkedStatus === Const.CHECKBOX_STATUS_CHECKED ||
+ checkedStatus === Const.CHECKBOX_STATUS_INDETERMINATE;
- onAllRowsSelect(e);
+ onAllRowsSelect(e, isUnSelect);
}
render() {
@@ -64,27 +71,36 @@ export default class SelectionHeaderCell extends Component {
const attrs = {};
let content;
- if (selectionHeaderRenderer) {
- content = selectionHeaderRenderer({
- mode,
- checked,
- indeterminate
- });
- attrs.onClick = this.handleCheckBoxClick;
- } else if (mode === ROW_SELECT_MULTIPLE) {
- content = (
-
- );
+ if (selectionHeaderRenderer || mode === ROW_SELECT_MULTIPLE) {
attrs.onClick = this.handleCheckBoxClick;
}
return (
- { content }
+
+ {
+ ({ bootstrap4 }) => {
+ if (selectionHeaderRenderer) {
+ content = selectionHeaderRenderer({
+ mode,
+ checked,
+ indeterminate
+ });
+ } else if (mode === ROW_SELECT_MULTIPLE) {
+ content = (
+
+ );
+ }
+ return (
+ { content }
+ );
+ }
+ }
+
);
}
}
diff --git a/packages/react-bootstrap-table2/src/row-selection/wrapper.js b/packages/react-bootstrap-table2/src/row-selection/wrapper.js
deleted file mode 100644
index 51472dc..0000000
--- a/packages/react-bootstrap-table2/src/row-selection/wrapper.js
+++ /dev/null
@@ -1,107 +0,0 @@
-/* eslint no-param-reassign: 0 */
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-
-import Const from '../const';
-import {
- isAnySelectedRow,
- selectableKeys,
- unSelectableKeys,
- getSelectedRows
-} from '../store/selection';
-import { getRowByRowId } from '../store/rows';
-
-export default Base =>
- class RowSelectionWrapper extends Component {
- static propTypes = {
- store: PropTypes.object.isRequired,
- selectRow: PropTypes.object.isRequired
- }
-
- constructor(props) {
- super(props);
- this.handleRowSelect = this.handleRowSelect.bind(this);
- this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this);
-
- props.store.selected = props.selectRow.selected || [];
- this.state = {
- selectedRowKeys: props.store.selected
- };
- }
-
- componentWillReceiveProps(nextProps) {
- nextProps.store.selected = nextProps.selectRow.selected || [];
- this.setState(() => ({
- selectedRowKeys: nextProps.store.selected
- }));
- }
-
- /**
- * row selection handler
- * @param {String} rowKey - row key of what was selected.
- * @param {Boolean} checked - next checked status of input button.
- */
- handleRowSelect(rowKey, checked, rowIndex, e) {
- const { selectRow: { mode, onSelect }, store } = this.props;
- const { ROW_SELECT_SINGLE } = Const;
-
- let currSelected = [...store.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);
- }
-
- store.selected = currSelected;
-
- if (onSelect) {
- const row = getRowByRowId(store)(rowKey);
- onSelect(row, checked, rowIndex, e);
- }
-
- this.setState(() => ({
- selectedRowKeys: currSelected
- }));
- }
-
- /**
- * handle all rows selection on header cell by store.selected
- */
- handleAllRowsSelect(e) {
- const { store, selectRow: {
- onSelectAll,
- nonSelectable
- } } = this.props;
- const selected = isAnySelectedRow(store)(nonSelectable);
-
- const result = !selected;
-
- const currSelected = result ?
- selectableKeys(store)(nonSelectable) :
- unSelectableKeys(store)(nonSelectable);
-
-
- store.selected = currSelected;
-
- if (onSelectAll) {
- onSelectAll(result, getSelectedRows(store), e);
- }
-
- this.setState(() => ({
- selectedRowKeys: currSelected
- }));
- }
-
- render() {
- return (
-
- );
- }
- };
diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js
index b6222bb..e3254b5 100644
--- a/packages/react-bootstrap-table2/src/row.js
+++ b/packages/react-bootstrap-table2/src/row.js
@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
import _ from './utils';
import Cell from './cell';
import SelectionCell from './row-selection/selection-cell';
+import ExpandCell from './row-expand/expand-cell';
import eventDelegater from './row-event-delegater';
import Const from './const';
@@ -22,6 +23,8 @@ class Row extends eventDelegater(Component) {
cellEdit,
selected,
selectRow,
+ expanded,
+ expandRow,
selectable,
editable: editableRow
} = this.props;
@@ -39,10 +42,21 @@ class Row extends eventDelegater(Component) {
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)
? (
@@ -88,6 +102,45 @@ class Row extends eventDelegater(Component) {
/>
);
}
+ // 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 (
|
);
}
diff --git a/packages/react-bootstrap-table2/src/sort/caret.js b/packages/react-bootstrap-table2/src/sort/caret.js
index cc64f9d..9a1fd76 100644
--- a/packages/react-bootstrap-table2/src/sort/caret.js
+++ b/packages/react-bootstrap-table2/src/sort/caret.js
@@ -3,19 +3,31 @@ import cs from 'classnames';
import PropTypes from 'prop-types';
import Const from '../const';
+import { BootstrapContext } from '../contexts/bootstrap';
+
const SortCaret = ({ order }) => {
const orderClass = cs('react-bootstrap-table-sort-order', {
dropup: order === Const.SORT_ASC
});
+
return (
-
-
-
+
+ {
+ ({ bootstrap4 }) => (bootstrap4 ? (
+
+ ) : (
+
+
+
+ ))
+ }
+
);
};
SortCaret.propTypes = {
order: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC]).isRequired
};
+
export default SortCaret;
diff --git a/packages/react-bootstrap-table2/src/sort/symbol.js b/packages/react-bootstrap-table2/src/sort/symbol.js
index ecaf324..76c7e8f 100644
--- a/packages/react-bootstrap-table2/src/sort/symbol.js
+++ b/packages/react-bootstrap-table2/src/sort/symbol.js
@@ -1,13 +1,23 @@
import React from 'react';
+import { BootstrapContext } from '../contexts/bootstrap';
const SortSymbol = () => (
-
-
-
-
-
-
-
- );
+
+ {
+ ({ bootstrap4 }) => (bootstrap4 ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+ ))
+ }
+
+);
export default SortSymbol;
diff --git a/packages/react-bootstrap-table2/src/sort/wrapper.js b/packages/react-bootstrap-table2/src/sort/wrapper.js
deleted file mode 100644
index 6d55f1c..0000000
--- a/packages/react-bootstrap-table2/src/sort/wrapper.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/* eslint react/prop-types: 0 */
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import remoteResolver from '../props-resolver/remote-resolver';
-
-export default Base =>
- class SortWrapper extends remoteResolver(Component) {
- static propTypes = {
- store: PropTypes.object.isRequired
- }
-
- constructor(props) {
- super(props);
- this.handleSort = this.handleSort.bind(this);
- }
-
- componentWillMount() {
- const { columns, defaultSorted, defaultSortDirection, store } = this.props;
- // defaultSorted is an array, it's ready to use as multi / single sort
- // when we start to support multi sort, please update following code to use array.forEach
- if (defaultSorted && defaultSorted.length > 0) {
- const dataField = defaultSorted[0].dataField;
- const order = defaultSorted[0].order;
- const column = columns.filter(col => col.dataField === dataField);
- if (column.length > 0) {
- store.setSort(column[0], order, defaultSortDirection);
-
- if (column[0].onSort) {
- column[0].onSort(store.sortField, store.sortOrder);
- }
-
- if (this.isRemoteSort() || this.isRemotePagination()) {
- this.handleSortChange();
- } else {
- store.sortBy(column[0]);
- }
- }
- }
- }
-
- componentWillReceiveProps(nextProps) {
- if (!this.isRemoteSort() && !this.isRemotePagination()) {
- let sortedColumn;
- for (let i = 0; i < nextProps.columns.length; i += 1) {
- if (nextProps.columns[i].dataField === nextProps.store.sortField) {
- sortedColumn = nextProps.columns[i];
- break;
- }
- }
- if (sortedColumn && sortedColumn.sort) {
- nextProps.store.sortBy(sortedColumn);
- }
- }
- }
-
- handleSort(column) {
- const { store } = this.props;
- store.setSort(column, undefined, this.props.defaultSortDirection);
-
- if (column.onSort) {
- column.onSort(store.sortField, store.sortOrder);
- }
-
- if (this.isRemoteSort() || this.isRemotePagination()) {
- this.handleSortChange();
- } else {
- store.sortBy(column);
- this.forceUpdate();
- }
- }
-
- render() {
- return (
-
- );
- }
- };
diff --git a/packages/react-bootstrap-table2/src/store/expand.js b/packages/react-bootstrap-table2/src/store/expand.js
new file mode 100644
index 0000000..31feb0a
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/store/expand.js
@@ -0,0 +1,28 @@
+import _ from '../utils';
+import { getRowByRowId } from './rows';
+
+export const isAnyExpands = (
+ data,
+ keyField,
+ expanded = []
+) => {
+ for (let i = 0; i < data.length; i += 1) {
+ const rowKey = _.get(data[i], keyField);
+ if (typeof expanded.find(x => x === rowKey) !== 'undefined') {
+ return true;
+ }
+ }
+ return false;
+};
+
+export const expandableKeys = (data, keyField, skips = []) => {
+ if (skips.length === 0) {
+ return data.map(row => _.get(row, keyField));
+ }
+ return data
+ .filter(row => !skips.includes(_.get(row, keyField)))
+ .map(row => _.get(row, keyField));
+};
+
+export const getExpandedRows = (data, keyField, expanded) =>
+ expanded.map(k => getRowByRowId(data, keyField, k));
diff --git a/packages/react-bootstrap-table2/src/store/index.js b/packages/react-bootstrap-table2/src/store/index.js
deleted file mode 100644
index f0d7933..0000000
--- a/packages/react-bootstrap-table2/src/store/index.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/* eslint no-underscore-dangle: 0 */
-import _ from '../utils';
-import { sort, nextOrder } from './sort';
-import { getRowByRowId } from './rows';
-
-export default class Store {
- constructor(keyField) {
- this._data = [];
- this._filteredData = [];
- this._keyField = keyField;
- this._sortOrder = undefined;
- this._sortField = undefined;
- this._selected = [];
- this._filters = {};
- this._page = undefined;
- this._sizePerPage = undefined;
- }
-
- edit(rowId, dataField, newValue) {
- const row = getRowByRowId(this)(rowId);
- if (row) _.set(row, dataField, newValue);
- }
-
- setSort({ dataField }, order, defaultOrder) {
- this.sortOrder = nextOrder(this)(dataField, order, defaultOrder);
- this.sortField = dataField;
- }
-
- sortBy({ sortFunc }) {
- this.data = sort(this)(sortFunc);
- }
-
- getAllData() {
- return this._data;
- }
-
- setAllData(data) {
- this._data = data;
- }
-
- get data() {
- if (Object.keys(this._filters).length > 0) {
- return this._filteredData;
- }
- return this._data;
- }
- set data(data) {
- if (Object.keys(this._filters).length > 0) {
- this._filteredData = data;
- } else {
- this._data = (data ? JSON.parse(JSON.stringify(data)) : []);
- }
- }
-
- get filteredData() { return this._filteredData; }
- set filteredData(filteredData) { this._filteredData = filteredData; }
-
- get keyField() { return this._keyField; }
- set keyField(keyField) { this._keyField = keyField; }
-
- get sortOrder() { return this._sortOrder; }
- set sortOrder(sortOrder) { this._sortOrder = sortOrder; }
-
- get page() { return this._page; }
- set page(page) { this._page = page; }
-
- get sizePerPage() { return this._sizePerPage; }
- set sizePerPage(sizePerPage) { this._sizePerPage = sizePerPage; }
-
- get sortField() { return this._sortField; }
- set sortField(sortField) { this._sortField = sortField; }
-
- get selected() { return this._selected; }
- set selected(selected) { this._selected = selected; }
-
- get filters() { return this._filters; }
- set filters(filters) { this._filters = filters; }
-}
diff --git a/packages/react-bootstrap-table2/src/store/mutate.js b/packages/react-bootstrap-table2/src/store/mutate.js
new file mode 100644
index 0000000..9e59bab
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/store/mutate.js
@@ -0,0 +1,7 @@
+import _ from '../utils';
+import { getRowByRowId } from './rows';
+
+export const editCell = (data, keyField, rowId, dataField, newValue) => {
+ const row = getRowByRowId(data, keyField, rowId);
+ if (row) _.set(row, dataField, newValue);
+};
diff --git a/packages/react-bootstrap-table2/src/store/operators.js b/packages/react-bootstrap-table2/src/store/operators.js
new file mode 100644
index 0000000..de055ee
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/store/operators.js
@@ -0,0 +1,13 @@
+import * as rows from './rows';
+import * as selection from './selection';
+import * as expand from './expand';
+import * as mutate from './mutate';
+import * as sort from './sort';
+
+export default {
+ ...rows,
+ ...selection,
+ ...expand,
+ ...mutate,
+ ...sort
+};
diff --git a/packages/react-bootstrap-table2/src/store/rows.js b/packages/react-bootstrap-table2/src/store/rows.js
index 4115122..c57de06 100644
--- a/packages/react-bootstrap-table2/src/store/rows.js
+++ b/packages/react-bootstrap-table2/src/store/rows.js
@@ -1,4 +1,4 @@
export const matchRow = (keyField, id) => row => row[keyField] === id;
-export const getRowByRowId = ({ data, keyField }) => id => data.find(matchRow(keyField, id));
+export const getRowByRowId = (data, keyField, id) => data.find(matchRow(keyField, id));
diff --git a/packages/react-bootstrap-table2/src/store/selection.js b/packages/react-bootstrap-table2/src/store/selection.js
index 4c6c10a..7d3e741 100644
--- a/packages/react-bootstrap-table2/src/store/selection.js
+++ b/packages/react-bootstrap-table2/src/store/selection.js
@@ -1,16 +1,30 @@
import _ from '../utils';
import { getRowByRowId } from './rows';
-export const isSelectedAll = ({ data, selected }) => data.length === selected.length;
+export const getSelectionSummary = (
+ data,
+ keyField,
+ selected = []
+) => {
+ let allRowsSelected = true;
+ let allRowsNotSelected = true;
-export const isAnySelectedRow = ({ selected }) => (skips = []) => {
- if (skips.length === 0) {
- return selected.length > 0;
+ const rowKeys = data.map(d => d[keyField]);
+ for (let i = 0; i < rowKeys.length; i += 1) {
+ const curr = rowKeys[i];
+ if (typeof selected.find(x => x === curr) === 'undefined') {
+ allRowsSelected = false;
+ } else {
+ allRowsNotSelected = false;
+ }
}
- return selected.filter(x => !skips.includes(x)).length;
+ return {
+ allRowsSelected,
+ allRowsNotSelected
+ };
};
-export const selectableKeys = ({ data, keyField }) => (skips = []) => {
+export const selectableKeys = (data, keyField, skips = []) => {
if (skips.length === 0) {
return data.map(row => _.get(row, keyField));
}
@@ -19,15 +33,13 @@ export const selectableKeys = ({ data, keyField }) => (skips = []) => {
.map(row => _.get(row, keyField));
};
-export const unSelectableKeys = ({ selected }) => (skips = []) => {
+export const unSelectableKeys = (selected, skips = []) => {
if (skips.length === 0) {
return [];
}
return selected.filter(x => skips.includes(x));
};
-export const getSelectedRows = (store) => {
- const getRow = getRowByRowId(store);
- return store.selected.map(k => getRow(k));
-};
+export const getSelectedRows = (data, keyField, selected) =>
+ selected.map(k => getRowByRowId(data, keyField, k));
diff --git a/packages/react-bootstrap-table2/src/store/sort.js b/packages/react-bootstrap-table2/src/store/sort.js
index 1987e0e..ecbfbd7 100644
--- a/packages/react-bootstrap-table2/src/store/sort.js
+++ b/packages/react-bootstrap-table2/src/store/sort.js
@@ -14,17 +14,17 @@ function comparator(a, b) {
return result;
}
-export const sort = ({ data, sortOrder, sortField }) => (sortFunc) => {
+export const sort = (data, sortOrder, { dataField, sortFunc }) => {
const _data = [...data];
_data.sort((a, b) => {
let result;
- let valueA = _.get(a, sortField);
- let valueB = _.get(b, sortField);
+ let valueA = _.get(a, dataField);
+ let valueB = _.get(b, dataField);
valueA = _.isDefined(valueA) ? valueA : '';
valueB = _.isDefined(valueB) ? valueB : '';
if (sortFunc) {
- result = sortFunc(valueA, valueB, sortOrder, sortField);
+ result = sortFunc(valueA, valueB, sortOrder, dataField);
} else {
if (sortOrder === Const.SORT_DESC) {
result = comparator(valueA, valueB);
@@ -37,11 +37,11 @@ export const sort = ({ data, sortOrder, sortField }) => (sortFunc) => {
return _data;
};
-export const nextOrder = store => (field, order, defaultOrder = Const.SORT_DESC) => {
- if (order) return order;
-
- if (field !== store.sortField) {
- return defaultOrder;
- }
- return store.sortOrder === Const.SORT_DESC ? Const.SORT_ASC : Const.SORT_DESC;
+export const nextOrder = (
+ currentSortColumn,
+ { sortOrder, sortColumn },
+ defaultOrder = Const.SORT_DESC
+) => {
+ if (!sortColumn || currentSortColumn.dataField !== sortColumn.dataField) return defaultOrder;
+ return sortOrder === Const.SORT_DESC ? Const.SORT_ASC : Const.SORT_DESC;
};
diff --git a/packages/react-bootstrap-table2/src/utils.js b/packages/react-bootstrap-table2/src/utils.js
index 16572b2..ac8dd5a 100644
--- a/packages/react-bootstrap-table2/src/utils.js
+++ b/packages/react-bootstrap-table2/src/utils.js
@@ -1,6 +1,7 @@
/* eslint no-empty: 0 */
/* eslint no-param-reassign: 0 */
/* eslint prefer-rest-params: 0 */
+import _ from 'underscore';
function splitNested(str) {
return [str]
@@ -38,22 +39,8 @@ function set(target, field, value, safe = false) {
}, target);
}
-function isFunction(obj) {
- return obj && (typeof obj === 'function');
-}
-
-/**
- * Checks if `value` is the Object. the `Object` except `Function` and `Array.`
- *
- * @param {*} obj - The value gonna check
- */
-function isObject(obj) {
- const type = typeof obj;
- return obj !== null && type === 'object' && obj.constructor === Object;
-}
-
function isEmptyObject(obj) {
- if (!isObject(obj)) return false;
+ if (!_.isObject(obj)) return false;
const hasOwnProperty = Object.prototype.hasOwnProperty;
const keys = Object.keys(obj);
@@ -91,18 +78,9 @@ function debounce(func, wait, immediate) {
timeout = setTimeout(later, wait || 0);
if (callNow) {
- func.appy(this, arguments);
+ func.apply(this, arguments);
}
};
}
-export default {
- get,
- set,
- isFunction,
- isObject,
- isEmptyObject,
- isDefined,
- sleep,
- debounce
-};
+export default Object.assign(_, { get, set, isDefined, isEmptyObject, sleep, debounce });
diff --git a/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss b/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss
index 2476781..5974e45 100644
--- a/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss
+++ b/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss
@@ -8,6 +8,7 @@
cursor: pointer;
}
+ // bootstrap 3 sort
th .order > .dropdown > .caret {
margin: 10px 0 10px 5px;
color: #cccccc;
@@ -22,14 +23,55 @@
margin: 10px 6.5px;
}
+ // bootstrap 4 sort
+ th .order-4:before {
+ margin-left: 3.5px;
+ content: "\2191";
+ opacity: 0.4;
+ }
+
+ th .order-4:after {
+ content: "\2193";
+ opacity: 0.4;
+ }
+
+ th .caret-4-asc:before {
+ margin-left: 3.5px;
+ content: "\2191";
+ }
+
+ th .caret-4-asc:after {
+ content: "\2193";
+ opacity: 0.4;
+ }
+
+ th .caret-4-desc:before {
+ margin-left: 3.5px;
+ content: "\2191";
+ opacity: 0.4;
+ }
+
+ th .caret-4-desc:after {
+ content: "\2193";
+ }
+
th[data-row-selection] {
width: 30px;
}
+ th > .selection-input-4,
+ td > .selection-input-4 {
+ margin: -4px;
+ }
+
td.react-bs-table-no-data {
text-align: center;
}
+ tr.expanding-row {
+ padding: 5px;
+ }
+
td.react-bootstrap-table-editing-cell {
.animated {
animation-fill-mode: both;
diff --git a/packages/react-bootstrap-table2/test/bootstrap-table.test.js b/packages/react-bootstrap-table2/test/bootstrap-table.test.js
index b1d4062..23986a8 100644
--- a/packages/react-bootstrap-table2/test/bootstrap-table.test.js
+++ b/packages/react-bootstrap-table2/test/bootstrap-table.test.js
@@ -2,7 +2,6 @@ import React from 'react';
import { shallow } from 'enzyme';
import Caption from '../src/caption';
-import Store from '../src/store';
import Header from '../src/header';
import Body from '../src/body';
import BootstrapTable from '../src/bootstrap-table';
@@ -25,13 +24,10 @@ describe('BootstrapTable', () => {
name: 'B'
}];
- const store = new Store('id');
- store.data = data;
-
describe('simplest table', () => {
beforeEach(() => {
wrapper = shallow(
- );
+ );
});
it('should render successfully', () => {
@@ -41,11 +37,6 @@ describe('BootstrapTable', () => {
expect(wrapper.find(Body).length).toBe(1);
});
- it('should have correct default state', () => {
- expect(wrapper.state().data).toBeDefined();
- expect(wrapper.state().data).toEqual(store.data);
- });
-
it("should only have classes 'table' and 'table-bordered' as default", () => {
expect(wrapper.find('table').prop('className')).toBe('table table-bordered');
});
@@ -55,6 +46,40 @@ describe('BootstrapTable', () => {
});
});
+ describe('getData', () => {
+ let instance;
+
+ beforeEach(() => {
+ wrapper = shallow(
+ );
+ instance = wrapper.instance();
+ });
+
+ it('should return props.data', () => {
+ expect(instance.getData()).toEqual(data);
+ });
+ });
+
+ describe('when props.registerExposedAPI is defined', () => {
+ const registerExposedAPI = jest.fn();
+ beforeEach(() => {
+ registerExposedAPI.mockClear();
+ wrapper = shallow(
+
+ );
+ });
+
+ it('should call props.registerExposedAPI correctly', () => {
+ expect(registerExposedAPI).toHaveBeenCalledTimes(1);
+ expect(registerExposedAPI.mock.calls[0][0].name).toEqual('getData');
+ });
+ });
+
describe('when props.classes was defined', () => {
const classes = 'foo';
@@ -64,7 +89,6 @@ describe('BootstrapTable', () => {
keyField="id"
columns={ columns }
data={ data }
- store={ store }
classes={ classes }
/>);
});
@@ -83,7 +107,6 @@ describe('BootstrapTable', () => {
keyField="id"
columns={ columns }
data={ data }
- store={ store }
wrapperClasses={ classes }
/>);
});
@@ -102,7 +125,6 @@ describe('BootstrapTable', () => {
keyField="id"
columns={ columns }
data={ data }
- store={ store }
id={ id }
/>);
});
@@ -115,7 +137,7 @@ describe('BootstrapTable', () => {
describe('when hover props is true', () => {
beforeEach(() => {
wrapper = shallow(
- );
+ );
});
it('should have table-hover class on table', () => {
@@ -126,7 +148,7 @@ describe('BootstrapTable', () => {
describe('when striped props is true', () => {
beforeEach(() => {
wrapper = shallow(
- );
+ );
});
it('should have table-striped class on table', () => {
@@ -137,7 +159,7 @@ describe('BootstrapTable', () => {
describe('when condensed props is true', () => {
beforeEach(() => {
wrapper = shallow(
- );
+ );
});
it('should have table-condensed class on table', () => {
@@ -148,7 +170,7 @@ describe('BootstrapTable', () => {
describe('when bordered props is false', () => {
beforeEach(() => {
wrapper = shallow(
- );
+ );
});
it('should not have table-condensed class on table', () => {
@@ -160,7 +182,6 @@ describe('BootstrapTable', () => {
beforeEach(() => {
wrapper = shallow(
test }
keyField="id"
columns={ columns }
diff --git a/packages/react-bootstrap-table2/test/cell.test.js b/packages/react-bootstrap-table2/test/cell.test.js
index 1695240..86bf393 100644
--- a/packages/react-bootstrap-table2/test/cell.test.js
+++ b/packages/react-bootstrap-table2/test/cell.test.js
@@ -79,362 +79,6 @@ describe('Cell', () => {
});
});
- describe('when column.style prop is defined', () => {
- let column;
- const columnIndex = 1;
- const rowIndex = 1;
-
- beforeEach(() => {
- column = {
- dataField: 'id',
- text: 'ID'
- };
- });
-
- describe('when style is an object', () => {
- beforeEach(() => {
- column.style = { backgroundColor: 'red' };
- wrapper = shallow(
- | );
- });
-
- it('should render successfully', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('td').prop('style')).toEqual(column.style);
- });
- });
-
- describe('when style is a function', () => {
- const returnStyle = { backgroundColor: 'red' };
- let styleCallBack;
-
- beforeEach(() => {
- styleCallBack = sinon.stub()
- .withArgs(row[column.dataField], row, rowIndex, columnIndex)
- .returns(returnStyle);
- column.style = styleCallBack;
- wrapper = shallow(
- | );
- });
-
- afterEach(() => { styleCallBack.reset(); });
-
- it('should render successfully', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('td').prop('style')).toEqual(returnStyle);
- });
-
- it('should call custom style function correctly', () => {
- expect(styleCallBack.callCount).toBe(1);
- expect(
- styleCallBack.calledWith(row[column.dataField], row, rowIndex, columnIndex)
- ).toBe(true);
- });
- });
- });
-
- describe('when column.classes prop is defined', () => {
- let column;
- const columnIndex = 1;
- const rowIndex = 1;
-
- beforeEach(() => {
- column = {
- dataField: 'id',
- text: 'ID'
- };
- });
-
- describe('when classes is an object', () => {
- beforeEach(() => {
- column.classes = 'td-test-class';
- wrapper = shallow(
- | );
- });
-
- it('should render successfully', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.hasClass(column.classes)).toBe(true);
- });
- });
-
- describe('when classes is a function', () => {
- const returnClasses = 'td-test-class';
- let classesCallBack;
-
- beforeEach(() => {
- classesCallBack = sinon.stub()
- .withArgs(row[column.dataField], row, rowIndex, columnIndex)
- .returns(returnClasses);
- column.classes = classesCallBack;
- wrapper = shallow(
- | );
- });
-
- afterEach(() => { classesCallBack.reset(); });
-
- it('should render successfully', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.hasClass(returnClasses)).toBe(true);
- });
-
- it('should call custom classes function correctly', () => {
- expect(classesCallBack.callCount).toBe(1);
- expect(
- classesCallBack.calledWith(row[column.dataField], row, rowIndex, columnIndex)
- ).toBe(true);
- });
- });
- });
-
- describe('when column.title prop is defined', () => {
- let column;
- const columnIndex = 1;
- const rowIndex = 1;
-
- beforeEach(() => {
- column = {
- dataField: 'id',
- text: 'ID'
- };
- });
-
- describe('when title is boolean', () => {
- beforeEach(() => {
- column.title = true;
- wrapper = shallow(
- | );
- });
-
- it('should render title as cell value as default', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('td').prop('title')).toEqual(row[column.dataField]);
- });
- });
-
- describe('when title is custom function', () => {
- const customTitle = 'test_title';
- let titleCallBack;
-
- beforeEach(() => {
- titleCallBack = sinon.stub()
- .withArgs(row[column.dataField], row, rowIndex, columnIndex)
- .returns(customTitle);
- column.title = titleCallBack;
- wrapper = shallow(
- | );
- });
-
- it('should render title correctly by custom title function', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('td').prop('title')).toBe(customTitle);
- });
-
- it('should call custom title function correctly', () => {
- expect(titleCallBack.callCount).toBe(1);
- expect(
- titleCallBack.calledWith(row[column.dataField], row, rowIndex, columnIndex)
- ).toBe(true);
- });
- });
- });
-
- describe('when column.events prop is defined', () => {
- let column;
- const columnIndex = 1;
- const rowIndex = 1;
-
- beforeEach(() => {
- column = {
- dataField: 'id',
- text: 'ID',
- events: {
- onClick: sinon.stub()
- }
- };
-
- wrapper = shallow(
- | );
- });
-
- it('should attachs DOM event successfully', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('td').prop('onClick')).toBeDefined();
- });
-
- it('event hook should be called when triggering', () => {
- wrapper.find('td').simulate('click');
- expect(column.events.onClick.callCount).toBe(1);
- });
- });
-
- describe('when column.align prop is defined', () => {
- let column;
- const columnIndex = 1;
- const rowIndex = 1;
-
- beforeEach(() => {
- column = {
- dataField: 'id',
- text: 'ID'
- };
- });
-
- describe('when align is string', () => {
- beforeEach(() => {
- column.align = 'center';
- wrapper = shallow(
- | );
- });
-
- it('should render style.textAlign correctly', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('td').prop('style').textAlign).toEqual(column.align);
- });
- });
-
- describe('when align is custom function', () => {
- const customAlign = 'center';
- let alignCallBack;
-
- beforeEach(() => {
- alignCallBack = sinon.stub()
- .withArgs(row[column.dataField], row, rowIndex, columnIndex)
- .returns(customAlign);
- column.align = alignCallBack;
- wrapper = shallow(
- | );
- });
-
- it('should render style.textAlign correctly', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('td').prop('style').textAlign).toEqual(customAlign);
- });
-
- it('should call custom headerAlign function correctly', () => {
- expect(alignCallBack.callCount).toBe(1);
- expect(
- alignCallBack.calledWith(row[column.dataField], row, rowIndex, columnIndex)
- ).toBe(true);
- });
- });
- });
-
- describe('when column.attrs prop is defined', () => {
- let column;
- const columnIndex = 1;
- const rowIndex = 1;
-
- beforeEach(() => {
- column = {
- dataField: 'id',
- text: 'ID'
- };
- });
-
- describe('when attrs is an object', () => {
- it('should render column.attrs correctly', () => {
- column.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('td').prop('data-test')).toEqual(column.attrs['data-test']);
- expect(wrapper.find('td').prop('title')).toEqual(column.attrs.title);
- expect(wrapper.hasClass(column.attrs.className)).toBe(true);
- expect(wrapper.find('td').prop('style')).toEqual(column.attrs.style);
- expect(wrapper.find('td').prop('style').textAlign).toEqual(column.attrs.style.textAlign);
- });
-
- describe('when column.title prop is defined', () => {
- it('attrs.title should be overwrited', () => {
- column.title = true;
- column.attrs = { title: 'title' };
-
- wrapper = shallow(
- | );
-
- expect(wrapper.find('td').prop('title')).toEqual(row[column.dataField]);
- });
- });
-
- describe('when column.classes prop is defined', () => {
- it('attrs.class should be overwrited', () => {
- column.classes = 'td-test-class';
- column.attrs = { className: 'attrs-class' };
-
- wrapper = shallow(
- | );
-
- expect(wrapper.hasClass(column.classes)).toBe(true);
- });
- });
-
- describe('when column.style prop is defined', () => {
- it('attrs.style should be overwrited', () => {
- column.style = { backgroundColor: 'red' };
- column.attrs = { style: { backgroundColor: 'attrs-style-test' } };
-
- wrapper = shallow(
- | );
-
- expect(wrapper.find('td').prop('style')).toEqual(column.style);
- });
- });
-
- describe('when column.align prop is defined', () => {
- it('attrs.style.textAlign should be overwrited', () => {
- column.align = 'center';
- column.attrs = { style: { textAlign: 'right' } };
-
- wrapper = shallow(
- | );
-
- expect(wrapper.find('td').prop('style').textAlign).toEqual(column.align);
- });
- });
- });
-
- describe('when attrs is custom function', () => {
- let attrsCallBack;
- const customAttrs = {
- title: 'title',
- 'data-test': 'test'
- };
-
- beforeEach(() => {
- attrsCallBack = sinon.stub()
- .withArgs(row[column.dataField], row, rowIndex, columnIndex)
- .returns(customAttrs);
- column.attrs = attrsCallBack;
- wrapper = shallow(
- | );
- });
-
- it('should render style.attrs correctly', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('td').prop('data-test')).toEqual(customAttrs['data-test']);
- expect(wrapper.find('td').prop('title')).toEqual(customAttrs.title);
- });
-
- it('should call custom attrs function correctly', () => {
- expect(attrsCallBack.callCount).toBe(1);
- expect(
- attrsCallBack.calledWith(row[column.dataField], row, rowIndex, columnIndex)
- ).toBe(true);
- });
- });
- });
-
describe('when editable prop is true', () => {
let onStartCallBack;
const rowIndex = 1;
@@ -528,4 +172,259 @@ describe('Cell', () => {
});
});
});
+
+ describe('shouldComponentUpdate', () => {
+ let props;
+ let nextProps;
+
+ describe('when content is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, row: { id: 1, name: 'CDE' } };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when column.hidden is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, column: { ...column, hidden: true } };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when props.rowIndex is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, rowIndex: 2 };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when props.columnIndex is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, columnIndex: 2 };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when props.className is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column,
+ className: 'test'
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, className: null };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when props.title is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column,
+ title: 'test'
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, title: '123' };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when props.title is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, editable: true };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when props.clickToEdit is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, clickToEdit: true };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when props.dbclickToEdit is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, dbclickToEdit: true };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when props.style is change', () => {
+ const column = { dataField: 'name', text: 'Product Name' };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column,
+ style: {}
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, style: { color: 'red' } };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when column.formatExtraData is change', () => {
+ const column = { dataField: 'name', text: 'Product Name', formatExtraData: { a: 1 } };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, column: { ...column, formatExtraData: { b: 2 } } };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when column.events is change', () => {
+ const column = { dataField: 'name', text: 'Product Name', events: { a: jest.fn() } };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, column: { ...column, events: { b: jest.fn() } } };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+
+ describe('when column.attrs is change', () => {
+ const column = { dataField: 'name', text: 'Product Name', attrs: { 'data-att': 1 } };
+ beforeEach(() => {
+ props = {
+ row,
+ columnIndex: 1,
+ rowIndex: 1,
+ column
+ };
+ wrapper = shallow(
+ | );
+ });
+
+ it('should return true', () => {
+ nextProps = { ...props, column: { ...column, attrs: null } };
+ expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
+ });
+ });
+ });
});
diff --git a/packages/react-bootstrap-table2/test/container.test.js b/packages/react-bootstrap-table2/test/container.test.js
deleted file mode 100644
index 8ceb5a5..0000000
--- a/packages/react-bootstrap-table2/test/container.test.js
+++ /dev/null
@@ -1,209 +0,0 @@
-/* eslint react/prefer-stateless-function: 0 */
-/* eslint react/no-multi-comp: 0 */
-import React from 'react';
-import { shallow } from 'enzyme';
-
-import BootstrapTable from '../src/bootstrap-table';
-import Container from '../index.js';
-
-describe('container', () => {
- let wrapper;
-
- const keyField = 'id';
-
- const columns = [{
- dataField: keyField,
- text: 'ID'
- }, {
- dataField: 'name',
- text: 'Name'
- }];
-
- const data = [{
- id: 1,
- name: 'A'
- }, {
- id: 2,
- name: 'B'
- }];
-
- describe('initialization', () => {
- beforeEach(() => {
- wrapper = shallow(
-
- );
- });
-
- it('should initialize BaseComponent', () => {
- expect(wrapper.instance().BaseComponent.name).toBe('BootstrapTable');
- });
-
- it('should render BootstrapTable successfully', () => {
- expect(wrapper.find(BootstrapTable)).toHaveLength(1);
- });
-
- it('should creating store successfully', () => {
- const store = wrapper.instance().store;
- expect(store).toBeDefined();
- expect(store.data).toEqual(data);
- expect(store.keyField).toEqual(keyField);
- });
- });
-
- describe('when cellEdit prop is defined', () => {
- const wrapperFactory = Base => class CellEditWrapper extends React.Component {
- render() { return ; }
- };
-
- const cellEdit = {
- wrapperFactory,
- options: {
- mode: 'click'
- }
- };
-
- beforeEach(() => {
- wrapper = shallow(
-
- );
- });
-
- it('should initialize BaseComponent correctly', () => {
- expect(wrapper.instance().BaseComponent.name).toBe('CellEditWrapper');
- });
-
- it('should render CellEditWrapper component successfully', () => {
- expect(wrapper.find('CellEditWrapper')).toHaveLength(1);
- });
-
- it('should render BootstrapTable component successfully', () => {
- expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1);
- });
- });
-
- describe('when selectRow prop is defined', () => {
- const selectRow = {
- mode: 'checkbox'
- };
-
- beforeEach(() => {
- wrapper = shallow(
-
- );
- });
-
- it('should initialize BaseComponent correctly', () => {
- expect(wrapper.instance().BaseComponent.name).toBe('RowSelectionWrapper');
- });
-
- it('should render BootstrapTable component successfully', () => {
- expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1);
- });
-
- it('should render RowSelectionWrapper component successfully', () => {
- expect(wrapper.find('RowSelectionWrapper').length).toBe(1);
- });
- });
-
- describe('when pagination prop is defined', () => {
- const wrapperFactory = Base => class PaginationWrapper extends React.Component {
- render() { return ; }
- };
- const pagination = {
- wrapperFactory
- };
-
- beforeEach(() => {
- wrapper = shallow(
-
- );
- });
-
- it('should initialize BaseComponent correctly', () => {
- expect(wrapper.instance().BaseComponent.name).toBe('PaginationWrapper');
- });
-
- it('should render BootstrapTable component successfully', () => {
- expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1);
- });
-
- it('should render PaginationWrapper component successfully', () => {
- expect(wrapper.find('PaginationWrapper').length).toBe(1);
- });
- });
-
- describe('when filter prop is defined', () => {
- const wrapperFactory = Base => class FilterWrapper extends React.Component {
- render() { return ; }
- };
-
- const filter = { wrapperFactory };
-
- beforeEach(() => {
- wrapper = shallow(
-
- );
- });
-
- it('should initialize BaseComponent correctly', () => {
- expect(wrapper.instance().BaseComponent.name).toBe('FilterWrapper');
- });
-
- it('should render BootstrapTable component successfully', () => {
- expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1);
- });
-
- it('should render FilterWrapper component successfully', () => {
- expect(wrapper.find('FilterWrapper').length).toBe(1);
- });
- });
-
- describe('when any column.sort is defined', () => {
- beforeEach(() => {
- const columnsWithSort = [{
- dataField: keyField,
- text: 'ID',
- sort: true
- }];
- wrapper = shallow(
-
- );
- });
-
- it('should initialize BaseComponent correctly', () => {
- expect(wrapper.instance().BaseComponent.name).toBe('SortWrapper');
- });
-
- it('should render BootstrapTable component successfully', () => {
- expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1);
- });
-
- it('should render SortWrapper component successfully', () => {
- expect(wrapper.find('SortWrapper').length).toBe(1);
- });
- });
-});
diff --git a/packages/react-bootstrap-table2/test/contexts/data-context.test.js b/packages/react-bootstrap-table2/test/contexts/data-context.test.js
new file mode 100644
index 0000000..0761640
--- /dev/null
+++ b/packages/react-bootstrap-table2/test/contexts/data-context.test.js
@@ -0,0 +1,130 @@
+import 'jsdom-global/register';
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import BootstrapTable from '../../src/bootstrap-table';
+import createDataContext from '../../src/contexts/data-context';
+
+describe('DataContext', () => {
+ let wrapper;
+
+ const data = [{
+ id: 1,
+ name: 'A'
+ }, {
+ id: 2,
+ name: 'B'
+ }];
+
+ const columns = [{
+ dataField: 'id',
+ text: 'ID'
+ }, {
+ dataField: 'name',
+ text: 'Name'
+ }];
+
+ const mockBase = jest.fn((props => (
+
+ )));
+
+ const DataContext = createDataContext();
+
+ function shallowContext() {
+ return (
+
+
+ {
+ dataProps => mockBase(dataProps)
+ }
+
+
+ );
+ }
+
+ describe('default render', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ });
+
+ it('should have correct Provider property after calling createDataContext', () => {
+ expect(DataContext.Provider).toBeDefined();
+ });
+
+ it('should have correct Consumer property after calling createDataContext', () => {
+ expect(DataContext.Consumer).toBeDefined();
+ });
+
+ it('should have correct state.data', () => {
+ expect(wrapper.state().data).toEqual(data);
+ });
+
+ it('should pass correct sort props to children element', () => {
+ expect(wrapper.length).toBe(1);
+ expect(mockBase).toHaveBeenCalledWith({
+ data,
+ getData: wrapper.instance().getData
+ });
+ });
+ });
+
+ describe('componentWillReceiveProps', () => {
+ const newData = [...data, { id: 3, name: 'test' }];
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.instance().componentWillReceiveProps({
+ data: newData
+ });
+ });
+
+ it('should have correct state.data', () => {
+ expect(wrapper.state().data).toEqual(newData);
+ });
+ });
+
+ describe('getData', () => {
+ let result;
+ const fakeData = [...data, { id: 3, name: 'test' }];
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ });
+
+ describe('if third argument is give', () => {
+ it('should return the data property from third argument', () => {
+ result = wrapper.instance().getData(null, null, { data: fakeData });
+ expect(result).toEqual(fakeData);
+ });
+ });
+
+ describe('if second argument is give', () => {
+ it('should return the data property from second argument', () => {
+ result = wrapper.instance().getData(null, { data: fakeData });
+ expect(result).toEqual(fakeData);
+ });
+ });
+
+ describe('if first argument is give', () => {
+ it('should return the data property from first argument', () => {
+ result = wrapper.instance().getData({ data: fakeData });
+ expect(result).toEqual(fakeData);
+ });
+ });
+
+ describe('if no argument is give', () => {
+ it('should return default props.data', () => {
+ result = wrapper.instance().getData();
+ expect(result).toEqual(data);
+ });
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2/test/contexts/index.test.js b/packages/react-bootstrap-table2/test/contexts/index.test.js
new file mode 100644
index 0000000..4a2b1de
--- /dev/null
+++ b/packages/react-bootstrap-table2/test/contexts/index.test.js
@@ -0,0 +1,225 @@
+/* eslint no-param-reassign: 0 */
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import Base from '../../src/bootstrap-table';
+import withContext from '../../src/contexts';
+
+describe('Context', () => {
+ let wrapper;
+
+ const keyField = 'id';
+
+ let columns;
+
+ const data = [{
+ id: 1,
+ name: 'A'
+ }, {
+ id: 2,
+ name: 'B'
+ }];
+
+ const BootstrapTable = withContext(Base);
+
+ beforeEach(() => {
+ columns = [{
+ dataField: keyField,
+ text: 'ID'
+ }, {
+ dataField: 'name',
+ text: 'Name'
+ }];
+ });
+
+ describe('basic render', () => {
+ beforeEach(() => {
+ wrapper = shallow(
+
+ );
+ wrapper.render();
+ });
+
+ it('should create contexts correctly', () => {
+ expect(wrapper.instance().DataContext).toBeDefined();
+ expect(wrapper.instance().SortContext).not.toBeDefined();
+ expect(wrapper.instance().SelectionContext).not.toBeDefined();
+ expect(wrapper.instance().CellEditContext).not.toBeDefined();
+ expect(wrapper.instance().FilterContext).not.toBeDefined();
+ expect(wrapper.instance().PaginationContext).not.toBeDefined();
+ });
+
+ it('should render correctly', () => {
+ const dataProvider = wrapper.find(wrapper.instance().DataContext.Provider);
+ expect(dataProvider).toHaveLength(1);
+ expect(dataProvider.props().data).toEqual(data);
+ expect(dataProvider.props().keyField).toEqual(keyField);
+ expect(dataProvider.props().columns).toEqual(columns);
+ });
+ });
+
+ describe('if there\'s sort is enable', () => {
+ beforeEach(() => {
+ const columnsWithSort = columns.map((c) => {
+ c.sort = true;
+ return c;
+ });
+ wrapper = shallow(
+
+ );
+ wrapper.render();
+ });
+
+ it('should create contexts correctly', () => {
+ expect(wrapper.instance().DataContext).toBeDefined();
+ expect(wrapper.instance().SortContext).toBeDefined();
+ expect(wrapper.instance().SelectionContext).not.toBeDefined();
+ expect(wrapper.instance().CellEditContext).not.toBeDefined();
+ expect(wrapper.instance().FilterContext).not.toBeDefined();
+ expect(wrapper.instance().PaginationContext).not.toBeDefined();
+ });
+ });
+
+ describe('if row selection is enable', () => {
+ beforeEach(() => {
+ const selectRow = { mode: 'radio' };
+ wrapper = shallow(
+
+ );
+ wrapper.render();
+ });
+
+ it('should create contexts correctly', () => {
+ expect(wrapper.instance().DataContext).toBeDefined();
+ expect(wrapper.instance().SortContext).not.toBeDefined();
+ expect(wrapper.instance().SelectionContext).toBeDefined();
+ expect(wrapper.instance().CellEditContext).not.toBeDefined();
+ expect(wrapper.instance().FilterContext).not.toBeDefined();
+ expect(wrapper.instance().PaginationContext).not.toBeDefined();
+ });
+ });
+
+ describe('if cell editing is enable', () => {
+ beforeEach(() => {
+ const CellEditContext = React.createContext();
+ const cellEdit = {
+ createContext: jest.fn().mockReturnValue({
+ Provider: CellEditContext.Provider,
+ Consumer: CellEditContext.Consumer
+ })
+ };
+ wrapper = shallow(
+
+ );
+ wrapper.render();
+ });
+
+ it('should create contexts correctly', () => {
+ expect(wrapper.instance().DataContext).toBeDefined();
+ expect(wrapper.instance().SortContext).not.toBeDefined();
+ expect(wrapper.instance().SelectionContext).not.toBeDefined();
+ expect(wrapper.instance().CellEditContext).toBeDefined();
+ expect(wrapper.instance().FilterContext).not.toBeDefined();
+ expect(wrapper.instance().PaginationContext).not.toBeDefined();
+ });
+ });
+
+ describe('if search is enable', () => {
+ beforeEach(() => {
+ const SearchContext = React.createContext();
+ const search = {
+ searchContext: jest.fn().mockReturnValue(SearchContext),
+ searchText: ''
+ };
+ wrapper = shallow(
+
+ );
+ wrapper.render();
+ });
+
+ it('should create contexts correctly', () => {
+ expect(wrapper.instance().DataContext).toBeDefined();
+ expect(wrapper.instance().SearchContext).toBeDefined();
+ expect(wrapper.instance().SortContext).not.toBeDefined();
+ expect(wrapper.instance().SelectionContext).not.toBeDefined();
+ expect(wrapper.instance().CellEditContext).not.toBeDefined();
+ expect(wrapper.instance().FilterContext).not.toBeDefined();
+ expect(wrapper.instance().PaginationContext).not.toBeDefined();
+ });
+ });
+
+ describe('if column filter is enable', () => {
+ beforeEach(() => {
+ const FilterContext = React.createContext();
+ const filter = {
+ createContext: jest.fn().mockReturnValue({
+ Provider: FilterContext.Provider,
+ Consumer: FilterContext.Consumer
+ })
+ };
+ wrapper = shallow(
+
+ );
+ wrapper.render();
+ });
+
+ it('should create contexts correctly', () => {
+ expect(wrapper.instance().DataContext).toBeDefined();
+ expect(wrapper.instance().SortContext).not.toBeDefined();
+ expect(wrapper.instance().SelectionContext).not.toBeDefined();
+ expect(wrapper.instance().CellEditContext).not.toBeDefined();
+ expect(wrapper.instance().FilterContext).toBeDefined();
+ expect(wrapper.instance().PaginationContext).not.toBeDefined();
+ });
+ });
+
+ describe('if pagination is enable', () => {
+ beforeEach(() => {
+ const PaginationContext = React.createContext();
+ const paginator = {
+ createContext: jest.fn().mockReturnValue({
+ Provider: PaginationContext.Provider,
+ Consumer: PaginationContext.Consumer
+ })
+ };
+ wrapper = shallow(
+
+ );
+ wrapper.render();
+ });
+
+ it('should create contexts correctly', () => {
+ expect(wrapper.instance().DataContext).toBeDefined();
+ expect(wrapper.instance().SortContext).not.toBeDefined();
+ expect(wrapper.instance().SelectionContext).not.toBeDefined();
+ expect(wrapper.instance().CellEditContext).not.toBeDefined();
+ expect(wrapper.instance().FilterContext).not.toBeDefined();
+ expect(wrapper.instance().PaginationContext).toBeDefined();
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2/test/contexts/selection-context.test.js b/packages/react-bootstrap-table2/test/contexts/selection-context.test.js
new file mode 100644
index 0000000..819fada
--- /dev/null
+++ b/packages/react-bootstrap-table2/test/contexts/selection-context.test.js
@@ -0,0 +1,258 @@
+import 'jsdom-global/register';
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import dataOperator from '../../src/store/operators';
+import BootstrapTable from '../../src/bootstrap-table';
+import createSelectionContext from '../../src/contexts/selection-context';
+
+describe('DataContext', () => {
+ let wrapper;
+
+ const data = [{
+ id: 1,
+ name: 'A'
+ }, {
+ id: 2,
+ name: 'B'
+ }, {
+ id: 3,
+ name: 'B'
+ }];
+
+ const keyField = 'id';
+
+ const columns = [{
+ dataField: 'id',
+ text: 'ID'
+ }, {
+ dataField: 'name',
+ text: 'Name'
+ }];
+
+ const mockBase = jest.fn((props => (
+
+ )));
+
+ const defaultSelectRow = {
+ mode: 'checkbox'
+ };
+ const SelectionContext = createSelectionContext(dataOperator);
+
+ function shallowContext(selectRow = defaultSelectRow) {
+ return (
+
+
+ {
+ selectionProps => mockBase(selectionProps)
+ }
+
+
+ );
+ }
+
+ describe('default render', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ });
+
+ it('should have correct Provider property after calling createSelectionContext', () => {
+ expect(SelectionContext.Provider).toBeDefined();
+ });
+
+ it('should have correct Consumer property after calling createSelectionContext', () => {
+ expect(SelectionContext.Consumer).toBeDefined();
+ });
+
+ it('should have correct state.data', () => {
+ expect(wrapper.state().selected).toEqual([]);
+ });
+
+ it('should pass correct sort props to children element', () => {
+ expect(wrapper.length).toBe(1);
+ expect(mockBase).toHaveBeenCalledWith({
+ selected: wrapper.state().selected,
+ onRowSelect: wrapper.instance().handleRowSelect,
+ onAllRowsSelect: wrapper.instance().handleAllRowsSelect
+ });
+ });
+ });
+
+ describe('componentWillReceiveProps', () => {
+ const newSelectRow = {
+ ...defaultSelectRow,
+ selected: [1]
+ };
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.instance().componentWillReceiveProps({
+ selectRow: newSelectRow
+ });
+ });
+
+ it('should have correct state.selected', () => {
+ expect(wrapper.state().selected).toEqual(newSelectRow.selected);
+ });
+
+ describe('if nextProps.selectRow is not existing', () => {
+ const defaultSelected = [1];
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultSelectRow,
+ selected: defaultSelected
+ }));
+ wrapper.instance().componentWillReceiveProps({
+ selectRow: defaultSelectRow
+ });
+ });
+
+ it('should keep origin state.selected', () => {
+ expect(wrapper.state().selected).toEqual(defaultSelected);
+ });
+ });
+
+ describe('if nextProps.selectRow is not existing', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.instance().componentWillReceiveProps({});
+ });
+
+ it('should not set state.selected', () => {
+ expect(wrapper.state().selected).toEqual([]);
+ });
+ });
+ });
+
+ describe('when selectRow.selected prop is defined', () => {
+ let selectRow;
+
+ beforeEach(() => {
+ selectRow = {
+ ...defaultSelectRow,
+ selected: [1]
+ };
+ wrapper = shallow(shallowContext(selectRow));
+ });
+
+ it('should have correct state.data', () => {
+ expect(wrapper.state().selected).toEqual(selectRow.selected);
+ });
+ });
+
+ describe('handleRowSelect', () => {
+ const rowIndex = 1;
+ const firstSelectedRow = data[0][keyField];
+ const secondSelectedRow = data[1][keyField];
+
+ describe('when selectRow.mode is \'radio\'', () => {
+ beforeEach(() => {
+ const selectRow = { mode: 'radio' };
+ wrapper = shallow(shallowContext(selectRow));
+ });
+
+ it('should set state.selected correctly', () => {
+ wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex);
+ expect(wrapper.state('selected')).toEqual([firstSelectedRow]);
+
+ wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex);
+ expect(wrapper.state('selected')).toEqual([secondSelectedRow]);
+ });
+ });
+
+ describe('when selectRow.mode is \'checkbox\'', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ });
+
+ it('should set state.selected correctly', () => {
+ wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex);
+ expect(wrapper.state('selected')).toEqual(expect.arrayContaining([firstSelectedRow]));
+
+ wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex);
+ expect(wrapper.state('selected')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow]));
+
+ wrapper.instance().handleRowSelect(firstSelectedRow, false, rowIndex);
+ expect(wrapper.state('selected')).toEqual(expect.arrayContaining([secondSelectedRow]));
+
+ wrapper.instance().handleRowSelect(secondSelectedRow, false, rowIndex);
+ expect(wrapper.state('selected')).toEqual([]);
+ });
+ });
+
+ describe('when selectRow.onSelect is defined', () => {
+ const onSelect = jest.fn();
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultSelectRow,
+ onSelect
+ }));
+ });
+
+ it('call selectRow.onSelect correctly', () => {
+ const e = { target: {} };
+ const row = dataOperator.getRowByRowId(data, keyField, firstSelectedRow);
+ wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex, e);
+ expect(onSelect).toHaveBeenCalledWith(row, true, rowIndex, e);
+ });
+ });
+ });
+
+ describe('handleAllRowsSelect', () => {
+ const e = { target: {} };
+
+ describe('when isUnSelect argument is false', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.instance().handleAllRowsSelect(e, false);
+ });
+
+ it('should set state.selected correctly', () => {
+ expect(wrapper.state('selected')).toEqual(data.map(d => d[keyField]));
+ });
+ });
+
+ describe('when isUnSelect argument is true', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultSelectRow,
+ selected: data.map(d => d[keyField])
+ }));
+ wrapper.instance().handleAllRowsSelect(e, true);
+ });
+
+ it('should set state.selected correctly', () => {
+ expect(wrapper.state('selected')).toEqual([]);
+ });
+ });
+
+ describe('when selectRow.onSelectAll is defined', () => {
+ const onSelectAll = jest.fn();
+ beforeEach(() => {
+ wrapper = shallow(shallowContext({
+ ...defaultSelectRow,
+ onSelectAll
+ }));
+ wrapper.instance().handleAllRowsSelect(e, false);
+ });
+
+ it('should call selectRow.onSelectAll correctly', () => {
+ expect(onSelectAll).toHaveBeenCalledWith(
+ true,
+ dataOperator.getSelectedRows(data, keyField, wrapper.state('selected')),
+ e
+ );
+ });
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2/test/contexts/sort-context.test.js b/packages/react-bootstrap-table2/test/contexts/sort-context.test.js
new file mode 100644
index 0000000..0a05861
--- /dev/null
+++ b/packages/react-bootstrap-table2/test/contexts/sort-context.test.js
@@ -0,0 +1,255 @@
+import 'jsdom-global/register';
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import Const from '../../src/const';
+import dataOperator from '../../src/store/operators';
+import BootstrapTable from '../../src/bootstrap-table';
+import createSortContext from '../../src/contexts/sort-context';
+
+describe('SortContext', () => {
+ let wrapper;
+ let columns;
+ let SortContext;
+
+ let data;
+
+ const mockBase = jest.fn((props => (
+
+ )));
+
+ beforeEach(() => {
+ data = [{
+ id: 1,
+ name: 'A'
+ }, {
+ id: 2,
+ name: 'B'
+ }];
+ columns = [{
+ dataField: 'id',
+ text: 'ID',
+ sort: true
+ }, {
+ dataField: 'name',
+ text: 'Name',
+ sort: true
+ }];
+ });
+
+ const handleRemoteSortChange = jest.fn();
+
+ function shallowContext(enableRemote = false, providerProps = {}) {
+ handleRemoteSortChange.mockReset();
+ mockBase.mockReset();
+ SortContext = createSortContext(
+ dataOperator,
+ jest.fn().mockReturnValue(enableRemote),
+ handleRemoteSortChange
+ );
+ return (
+
+
+ {
+ sortProps => mockBase(sortProps)
+ }
+
+
+ );
+ }
+
+ describe('default render', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ });
+
+ it('should have correct Provider property after calling createSortContext', () => {
+ expect(SortContext.Provider).toBeDefined();
+ });
+
+ it('should have correct Consumer property after calling createSortContext', () => {
+ expect(SortContext.Consumer).toBeDefined();
+ });
+
+ it('should have correct state.sortOrder', () => {
+ expect(wrapper.state().sortOrder).toBe(undefined);
+ });
+
+ it('should have correct state.sortColumn', () => {
+ expect(wrapper.state().sortColumn).toBe(undefined);
+ });
+
+ it('should pass correct sort props to children element', () => {
+ expect(wrapper.length).toBe(1);
+ expect(mockBase).toHaveBeenCalledWith({
+ data,
+ sortOrder: undefined,
+ onSort: wrapper.instance().handleSort,
+ sortField: null
+ });
+ });
+ });
+
+ describe('handleSort function', () => {
+ let sortColumn;
+ let nextOrderSpy;
+
+ beforeEach(() => {
+ sortColumn = columns[0];
+ nextOrderSpy = jest.spyOn(dataOperator, 'nextOrder');
+ });
+
+ afterEach(() => {
+ nextOrderSpy.mockRestore();
+ });
+
+ describe('when remote.sort is false', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext());
+ wrapper.render();
+ wrapper.instance().handleSort(sortColumn);
+ wrapper.update();
+ wrapper.render();
+ });
+
+ it('should set state correctly', () => {
+ expect(wrapper.state().sortColumn).toEqual(sortColumn);
+ expect(wrapper.state().sortOrder).toEqual(Const.SORT_DESC);
+ });
+
+ it('should call dataOperator.nextOrder correctly', () => {
+ expect(nextOrderSpy).toHaveBeenCalledTimes(1);
+ expect(nextOrderSpy).toHaveBeenCalledWith(
+ sortColumn,
+ { sortColumn: undefined, sortOrder: undefined },
+ wrapper.props().defaultSortDirection
+ );
+ });
+
+ it('should pass correct sort props to children element', () => {
+ expect(wrapper.length).toBe(1);
+ expect(mockBase).toHaveBeenLastCalledWith({
+ data: data.reverse(),
+ sortOrder: wrapper.state().sortOrder,
+ onSort: wrapper.instance().handleSort,
+ sortField: wrapper.state().sortColumn.dataField
+ });
+ });
+ });
+
+ describe('when remote.sort is true', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext(true));
+ wrapper.render();
+
+ nextOrderSpy = jest.spyOn(dataOperator, 'nextOrder');
+ wrapper.instance().handleSort(sortColumn);
+ });
+
+ it('should set state correctly', () => {
+ expect(wrapper.state().sortColumn).toEqual(sortColumn);
+ expect(wrapper.state().sortOrder).toEqual(Const.SORT_DESC);
+ });
+
+ it('should call dataOperator.nextOrder correctly', () => {
+ expect(nextOrderSpy).toHaveBeenCalledTimes(1);
+ expect(nextOrderSpy).toHaveBeenCalledWith(
+ sortColumn,
+ { sortColumn: undefined, sortOrder: undefined },
+ wrapper.props().defaultSortDirection
+ );
+ });
+
+ it('should calling handleRemoteSortChange correctly', () => {
+ expect(handleRemoteSortChange).toHaveBeenCalledTimes(1);
+ expect(handleRemoteSortChange).toHaveBeenCalledWith(sortColumn.dataField, Const.SORT_DESC);
+ });
+ });
+
+ describe('when column.onSort prop is defined', () => {
+ const onSortCB = jest.fn();
+
+ beforeEach(() => {
+ columns[0].onSort = onSortCB;
+ wrapper = shallow(shallowContext());
+ wrapper.instance().handleSort(sortColumn);
+ });
+
+ it('should calling column.onSort function correctly', () => {
+ expect(onSortCB).toHaveBeenCalledTimes(1);
+ expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_DESC);
+
+ wrapper.instance().handleSort(sortColumn);
+ expect(onSortCB).toHaveBeenCalledTimes(2);
+ expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_ASC);
+ });
+ });
+ });
+
+ describe('when defaultSorted prop is defined', () => {
+ const defaultSorted = [{
+ dataField: 'name',
+ order: Const.SORT_DESC
+ }];
+
+ beforeEach(() => {
+ wrapper = shallow(shallowContext(false, { defaultSorted }));
+ wrapper.render();
+ });
+
+ it('should pass correct sort props to children element', () => {
+ expect(wrapper.length).toBe(1);
+ expect(mockBase).toHaveBeenLastCalledWith({
+ data: data.reverse(),
+ sortOrder: wrapper.state().sortOrder,
+ onSort: wrapper.instance().handleSort,
+ sortField: wrapper.state().sortColumn.dataField
+ });
+ });
+
+ it('should have correct state.sortOrder', () => {
+ expect(wrapper.state().sortOrder).toBe(defaultSorted[0].order);
+ });
+
+ it('should have correct state.sortColumn', () => {
+ expect(wrapper.state().sortColumn).toBe(columns[1]);
+ });
+
+ describe('when column.onSort prop is defined', () => {
+ const onSortCB = jest.fn();
+
+ beforeEach(() => {
+ columns[1].onSort = onSortCB;
+ wrapper = shallow(shallowContext(false, { defaultSorted }));
+ });
+
+ it('should calling column.onSort function correctly', () => {
+ expect(onSortCB).toHaveBeenCalledTimes(1);
+ expect(onSortCB).toHaveBeenCalledWith(defaultSorted[0].dataField, defaultSorted[0].order);
+ });
+ });
+
+ describe('when remote.sort is true', () => {
+ beforeEach(() => {
+ wrapper = shallow(shallowContext(true, { defaultSorted }));
+ wrapper.render();
+ });
+
+ it('should calling handleRemoteSortChange correctly', () => {
+ expect(handleRemoteSortChange).toHaveBeenCalledTimes(1);
+ expect(handleRemoteSortChange)
+ .toHaveBeenCalledWith(defaultSorted[0].dataField, defaultSorted[0].order);
+ });
+ });
+ });
+});
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 cb3a54b..d12ed1f 100644
--- a/packages/react-bootstrap-table2/test/props-resolver/index.test.js
+++ b/packages/react-bootstrap-table2/test/props-resolver/index.test.js
@@ -225,17 +225,9 @@ describe('TableResolver', () => {
bar: expect.any(Function)
}));
});
-
- it('should return object which can not contain allRowsSelected option', () => {
- expect(headerCellSelectionInfo.allRowsSelected).not.toBeDefined();
- });
-
- it('should return object which can not contain allRowsSelected option', () => {
- expect(headerCellSelectionInfo.selected).not.toBeDefined();
- });
});
- describe('if all rows were selected', () => {
+ describe('if options.allRowsSelected is true', () => {
beforeEach(() => {
selectRow = {};
const selectedRowKeys = [1, 2];
@@ -258,7 +250,7 @@ describe('TableResolver', () => {
});
});
- describe('if part of rows were selected', () => {
+ describe('if options.allRowsSelected and options.allRowsNotSelected both are false', () => {
beforeEach(() => {
selectRow = {};
const selectedRowKeys = [1];
@@ -269,6 +261,7 @@ describe('TableResolver', () => {
wrapper = shallow(mockElement);
headerCellSelectionInfo = wrapper.instance().resolveSelectRowPropsForHeader({
allRowsSelected: false,
+ allRowsNotSelected: false,
selected: selectedRowKeys
});
});
@@ -280,7 +273,7 @@ describe('TableResolver', () => {
});
});
- describe('if none of row was selected', () => {
+ describe('if options.allRowsNotSelected is true', () => {
beforeEach(() => {
selectRow = {};
const selectedRowKeys = [];
@@ -292,6 +285,7 @@ describe('TableResolver', () => {
headerCellSelectionInfo = wrapper.instance().resolveSelectRowPropsForHeader({
allRowsSelected: false,
+ allRowsNotSelected: true,
selected: selectedRowKeys
});
});
diff --git a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js
index 4cfcc04..d3de234 100644
--- a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js
+++ b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js
@@ -3,7 +3,7 @@ import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
-import Container from '../../';
+import Container from '../../index';
// import remoteResolver from '../../src/props-resolver/remote-resolver';
describe('remoteResolver', () => {
@@ -100,6 +100,16 @@ describe('remoteResolver', () => {
expect(wrapper.instance().isRemoteFiltering()).toBeTruthy();
});
});
+
+ describe('when this.isRemotePagination return true', () => {
+ beforeEach(() => {
+ shallowContainer({ remote: { pagination: true } });
+ });
+
+ it('should return true', () => {
+ expect(wrapper.instance().isRemoteFiltering()).toBeTruthy();
+ });
+ });
});
describe('isRemoteSort', () => {
@@ -132,6 +142,16 @@ describe('remoteResolver', () => {
expect(wrapper.instance().isRemoteSort()).toBeTruthy();
});
});
+
+ describe('when this.isRemotePagination return true', () => {
+ beforeEach(() => {
+ shallowContainer({ remote: { pagination: true } });
+ });
+
+ it('should return true', () => {
+ expect(wrapper.instance().isRemoteSort()).toBeTruthy();
+ });
+ });
});
describe('isRemoteCellEdit', () => {
@@ -166,7 +186,49 @@ describe('remoteResolver', () => {
});
});
- describe('handleCellChange', () => {
+ describe('isRemoteSearch', () => {
+ describe('when remote is false', () => {
+ beforeEach(() => {
+ shallowContainer();
+ });
+
+ it('should return false', () => {
+ expect(wrapper.instance().isRemoteSearch()).toBeFalsy();
+ });
+ });
+
+ describe('when remote is true', () => {
+ beforeEach(() => {
+ shallowContainer({ remote: true });
+ });
+
+ it('should return true', () => {
+ expect(wrapper.instance().isRemoteSearch()).toBeTruthy();
+ });
+ });
+
+ describe('when remote.search is true', () => {
+ beforeEach(() => {
+ shallowContainer({ remote: { search: true } });
+ });
+
+ it('should return true', () => {
+ expect(wrapper.instance().isRemoteSearch()).toBeTruthy();
+ });
+ });
+
+ describe('when this.isRemotePagination return true', () => {
+ beforeEach(() => {
+ shallowContainer({ remote: { pagination: true } });
+ });
+
+ it('should return true', () => {
+ expect(wrapper.instance().isRemoteSearch()).toBeTruthy();
+ });
+ });
+ });
+
+ describe('handleRemoteCellChange', () => {
const onTableChangeCB = sinon.stub();
const rowId = 1;
const dataField = 'name';
@@ -175,7 +237,7 @@ describe('remoteResolver', () => {
beforeEach(() => {
onTableChangeCB.reset();
shallowContainer({ onTableChange: onTableChangeCB });
- wrapper.instance().handleCellChange(rowId, dataField, newValue);
+ wrapper.instance().handleRemoteCellChange(rowId, dataField, newValue);
});
it('should calling props.onTableChange correctly', () => {
@@ -188,35 +250,65 @@ describe('remoteResolver', () => {
describe('handleSortChange', () => {
const onTableChangeCB = sinon.stub();
+ const newSortFiled = 'name';
+ const newSortOrder = 'asc';
beforeEach(() => {
onTableChangeCB.reset();
shallowContainer({ onTableChange: onTableChangeCB });
- wrapper.instance().handleSortChange();
+ wrapper.instance().handleRemoteSortChange(newSortFiled, newSortOrder);
});
it('should calling props.onTableChange correctly', () => {
expect(onTableChangeCB.calledOnce).toBeTruthy();
- expect(onTableChangeCB.calledWith('sort', wrapper.instance().getNewestState())).toBeTruthy();
+ expect(onTableChangeCB.calledWith('sort', wrapper.instance().getNewestState({
+ sortField: newSortFiled,
+ sortOrder: newSortOrder
+ }))).toBeTruthy();
});
});
describe('handleRemotePageChange', () => {
const onTableChangeCB = sinon.stub();
+ const newPage = 2;
+ const newSizePerPage = 10;
beforeEach(() => {
onTableChangeCB.reset();
shallowContainer({ onTableChange: onTableChangeCB });
- wrapper.instance().handleRemotePageChange();
+ wrapper.instance().handleRemotePageChange(newPage, newSizePerPage);
});
it('should calling props.onTableChange correctly', () => {
expect(onTableChangeCB.calledOnce).toBeTruthy();
- expect(onTableChangeCB.calledWith('pagination', wrapper.instance().getNewestState())).toBeTruthy();
+ expect(onTableChangeCB.calledWith('pagination', wrapper.instance().getNewestState({
+ page: newPage,
+ sizePerPage: newSizePerPage
+ }))).toBeTruthy();
+ });
+ });
+
+ describe('handleRemoteSearchChange', () => {
+ const onTableChangeCB = sinon.stub();
+ const searchText = 'abc';
+
+ beforeEach(() => {
+ onTableChangeCB.reset();
+ shallowContainer({
+ onTableChange: onTableChangeCB
+ });
+ wrapper.instance().handleRemoteSearchChange(searchText);
+ });
+
+ it('should calling props.onTableChange correctly', () => {
+ expect(onTableChangeCB.calledOnce).toBeTruthy();
+ expect(onTableChangeCB.calledWith('search', wrapper.instance().getNewestState({
+ searchText
+ }))).toBeTruthy();
});
});
describe('handleRemoteFilterChange', () => {
const onTableChangeCB = sinon.stub();
-
+ const filters = { price: { filterVal: 20, filterType: 'TEXT' } };
beforeEach(() => {
onTableChangeCB.reset();
shallowContainer({ onTableChange: onTableChangeCB });
@@ -224,16 +316,16 @@ describe('remoteResolver', () => {
describe('when remote pagination is disabled', () => {
it('should calling props.onTableChange correctly', () => {
- wrapper.instance().handleRemoteFilterChange();
+ wrapper.instance().handleRemoteFilterChange(filters);
expect(onTableChangeCB.calledOnce).toBeTruthy();
- expect(onTableChangeCB.calledWith('filter', wrapper.instance().getNewestState())).toBeTruthy();
+ expect(onTableChangeCB.calledWith('filter', wrapper.instance().getNewestState({
+ filters
+ }))).toBeTruthy();
});
});
describe('when remote pagination is enabled', () => {
- const wrapperFactory = Base => class FilterWrapper extends React.Component {
- render() { return ; }
- };
+ const createContext = () => {};
describe('and pagination.options.pageStartIndex is defined', () => {
const options = { pageStartIndex: 0 };
@@ -241,16 +333,16 @@ describe('remoteResolver', () => {
shallowContainer({
remote: true,
onTableChange: onTableChangeCB,
- pagination: { options, wrapperFactory }
+ pagination: { options, createContext }
});
- wrapper.instance().store.page = 1;
- wrapper.instance().store.sizePerPage = 10;
- wrapper.instance().handleRemoteFilterChange();
+ wrapper.instance().handleRemoteFilterChange(filters);
});
- it('should calling onTableChange correctly', () => {
+ it('should calling onTableChange with page property by pageStartIndex', () => {
expect(onTableChangeCB.calledOnce).toBeTruthy();
- const newState = wrapper.instance().getNewestState();
+ const newState = wrapper.instance().getNewestState({
+ filters
+ });
newState.page = options.pageStartIndex;
expect(onTableChangeCB.calledWith('filter', newState)).toBeTruthy();
});
@@ -261,14 +353,14 @@ describe('remoteResolver', () => {
shallowContainer({
remote: true,
onTableChange: onTableChangeCB,
- pagination: { wrapperFactory }
+ pagination: { createContext }
});
- wrapper.instance().handleRemoteFilterChange();
+ wrapper.instance().handleRemoteFilterChange(filters);
});
- it('should calling onTableChange correctly', () => {
+ it('should calling onTableChange with page property by default 1', () => {
expect(onTableChangeCB.calledOnce).toBeTruthy();
- const newState = wrapper.instance().getNewestState();
+ const newState = wrapper.instance().getNewestState({ filters });
newState.page = 1;
expect(onTableChangeCB.calledWith('filter', newState)).toBeTruthy();
});
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 02e9d85..967c626 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
@@ -1,7 +1,10 @@
+import 'jsdom-global/register';
import React from 'react';
import { shallow } from 'enzyme';
+import toJson from 'enzyme-to-json';
import sinon from 'sinon';
+import { shallowWithContext } from '../test-helpers/new-context';
import SelectionCell from '../../src/row-selection/selection-cell';
describe(' ', () => {
@@ -52,14 +55,15 @@ describe(' ', () => {
describe('when disabled prop is false', () => {
beforeEach(() => {
- wrapper = shallow(
+ wrapper = shallowWithContext(
+ />,
+ { bootstrap4: false }
);
wrapper.find('td').simulate('click');
});
@@ -70,15 +74,13 @@ describe(' ', () => {
it('should calling onRowSelect callback correctly', () => {
expect(mockOnRowSelect.calledOnce).toBe(true);
- expect(
- mockOnRowSelect.calledWith(rowKey, !selected, rowIndex)
- ).toBe(true);
+ expect(mockOnRowSelect.calledWith(rowKey, !selected, rowIndex)).toBe(true);
});
});
describe('when disabled prop is true', () => {
beforeEach(() => {
- wrapper = shallow(
+ wrapper = shallowWithContext(
', () => {
rowIndex={ rowIndex }
onRowSelect={ mockOnRowSelect }
disabled
- />
+ />,
+ { bootstrap4: false }
);
wrapper.find('td').simulate('click');
});
@@ -102,14 +105,15 @@ describe(' ', () => {
describe('if selectRow.mode is radio', () => {
beforeEach(() => {
- wrapper = shallow(
+ wrapper = shallowWithContext(
+ />,
+ { bootstrap4: false }
);
});
@@ -118,38 +122,28 @@ describe(' ', () => {
wrapper.find('td').simulate('click');
expect(mockOnRowSelect.callCount).toBe(1);
expect(mockOnRowSelect.calledWith(rowKey, true, rowIndex)).toBe(true);
-
- // second click
- wrapper.find('td').simulate('click');
- expect(mockOnRowSelect.callCount).toBe(2);
- expect(mockOnRowSelect.calledWith(rowKey, true, rowIndex)).toBe(true);
});
});
describe('if selectRow.mode is checkbox', () => {
beforeEach(() => {
- wrapper = shallow(
+ wrapper = shallowWithContext(
+ />,
+ { bootstrap4: false }
);
});
- it('should be called with correct paramters', () => {
+ it('should be called with correct parameters', () => {
// first click
- wrapper.setProps({ selected: true });
wrapper.find('td').simulate('click');
expect(mockOnRowSelect.callCount).toBe(1);
- expect(mockOnRowSelect.calledWith(rowKey, false, rowIndex)).toBe(true);
-
- // second click
- wrapper.setProps({ selected: false });
- wrapper.find('td').simulate('click');
- expect(mockOnRowSelect.callCount).toBe(2);
- expect(mockOnRowSelect.calledWith(rowKey, true, rowIndex)).toBe(true);
+ expect(mockOnRowSelect.calledWith(rowKey, false, rowIndex, undefined)).toBe(true);
});
});
});
@@ -159,33 +153,25 @@ describe(' ', () => {
const selected = true;
beforeEach(() => {
- wrapper = shallow(
-
+ wrapper = shallowWithContext(
+ ,
+ { bootstrap4: false }
);
});
it('should render component correctly', () => {
expect(wrapper.find('td').length).toBe(1);
- expect(wrapper.find('input').length).toBe(1);
+ expect(wrapper.find('input')).toHaveLength(1);
expect(wrapper.find('input').get(0).props.type).toBe(mode);
expect(wrapper.find('input').get(0).props.checked).toBe(selected);
+ expect(toJson(wrapper)).toMatchSnapshot();
});
describe('when disabled prop give as true', () => {
beforeEach(() => {
- wrapper = shallow(
-
+ wrapper = shallowWithContext(
+ ,
+ { bootstrap4: false }
);
});
@@ -200,14 +186,15 @@ describe(' ', () => {
beforeEach(() => {
selectionRenderer.mockClear();
- wrapper = shallow(
+ wrapper = shallowWithContext(
+ />,
+ { bootstrap4: false }
);
});
@@ -224,5 +211,19 @@ describe(' ', () => {
});
});
});
+
+ describe('when bootstrap4 context is true', () => {
+ beforeEach(() => {
+ wrapper = shallowWithContext(
+ ,
+ { bootstrap4: true }
+ );
+ });
+
+ it('should render component correctly', () => {
+ expect(wrapper.find('td').length).toBe(1);
+ expect(wrapper.find('.selection-input-4')).toHaveLength(1);
+ });
+ });
});
});
diff --git a/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js b/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js
index 73571d4..be9a519 100644
--- a/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js
+++ b/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js
@@ -1,7 +1,9 @@
import React from 'react';
import { shallow } from 'enzyme';
+import toJson from 'enzyme-to-json';
import sinon from 'sinon';
+import { shallowWithContext } from '../test-helpers/new-context';
import Const from '../../src/const';
import SelectionHeaderCell, { CheckBox } from '../../src/row-selection/selection-header-cell';
@@ -11,7 +13,7 @@ describe(' ', () => {
describe('shouldComponentUpdate', () => {
describe('when props.mode is radio', () => {
it('should not update component', () => {
- wrapper = shallow( );
+ wrapper = shallow( , { bootstrap4: false });
expect(wrapper.instance().shouldComponentUpdate({})).toBe(false);
});
@@ -24,7 +26,9 @@ describe(' ', () => {
const nextProps = { checkedStatus };
wrapper = shallow(
- );
+ ,
+ { bootstrap4: false }
+ );
expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(false);
});
@@ -37,7 +41,9 @@ describe(' ', () => {
const nextProps = { checkedStatus };
wrapper = shallow(
- );
+ ,
+ { bootstrap4: false }
+ );
expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
});
@@ -57,12 +63,14 @@ describe(' ', () => {
describe('if props.mode is radio', () => {
beforeEach(() => {
- wrapper = shallow(
+ wrapper = shallowWithContext(
);
+ />,
+ { bootstrap4: false }
+ );
});
it('should do nothing', () => {
@@ -75,12 +83,14 @@ describe(' ', () => {
describe('if props.mode is checkbox', () => {
beforeEach(() => {
- wrapper = shallow(
+ wrapper = shallowWithContext(
);
+ />,
+ { bootstrap4: false }
+ );
});
it('should call handleCheckBoxClick', () => {
@@ -98,7 +108,10 @@ describe(' ', () => {
beforeEach(() => {
const checkedStatus = Const.CHECKBOX_STATUS_CHECKED;
- wrapper = shallow( );
+ wrapper = shallowWithContext(
+ ,
+ { bootstrap4: false }
+ );
});
it('should not render checkbox', () => {
@@ -112,7 +125,10 @@ describe(' ', () => {
const checkedStatus = Const.CHECKBOX_STATUS_CHECKED;
beforeEach(() => {
- wrapper = shallow( );
+ wrapper = shallowWithContext(
+ ,
+ { bootstrap4: false }
+ );
});
it('should render checkbox', () => {
@@ -134,12 +150,13 @@ describe(' ', () => {
beforeEach(() => {
selectionHeaderRenderer.mockClear();
- wrapper = shallow(
+ wrapper = shallowWithContext(
+ />,
+ { bootstrap4: false }
);
});
@@ -156,6 +173,22 @@ describe(' ', () => {
});
});
});
+
+ describe('when bootstrap4 context is true', () => {
+ beforeEach(() => {
+ const checkedStatus = Const.CHECKBOX_STATUS_CHECKED;
+
+ wrapper = shallowWithContext(
+ ,
+ { bootstrap4: true }
+ );
+ });
+
+ it('should not render checkbox', () => {
+ expect(wrapper.find('th').length).toBe(1);
+ expect(wrapper.find('.selection-input-4').length).toBe(1);
+ });
+ });
});
});
@@ -169,6 +202,7 @@ describe(' ', () => {
expect(wrapper.find('input').length).toBe(1);
expect(wrapper.find('input').prop('checked')).toBe(checked);
expect(wrapper.find('input').prop('type')).toBe('checkbox');
+ expect(toJson(wrapper)).toMatchSnapshot();
});
});
});
diff --git a/packages/react-bootstrap-table2/test/row-selection/wrapper.test.js b/packages/react-bootstrap-table2/test/row-selection/wrapper.test.js
deleted file mode 100644
index aa397ca..0000000
--- a/packages/react-bootstrap-table2/test/row-selection/wrapper.test.js
+++ /dev/null
@@ -1,224 +0,0 @@
-import React from 'react';
-import sinon from 'sinon';
-import { shallow } from 'enzyme';
-
-import Store from '../../src/store';
-import BootstrapTable from '../../src/bootstrap-table';
-import wrapperFactory from '../../src/row-selection/wrapper';
-
-describe('RowSelectionWrapper', () => {
- let wrapper;
- let selectRow;
-
- const columns = [{
- dataField: 'id',
- text: 'ID'
- }, {
- dataField: 'name',
- text: 'Name'
- }];
-
- const data = [{
- id: 1,
- name: 'A'
- }, {
- id: 2,
- name: 'B'
- }];
-
- const rowIndex = 1;
-
- const keyField = 'id';
-
- const store = new Store(keyField);
- store.data = data;
- const RowSelectionWrapper = wrapperFactory(BootstrapTable);
-
- beforeEach(() => {
- selectRow = {
- mode: 'radio'
- };
- wrapper = shallow(
-
- );
- });
-
- it('should render RowSelectionWrapper correctly', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find(BootstrapTable)).toBeDefined();
- });
-
- it('should have correct store.selected value', () => {
- expect(store.selected).toEqual([]);
- });
-
- it('should have correct state', () => {
- expect(wrapper.state().selectedRowKeys).toBeDefined();
- expect(wrapper.state().selectedRowKeys.length).toEqual(0);
- });
-
- it('should inject correct props to base component', () => {
- expect(wrapper.props().onRowSelect).toBeDefined();
- expect(wrapper.props().onAllRowsSelect).toBeDefined();
- });
-
- describe('componentWillReceiveProps', () => {
- const nextSelected = [0];
- const nextProps = {
- store: {
- selected: nextSelected
- },
- selectRow: {
- mode: 'checkbox',
- selected: nextSelected
- }
- };
-
- it('should update state.selectedRowKeys with next selected rows', () => {
- wrapper.instance().componentWillReceiveProps(nextProps);
- expect(nextProps.store.selected).toEqual(nextSelected);
- expect(wrapper.state('selectedRowKeys')).toEqual(nextSelected);
- });
- });
-
- describe('when selectRow.selected is defined', () => {
- beforeEach(() => {
- selectRow.mode = 'checkbox';
- selectRow.selected = [1, 3];
- wrapper = shallow(
-
- );
- });
-
- it('should have correct store.selected value', () => {
- expect(store.selected).toEqual(selectRow.selected);
- });
-
- it('should have correct state', () => {
- expect(wrapper.state().selectedRowKeys).toEqual(selectRow.selected);
- });
- });
-
- describe('when selectRow.mode is \'radio\'', () => {
- const firstSelectedRow = data[0][keyField];
- const secondSelectedRow = data[1][keyField];
-
- it('call handleRowSelect function should setting correct state.selectedRowKeys', () => {
- wrapper.instance().handleRowSelect(firstSelectedRow, rowIndex);
- expect(wrapper.state('selectedRowKeys')).toEqual([firstSelectedRow]);
-
- wrapper.instance().handleRowSelect(secondSelectedRow, rowIndex);
- expect(wrapper.state('selectedRowKeys')).toEqual([secondSelectedRow]);
- });
- });
-
- describe('when selectRow.mode is \'checkbox\'', () => {
- const firstSelectedRow = data[0][keyField];
- const secondSelectedRow = data[1][keyField];
-
- beforeEach(() => {
- selectRow.mode = 'checkbox';
- wrapper = shallow(
-
- );
- });
-
- it('call handleRowSelect function should setting correct state.selectedRowKeys', () => {
- wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex);
- expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow]));
-
- wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex);
- expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow]));
-
- wrapper.instance().handleRowSelect(firstSelectedRow, false, rowIndex);
- expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([secondSelectedRow]));
-
- wrapper.instance().handleRowSelect(secondSelectedRow, false, rowIndex);
- expect(wrapper.state('selectedRowKeys')).toEqual([]);
- });
-
- it('call handleAllRowsSelect function should setting correct state.selectedRowKeys', () => {
- wrapper.instance().handleAllRowsSelect();
- expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow]));
-
- wrapper.instance().handleAllRowsSelect();
- expect(wrapper.state('selectedRowKeys')).toEqual([]);
- });
- });
-
- describe('when selectRow.onSelect is defined', () => {
- const selectedRow = data[0][keyField];
- const onSelectCallBack = sinon.stub();
-
- beforeEach(() => {
- selectRow.mode = 'checkbox';
- selectRow.onSelect = onSelectCallBack;
- wrapper = shallow(
-
- );
- });
-
- it('selectRow.onSelect callback should be called correctly when calling handleRowSelect function', () => {
- wrapper.instance().handleRowSelect(selectedRow, true, rowIndex);
- expect(onSelectCallBack.callCount).toEqual(1);
- expect(onSelectCallBack.calledWith(data[0], true, rowIndex)).toBeTruthy();
-
- wrapper.instance().handleRowSelect(selectedRow, false, rowIndex);
- expect(onSelectCallBack.callCount).toEqual(2);
- expect(onSelectCallBack.calledWith(data[0], false, rowIndex)).toBeTruthy();
- });
- });
-
- describe('when selectRow.onSelectAll is defined', () => {
- const onSelectAllCallBack = sinon.stub();
-
- beforeEach(() => {
- selectRow.mode = 'checkbox';
- selectRow.onSelectAll = onSelectAllCallBack;
- wrapper = shallow(
-
- );
- });
-
- it('selectRow.onSelect callback should be called correctly when calling handleRowSelect function', () => {
- const e = {};
- wrapper.instance().handleAllRowsSelect(e);
- expect(onSelectAllCallBack.callCount).toEqual(1);
- expect(onSelectAllCallBack.calledWith(true, data, e)).toBeTruthy();
-
- wrapper.instance().handleAllRowsSelect(e);
- expect(onSelectAllCallBack.callCount).toEqual(2);
- expect(onSelectAllCallBack.calledWith(false, [], e)).toBeTruthy();
- });
- });
-});
diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row.test.js
index 117a24f..621f140 100644
--- a/packages/react-bootstrap-table2/test/row.test.js
+++ b/packages/react-bootstrap-table2/test/row.test.js
@@ -8,7 +8,7 @@ import Const from '../src/const';
import SelectionCell from '../src//row-selection/selection-cell';
import mockBodyResolvedProps from './test-helpers/mock/body-resolved-props';
-const defaultColumns = [{
+let defaultColumns = [{
dataField: 'id',
text: 'ID'
}, {
@@ -31,6 +31,19 @@ describe('Row', () => {
price: 1000
};
+ beforeEach(() => {
+ defaultColumns = [{
+ dataField: 'id',
+ text: 'ID'
+ }, {
+ dataField: 'name',
+ text: 'Name'
+ }, {
+ dataField: 'price',
+ text: 'Price'
+ }];
+ });
+
describe('simplest row', () => {
beforeEach(() => {
wrapper = shallow(
@@ -502,7 +515,7 @@ describe('Row', () => {
});
});
- describe('when cloumn.hidden is true', () => {
+ describe('when column.hidden is true', () => {
beforeEach(() => {
const newColumns = [{
dataField: 'id',
@@ -870,4 +883,445 @@ describe('Row', () => {
});
});
});
+
+ 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/sort/caret.test.js b/packages/react-bootstrap-table2/test/sort/caret.test.js
index f2f5407..50c89da 100644
--- a/packages/react-bootstrap-table2/test/sort/caret.test.js
+++ b/packages/react-bootstrap-table2/test/sort/caret.test.js
@@ -1,37 +1,67 @@
import React from 'react';
-import { shallow } from 'enzyme';
import Const from '../../src/const';
import SortCaret from '../../src/sort/caret';
+import { shallowWithContext } from '../test-helpers/new-context';
describe('SortCaret', () => {
let wrapper;
- describe(`when order prop is ${Const.SORT_ASC}`, () => {
- beforeEach(() => {
- wrapper = shallow(
- );
+ describe('when bootstrap4 context is false', () => {
+ describe(`when order prop is ${Const.SORT_ASC}`, () => {
+ beforeEach(() => {
+ wrapper = shallowWithContext(
+ ,
+ { bootstrap4: false }
+ );
+ });
+
+ it('should render caret correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('span').length).toBe(2);
+ expect(wrapper.find('.caret').length).toBe(1);
+ expect(wrapper.find('.dropup').length).toBe(1);
+ });
});
- it('should render caret correctly', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('span').length).toBe(2);
- expect(wrapper.find('.caret').length).toBe(1);
- expect(wrapper.find('.dropup').length).toBe(1);
+ describe(`when order prop is ${Const.SORT_DESC}`, () => {
+ beforeEach(() => {
+ wrapper = shallowWithContext(
+ ,
+ { bootstrap4: false }
+ );
+ });
+
+ it('should render caret correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('span').length).toBe(2);
+ expect(wrapper.find('.caret').length).toBe(1);
+ expect(wrapper.find('.dropup').length).toBe(0);
+ });
});
});
- describe(`when order prop is ${Const.SORT_DESC}`, () => {
- beforeEach(() => {
- wrapper = shallow(
- );
+ describe('when bootstrap4 context is true', () => {
+ describe(`when order prop is ${Const.SORT_ASC}`, () => {
+ beforeEach(() => {
+ wrapper = shallowWithContext( , { bootstrap4: true });
+ });
+
+ it('should render caret correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('.caret-4-asc').length).toBe(1);
+ });
});
- it('should render caret correctly', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find('span').length).toBe(2);
- expect(wrapper.find('.caret').length).toBe(1);
- expect(wrapper.find('.dropup').length).toBe(0);
+ describe(`when order prop is ${Const.SORT_DESC}`, () => {
+ beforeEach(() => {
+ wrapper = shallowWithContext( , { bootstrap4: true });
+ });
+
+ it('should render caret correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('.caret-4-desc').length).toBe(1);
+ });
});
});
});
diff --git a/packages/react-bootstrap-table2/test/sort/symbol.test.js b/packages/react-bootstrap-table2/test/sort/symbol.test.js
index 4e7cd07..6302b73 100644
--- a/packages/react-bootstrap-table2/test/sort/symbol.test.js
+++ b/packages/react-bootstrap-table2/test/sort/symbol.test.js
@@ -1,13 +1,12 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { shallowWithContext } from '../test-helpers/new-context';
import SortSymbol from '../../src/sort/symbol';
describe('SortSymbol', () => {
let wrapper;
beforeEach(() => {
- wrapper = shallow(
- );
+ wrapper = shallowWithContext( , { bootstrap4: false });
});
it('should render sort symbol correctly', () => {
expect(wrapper.length).toBe(1);
@@ -16,4 +15,14 @@ describe('SortSymbol', () => {
expect(wrapper.find('.dropdown').length).toBe(1);
expect(wrapper.find('.dropup').length).toBe(1);
});
+
+ describe('if bootstrap4 prop is true', () => {
+ beforeEach(() => {
+ wrapper = shallowWithContext( , { bootstrap4: true });
+ });
+ it('should render sort symbol correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('.order-4').length).toBe(1);
+ });
+ });
});
diff --git a/packages/react-bootstrap-table2/test/sort/wrapper.test.js b/packages/react-bootstrap-table2/test/sort/wrapper.test.js
deleted file mode 100644
index 2575e86..0000000
--- a/packages/react-bootstrap-table2/test/sort/wrapper.test.js
+++ /dev/null
@@ -1,230 +0,0 @@
-import 'jsdom-global/register';
-import React from 'react';
-import sinon from 'sinon';
-import { shallow } from 'enzyme';
-
-import Const from '../../src/const';
-import Store from '../../src/store';
-import BootstrapTable from '../../src/bootstrap-table';
-import wrapperFactory from '../../src/sort/wrapper';
-
-describe('SortWrapper', () => {
- let wrapper;
- let columns;
-
- const data = [{
- id: 1,
- name: 'A'
- }, {
- id: 2,
- name: 'B'
- }];
-
- const keyField = 'id';
-
- let store = new Store(keyField);
- store.data = data;
-
- const SortWrapper = wrapperFactory(BootstrapTable);
-
- beforeEach(() => {
- columns = [{
- dataField: 'id',
- text: 'ID',
- sort: true
- }, {
- dataField: 'name',
- text: 'Name',
- sort: true
- }];
- wrapper = shallow(
-
- );
- });
-
- it('should render SortWrapper correctly', () => {
- expect(wrapper.length).toBe(1);
- expect(wrapper.find(BootstrapTable)).toBeDefined();
- });
-
- it('should inject correct props to base component', () => {
- expect(wrapper.props().onSort).toBeDefined();
- });
-
- describe('call handleSort function', () => {
- let sortBySpy;
- let sortColumn;
-
- beforeEach(() => {
- sortColumn = columns[0];
- store = new Store(keyField);
- store.data = data;
- sortBySpy = sinon.spy(store, 'sortBy');
- });
-
- describe('when remote.sort is false', () => {
- beforeEach(() => {
- wrapper = shallow(
-
- );
-
- wrapper.instance().handleSort(sortColumn);
- });
-
- it('should operating on store correctly', () => {
- expect(store.sortOrder).toEqual(Const.SORT_DESC);
- expect(store.sortField).toEqual(sortColumn.dataField);
-
- wrapper.instance().handleSort(sortColumn); // sort same column again
- expect(store.sortOrder).toEqual(Const.SORT_ASC);
- expect(store.sortField).toEqual(sortColumn.dataField);
- });
-
- it('should calling store.sortBy correctly', () => {
- expect(sortBySpy.calledOnce).toBeTruthy();
- expect(sortBySpy.calledWith(sortColumn)).toBeTruthy();
- });
- });
-
- describe('when remote.sort is true', () => {
- let onTableChangeCB;
-
- beforeEach(() => {
- onTableChangeCB = sinon.stub();
- wrapper = shallow(
-
- );
- wrapper.instance().handleSort(sortColumn);
- });
-
- it('should operating on store correctly', () => {
- expect(store.sortOrder).toEqual(Const.SORT_DESC);
- expect(store.sortField).toEqual(sortColumn.dataField);
-
- wrapper.instance().handleSort(sortColumn); // sort same column again
- expect(store.sortOrder).toEqual(Const.SORT_ASC);
- expect(store.sortField).toEqual(sortColumn.dataField);
- });
-
- it('should not calling store.sortBy', () => {
- expect(sortBySpy.calledOnce).toBeFalsy();
- });
-
- it('should calling props.onTableChange', () => {
- expect(onTableChangeCB.calledOnce).toBeTruthy();
- });
- });
-
- describe('when column.onSort prop is defined', () => {
- const onSortCB = jest.fn();
-
- beforeEach(() => {
- columns[0].onSort = onSortCB;
- wrapper = shallow(
-
- );
- wrapper.instance().handleSort(sortColumn);
- });
-
- it('should calling column.onSort function correctly', () => {
- expect(onSortCB).toHaveBeenCalledTimes(1);
- expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_DESC);
-
- wrapper.instance().handleSort(sortColumn);
- expect(onSortCB).toHaveBeenCalledTimes(2);
- expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_ASC);
- });
- });
- });
-
- describe('when defaultSorted prop is defined', () => {
- const defaultSorted = [{
- dataField: 'name',
- order: Const.SORT_DESC
- }];
-
- beforeEach(() => {
- wrapper = shallow(
-
- );
- });
-
- it('should render table with correct default sorted', () => {
- expect(wrapper.props().data).toEqual(store.data);
- });
-
- it('should update store.sortField correctly', () => {
- expect(store.sortField).toEqual(defaultSorted[0].dataField);
- });
-
- it('should update store.sortOrder correctly', () => {
- expect(store.sortOrder).toEqual(defaultSorted[0].order);
- });
-
- describe('when column.onSort prop is defined', () => {
- const onSortCB = jest.fn();
-
- beforeEach(() => {
- columns[1].onSort = onSortCB;
- wrapper = shallow(
-
- );
- });
-
- it('should calling column.onSort function correctly', () => {
- expect(onSortCB).toHaveBeenCalledTimes(1);
- expect(onSortCB).toHaveBeenCalledWith(defaultSorted[0].dataField, defaultSorted[0].order);
- });
- });
- });
-
- describe('componentWillReceiveProps', () => {
- let nextProps;
-
- beforeEach(() => {
- nextProps = { columns, store };
- store.sortField = columns[1].dataField;
- store.sortOrder = Const.SORT_DESC;
- store.sortBy = sinon.stub();
- });
-
- it('should sorting again', () => {
- wrapper.instance().componentWillReceiveProps(nextProps);
- expect(store.sortBy.calledOnce).toBeTruthy();
- });
- });
-});
diff --git a/packages/react-bootstrap-table2/test/store/index.test.js b/packages/react-bootstrap-table2/test/store/index.test.js
deleted file mode 100644
index a257272..0000000
--- a/packages/react-bootstrap-table2/test/store/index.test.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import Store from '../../src/store';
-import Const from '../../src/const';
-
-describe('Store Base', () => {
- let store;
- let data;
-
- beforeEach(() => {
- data = [
- { id: 3, name: 'name2' },
- { id: 2, name: 'ABC' },
- { id: 4, name: '123tester' },
- { id: 1, name: '!@#' }
- ];
- store = new Store('id');
- store.data = data;
- });
-
- describe('initialize', () => {
- it('should have correct initialize data', () => {
- expect(store.sortOrder).toBeUndefined();
- expect(store.sortField).toBeUndefined();
- expect(store.data.length).toEqual(data.length);
- });
- });
-
- describe('setSort', () => {
- let dataField;
-
- beforeEach(() => {
- dataField = 'name';
- });
-
- it('should change sortField by dataField param', () => {
- store.setSort({ dataField });
- expect(store.sortField).toEqual(dataField);
- });
-
- it('should change sortOrder correctly when sortBy same dataField', () => {
- store.setSort({ dataField });
- expect(store.sortOrder).toEqual(Const.SORT_DESC);
- store.setSort({ dataField });
- expect(store.sortOrder).toEqual(Const.SORT_ASC);
- });
-
- it('should change sortOrder correctly when sortBy different dataField', () => {
- store.setSort({ dataField });
- expect(store.sortOrder).toEqual(Const.SORT_DESC);
-
- dataField = 'id';
- store.setSort({ dataField });
- expect(store.sortOrder).toEqual(Const.SORT_DESC);
-
- dataField = 'name';
- store.setSort({ dataField });
- expect(store.sortOrder).toEqual(Const.SORT_DESC);
- });
-
- it('should force assign sortOrder correctly if second argument is given', () => {
- store.setSort({ dataField }, Const.SORT_DESC);
- expect(store.sortOrder).toEqual(Const.SORT_DESC);
- });
-
- it('should force assign sortOrder correctly if third argument is given', () => {
- store.setSort({ dataField }, undefined, Const.SORT_ASC);
- expect(store.sortOrder).toEqual(Const.SORT_ASC);
- });
- });
-
- describe('sortBy', () => {
- let dataField;
-
- beforeEach(() => {
- dataField = 'name';
- });
-
- it('should have correct result after sortBy', () => {
- store.sortBy({ dataField });
- const result = store.data.map(e => e[dataField]).sort((a, b) => b - a);
- store.data.forEach((e, i) => {
- expect(e[dataField]).toEqual(result[i]);
- });
- });
- });
-
- describe('edit', () => {
- it('should update a specified field correctly', () => {
- const newValue = 'newValue';
- const dataField = 'name';
- const rowId = 2;
- store.edit(rowId, dataField, newValue);
-
- const row = store.data.find(d => d[store.keyField] === rowId);
- expect(row[dataField]).toEqual(newValue);
- });
-
- it('should not throw any error even if rowId is not existing', () => {
- expect(() => {
- store.edit('123', 'name', 'value');
- }).not.toThrow();
- });
-
- it('should throwing error if dataField is not existing', () => {
- expect(() => {
- store.edit(2, 'non_exist_field', 'value');
- }).toThrow();
- });
- });
-});
diff --git a/packages/react-bootstrap-table2/test/store/mutate.test.js b/packages/react-bootstrap-table2/test/store/mutate.test.js
new file mode 100644
index 0000000..7a6b050
--- /dev/null
+++ b/packages/react-bootstrap-table2/test/store/mutate.test.js
@@ -0,0 +1,32 @@
+import { editCell } from '../../src/store/mutate';
+
+describe('Mutate Function', () => {
+ const data = [
+ { id: 3, name: 'name2' },
+ { id: 2, name: 'ABC' },
+ { id: 4, name: '123tester' },
+ { id: 1, name: '!@#' }
+ ];
+
+ const keyField = 'id';
+
+ describe('editCell', () => {
+ let rowId;
+ const editField = 'name';
+ const newValue = 'tester';
+
+ it('should edit successfully if row is existing', () => {
+ rowId = data[0][keyField];
+
+ editCell(data, keyField, rowId, editField, newValue);
+ expect(data[0][editField]).toEqual(newValue);
+ });
+
+ it('should not mutate cell if row is not existing', () => {
+ rowId = 100;
+
+ editCell(data, keyField, rowId, editField, newValue);
+ expect(data).toEqual(data);
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2/test/store/rows.test.js b/packages/react-bootstrap-table2/test/store/rows.test.js
index e8d1731..876d085 100644
--- a/packages/react-bootstrap-table2/test/store/rows.test.js
+++ b/packages/react-bootstrap-table2/test/store/rows.test.js
@@ -1,5 +1,4 @@
-import Store from '../../src/store';
-import { getRowByRowId } from '../../src/store/rows';
+import { getRowByRowId, matchRow } from '../../src/store/rows';
describe('Rows Function', () => {
const data = [
@@ -9,22 +8,28 @@ describe('Rows Function', () => {
{ id: 1, name: '!@#' }
];
const keyField = 'id';
- let store;
- let fn;
-
- beforeEach(() => {
- store = new Store(keyField);
- store.data = data;
- fn = getRowByRowId(store);
- });
describe('getRowByRowId', () => {
it('should returning correct row', () => {
- expect(fn(2)).toEqual(data[1]);
+ expect(getRowByRowId(data, keyField, 2)).toEqual(data[1]);
});
it('should returning undefined if not existing', () => {
- expect(fn(20)).not.toBeDefined();
+ expect(getRowByRowId(data, keyField, 20)).not.toBeDefined();
+ });
+ });
+
+ describe('matchRow', () => {
+ it('should return true if keyField and id is match', () => {
+ const row = data[0];
+ const fn = matchRow(keyField, row[keyField]);
+ expect(fn(row)).toBeTruthy();
+ });
+
+ it('should return false if keyField and id is not match', () => {
+ const row = data[0];
+ const fn = matchRow(keyField, 0);
+ expect(fn(row)).toBeFalsy();
});
});
});
diff --git a/packages/react-bootstrap-table2/test/store/selection.test.js b/packages/react-bootstrap-table2/test/store/selection.test.js
index 6d453c7..bed3f30 100644
--- a/packages/react-bootstrap-table2/test/store/selection.test.js
+++ b/packages/react-bootstrap-table2/test/store/selection.test.js
@@ -1,10 +1,8 @@
-import Store from '../../src/store';
import {
- isSelectedAll,
- isAnySelectedRow,
selectableKeys,
unSelectableKeys,
- getSelectedRows
+ getSelectedRows,
+ getSelectionSummary
} from '../../src/store/selection';
describe('Selection Function', () => {
@@ -15,90 +13,73 @@ describe('Selection Function', () => {
{ id: 1, name: '!@#' }
];
const keyField = 'id';
- let store;
let skip;
- let fn;
-
- beforeEach(() => {
- store = new Store(keyField);
- store.data = data;
- });
-
- describe('isSelectedAll', () => {
- it('should returning false when store.selected is not cover all rows', () => {
- expect(isSelectedAll(store)).toBeFalsy();
- store.selected = [data[0][keyField]];
- expect(isSelectedAll(store)).toBeFalsy();
- });
-
- it('should returning true when store.selected is cover all rows', () => {
- store.selected = data.map(d => d[keyField]);
- expect(isSelectedAll(store)).toBeTruthy();
- });
- });
-
- describe('isAnySelectedRow', () => {
- it('should returning false if any store.selected is empty', () => {
- fn = isAnySelectedRow(store);
- expect(fn()).toBeFalsy();
- });
-
- it('should returning false if store.selected is have same key as skips', () => {
- fn = isAnySelectedRow(store);
- skip = [data[0][keyField]];
- store.selected = [data[0][keyField]];
- expect(fn(skip)).toBeFalsy();
- });
-
- it('should returning true if store.selected is not empty', () => {
- store.selected = [data[0][keyField]];
- fn = isAnySelectedRow(store);
- expect(fn()).toBeTruthy();
- });
-
- it('should returning true if length of store.selected is bigger than skips', () => {
- store.selected = [data[0][keyField], data[2][keyField]];
- skip = [data[0][keyField]];
- fn = isAnySelectedRow(store);
- expect(fn(skip)).toBeTruthy();
- });
- });
describe('selectableKeys', () => {
- beforeEach(() => {
- fn = selectableKeys(store);
- });
-
it('should returning all row keys if skip is empty', () => {
- expect(fn()).toEqual(data.map(d => d[keyField]));
+ expect(selectableKeys(data, keyField)).toEqual(data.map(d => d[keyField]));
});
it('should returngin row keys expect the skip', () => {
skip = [data[1][keyField]];
- expect(fn(skip)).toHaveLength(data.length - skip.length);
+ expect(selectableKeys(data, keyField, skip)).toHaveLength(data.length - skip.length);
});
});
describe('unSelectableKeys', () => {
it('should returning empty array if skip is empty', () => {
- fn = unSelectableKeys(store);
- expect(fn()).toHaveLength(0);
+ expect(unSelectableKeys()).toHaveLength(0);
});
it('should returning array which must contain skip', () => {
skip = [data[1][keyField]];
- store.selected = data.map(d => d[keyField]);
- fn = unSelectableKeys(store);
- expect(fn(skip)).toHaveLength(skip.length);
+ const selected = data.map(d => d[keyField]);
+ expect(unSelectableKeys(selected, skip)).toHaveLength(skip.length);
});
});
describe('getSelectedRows', () => {
it('should returning rows object correctly', () => {
- store.selected = data.map(d => d[keyField]);
- const result = getSelectedRows(store);
- expect(result).toHaveLength(store.selected.length);
- expect(result).toEqual(store.data);
+ const selected = data.map(d => d[keyField]);
+ const result = getSelectedRows(data, keyField, selected);
+ expect(result).toHaveLength(selected.length);
+ expect(result).toEqual(data);
+ });
+ });
+
+ describe('getSelectionSummary', () => {
+ let result;
+
+ describe('if selected argument is able to cover all the data argument', () => {
+ it('should return an obj which allRowsSelected is true and allRowsNotSelected is false', () => {
+ const selected = data.map(d => d[keyField]);
+ result = getSelectionSummary(data, keyField, selected);
+ expect(result).toEqual({
+ allRowsSelected: true,
+ allRowsNotSelected: false
+ });
+ });
+ });
+
+ describe('if selected argument empty', () => {
+ it('should return an obj which allRowsSelected is false but allRowsNotSelected is true', () => {
+ result = getSelectionSummary(data, keyField);
+ expect(result).toEqual({
+ allRowsSelected: false,
+ allRowsNotSelected: true
+ });
+ });
+ });
+
+ describe('if selected argument is only cover partial data', () => {
+ it('should return an obj which allRowsSelected and allRowsNotSelected both are false', () => {
+ const selected = [1, 2];
+ result = getSelectionSummary(data, keyField, selected);
+ expect(result).toEqual({
+ allRowsSelected: false,
+ allRowsNotSelected: false
+ });
+ });
});
});
});
diff --git a/packages/react-bootstrap-table2/test/store/sort.test.js b/packages/react-bootstrap-table2/test/store/sort.test.js
index 33422b9..5ca05c1 100644
--- a/packages/react-bootstrap-table2/test/store/sort.test.js
+++ b/packages/react-bootstrap-table2/test/store/sort.test.js
@@ -1,6 +1,5 @@
import sinon from 'sinon';
-import Store from '../../src/store';
import { sort, nextOrder } from '../../src/store/sort';
import Const from '../../src/const';
@@ -12,18 +11,17 @@ describe('Sort Function', () => {
{ id: 1, name: '!@#' }
];
- let store;
-
describe('sort', () => {
- beforeEach(() => {
- store = new Store('id');
- store.data = data;
- });
+ const sortColumn = {
+ dataField: 'id',
+ text: 'ID'
+ };
+ let sortOrder;
+ let result;
it('should sort array with ASC order correctly', () => {
- store.sortField = 'id';
- store.sortOrder = Const.SORT_ASC;
- const result = sort(store)();
+ sortOrder = Const.SORT_ASC;
+ result = sort(data, sortOrder, sortColumn);
expect(result.length).toEqual(data.length);
const sortedArray = data.map(e => e.id).sort((a, b) => a - b);
@@ -33,9 +31,8 @@ describe('Sort Function', () => {
});
it('should sort array with DESC order correctly', () => {
- store.sortField = 'id';
- store.sortOrder = Const.SORT_DESC;
- const result = sort(store)();
+ sortOrder = Const.SORT_DESC;
+ result = sort(data, sortOrder, sortColumn);
expect(result.length).toEqual(data.length);
const sortedArray = data.map(e => e.id).sort((a, b) => b - a);
@@ -46,35 +43,49 @@ describe('Sort Function', () => {
it('should call custom sort function when sortFunc given', () => {
const sortFunc = sinon.stub().returns(1);
- store.sortField = 'id';
- store.sortOrder = Const.SORT_DESC;
- sort(store)(sortFunc);
+ sortOrder = Const.SORT_DESC;
+ sort(data, sortOrder, { ...sortColumn, sortFunc });
expect(sortFunc.callCount).toBe(6);
});
});
describe('nextOrder', () => {
- beforeEach(() => {
- store = new Store('id');
- store.data = data;
+ const currentSortColumn = {
+ dataField: 'name',
+ text: 'Product Name'
+ };
+ it('should return correcly order when current sortField is not eq next sort field', () => {
+ const nextSort = {
+ sortColumn: {
+ dataField: 'id',
+ text: 'ID'
+ },
+ sortOrder: Const.SORT_DESC
+ };
+ expect(nextOrder(currentSortColumn, nextSort)).toBe(Const.SORT_DESC);
});
- it('should return correcly order when store.sortField is not eq next sort field', () => {
- expect(nextOrder(store)('name')).toBe(Const.SORT_DESC);
+ it('should return correcly order if even next sort column is undefined', () => {
+ expect(nextOrder(currentSortColumn, {})).toBe(Const.SORT_DESC);
});
- it('should return correcly order when store.sortField is not eq next sort field and default sort direction is given', () => {
- expect(nextOrder(store)('name', undefined, Const.SORT_ASC)).toBe(Const.SORT_ASC);
+ it('should return correcly order when current sortField is not eq next sort field and default sort direction is given', () => {
+ const nextSort = {
+ sortColumn: {
+ dataField: 'id',
+ text: 'ID'
+ },
+ sortOrder: Const.SORT_DESC
+ };
+ expect(nextOrder(currentSortColumn, nextSort, Const.SORT_ASC)).toBe(Const.SORT_ASC);
});
- it('should return correcly order when store.sortField is eq next sort field', () => {
- store.sortField = 'name';
- store.sortOrder = Const.SORT_DESC;
- expect(nextOrder(store)('name')).toBe(Const.SORT_ASC);
- });
-
- it('should return correcly order when order is specified', () => {
- expect(nextOrder(store)('name', Const.SORT_ASC)).toBe(Const.SORT_ASC);
+ it('should return correcly order when current sortField is eq next sort field', () => {
+ const nextSort = {
+ sortColumn: currentSortColumn,
+ sortOrder: Const.SORT_ASC
+ };
+ expect(nextOrder(currentSortColumn, nextSort)).toBe(Const.SORT_DESC);
});
});
});
diff --git a/packages/react-bootstrap-table2/test/test-helpers/mock-component.js b/packages/react-bootstrap-table2/test/test-helpers/mock-component.js
index c3282a4..cb1d5b5 100644
--- a/packages/react-bootstrap-table2/test/test-helpers/mock-component.js
+++ b/packages/react-bootstrap-table2/test/test-helpers/mock-component.js
@@ -1,16 +1,5 @@
-import Store from '../../src/store';
export const extendTo = Base =>
class MockComponent extends Base {
- constructor(props) {
- super(props);
-
- const { data } = props;
-
- this.store = new Store(props.keyField);
- this.store.data = data;
- this.state = { data };
- }
-
render() { return null; }
};
diff --git a/packages/react-bootstrap-table2/test/test-helpers/new-context.js b/packages/react-bootstrap-table2/test/test-helpers/new-context.js
new file mode 100644
index 0000000..cfbcc58
--- /dev/null
+++ b/packages/react-bootstrap-table2/test/test-helpers/new-context.js
@@ -0,0 +1,7 @@
+import { shallow } from 'enzyme';
+
+export const shallowWithContext = (elem, context = {}) => {
+ const wrapper = shallow(elem);
+ const Children = wrapper.props().children(context);
+ return shallow(Children);
+};
diff --git a/packages/react-bootstrap-table2/test/utils.test.js b/packages/react-bootstrap-table2/test/utils.test.js
index 2aa43b1..89d9ff1 100644
--- a/packages/react-bootstrap-table2/test/utils.test.js
+++ b/packages/react-bootstrap-table2/test/utils.test.js
@@ -58,32 +58,6 @@ describe('Utils', () => {
});
});
- describe('isObject', () => {
- describe('when given Object', () => {
- it('should return true', () => {
- expect(_.isObject({})).toBe(true);
- });
- });
-
- describe('when given Function', () => {
- it('should return false', () => {
- expect(_.isObject(() => 'test')).toBe(false);
- });
- });
-
- describe('when given Array', () => {
- it('should return false', () => {
- expect(_.isObject([])).toBe(false);
- });
- });
-
- describe('when given null', () => {
- it('should return false', () => {
- expect(_.isObject(null)).toBe(false);
- });
- });
- });
-
describe('isEmptyObject', () => {
describe('when given empty Object', () => {
it('should return true', () => {
@@ -98,14 +72,14 @@ describe('Utils', () => {
});
describe('when given Function', () => {
- it('should return false', () => {
- expect(_.isEmptyObject(() => 'test')).toBe(false);
+ it('should return true', () => {
+ expect(_.isEmptyObject(() => 'test')).toBe(true);
});
});
describe('when given Array', () => {
- it('should return false', () => {
- expect(_.isEmptyObject([])).toBe(false);
+ it('should return true', () => {
+ expect(_.isEmptyObject([])).toBe(true);
});
});
diff --git a/webpack/toolkit.umd.babel.js b/webpack/toolkit.umd.babel.js
new file mode 100644
index 0000000..677eacc
--- /dev/null
+++ b/webpack/toolkit.umd.babel.js
@@ -0,0 +1,16 @@
+import * as path from 'path';
+import umdConfig from './webpack.umd.babel';
+
+module.exports = {
+ ...umdConfig,
+ entry: {
+ 'react-bootstrap-table2-toolkit/dist/react-bootstrap-table2-toolkit': './packages/react-bootstrap-table2-toolkit/index.js',
+ 'react-bootstrap-table2-toolkit/dist/react-bootstrap-table2-toolkit.min': './packages/react-bootstrap-table2-toolkit/index.js'
+ },
+ output: {
+ path: path.join(__dirname, '../packages'),
+ filename: '[name].js',
+ library: 'ReactBootstrapTable2Toolkit',
+ libraryTarget: 'umd'
+ }
+};
diff --git a/yarn.lock b/yarn.lock
index 8c31ec3..4329d62 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2692,38 +2692,44 @@ 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.0.4:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.4.tgz#67f898cc053452f5c786424e395fe0c63a0607fe"
+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.1.0"
+ enzyme-adapter-utils "^1.3.0"
lodash "^4.17.4"
object.assign "^4.0.4"
object.values "^1.0.4"
- prop-types "^15.5.10"
+ prop-types "^15.6.0"
+ react-reconciler "^0.7.0"
react-test-renderer "^16.0.0-0"
-enzyme-adapter-utils@^1.1.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz#d6c85756826c257a8544d362cc7a67e97ea698c7"
+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"
dependencies:
- lodash "^4.17.4"
- object.assign "^4.0.4"
+ object.assign "^4.1.0"
prop-types "^15.6.0"
-enzyme@3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.1.1.tgz#c6948dfccd055d75fbd8627ad1c96a024d0e247b"
+enzyme@3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.3.0.tgz#0971abd167f2d4bf3f5bd508229e1c4b6dc50479"
dependencies:
cheerio "^1.0.0-rc.2"
function.prototype.name "^1.0.3"
+ has "^1.0.1"
+ is-boolean-object "^1.0.0"
+ is-callable "^1.1.3"
+ 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-is "^1.0.1"
- object.assign "^4.0.4"
+ object.assign "^4.1.0"
object.entries "^1.0.4"
object.values "^1.0.4"
- raf "^3.3.2"
+ raf "^3.4.0"
rst-selector-parser "^2.2.3"
errno@^0.1.3, errno@^0.1.4:
@@ -4293,6 +4299,10 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
+is-boolean-object@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
+
is-buffer@^1.0.2, is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@@ -4420,6 +4430,10 @@ is-negated-glob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
+is-number-object@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799"
+
is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@@ -4516,6 +4530,10 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+is-string@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64"
+
is-subset@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6"
@@ -5978,6 +5996,10 @@ 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:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b"
+
object-is@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6"
@@ -5992,7 +6014,7 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
-object.assign@^4.0.4:
+object.assign@^4.0.4, object.assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
dependencies:
@@ -6725,7 +6747,7 @@ prop-types@15.5.10:
fbjs "^0.8.9"
loose-envify "^1.3.1"
-prop-types@^15.5.10, prop-types@^15.6.0:
+prop-types@^15.6.0:
version "15.6.0"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
dependencies:
@@ -6824,7 +6846,7 @@ querystringify@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb"
-raf@^3.3.2:
+raf@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
dependencies:
@@ -6892,9 +6914,18 @@ rc@^1.1.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-dom@16.0.0:
- version "16.0.0"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.0.0.tgz#9cc3079c3dcd70d4c6e01b84aab2a7e34c303f58"
+react-dom@16.3.2:
+ version "16.3.2"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df"
+ dependencies:
+ fbjs "^0.8.16"
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+ prop-types "^15.6.0"
+
+react-reconciler@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
@@ -6916,9 +6947,9 @@ react-test-renderer@^16.0.0-0:
object-assign "^4.1.1"
prop-types "^15.6.0"
-react@16.0.0:
- version "16.0.0"
- resolved "https://registry.yarnpkg.com/react/-/react-16.0.0.tgz#ce7df8f1941b036f02b2cca9dbd0cb1f0e855e2d"
+react@16.3.2:
+ version "16.3.2"
+ resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
@@ -8255,6 +8286,10 @@ unc-path-regex@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
+underscore@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
+
underscore@~1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604"