diff --git a/docs/migration.md b/docs/migration.md index 60edbd2..32d137d 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -88,7 +88,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. diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-multi-select-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-multi-select-filter.js new file mode 100644 index 0000000..0f90ac9 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-multi-select-filter.js @@ -0,0 +1,80 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsQualityGenerator } from 'utils/common'; + +const products = productsQualityGenerator(6); + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quailty', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + withoutEmptyOption: true, + style: { + backgroundColor: 'pink' + }, + className: 'test-classname', + datamycustomattr: 'datamycustomattr' + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quailty', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + withoutEmptyOption: true, + style: { + backgroundColor: 'pink' + }, + className: 'test-classname', + datamycustomattr: 'datamycustomattr' + }) +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter-default-value.js b/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter-default-value.js new file mode 100644 index 0000000..d2b4e19 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter-default-value.js @@ -0,0 +1,69 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsQualityGenerator } from 'utils/common'; + +const products = productsQualityGenerator(6); + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quailty', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + defaultValue: [0, 2] + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quailty', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + defaultValue: [0, 2] + }) +}]; + + +`; +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter.js new file mode 100644 index 0000000..dce63cd --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/multi-select-filter.js @@ -0,0 +1,67 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsQualityGenerator } from 'utils/common'; + +const products = productsQualityGenerator(6); + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quailty', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quailty', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions + }) +}]; + + +`; +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-multi-select-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-multi-select-filter.js new file mode 100644 index 0000000..e6c20e3 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-multi-select-filter.js @@ -0,0 +1,95 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsQualityGenerator } from 'utils/common'; + +const products = productsQualityGenerator(6); + +let qualityFilter; + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quality', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + getFilter: (filter) => { + // qualityFilter was assigned once the component has been mounted. + qualityFilter = filter; + } + }) +}]; + +const handleClick = () => { + qualityFilter([0, 2]); +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { multiSelectFilter } from 'react-bootstrap-table2-filter'; + +let qualityFilter; + +const selectOptions = { + 0: 'good', + 1: 'Bad', + 2: 'unknown' +}; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'quality', + text: 'Product Quality', + formatter: cell => selectOptions[cell], + filter: multiSelectFilter({ + options: selectOptions, + getFilter: (filter) => { + // qualityFilter was assigned once the component has been mounted. + qualityFilter = filter; + } + }) +}]; + +const handleClick = () => { + qualityFilter([0, 2]); +}; + +export default () => ( +
+ + +
+); +`; + +export default () => ( +
+ + + + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 77dc4c9..0617922 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -46,6 +46,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,6 +59,7 @@ 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'; @@ -178,6 +182,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,11 +192,13 @@ 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', () => ); diff --git a/packages/react-bootstrap-table2-filter/README.md b/packages/react-bootstrap-table2-filter/README.md index 6cf47a0..38c278e 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 diff --git a/packages/react-bootstrap-table2-filter/index.js b/packages/react-bootstrap-table2-filter/index.js index fe60268..6d9368a 100644 --- a/packages/react-bootstrap-table2-filter/index.js +++ b/packages/react-bootstrap-table2-filter/index.js @@ -1,5 +1,6 @@ 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'; @@ -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/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(( + + )); + } + Object.keys(options).forEach(key => + optionTags.push() + ); + 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 ( + + ); + } +} + +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/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/filter.js b/packages/react-bootstrap-table2-filter/src/filter.js index a65d52a..c1a8666 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; diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js index c2b24e8..0602ee8 100644 --- a/packages/react-bootstrap-table2-filter/src/wrapper.js +++ b/packages/react-bootstrap-table2-filter/src/wrapper.js @@ -53,12 +53,20 @@ export default (Base, { const currFilters = Object.assign({}, store.filters); const { dataField, filter } = column; - if (!_.isDefined(filterVal) || filterVal === '') { + 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), + comparator = ( + (filterType === FILTER_TYPE.SELECT) || ( + filterType === FILTER_TYPE.MULTISELECT) ? EQ : LIKE + ), caseSensitive = false } = filter.props; currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive }; 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/filter.test.js b/packages/react-bootstrap-table2-filter/test/filter.test.js index 25f629d..79d0407 100644 --- a/packages/react-bootstrap-table2-filter/test/filter.test.js +++ b/packages/react-bootstrap-table2-filter/test/filter.test.js @@ -123,6 +123,55 @@ describe('filter', () => { }); }); + describe('filterByArray', () => { + beforeEach(() => { + filterFn = filters(store, columns, _); + }); + + describe('when filter value is empty array', () => { + it('should return original data', () => { + currFilters.name = { + filterVal: [], + filterType: FILTER_TYPE.MULTISELECT + }; + + const result = filterFn(currFilters); + expect(result).toBeDefined(); + expect(result).toHaveLength(store.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 = filterFn(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 = filterFn(currFilters); + expect(result).toBeDefined(); + expect(result).toHaveLength(3); + }); + }); + }); + }); + describe('filterByNumber', () => { beforeEach(() => { filterFn = filters(store, columns, _); diff --git a/packages/react-bootstrap-table2-filter/test/wrapper.test.js b/packages/react-bootstrap-table2-filter/test/wrapper.test.js index 1442658..751fc2e 100644 --- a/packages/react-bootstrap-table2-filter/test/wrapper.test.js +++ b/packages/react-bootstrap-table2-filter/test/wrapper.test.js @@ -163,7 +163,7 @@ describe('Wrapper', () => { }); describe('when filterVal is empty or undefined', () => { - const filterVals = ['', undefined]; + const filterVals = ['', undefined, []]; it('should setting store object correctly', () => { filterVals.forEach((filterVal) => {