diff --git a/enzyme-setup.js b/enzyme-setup.js new file mode 100644 index 000000000..e952b30e8 --- /dev/null +++ b/enzyme-setup.js @@ -0,0 +1,8 @@ +import Adapter from 'enzyme-adapter-react-16'; +import { configure } from 'enzyme'; + +const configureEnzyme = () => { + configure({ adapter: new Adapter() }); +}; + +configureEnzyme(); diff --git a/package.json b/package.json index 4e1840d6b..c4eb89916 100644 --- a/package.json +++ b/package.json @@ -24,45 +24,46 @@ }, "homepage": "https://github.com/react-bootstrap-table/react-bootstrap-table2#readme", "devDependencies": { - "babel-core": "^6.25.0", - "babel-eslint": "^7.2.3", - "babel-jest": "^20.0.3", - "babel-loader": "^7.1.1", - "babel-preset-es2015": "^6.24.1", - "babel-preset-react": "^6.24.1", - "babel-preset-stage-0": "^6.24.1", - "babel-register": "^6.24.1", - "css-loader": "^0.28.1", - "enzyme": "^2.9.1", - "eslint": "^4.5.0", + "babel-core": "6.25.0", + "babel-eslint": "7.2.3", + "babel-jest": "20.0.3", + "babel-loader": "7.1.1", + "babel-preset-es2015": "6.24.1", + "babel-preset-react": "6.24.1", + "babel-preset-stage-0": "6.24.1", + "babel-register": "6.24.1", + "css-loader": "0.28.1", + "enzyme": "3.1.1", + "enzyme-adapter-react-16": "1.0.4", + "eslint": "4.5.0", "eslint-config-airbnb": "15.1.0", - "eslint-loader": "^1.9.0", - "eslint-plugin-import": "^2.7.0", + "eslint-loader": "1.9.0", + "eslint-plugin-import": "2.7.0", "eslint-plugin-jsx-a11y": "5.1.1", - "eslint-plugin-react": "^7.2.1", - "html-webpack-plugin": "^2.30.1", - "jest": "^20.0.4", - "jsdom": "^11.2.0", - "jsdom-global": "^3.0.2", - "lerna": "^2.0.0", - "node-sass": "^4.5.3", - "react-test-renderer": "^15.6.1", - "sass-loader": "^6.0.6", - "sinon": "^3.2.1", - "style-loader": "^0.17.0", - "webpack": "^3.5.4", - "webpack-dev-server": "^2.7.1" + "eslint-plugin-react": "7.2.1", + "html-webpack-plugin": "2.30.1", + "jest": "20.0.4", + "jsdom": "11.2.0", + "jsdom-global": "3.0.2", + "lerna": "2.0.0", + "node-sass": "4.5.3", + "react-test-renderer": "16.0.0", + "sass-loader": "6.0.6", + "sinon": "3.2.1", + "style-loader": "0.17.0", + "webpack": "3.5.4", + "webpack-dev-server": "2.7.1" }, "dependencies": { - "classnames": "^2.2.5", - "prop-types": "^15.5.10", - "react": "^15.6.1", - "react-dom": "^15.6.1" + "classnames": "2.2.5", + "prop-types": "15.5.10", + "react": "16.0.0", + "react-dom": "16.0.0" }, "peerDependencies": { "prop-types": "^15.0.0", - "react": "^15.0.0", - "react-dom": "^15.0.0" + "react": "^16.0.0", + "react-dom": "^16.0.0" }, "jest": { "collectCoverageFrom": [ @@ -71,6 +72,12 @@ "roots": [ "/packages" ], + "setupFiles": [ + "/enzyme-setup.js" + ], + "modulePaths": [ + "/packages/react-bootstrap-table2" + ], "testEnvironment": "node", "testMatch": [ "**/test/**/*.test.js" diff --git a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js index cf7afd1e9..3e55ec85a 100644 --- a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js +++ b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js @@ -1,7 +1,9 @@ const path = require('path'); const sourcePath = path.join(__dirname, '../../react-bootstrap-table2/src'); +const paginationSourcePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/src'); const sourceStylePath = path.join(__dirname, '../../react-bootstrap-table2/style'); +const paginationStylePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/style'); const storyPath = path.join(__dirname, '../stories'); const examplesPath = path.join(__dirname, '../examples'); const srcPath = path.join(__dirname, '../src'); @@ -23,14 +25,14 @@ const loaders = [{ test: /\.js?$/, use: ['babel-loader'], exclude: /node_modules/, - include: [sourcePath, storyPath], + include: [sourcePath, paginationSourcePath, storyPath], }, { test: /\.css$/, use: ['style-loader', 'css-loader'], }, { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'], - include: [storyPath, sourceStylePath], + include: [storyPath, sourceStylePath, paginationStylePath], }, { test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000', diff --git a/packages/react-bootstrap-table2-example/examples/pagination/custom-pagination.js b/packages/react-bootstrap-table2-example/examples/pagination/custom-pagination.js new file mode 100644 index 000000000..b9b2fdb6c --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/pagination/custom-pagination.js @@ -0,0 +1,82 @@ +/* eslint react/prefer-stateless-function: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table2'; +import paginator from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table2'; +import paginator from 'react-bootstrap-table2-paginator'; +// ... + +const options = { + paginationSize: 4, + pageStartIndex: 0, + // alwaysShowAllBtns: true, // Always show next and previous button + // withFirstAndLast: false, // Hide the going to First and Last page button + // hideSizePerPage: true, // Hide the sizePerPage dropdown always + // hidePageListOnlyOnePage: true, // Hide the pagination list when only one page + firstPageText: 'First', + prePageText: 'Back', + nextPageText: 'Next', + lastPageText: 'Last', + nextPageTitle: 'First page', + prePageTitle: 'Pre page', + firstPageTitle: 'Next page', + lastPageTitle: 'Last page', + sizePerPageList: [{ + text: '5', value: 5 + }, { + text: '10', value: 10 + }, { + text: 'All', value: products.length + }] // A numeric array is also available. the purpose of above example is custom the text +}; + + +`; +const options = { + paginationSize: 4, + pageStartIndex: 0, + // alwaysShowAllBtns: true // Always show next and previous button + // withFirstAndLast: false // Hide the going to First and Last page button + // hideSizePerPage: true, // Hide the sizePerPage dropdown always + // hidePageListOnlyOnePage: true, // Hide the pagination list when only one page + firstPageText: 'First', + prePageText: 'Back', + nextPageText: 'Next', + lastPageText: 'Last', + nextPageTitle: 'First page', + prePageTitle: 'Pre page', + firstPageTitle: 'Next page', + lastPageTitle: 'Last page', + sizePerPageList: [{ + text: '5', value: 5 + }, { + text: '10', value: 10 + }, { + text: 'All', value: products.length + }] // A numeric array is also available. the purpose of above example is custom the text +}; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/pagination/index.js b/packages/react-bootstrap-table2-example/examples/pagination/index.js new file mode 100644 index 000000000..979d59baa --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/pagination/index.js @@ -0,0 +1,45 @@ +/* eslint react/prefer-stateless-function: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table2'; +import paginator from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table2'; +import paginator from 'react-bootstrap-table2-paginator'; +// ... +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/pagination/pagination-hooks.js b/packages/react-bootstrap-table2-example/examples/pagination/pagination-hooks.js new file mode 100644 index 000000000..3ff1b15f3 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/pagination/pagination-hooks.js @@ -0,0 +1,82 @@ +/* eslint react/prefer-stateless-function: 0 */ +/* eslint no-console: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table2'; +import paginator from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table2'; +import paginator from 'react-bootstrap-table2-paginator'; +// ... +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const options = { + onSizePerPageChange: (sizePerPage, page) => { + console.log('Size per page change!!!'); + console.log('Newest size per page:' + sizePerPage); + console.log('Newest page:' + page); + }, + onPageChange: (page, sizePerPage) => { + console.log('Page change!!!'); + console.log('Newest size per page:' + sizePerPage); + console.log('Newest page:' + page); + } +}; + + +`; + +const options = { + onSizePerPageChange: (sizePerPage, page) => { + console.log('Size per page change!!!'); + console.log(`Newest size per page: ${sizePerPage}`); + console.log(`Newest page: ${page}`); + }, + onPageChange: (page, sizePerPage) => { + console.log('Page change!!!'); + console.log(`Newest size per page: ${sizePerPage}`); + console.log(`Newest page: ${page}`); + } +}; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/package.json b/packages/react-bootstrap-table2-example/package.json index 587231155..563432b05 100644 --- a/packages/react-bootstrap-table2-example/package.json +++ b/packages/react-bootstrap-table2-example/package.json @@ -17,7 +17,8 @@ }, "dependencies": { "bootstrap": "^3.3.7", - "react-bootstrap-table2": "0.0.1" + "react-bootstrap-table2": "0.0.1", + "react-bootstrap-table2-paginator": "0.0.1" }, "devDependencies": { "@storybook/addon-console": "^1.0.0", diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index c97b373a4..78a1c0e8b 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -68,11 +68,17 @@ 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'; +// pagination +import PaginationTable from 'examples/pagination'; +import PaginationHooksTable from 'examples/pagination/pagination-hooks'; +import CustomPaginationTable from 'examples/pagination/custom-pagination'; + // css style import 'bootstrap/dist/css/bootstrap.min.css'; import 'stories/stylesheet/tomorrow.min.css'; import 'stories/stylesheet/storybook.scss'; import 'react-bootstrap-table2/style/react-bootstrap-table.scss'; +import 'react-bootstrap-table2-paginator/style/react-bootstrap-table-paginator.scss'; // import { action } from '@storybook/addon-actions'; @@ -143,3 +149,8 @@ storiesOf('Row Selection', module) .add('Not Selectabled Rows', () => ) .add('Selection Hooks', () => ) .add('Hide Selection Column', () => ); + +storiesOf('Pagination', module) + .add('Basic Pagination Table', () => ) + .add('Pagination Hooks', () => ) + .add('Custom Pagination', () => ); diff --git a/packages/react-bootstrap-table2-paginator/package.json b/packages/react-bootstrap-table2-paginator/package.json new file mode 100644 index 000000000..bb30b4bb8 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/package.json @@ -0,0 +1,11 @@ +{ + "name": "react-bootstrap-table2-paginator", + "version": "0.0.1", + "description": "it's the pagination addon for react-bootstrap-table2", + "main": "src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/packages/react-bootstrap-table2-paginator/src/const.js b/packages/react-bootstrap-table2-paginator/src/const.js new file mode 100644 index 000000000..eaa1bac43 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/const.js @@ -0,0 +1,17 @@ +export default { + PAGINATION_SIZE: 5, + PAGE_START_INDEX: 1, + With_FIRST_AND_LAST: true, + SHOW_ALL_PAGE_BTNS: false, + FIRST_PAGE_TEXT: '<<', + PRE_PAGE_TEXT: '<', + NEXT_PAGE_TEXT: '>', + LAST_PAGE_TEXT: '>>', + NEXT_PAGE_TITLE: 'next page', + LAST_PAGE_TITLE: 'last page', + PRE_PAGE_TITLE: 'previous page', + FIRST_PAGE_TITLE: 'first page', + SIZE_PER_PAGE_LIST: [10, 25, 30, 50], + HIDE_SIZE_PER_PAGE: false, + HIDE_PAGE_LIST_ONLY_ONE_PAGE: false +}; diff --git a/packages/react-bootstrap-table2-paginator/src/index.js b/packages/react-bootstrap-table2-paginator/src/index.js new file mode 100644 index 000000000..c74c016b4 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/index.js @@ -0,0 +1,8 @@ +import Pagination from './pagination'; +import wrapper from './wrapper'; + +export default (options = {}) => ({ + Pagination, + wrapper, + options +}); diff --git a/packages/react-bootstrap-table2-paginator/src/page-button.js b/packages/react-bootstrap-table2-paginator/src/page-button.js new file mode 100644 index 000000000..272fa19b4 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/page-button.js @@ -0,0 +1,47 @@ +/* eslint react/require-default-props: 0 */ +/* eslint jsx-a11y/href-no-hash: 0 */ +import cs from 'classnames'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +class PageButton extends Component { + constructor(props) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + + handleClick(e) { + e.preventDefault(); + this.props.onPageChange(this.props.page); + } + + render() { + const { + page, + title, + active, + disabled + } = this.props; + const classes = cs({ + active, + disabled, + 'page-item': true + }); + + return ( +
  • + { page } +
  • + ); + } +} + +PageButton.propTypes = { + onPageChange: PropTypes.func.isRequired, + page: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + active: PropTypes.bool.isRequired, + disabled: PropTypes.bool.isRequired, + title: PropTypes.string +}; + +export default PageButton; diff --git a/packages/react-bootstrap-table2-paginator/src/page-resolver.js b/packages/react-bootstrap-table2-paginator/src/page-resolver.js new file mode 100644 index 000000000..ef2e6d2e9 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/page-resolver.js @@ -0,0 +1,134 @@ +/* eslint no-mixed-operators: 0 */ +export default ExtendBase => + class PageResolver extends ExtendBase { + backToPrevPage() { + const { currPage, pageStartIndex } = this.props; + return (currPage - 1) < pageStartIndex ? pageStartIndex : currPage - 1; + } + + goToNextPage() { + const { currPage } = this.props; + const { lastPage } = this.state; + return (currPage + 1) > lastPage ? lastPage : currPage + 1; + } + + initialState() { + const totalPages = this.calculateTotalPage(); + const lastPage = this.calculateLastPage(totalPages); + return { totalPages, lastPage, dropdownOpen: false }; + } + + calculateTotalPage(sizePerPage = this.props.currSizePerPage) { + const { dataSize } = this.props; + return Math.ceil(dataSize / sizePerPage); + } + + calculateLastPage(totalPages) { + const { pageStartIndex } = this.props; + return pageStartIndex + totalPages - 1; + } + + calculatePages( + totalPages = this.state.totalPages, + lastPage = this.state.lastPage) { + const { + currPage, + paginationSize, + pageStartIndex, + withFirstAndLast, + firstPageText, + prePageText, + nextPageText, + lastPageText, + alwaysShowAllBtns + } = this.props; + + let pages; + let endPage = totalPages; + if (endPage <= 0) return []; + + let startPage = Math.max(currPage - Math.floor(paginationSize / 2), pageStartIndex); + endPage = startPage + paginationSize - 1; + + if (endPage > lastPage) { + endPage = lastPage; + startPage = endPage - paginationSize + 1; + } + + if (startPage !== pageStartIndex && totalPages > paginationSize && withFirstAndLast) { + pages = [firstPageText, prePageText]; + } else if (totalPages > 1 || alwaysShowAllBtns) { + pages = [prePageText]; + } else { + pages = []; + } + + for (let i = startPage; i <= endPage; i += 1) { + if (i >= pageStartIndex) pages.push(i); + } + + if (endPage <= lastPage && pages.length > 1) { + pages.push(nextPageText); + } + if (endPage !== lastPage && withFirstAndLast) { + pages.push(lastPageText); + } + return pages; + } + + calculatePageStatus(pages = [], lastPage = this.state.lastPage) { + const { + currPage, + pageStartIndex, + firstPageText, + prePageText, + nextPageText, + lastPageText, + alwaysShowAllBtns + } = this.props; + const isStart = page => + (currPage === pageStartIndex && (page === firstPageText || page === prePageText)); + const isEnd = page => + (currPage === lastPage && (page === nextPageText || page === lastPageText)); + + return pages + .filter((page) => { + if (alwaysShowAllBtns) { + return true; + } + return !(isStart(page) || isEnd(page)); + }) + .map((page) => { + let title; + const active = page === currPage; + const disabled = (isStart(page) || isEnd(page)); + + if (page === nextPageText) { + title = this.props.nextPageTitle; + } else if (page === prePageText) { + title = this.props.prePageTitle; + } else if (page === firstPageText) { + title = this.props.firstPageTitle; + } else if (page === lastPageText) { + title = this.props.lastPageTitle; + } else { + title = `${page}`; + } + + return { page, active, disabled, title }; + }); + } + + calculateSizePerPageStatus() { + const { sizePerPageList } = this.props; + return sizePerPageList.map((_sizePerPage) => { + const pageText = _sizePerPage.text || _sizePerPage; + const pageNumber = _sizePerPage.value || _sizePerPage; + return { + text: `${pageText}`, + page: pageNumber + }; + }); + } + }; + diff --git a/packages/react-bootstrap-table2-paginator/src/pagination-list.js b/packages/react-bootstrap-table2-paginator/src/pagination-list.js new file mode 100644 index 000000000..805af5323 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/pagination-list.js @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import PageButton from './page-button'; + +const PaginatonList = props => ( +
      + { + props.pages.map(pageProps => ( + + )) + } +
    +); + +PaginatonList.propTypes = { + pages: PropTypes.arrayOf(PropTypes.shape({ + page: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + active: PropTypes.bool, + disable: PropTypes.bool, + title: PropTypes.string + })).isRequired, + onPageChange: PropTypes.func.isRequired +}; + +export default PaginatonList; diff --git a/packages/react-bootstrap-table2-paginator/src/pagination.js b/packages/react-bootstrap-table2-paginator/src/pagination.js new file mode 100644 index 000000000..ee685c735 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/pagination.js @@ -0,0 +1,171 @@ +/* eslint react/require-default-props: 0 */ +/* eslint arrow-body-style: 0 */ +import cs from 'classnames'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import pageResolver from './page-resolver'; +import SizePerPageDropDown from './size-per-page-dropdown'; +import PaginationList from './pagination-list'; +import Const from './const'; + +class Pagination extends pageResolver(Component) { + constructor(props) { + super(props); + this.closeDropDown = this.closeDropDown.bind(this); + this.toggleDropDown = this.toggleDropDown.bind(this); + this.handleChangePage = this.handleChangePage.bind(this); + this.handleChangeSizePerPage = this.handleChangeSizePerPage.bind(this); + this.state = this.initialState(); + } + + componentWillReceiveProps(nextProps) { + const { dataSize, currSizePerPage } = nextProps; + + if (currSizePerPage !== this.props.currSizePerPage || dataSize !== this.props.dataSize) { + const totalPages = this.calculateTotalPage(currSizePerPage); + const lastPage = this.calculateLastPage(totalPages); + this.setState({ totalPages, lastPage }); + } + } + + toggleDropDown() { + const dropdownOpen = !this.state.dropdownOpen; + this.setState(() => { + return { dropdownOpen }; + }); + } + + closeDropDown() { + this.setState(() => { + return { dropdownOpen: false }; + }); + } + + handleChangeSizePerPage(sizePerPage) { + const { currSizePerPage, onSizePerPageChange } = this.props; + const selectedSize = typeof sizePerPage === 'string' ? parseInt(sizePerPage, 10) : sizePerPage; + let { currPage } = this.props; + if (selectedSize !== currSizePerPage) { + const newTotalPages = this.calculateTotalPage(selectedSize); + const newLastPage = this.calculateLastPage(newTotalPages); + if (currPage > newLastPage) currPage = newLastPage; + onSizePerPageChange(selectedSize, currPage); + } + this.closeDropDown(); + } + + handleChangePage(newPage) { + let page; + const { + currPage, + pageStartIndex, + prePageText, + nextPageText, + lastPageText, + firstPageText, + onPageChange + // keepSizePerPageState + } = this.props; + const { lastPage } = this.state; + + if (newPage === prePageText) { + page = this.backToPrevPage(); + } else if (newPage === nextPageText) { + page = (currPage + 1) > lastPage ? lastPage : currPage + 1; + } else if (newPage === lastPageText) { + page = lastPage; + } else if (newPage === firstPageText) { + page = pageStartIndex; + } else { + page = parseInt(newPage, 10); + } + + // if (keepSizePerPageState) { this.closeDropDown(); } + + if (page !== currPage) { + onPageChange(page); + } + } + + render() { + const { totalPages, lastPage, dropdownOpen: open } = this.state; + const { + sizePerPageList, + currSizePerPage, + hideSizePerPage, + hidePageListOnlyOnePage + } = this.props; + const pages = this.calculatePageStatus(this.calculatePages(totalPages), lastPage); + + const pageListClass = cs( + 'react-bootstrap-table-pagination-list', + 'col-md-6 col-xs-6 col-sm-6 col-lg-6', { + 'react-bootstrap-table-pagination-list-hidden': (hidePageListOnlyOnePage && totalPages === 1) + }); + return ( +
    +
    + { + sizePerPageList.length > 1 && !hideSizePerPage ? + ( + + ) : null + } +
    +
    + +
    +
    + ); + } +} + +Pagination.propTypes = { + dataSize: PropTypes.number.isRequired, + sizePerPageList: PropTypes.array.isRequired, + currPage: PropTypes.number.isRequired, + currSizePerPage: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, + onSizePerPageChange: PropTypes.func.isRequired, + pageStartIndex: PropTypes.number, + paginationSize: PropTypes.number, + firstPageText: PropTypes.string, + prePageText: PropTypes.string, + nextPageText: PropTypes.string, + lastPageText: PropTypes.string, + nextPageTitle: PropTypes.string, + prePageTitle: PropTypes.string, + firstPageTitle: PropTypes.string, + lastPageTitle: PropTypes.string, + withFirstAndLast: PropTypes.bool, + alwaysShowAllBtns: PropTypes.bool, + hideSizePerPage: PropTypes.bool, + hidePageListOnlyOnePage: PropTypes.bool +}; + +Pagination.defaultProps = { + pageStartIndex: Const.PAGE_START_INDEX, + paginationSize: Const.PAGINATION_SIZE, + withFirstAndLast: Const.With_FIRST_AND_LAST, + alwaysShowAllBtns: Const.SHOW_ALL_PAGE_BTNS, + firstPageText: Const.FIRST_PAGE_TEXT, + prePageText: Const.PRE_PAGE_TEXT, + nextPageText: Const.NEXT_PAGE_TEXT, + lastPageText: Const.LAST_PAGE_TEXT, + sizePerPageList: Const.SIZE_PER_PAGE_LIST, + nextPageTitle: Const.NEXT_PAGE_TITLE, + prePageTitle: Const.PRE_PAGE_TITLE, + firstPageTitle: Const.FIRST_PAGE_TITLE, + lastPageTitle: Const.LAST_PAGE_TITLE, + hideSizePerPage: Const.HIDE_SIZE_PER_PAGE, + hidePageListOnlyOnePage: Const.HIDE_PAGE_LIST_ONLY_ONE_PAGE +}; + +export default Pagination; 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 new file mode 100644 index 000000000..5ae42980e --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js @@ -0,0 +1,85 @@ +import React from 'react'; +import cs from 'classnames'; +import PropTypes from 'prop-types'; +import SizePerPageOption from './size-per-page-option'; + +const sizePerPageDefaultClass = 'react-bs-table-sizePerPage-dropdown'; + +const SizePerPageDropDown = (props) => { + const { + open, + hidden, + onClick, + onBlur, + options, + className, + variation, + btnContextual, + currSizePerPage, + onSizePerPageChange + } = props; + + const dropDownStyle = { visibility: hidden ? 'hidden' : 'visible' }; + const dropdownClasses = cs( + open ? 'open show' : '', + sizePerPageDefaultClass, + variation, + className, + ); + + return ( + + +
      + { + options.map(option => ( + + )) + } +
    +
    + ); +}; + +SizePerPageDropDown.propTypes = { + currSizePerPage: PropTypes.string.isRequired, + options: PropTypes.array.isRequired, + onClick: PropTypes.func.isRequired, + onBlur: PropTypes.func.isRequired, + onSizePerPageChange: PropTypes.func.isRequired, + open: PropTypes.bool, + hidden: PropTypes.bool, + btnContextual: PropTypes.string, + variation: PropTypes.oneOf(['dropdown', 'dropup']), + className: PropTypes.string +}; +SizePerPageDropDown.defaultProps = { + open: false, + hidden: false, + btnContextual: 'btn-default btn-secondary', + variation: 'dropdown', + className: '' +}; + + +export default SizePerPageDropDown; 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 new file mode 100644 index 000000000..eba6ca629 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js @@ -0,0 +1,32 @@ +/* eslint jsx-a11y/href-no-hash: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; + +const SizePerPageOption = ({ + text, + page, + onSizePerPageChange +}) => ( +
  • + { + e.preventDefault(); + onSizePerPageChange(page); + } } + > + { text } + +
  • +); + +SizePerPageOption.propTypes = { + text: PropTypes.string.isRequired, + page: PropTypes.number.isRequired, + onSizePerPageChange: PropTypes.func.isRequired +}; + +export default SizePerPageOption; diff --git a/packages/react-bootstrap-table2-paginator/src/wrapper.js b/packages/react-bootstrap-table2-paginator/src/wrapper.js new file mode 100644 index 000000000..fcc6cb737 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/wrapper.js @@ -0,0 +1,99 @@ +/* eslint react/prop-types: 0 */ +/* eslint arrow-body-style: 0 */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +import Const from './const'; + +const wrapperFactory = baseElement => + class PaginationWrapper extends Component { + static propTypes = { + store: PropTypes.object.isRequired + } + + constructor(props) { + super(props); + this.handleChangePage = this.handleChangePage.bind(this); + this.handleChangeSizePerPage = this.handleChangeSizePerPage.bind(this); + + const options = props.pagination.options || {}; + const currPage = options.pageStartIndex || Const.PAGE_START_INDEX; + const sizePerPageList = options.sizePerPageList || Const.SIZE_PER_PAGE_LIST; + const currSizePerPage = typeof sizePerPageList[0] === 'object' ? sizePerPageList[0].value : sizePerPageList[0]; + this.state = { currPage, currSizePerPage }; + } + + handleChangePage(currPage) { + const { pagination: { options } } = this.props; + if (options.onPageChange) { + options.onPageChange(currPage, this.state.currSizePerPage); + } + this.setState(() => { + return { + currPage + }; + }); + } + + handleChangeSizePerPage(currSizePerPage, currPage) { + const { pagination: { options } } = this.props; + if (options.onSizePerPageChange) { + options.onSizePerPageChange(currSizePerPage, currPage); + } + this.setState(() => { + return { + currPage, + currSizePerPage + }; + }); + } + + render() { + const { pagination: { 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 base = baseElement({ + ...this.props, + key: 'table', + data: store.getByCurrPage(currPage, currSizePerPage) + }); + + return [ + base, + + ]; + } + }; + +export default wrapperFactory; diff --git a/packages/react-bootstrap-table2-paginator/style/react-bootstrap-table-paginator.scss b/packages/react-bootstrap-table2-paginator/style/react-bootstrap-table-paginator.scss new file mode 100644 index 000000000..a99007e6b --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/style/react-bootstrap-table-paginator.scss @@ -0,0 +1,8 @@ +.react-bootstrap-table-page-btns-ul { + float: right; + margin-top: 0px; +} + +.react-bootstrap-table-pagination-list-hidden { + display: none; +} diff --git a/packages/react-bootstrap-table2-paginator/test/page-button.test.js b/packages/react-bootstrap-table2-paginator/test/page-button.test.js new file mode 100644 index 000000000..4e3d800ba --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/page-button.test.js @@ -0,0 +1,117 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import PageButton from '../src/page-button'; + +describe('PageButton', () => { + let wrapper; + const onPageChangeCallback = sinon.stub(); + const props = { + onPageChange: onPageChangeCallback, + page: 2 + }; + + describe('default PageButton', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should rendering PageButton correctly', () => { + expect(wrapper.find('a.page-link').length).toBe(1); + expect(wrapper.text()).toEqual(`${props.page}`); + }); + + describe('when clicking', () => { + let preventDefault; + beforeEach(() => { + preventDefault = sinon.stub(); + wrapper.find('a.page-link').simulate('click', { preventDefault }); + }); + + afterEach(() => { + onPageChangeCallback.reset(); + }); + + it('should calling e.preventDefault', () => { + expect(preventDefault.calledOnce).toBeTruthy(); + }); + + it('should calling onPageChange prop', () => { + expect(onPageChangeCallback.calledOnce).toBeTruthy(); + }); + + it('should calling onPageChange prop with correct argument', () => { + expect(onPageChangeCallback.calledWith(props.page)).toBeTruthy(); + }); + }); + }); + + describe('when active prop is true', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('active')).toBeTruthy(); + }); + }); + + describe('when active prop is false', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('active')).toBeFalsy(); + }); + }); + + describe('when disabled prop is true', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('disabled')).toBeTruthy(); + }); + }); + + describe('when disabled prop is false', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('disabled')).toBeFalsy(); + }); + }); + + describe('when title prop is defined', () => { + const title = 'aTitle'; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render PageButton correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.prop('title')).toEqual(title); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js b/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js new file mode 100644 index 000000000..cb5d5cf6f --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/page-resolver.test.js @@ -0,0 +1,416 @@ +import React, { Component } from 'react'; +import { shallow } from 'enzyme'; + +import pageResolver from '../src/page-resolver'; + +const extendTo = Base => + class MockComponent extends Base { + constructor(props) { + super(props); + this.state = this.initialState(); + } + render() { return null; } + }; + +describe('PageResolver', () => { + const ExtendBase = pageResolver(Component); + const MockComponent = extendTo(ExtendBase); + + const createMockProps = () => ({ + dataSize: 100, + sizePerPageList: [10, 20, 30, 50], + currPage: 1, + currSizePerPage: 10, + pageStartIndex: 1, + paginationSize: 5, + withFirstAndLast: true, + firstPageText: '<<', + prePageText: '<', + nextPageText: '>', + lastPageText: '>>', + alwaysShowAllBtns: false + }); + + let wrapper; + + describe('initialize', () => { + beforeEach(() => { + const mockElement = React.createElement(MockComponent, createMockProps(), null); + wrapper = shallow(mockElement); + }); + + it('should creating initial state correctly', () => { + const instance = wrapper.instance(); + expect(instance.state.totalPages).toBeDefined(); + expect(instance.state.totalPages).toEqual(instance.calculateTotalPage()); + expect(instance.state.lastPage).toBeDefined(); + expect(instance.state.lastPage).toEqual( + instance.calculateLastPage(instance.state.totalPages)); + expect(instance.state.dropdownOpen).toBeDefined(); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + }); + + describe('backToPrevPage', () => { + const props = createMockProps(); + + describe('when props.currPage is not hitting props.pageStartIndex', () => { + beforeEach(() => { + props.currPage = 2; + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting previous page correctly', () => { + const instance = wrapper.instance(); + expect(instance.backToPrevPage()).toEqual(props.currPage - 1); + }); + }); + + describe('when props.currPage is hitting props.pageStartIndex', () => { + beforeEach(() => { + props.currPage = props.pageStartIndex; + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should always getting page which must eq props.pageStartIndex', () => { + const instance = wrapper.instance(); + expect(instance.backToPrevPage()).toEqual(props.pageStartIndex); + }); + }); + }); + + describe('goToNextPage', () => { + const props = createMockProps(); + + describe('when props.currPage is not hitting state.lastPage', () => { + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting previous page correctly', () => { + const instance = wrapper.instance(); + expect(instance.goToNextPage()).toEqual(props.currPage + 1); + }); + }); + + describe('when props.currPage is hitting state.lastpage', () => { + beforeEach(() => { + props.currPage = 10; + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should always getting page which must eq props.pageStartIndex', () => { + const instance = wrapper.instance(); + expect(instance.goToNextPage()).toEqual(instance.state.lastPage); + }); + }); + }); + + describe('calculateTotalPage', () => { + const props = createMockProps(); + + describe('when missing sizePerPage argument', () => { + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting total pages correctly by default props.currSizePerPage', () => { + const instance = wrapper.instance(); + expect(instance.calculateTotalPage()).toEqual(10); + }); + }); + + describe('when sizePerPage argument given', () => { + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting total pages correctly by sizePerPage argument', () => { + const instance = wrapper.instance(); + expect(instance.calculateTotalPage(25)).toEqual(4); + }); + }); + }); + + describe('calculateLastPage', () => { + beforeEach(() => { + const props = createMockProps(); + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting last page correctly', () => { + const instance = wrapper.instance(); + expect(instance.calculateLastPage(instance.state.totalPages)).toEqual(10); + }); + }); + + describe('calculatePages', () => { + describe('calculate by state.totalPages and state.lastPage', () => { + const props = createMockProps(); + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting pages list correctly', () => { + const instance = wrapper.instance(); + expect(instance.calculatePages()).toEqual( + [props.prePageText, 1, 2, 3, 4, 5, props.nextPageText, props.lastPageText]); + + expect(instance.calculatePages(4, 4)).toEqual( + [props.prePageText, 1, 2, 3, 4, props.nextPageText]); + }); + }); + + describe('calculate by props.currPage', () => { + const props = createMockProps(); + const { firstPageText, prePageText, nextPageText, lastPageText } = props; + + it('should getting pages list correctly', () => { + const currPages = Array.from(Array(10).keys()); + currPages.forEach((currPage) => { + props.currPage = currPage + 1; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + + if (props.currPage < 4) { + expect(pageList).toEqual( + [prePageText, 1, 2, 3, 4, 5, nextPageText, lastPageText]); + } else if (props.currPage > 7) { + expect(pageList).toEqual( + [firstPageText, prePageText, 6, 7, 8, 9, 10, nextPageText]); + } else if (props.currPage === 4) { + expect(pageList).toEqual( + [firstPageText, prePageText, 2, 3, 4, 5, 6, nextPageText, lastPageText]); + } else if (props.currPage === 5) { + expect(pageList).toEqual( + [firstPageText, prePageText, 3, 4, 5, 6, 7, nextPageText, lastPageText]); + } else if (props.currPage === 6) { + expect(pageList).toEqual( + [firstPageText, prePageText, 4, 5, 6, 7, 8, nextPageText, lastPageText]); + } else { + expect(pageList).toEqual( + [firstPageText, prePageText, 5, 6, 7, 8, 9, nextPageText, lastPageText]); + } + }); + }); + }); + + describe('the quantity of pages is calculated by props.paginationSize', () => { + const props = createMockProps(); + const indicators = [ + props.firstPageText, props.prePageText, props.lastPageText, props.nextPageText + ]; + + it('should getting pages list correctly', () => { + [1, 3, 5, 8, 10].forEach((paginationSize) => { + props.paginationSize = paginationSize; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + const result = pageList.filter(p => indicators.indexOf(p) === -1); + expect(result.length).toEqual(props.paginationSize); + }); + }); + }); + + describe('when props.withFirstAndLast is true', () => { + const props = createMockProps(); + describe('and last page is not visible by props.currPage', () => { + it('should getting pages list which contain last page indication', () => { + [1, 2, 3, 4, 5, 6, 7].forEach((currPage) => { + props.currPage = currPage; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + expect(pageList.indexOf(props.lastPageText) > -1).toBeTruthy(); + }); + }); + }); + + describe('and first page is not visible by props.currPage', () => { + it('should getting pages list which contain first page indication', () => { + [10, 9, 8, 7, 6, 5, 4].forEach((currPage) => { + props.currPage = currPage; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + expect(pageList.indexOf(props.firstPageText) > -1).toBeTruthy(); + }); + }); + }); + }); + + describe('when props.withFirstAndLast is false', () => { + const props = createMockProps(); + it('should not contain first and last page indication always', () => { + const currPages = Array.from(Array(10).keys()); + currPages.forEach((currPage) => { + props.currPage = currPage + 1; + props.withFirstAndLast = false; + wrapper = shallow(); + const pageList = wrapper.instance().calculatePages(); + expect(pageList.indexOf(props.lastPageText) > -1).toBeFalsy(); + expect(pageList.indexOf(props.firstPageText) > -1).toBeFalsy(); + }); + }); + }); + + describe('when props.pageStartIndex is negative number', () => { + const props = createMockProps(); + props.pageStartIndex = -2; + props.currPage = -2; + + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting last page correctly', () => { + const pageList = wrapper.instance().calculatePages(); + expect(pageList).toEqual( + [props.prePageText, -2, -1, 0, 1, 2, props.nextPageText, props.lastPageText]); + }); + }); + + describe('when props.alwaysShowAllBtns is true', () => { + const props = createMockProps(); + props.alwaysShowAllBtns = true; + props.currPage = 1; + props.dataSize = 11; + + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should always having next and previous page indication', () => { + const pageList = wrapper.instance().calculatePages(); + expect(pageList.indexOf(props.nextPageText) > -1).toBeTruthy(); + expect(pageList.indexOf(props.prePageText) > -1).toBeTruthy(); + }); + }); + + describe('when state.totalPages is zero', () => { + const props = createMockProps(); + props.dataSize = 0; + + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting empty array', () => { + expect(wrapper.instance().calculatePages()).toEqual([]); + }); + }); + }); + + describe('calculatePageStatus', () => { + let instance; + let pageStatus; + + describe('default case', () => { + const props = createMockProps(); + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + instance = wrapper.instance(); + pageStatus = instance.calculatePageStatus(instance.calculatePages()); + }); + + it('should returning correct format for page status', () => { + pageStatus.forEach((p) => { + expect(Object.prototype.hasOwnProperty.call(p, 'page')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(p, 'active')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(p, 'disabled')).toBeTruthy(); + expect(Object.prototype.hasOwnProperty.call(p, 'title')).toBeTruthy(); + }); + }); + + it('should mark active status as true when it is props.currPage', () => { + expect(pageStatus.find(p => p.page === props.currPage).active).toBeTruthy(); + }); + + it('only have one page\'s active status is true', () => { + expect(pageStatus.filter(p => p.page === props.currPage).length).toEqual(1); + }); + }); + + describe('when alwaysShowAllBtns is false', () => { + const props = createMockProps(); + describe('and props.currPage is on first page', () => { + it('should filter out previous page indication', () => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + instance = wrapper.instance(); + const pageList = instance.calculatePages(); + pageStatus = instance.calculatePageStatus(pageList); + + expect(pageStatus.find(p => p.page === props.prePageText)).not.toBeDefined(); + }); + }); + + describe('and props.currPage is on last page', () => { + it('should filter out next page indication', () => { + props.currPage = 10; + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + instance = wrapper.instance(); + const pageList = instance.calculatePages(); + pageStatus = instance.calculatePageStatus(pageList); + + expect(pageStatus.find(p => p.page === props.nextPageText)).not.toBeDefined(); + }); + }); + }); + }); + + describe('calculateSizePerPageStatus', () => { + describe('when props.sizePerPageList is an number array', () => { + const props = createMockProps(); + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting correctly sizePerPage status', () => { + const instance = wrapper.instance(); + const result = instance.calculateSizePerPageStatus(); + expect(result.length).toEqual(props.sizePerPageList.length); + result.forEach((sizePerPage, i) => { + expect(sizePerPage.text).toEqual(`${props.sizePerPageList[i]}`); + expect(sizePerPage.page).toEqual(props.sizePerPageList[i]); + }); + }); + }); + + describe('when props.sizePerPageList is an object array', () => { + const props = createMockProps(); + props.sizePerPageList = [{ + text: 'ten', value: 10 + }, { + text: 'thirty', value: 30 + }]; + + beforeEach(() => { + const mockElement = React.createElement(MockComponent, props, null); + wrapper = shallow(mockElement); + }); + + it('should getting correctly sizePerPage status', () => { + const instance = wrapper.instance(); + const result = instance.calculateSizePerPageStatus(); + expect(result.length).toEqual(props.sizePerPageList.length); + result.forEach((sizePerPage, i) => { + expect(sizePerPage.text).toEqual(props.sizePerPageList[i].text); + expect(sizePerPage.page).toEqual(props.sizePerPageList[i].value); + }); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-paginator/test/pagination-list.test.js b/packages/react-bootstrap-table2-paginator/test/pagination-list.test.js new file mode 100644 index 000000000..054500635 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/pagination-list.test.js @@ -0,0 +1,42 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import PageButton from '../src/page-button'; +import PaginationList from '../src/pagination-list'; + +describe('PaginationList', () => { + let wrapper; + const onPageChange = sinon.stub(); + const pages = [{ + page: 1, + active: false, + disabled: false, + title: '1' + }, { + page: 2, + active: true, + disabled: false, + title: '2' + }, { + page: 3, + active: false, + disabled: false, + title: '3' + }]; + + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should rendering PaginatonList correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('ul.react-bootstrap-table-page-btns-ul').length).toBe(1); + expect(wrapper.find(PageButton).length).toBe(pages.length); + }); +}); diff --git a/packages/react-bootstrap-table2-paginator/test/pagination.test.js b/packages/react-bootstrap-table2-paginator/test/pagination.test.js new file mode 100644 index 000000000..cf8e44d71 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/pagination.test.js @@ -0,0 +1,287 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import SizePerPageDropDown from '../src/size-per-page-dropdown'; +import PaginationList from '../src/pagination-list'; +import Pagination from '../src/pagination'; + +describe('Pagination', () => { + let wrapper; + let instance; + + const createMockProps = props => ({ + dataSize: 100, + sizePerPageList: [10, 20, 30, 50], + currPage: 1, + currSizePerPage: 10, + pageStartIndex: 1, + paginationSize: 5, + withFirstAndLast: true, + firstPageText: '<<', + prePageText: '<', + nextPageText: '>', + lastPageText: '>>', + alwaysShowAllBtns: false, + onPageChange: sinon.stub(), + onSizePerPageChange: sinon.stub(), + hidePageListOnlyOnePage: false, + hideSizePerPage: false, + ...props + }); + + describe('default pagiantion', () => { + const props = createMockProps(); + + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should rendering correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('react-bootstrap-table-pagination')).toBeTruthy(); + expect(wrapper.find('.react-bootstrap-table-pagination-list-hidden').length).toBe(0); + }); + + it('should having correct state', () => { + expect(instance.state).toBeDefined(); + expect(instance.state.totalPages).toEqual(instance.calculateTotalPage()); + expect(instance.state.lastPage).toEqual( + instance.calculateLastPage(instance.state.totalPages)); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + + it('should rendering PaginationList component successfully', () => { + const paginationList = wrapper.find(PaginationList); + expect(paginationList.length).toBe(1); + expect(paginationList.prop('pages')).toEqual(instance.calculatePageStatus(instance.calculatePages())); + expect(paginationList.prop('onPageChange')).toEqual(instance.handleChangePage); + }); + + it('should rendering SizePerPageDropDown component successfully', () => { + const sizePerPageDropDown = wrapper.find(SizePerPageDropDown); + expect(sizePerPageDropDown.length).toBe(1); + + expect(sizePerPageDropDown.prop('currSizePerPage')).toEqual(`${props.currSizePerPage}`); + expect(sizePerPageDropDown.prop('options')).toEqual(instance.calculateSizePerPageStatus()); + expect(sizePerPageDropDown.prop('onSizePerPageChange')).toEqual(instance.handleChangeSizePerPage); + expect(sizePerPageDropDown.prop('onClick')).toEqual(instance.toggleDropDown); + expect(sizePerPageDropDown.prop('open')).toEqual(instance.state.dropdownOpen); + }); + }); + + describe('when props.sizePerPageList is empty array', () => { + beforeEach(() => { + const props = createMockProps({ sizePerPageList: [] }); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should not rendering SizePerPageDropDown component', () => { + const sizePerPageDropDown = wrapper.find(SizePerPageDropDown); + expect(sizePerPageDropDown.length).toBe(0); + }); + }); + + describe('when props.hideSizePerPage is true', () => { + beforeEach(() => { + const props = createMockProps({ hideSizePerPage: true }); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should not rendering SizePerPageDropDown component', () => { + const sizePerPageDropDown = wrapper.find(SizePerPageDropDown); + expect(sizePerPageDropDown.length).toBe(0); + }); + }); + + describe('when props.hidePageListOnlyOnePage is true', () => { + beforeEach(() => { + const props = createMockProps({ hidePageListOnlyOnePage: true, dataSize: 7 }); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should find react-bootstrap-table-pagination-list-hidden class when only one page', () => { + expect(wrapper.find('.react-bootstrap-table-pagination-list-hidden').length).toBe(1); + }); + }); + + describe('componentWillReceiveProps', () => { + describe('when next props.currSizePerPage is diff than current one', () => { + const nextProps = createMockProps({ currSizePerPage: 20 }); + + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should setting correct state.totalPages', () => { + instance.componentWillReceiveProps(nextProps); + expect(instance.state.totalPages).toEqual( + instance.calculateTotalPage(nextProps.currSizePerPage)); + }); + + it('should setting correct state.lastPage', () => { + instance.componentWillReceiveProps(nextProps); + const totalPages = instance.calculateTotalPage(nextProps.currSizePerPage); + expect(instance.state.lastPage).toEqual( + instance.calculateLastPage(totalPages)); + }); + }); + + describe('when next props.dataSize is diff than current one', () => { + const nextProps = createMockProps({ dataSize: 33 }); + + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should setting correct state.totalPages', () => { + instance.componentWillReceiveProps(nextProps); + expect(instance.state.totalPages).toEqual( + instance.calculateTotalPage(nextProps.currSizePerPage)); + }); + + it('should setting correct state.lastPage', () => { + instance.componentWillReceiveProps(nextProps); + const totalPages = instance.calculateTotalPage(nextProps.currSizePerPage); + expect(instance.state.lastPage).toEqual( + instance.calculateLastPage(totalPages)); + }); + }); + }); + + describe('toggleDropDown', () => { + beforeEach(() => { + const props = createMockProps(); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should setting state.dropdownOpen as true when it is false', () => { + instance.toggleDropDown(); + expect(instance.state.dropdownOpen).toBeTruthy(); + }); + + it('should setting state.dropdownOpen as false when it is true', () => { + instance.toggleDropDown(); + instance.toggleDropDown(); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + }); + + describe('closeDropDown', () => { + beforeEach(() => { + const props = createMockProps(); + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should always setting state.dropdownOpen as false', () => { + instance.closeDropDown(); + expect(instance.state.dropdownOpen).toBeFalsy(); + instance.closeDropDown(); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + }); + + describe('handleChangeSizePerPage', () => { + const props = createMockProps(); + + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should always setting state.dropdownOpen to false', () => { + instance.handleChangeSizePerPage(10); + expect(instance.state.dropdownOpen).toBeFalsy(); + }); + + describe('when new sizePerPage is same as current one', () => { + it('should not calling props.onSizePerPageChange callback', () => { + instance.handleChangeSizePerPage(10); + expect(props.onSizePerPageChange.callCount).toBe(0); + }); + }); + + describe('when new sizePerPage is diff than current one', () => { + it('should not calling props.onSizePerPageChange callback', () => { + instance.handleChangeSizePerPage(30); + expect(props.onSizePerPageChange.callCount).toBe(1); + }); + + describe('and new current page is still in the new lagination list', () => { + it('should calling props.onSizePerPageChange with correct argument', () => { + expect(props.onSizePerPageChange.calledWith(30, props.currPage)); + }); + }); + + describe('and new current page is still in the new lagination list', () => { + beforeEach(() => { + wrapper = shallow(); + instance = wrapper.instance(); + }); + + it('should calling props.onSizePerPageChange with correct argument', () => { + expect(props.onSizePerPageChange.calledWith(30, 4)); + }); + }); + }); + }); + + describe('handleChangePage', () => { + const props = createMockProps(); + + beforeEach(() => { + props.currPage = 6; + wrapper = shallow(); + instance = wrapper.instance(); + }); + + afterEach(() => { + props.onPageChange.reset(); + }); + + it('should calling props.onPageChange correctly when new page is eq props.prePageText', () => { + instance.handleChangePage(props.prePageText); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(5)).toBeTruthy(); + }); + + it('should calling props.onPageChange correctly when new page is eq props.nextPageText', () => { + instance.handleChangePage(props.nextPageText); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(7)).toBeTruthy(); + }); + + it('should calling props.onPageChange correctly when new page is eq props.lastPageText', () => { + instance.handleChangePage(props.lastPageText); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(10)).toBeTruthy(); + }); + + it('should calling props.onPageChange correctly when new page is eq props.firstPageText', () => { + instance.handleChangePage(props.firstPageText); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(props.pageStartIndex)).toBeTruthy(); + }); + + it('should calling props.onPageChange correctly when new page is a numeric page', () => { + const newPage = '8'; + instance.handleChangePage(newPage); + expect(props.onPageChange.callCount).toBe(1); + expect(props.onPageChange.calledWith(parseInt(newPage, 10))).toBeTruthy(); + }); + + it('should not calling props.onPageChange correctly when page is not changed', () => { + const newPage = props.currPage; + instance.handleChangePage(newPage); + expect(props.onPageChange.callCount).toBe(0); + }); + }); +}); 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 new file mode 100644 index 000000000..21e11cc53 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js @@ -0,0 +1,127 @@ +import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; + +import SizePerPageOption from '../src/size-per-page-option'; +import SizePerPageDropDown from '../src/size-per-page-dropdown'; + +describe('SizePerPageDropDown', () => { + let wrapper; + const currSizePerPage = '25'; + const options = [{ + text: '10', + page: 10 + }, { + text: '25', + page: 25 + }]; + const onClick = sinon.stub(); + const onBlur = sinon.stub(); + const onSizePerPageChange = sinon.stub(); + const props = { + currSizePerPage, + options, + onClick, + onBlur, + onSizePerPageChange + }; + + describe('default SizePerPageDropDown component', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + 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('onSizePerPageChange')).toEqual(onSizePerPageChange); + }); + }); + + 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( + + ); + }); + + it('should rendering SizePerPageDropDown correctly', () => { + expect(wrapper.hasClass('open show')).toBeTruthy(); + expect(wrapper.find('[aria-expanded=true]').length).toBe(1); + }); + }); + + describe('when hidden prop is true', () => { + beforeEach(() => { + wrapper = shallow( +