mirror of
https://github.com/gosticks/react-bootstrap-table2.git
synced 2025-10-16 11:55:39 +00:00
add react-bootstrap-table2-filter
This commit is contained in:
parent
ba93a6ce9c
commit
00185b80ca
@ -3,6 +3,7 @@ const path = require('path');
|
||||
const sourcePath = path.join(__dirname, '../../react-bootstrap-table2/src');
|
||||
const paginationSourcePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/src');
|
||||
const overlaySourcePath = path.join(__dirname, '../../react-bootstrap-table2-overlay/src');
|
||||
const filterSourcePath = path.join(__dirname, '../../react-bootstrap-table2-filter/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');
|
||||
@ -26,7 +27,7 @@ const loaders = [{
|
||||
test: /\.js?$/,
|
||||
use: ['babel-loader'],
|
||||
exclude: /node_modules/,
|
||||
include: [sourcePath, paginationSourcePath, overlaySourcePath, storyPath],
|
||||
include: [sourcePath, paginationSourcePath, overlaySourcePath, filterSourcePath, storyPath],
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
|
||||
@ -19,7 +19,8 @@
|
||||
"bootstrap": "^3.3.7",
|
||||
"react-bootstrap-table2": "0.0.1",
|
||||
"react-bootstrap-table2-paginator": "0.0.1",
|
||||
"react-bootstrap-table2-overlay": "0.0.1"
|
||||
"react-bootstrap-table2-overlay": "0.0.1",
|
||||
"react-bootstrap-table2-filter": "0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-console": "^1.0.0",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint react/require-default-props: 0 */
|
||||
/* eslint react/no-unused-prop-types: 0 */
|
||||
/* eslint react/prop-types: 0 */
|
||||
/* eslint no-return-assign: 0 */
|
||||
import React, { Component } from 'react';
|
||||
import { PropTypes } from 'prop-types';
|
||||
@ -11,6 +11,7 @@ class TextFilter extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.filter = this.filter.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.timeout = null;
|
||||
this.state = {
|
||||
value: props.defaultValue
|
||||
@ -30,14 +31,12 @@ class TextFilter extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timeout);
|
||||
this.cleanTimer();
|
||||
}
|
||||
|
||||
filter(e) {
|
||||
e.stopPropagation();
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
this.cleanTimer();
|
||||
const filterValue = e.target.value;
|
||||
this.setState(() => ({ value: filterValue }));
|
||||
this.timeout = setTimeout(() => {
|
||||
@ -45,6 +44,12 @@ class TextFilter extends Component {
|
||||
}, this.props.delay);
|
||||
}
|
||||
|
||||
cleanTimer() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
cleanFiltered() {
|
||||
const value = this.props.defaultValue;
|
||||
this.setState(() => ({ value }));
|
||||
@ -56,17 +61,25 @@ class TextFilter extends Component {
|
||||
this.props.onFilter(this.props.column, filterText, FILTER_TYPE.TEXT);
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
e.stopPropagation();
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(e);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { placeholder, column: { text }, style } = this.props;
|
||||
const { placeholder, column: { text }, style, className, onFilter, ...rest } = this.props;
|
||||
// stopPropagation for onClick event is try to prevent sort was triggered.
|
||||
return (
|
||||
<input
|
||||
{ ...rest }
|
||||
ref={ n => this.input = n }
|
||||
type="text"
|
||||
className="filter text-filter form-control"
|
||||
className={ `filter text-filter form-control ${className}` }
|
||||
style={ style }
|
||||
onChange={ this.filter }
|
||||
onClick={ e => e.stopPropagation() }
|
||||
onClick={ this.handleClick }
|
||||
placeholder={ placeholder || `Enter ${text}...` }
|
||||
value={ this.state.value }
|
||||
/>
|
||||
@ -76,12 +89,13 @@ class TextFilter extends Component {
|
||||
|
||||
TextFilter.propTypes = {
|
||||
onFilter: PropTypes.func.isRequired,
|
||||
column: PropTypes.object.isRequired,
|
||||
comparator: PropTypes.oneOf([LIKE, EQ]),
|
||||
defaultValue: PropTypes.string,
|
||||
delay: PropTypes.number,
|
||||
placeholder: PropTypes.string,
|
||||
column: PropTypes.object,
|
||||
style: PropTypes.object
|
||||
style: PropTypes.object,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
TextFilter.defaultProps = {
|
||||
|
||||
@ -0,0 +1,190 @@
|
||||
import 'jsdom-global/register';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { mount } from 'enzyme';
|
||||
import TextFilter from '../../src/components/text';
|
||||
import { FILTER_TYPE } from '../../src/const';
|
||||
|
||||
jest.useFakeTimers();
|
||||
describe('Text Filter', () => {
|
||||
let wrapper;
|
||||
let instance;
|
||||
const onFilter = sinon.stub();
|
||||
const column = {
|
||||
dataField: 'price',
|
||||
text: 'Price'
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
onFilter.reset();
|
||||
});
|
||||
|
||||
describe('initialization', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<TextFilter onFilter={ onFilter } column={ column } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
it('should have correct state', () => {
|
||||
expect(instance.state.value).toEqual(instance.props.defaultValue);
|
||||
});
|
||||
|
||||
it('should rendering component successfully', () => {
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('input[type="text"]')).toHaveLength(1);
|
||||
expect(instance.input.getAttribute('placeholder')).toEqual(`Enter ${column.text}...`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when defaultValue is defined', () => {
|
||||
const defaultValue = '123';
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<TextFilter onFilter={ onFilter } column={ column } defaultValue={ defaultValue } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
it('should have correct state', () => {
|
||||
expect(instance.state.value).toEqual(defaultValue);
|
||||
});
|
||||
|
||||
it('should rendering component successfully', () => {
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(instance.input.value).toEqual(defaultValue);
|
||||
});
|
||||
|
||||
it('should calling onFilter on componentDidMount', () => {
|
||||
expect(onFilter.calledOnce).toBeTruthy();
|
||||
expect(onFilter.calledWith(column, defaultValue, FILTER_TYPE.TEXT)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when placeholder is defined', () => {
|
||||
const placeholder = 'test';
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<TextFilter onFilter={ onFilter } column={ column } placeholder={ placeholder } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
it('should rendering component successfully', () => {
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(instance.input.getAttribute('placeholder')).toEqual(placeholder);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when style is defined', () => {
|
||||
const style = { backgroundColor: 'red' };
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<TextFilter onFilter={ onFilter } column={ column } style={ style } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
});
|
||||
|
||||
it('should rendering component successfully', () => {
|
||||
expect(wrapper).toHaveLength(1);
|
||||
expect(wrapper.find('input').prop('style')).toEqual(style);
|
||||
});
|
||||
});
|
||||
|
||||
describe('componentWillReceiveProps', () => {
|
||||
const nextDefaultValue = 'tester';
|
||||
const nextProps = {
|
||||
onFilter,
|
||||
column,
|
||||
defaultValue: nextDefaultValue
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<TextFilter onFilter={ onFilter } column={ column } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
instance.componentWillReceiveProps(nextProps);
|
||||
});
|
||||
|
||||
it('should setting state correctly when props.defaultValue is changed', () => {
|
||||
expect(instance.state.value).toEqual(nextDefaultValue);
|
||||
});
|
||||
|
||||
it('should calling onFilter correctly when props.defaultValue is changed', () => {
|
||||
expect(onFilter.calledOnce).toBeTruthy();
|
||||
expect(onFilter.calledWith(column, nextDefaultValue, FILTER_TYPE.TEXT)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanFiltered', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<TextFilter onFilter={ onFilter } column={ column } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
instance.cleanFiltered();
|
||||
});
|
||||
|
||||
it('should setting state correctly', () => {
|
||||
expect(instance.state.value).toEqual(instance.props.defaultValue);
|
||||
});
|
||||
|
||||
it('should calling onFilter correctly', () => {
|
||||
expect(onFilter.calledOnce).toBeTruthy();
|
||||
expect(onFilter.calledWith(
|
||||
column, instance.props.defaultValue, FILTER_TYPE.TEXT)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyFilter', () => {
|
||||
const filterText = 'test';
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<TextFilter onFilter={ onFilter } column={ column } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
instance.applyFilter(filterText);
|
||||
});
|
||||
|
||||
it('should setting state correctly', () => {
|
||||
expect(instance.state.value).toEqual(filterText);
|
||||
});
|
||||
|
||||
it('should calling onFilter correctly', () => {
|
||||
expect(onFilter.calledOnce).toBeTruthy();
|
||||
expect(onFilter.calledWith(column, filterText, FILTER_TYPE.TEXT)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('filter', () => {
|
||||
const event = { stopPropagation: sinon.stub(), target: { value: 'tester' } };
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = mount(
|
||||
<TextFilter onFilter={ onFilter } column={ column } />
|
||||
);
|
||||
instance = wrapper.instance();
|
||||
instance.filter(event);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setTimeout.mockClear();
|
||||
});
|
||||
|
||||
it('should calling e.stopPropagation', () => {
|
||||
expect(event.stopPropagation.calledOnce).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should setting state correctly', () => {
|
||||
expect(instance.state.value).toEqual(event.target.value);
|
||||
});
|
||||
|
||||
it('should calling setTimeout correctly', () => {
|
||||
expect(setTimeout.mock.calls).toHaveLength(1);
|
||||
expect(setTimeout.mock.calls[0]).toHaveLength(2);
|
||||
expect(setTimeout.mock.calls[0][1]).toEqual(instance.props.delay);
|
||||
});
|
||||
});
|
||||
});
|
||||
60
packages/react-bootstrap-table2-filter/test/filter.test.js
Normal file
60
packages/react-bootstrap-table2-filter/test/filter.test.js
Normal file
@ -0,0 +1,60 @@
|
||||
import _ from 'react-bootstrap-table2/src/utils';
|
||||
import Store from 'react-bootstrap-table2/src/store';
|
||||
|
||||
import { filters } from '../src/filter';
|
||||
import { FILTER_TYPE } from '../src/const';
|
||||
import { LIKE, EQ } from '../src/comparison';
|
||||
|
||||
const data = [];
|
||||
for (let i = 0; i < 20; i += 1) {
|
||||
data.push({
|
||||
id: i,
|
||||
name: `itme name ${i}`,
|
||||
price: 200 + i
|
||||
});
|
||||
}
|
||||
|
||||
describe('filter', () => {
|
||||
let store;
|
||||
let filterFn;
|
||||
let currFilters;
|
||||
|
||||
beforeEach(() => {
|
||||
store = new Store('id');
|
||||
store.data = data;
|
||||
currFilters = {};
|
||||
});
|
||||
|
||||
describe('text filter', () => {
|
||||
beforeEach(() => {
|
||||
filterFn = filters(store, _);
|
||||
});
|
||||
|
||||
describe(`when default comparator is ${LIKE}`, () => {
|
||||
it('should returning correct result', () => {
|
||||
currFilters.name = {
|
||||
filterVal: '3',
|
||||
filterType: FILTER_TYPE.TEXT
|
||||
};
|
||||
|
||||
const result = filterFn(currFilters);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when default comparator is ${EQ}`, () => {
|
||||
it('should returning correct result', () => {
|
||||
currFilters.name = {
|
||||
filterVal: 'itme name 3',
|
||||
filterType: FILTER_TYPE.TEXT,
|
||||
comparator: EQ
|
||||
};
|
||||
|
||||
const result = filterFn(currFilters);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
168
packages/react-bootstrap-table2-filter/test/wrapper.test.js
Normal file
168
packages/react-bootstrap-table2-filter/test/wrapper.test.js
Normal file
@ -0,0 +1,168 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import _ from 'react-bootstrap-table2/src/utils';
|
||||
import BootstrapTable from 'react-bootstrap-table2/src/bootstrap-table';
|
||||
import Store from 'react-bootstrap-table2/src/store';
|
||||
import filter, { textFilter } from '../src';
|
||||
import FilterWrapper 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 createTableProps = () => {
|
||||
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')
|
||||
};
|
||||
tableProps.store.data = data;
|
||||
return tableProps;
|
||||
};
|
||||
|
||||
const pureTable = props => (<BootstrapTable { ...props } />);
|
||||
|
||||
const createFilterWrapper = (props, renderFragment = true) => {
|
||||
wrapper = shallow(<FilterWrapper { ...props } baseElement={ pureTable } />);
|
||||
instance = wrapper.instance();
|
||||
if (renderFragment) {
|
||||
const fragment = instance.render();
|
||||
wrapper = shallow(<div>{ fragment }</div>);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
beforeEach(() => {
|
||||
nextProps = createTableProps();
|
||||
instance.componentWillReceiveProps(nextProps);
|
||||
});
|
||||
|
||||
it('should setting isDataChanged as false always(Temporary solution)', () => {
|
||||
expect(instance.state.isDataChanged).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
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], filterVal, FILTER_TYPE.TEXT);
|
||||
expect(props.store.filtering).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should setting state correctly', () => {
|
||||
filterVals.forEach((filterVal) => {
|
||||
instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT);
|
||||
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], filterVal, FILTER_TYPE.TEXT);
|
||||
expect(props.store.filtering).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should setting state correctly', () => {
|
||||
instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT);
|
||||
expect(instance.state.isDataChanged).toBeTruthy();
|
||||
expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('combination', () => {
|
||||
it('should setting store object correctly', () => {
|
||||
instance.onFilter(props.columns[1], '3', FILTER_TYPE.TEXT);
|
||||
expect(props.store.filtering).toBeTruthy();
|
||||
expect(instance.state.isDataChanged).toBeTruthy();
|
||||
expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
|
||||
|
||||
instance.onFilter(props.columns[1], '2', FILTER_TYPE.TEXT);
|
||||
expect(props.store.filtering).toBeTruthy();
|
||||
expect(instance.state.isDataChanged).toBeTruthy();
|
||||
expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
|
||||
|
||||
instance.onFilter(props.columns[2], '2', FILTER_TYPE.TEXT);
|
||||
expect(props.store.filtering).toBeTruthy();
|
||||
expect(instance.state.isDataChanged).toBeTruthy();
|
||||
expect(Object.keys(instance.state.currFilters)).toHaveLength(2);
|
||||
|
||||
instance.onFilter(props.columns[2], '', FILTER_TYPE.TEXT);
|
||||
expect(props.store.filtering).toBeTruthy();
|
||||
expect(instance.state.isDataChanged).toBeTruthy();
|
||||
expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
|
||||
|
||||
instance.onFilter(props.columns[1], '', FILTER_TYPE.TEXT);
|
||||
expect(props.store.filtering).toBeFalsy();
|
||||
expect(instance.state.isDataChanged).toBeTruthy();
|
||||
expect(Object.keys(instance.state.currFilters)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user