Skip to content

Commit

Permalink
feat: add "name" prop to Filter component
Browse files Browse the repository at this point in the history
This allows to customize filter name in order to have multiple filters that
work on the same prop.
  • Loading branch information
targos committed Aug 7, 2017
1 parent 59e96c2 commit e71c079
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 92 deletions.
10 changes: 6 additions & 4 deletions src/ConnectedFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ class ConnectedFilter extends Component {

setupSelector() {
const data = (props) => props.data;
const name = (props) => props.name;
const prop = (props) => props.prop;
const kind = (props) => props.kind;
const filters = (props) => props.filters;
this.selector = createSelector(
data,
name,
prop,
kind,
filters,
Expand All @@ -38,7 +40,7 @@ class ConnectedFilter extends Component {
}

render() {
const currentFilter = this.props.filters ? this.props.filters.get(this.props.prop) : null;
const currentFilter = this.props.filters ? this.props.filters.get(this.props.name) : null;
const newProps = {
onChange: this.props.filterUpdate
};
Expand Down Expand Up @@ -71,9 +73,9 @@ export default connect(
(dispatch, props) => {
const name = props.searchFilter.name;
return {
filterUpdate: (value) => dispatch(updateFilter(name, props.prop, props.kind, value)),
setOperator: (value) => dispatch(setOperator(name, props.prop, props.kind, value)),
setNegated: (value) => dispatch(setNegated(name, props.prop, props.kind, value))
filterUpdate: (value) => dispatch(updateFilter(name, props.name, props.prop, props.kind, value)),
setOperator: (value) => dispatch(setOperator(name, props.name, props.prop, props.kind, value)),
setNegated: (value) => dispatch(setNegated(name, props.name, props.prop, props.kind, value))
};
}
)(ConnectedFilter);
6 changes: 5 additions & 1 deletion src/Filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import ConnectedFilter from './ConnectedFilter';
export default class Filter extends Component {
render() {
const props = Object.assign({}, this.props, {searchFilter: this.context.searchFilter});
if (props.name === undefined) {
props.name = props.prop;
}
return createElement(ConnectedFilter, props);
}
}
Expand All @@ -17,5 +20,6 @@ Filter.contextTypes = {
Filter.propTypes = {
prop: PropTypes.string.isRequired,
kind: PropTypes.string.isRequired,
component: PropTypes.func.isRequired
component: PropTypes.func.isRequired,
name: PropTypes.string
};
21 changes: 14 additions & 7 deletions src/__tests__/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import {

describe('action creators', () => {
it('updateFilter', () => {
const action = updateFilter('name', 'prop', 'kind', 'value');
const action = updateFilter('name', 'filterName', 'prop', 'kind', 'value');
expect(action).toEqual({
type: UPDATE_FILTER,
meta: {
name: 'name',
filterName: 'filterName',
prop: 'prop',
kind: 'kind',
},
Expand All @@ -26,11 +27,12 @@ describe('action creators', () => {
});

it('updateFilter with event-like object', () => {
const action = updateFilter('name', 'prop', 'kind', {target: {value: 'xxx'}});
const action = updateFilter('name', 'filterName', 'prop', 'kind', {target: {value: 'xxx'}});
expect(action).toEqual({
type: UPDATE_FILTER,
meta: {
name: 'name',
filterName: 'filterName',
prop: 'prop',
kind: 'kind',
},
Expand All @@ -39,11 +41,12 @@ describe('action creators', () => {
});

it('updateFilter with falsy value', () => {
const action = updateFilter('name', 'prop', 'kind', '');
const action = updateFilter('name', 'filterName', 'prop', 'kind', '');
expect(action).toEqual({
type: UPDATE_FILTER,
meta: {
name: 'name',
filterName: 'filterName',
prop: 'prop',
kind: 'kind',
},
Expand All @@ -52,11 +55,12 @@ describe('action creators', () => {
});

it('setNegated', () => {
const action = setNegated('name', 'prop', 'kind', true);
const action = setNegated('name', 'filterName', 'prop', 'kind', true);
expect(action).toEqual({
type: SET_NEGATED,
meta: {
name: 'name',
filterName: 'filterName',
prop: 'prop',
kind: 'kind',
},
Expand All @@ -65,11 +69,12 @@ describe('action creators', () => {
});

it('setNegated with checkbox event', () => {
const action = setNegated('name', 'prop', 'kind', {target: {checked: true}});
const action = setNegated('name', 'filterName', 'prop', 'kind', {target: {checked: true}});
expect(action).toEqual({
type: SET_NEGATED,
meta: {
name: 'name',
filterName: 'filterName',
prop: 'prop',
kind: 'kind',
},
Expand All @@ -78,11 +83,12 @@ describe('action creators', () => {
});

it('setOperator', () => {
const action = setOperator('name', 'prop', 'kind', true);
const action = setOperator('name', 'filterName', 'prop', 'kind', true);
expect(action).toEqual({
type: SET_OPERATOR,
meta: {
name: 'name',
filterName: 'filterName',
prop: 'prop',
kind: 'kind',
},
Expand All @@ -91,11 +97,12 @@ describe('action creators', () => {
});

it('setOperator with event-like object', () => {
const action = setOperator('name', 'prop', 'kind', {target: {value: 'OR'}});
const action = setOperator('name', 'filterName', 'prop', 'kind', {target: {value: 'OR'}});
expect(action).toEqual({
type: SET_OPERATOR,
meta: {
name: 'name',
filterName: 'filterName',
prop: 'prop',
kind: 'kind',
},
Expand Down
6 changes: 5 additions & 1 deletion src/__tests__/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
actionTypes,
reset,
updateFilter,
getFilteredData
setOperator,
setNegated,
getFilteredData,
} from '../index';

test('check exports', () => {
Expand All @@ -15,5 +17,7 @@ test('check exports', () => {
expect(actionTypes).not.toEqual(undefined);
expect(reset).toBeInstanceOf(Function);
expect(updateFilter).toBeInstanceOf(Function);
expect(setOperator).toBeInstanceOf(Function);
expect(setNegated).toBeInstanceOf(Function);
expect(getFilteredData).toBeInstanceOf(Function);
});
44 changes: 22 additions & 22 deletions src/__tests__/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ data.forEach((d, idx) => d.idx = idx);
describe('Test filter', () => {
it('multiple', () => {
const filters = [
{value: [], expected: [0, 1, 2, 3]},
{value: ['Y'], expected: [0, 2]},
{value: ['Y'], negated: true, expected: [1, 3]},
{value: ['Y', 'Z'], expected: [2]},
{value: ['Y', 'Z'], operator: 'OR', expected: [0, 1, 2]},
{value: ['Y', 'Z'], operator: 'AND', negated: true, expected: [3]},
{value: ['Z', 'Y'], operator: 'OR', negated: true, expected: [0, 1, 3]},
{value: ['W'], expected: []}
{prop: 'A', value: [], expected: [0, 1, 2, 3]},
{prop: 'A', value: ['Y'], expected: [0, 2]},
{prop: 'A', value: ['Y'], negated: true, expected: [1, 3]},
{prop: 'A', value: ['Y', 'Z'], expected: [2]},
{prop: 'A', value: ['Y', 'Z'], operator: 'OR', expected: [0, 1, 2]},
{prop: 'A', value: ['Y', 'Z'], operator: 'AND', negated: true, expected: [3]},
{prop: 'A', value: ['Z', 'Y'], operator: 'OR', negated: true, expected: [0, 1, 3]},
{prop: 'A', value: ['W'], expected: []}
];
for (let filter of filters) {
const f = Map({
Expand All @@ -33,20 +33,20 @@ describe('Test filter', () => {

it('value', () => {
const filters = [
{value: ['Y'], expected: [0, 3]},
{value: ['W'], expected: []},
{value: ['Y'], negated: true, expected: [1, 2]},
{value: ['X', 'Y'], operator: 'AND', expected: []},
{value: ['X', 'Y'], operator: 'OR', expected: [0, 2, 3]},
{value: ['X'], expected: [2]},
{value: ['X', 'Y'], operator: 'AND', negated: true, expected: [1]},
{value: ['X', 'Y'], operator: 'OR', negated: true, expected: [0, 1, 2, 3]},
{value: ['Y'], negated: false, expected: [0, 3], operator: 'AND'}
{prop: 'B', value: ['Y'], expected: [0, 3]},
{prop: 'B', value: ['W'], expected: []},
{prop: 'B', value: ['Y'], negated: true, expected: [1, 2]},
{prop: 'B', value: ['X', 'Y'], operator: 'AND', expected: []},
{prop: 'B', value: ['X', 'Y'], operator: 'OR', expected: [0, 2, 3]},
{prop: 'B', value: ['X'], expected: [2]},
{prop: 'B', value: ['X', 'Y'], operator: 'AND', negated: true, expected: [1]},
{prop: 'B', value: ['X', 'Y'], operator: 'OR', negated: true, expected: [0, 1, 2, 3]},
{prop: 'B', value: ['Y'], negated: false, expected: [0, 3], operator: 'AND'}
];

for (let filter of filters) {
const f = Map({
B: filter
filterName: filter
});
const filterIdx = filterData(data, f).map(v => v.idx).sort(asc);
expect(filterIdx).toEqual(filter.expected.sort(asc));
Expand All @@ -55,10 +55,10 @@ describe('Test filter', () => {

it('range', () => {
const filters = [
{min: 0, max: 3, expected: [0, 1, 2]},
{min: -2, max: 4, expected: [0, 1, 2, 3]},
{min: 0, max: 0.5, expected: []},
{min: 0.5, max: 1.5, expected: [0]}
{prop: 'C', min: 0, max: 3, expected: [0, 1, 2]},
{prop: 'C', min: -2, max: 4, expected: [0, 1, 2, 3]},
{prop: 'C', min: 0, max: 0.5, expected: []},
{prop: 'C', min: 0.5, max: 1.5, expected: [0]}
];
for (let filter of filters) {
const f = Map({
Expand Down
53 changes: 34 additions & 19 deletions src/__tests__/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,39 +35,54 @@ describe('reducer', () => {
const initialState = ImmutableMap();
const state = reducer(initialState, {type: actionTypes.UPDATE_FILTER, meta: {name: 'test', prop: 'abc', kind: kinds.multiple}, payload: 'TEST'});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: false, operator: AND, value: ['TEST']}})
test: ImmutableMap({abc: {negated: false, operator: AND, prop: 'abc', value: ['TEST']}})
}));
});

it('UPDATE_FILTER array payload one value', () => {
const initialState = ImmutableMap();
const state = reducer(initialState, {type: actionTypes.UPDATE_FILTER, meta: {name: 'test', prop: 'abc', kind: kinds.multiple}, payload: ['TEST']});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: false, operator: AND, value: ['TEST']}})
test: ImmutableMap({abc: {negated: false, operator: AND, prop: 'abc', value: ['TEST']}})
}));
});

it('UPDATE_FILTER array payload two values', () => {
const initialState = ImmutableMap();
const state = reducer(initialState, {type: actionTypes.UPDATE_FILTER, meta: {name: 'test', prop: 'abc', kind: kinds.multiple}, payload: ['TEST1', 'TEST2']});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: false, operator: AND, value: ['TEST1', 'TEST2']}})
test: ImmutableMap({abc: {negated: false, operator: AND, prop: 'abc', value: ['TEST1', 'TEST2']}})
}));
});

it('UPDATE_FILTER multiple filters on same prop', () => {
const initialState = ImmutableMap({
test: ImmutableMap({
abc: {negated: false, operator: AND, prop: 'abc', value: ['TEST']}
})
});
const state = reducer(initialState, {type: actionTypes.UPDATE_FILTER, meta: {name: 'test', prop: 'abc', kind: kinds.multiple, filterName: 'abc2'}, payload: 'TEST1'});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({
abc: {negated: false, operator: AND, prop: 'abc', value: ['TEST']},
abc2: {negated: false, operator: AND, prop: 'abc', value: ['TEST1']}
})
}));
});

it('UPDATE_FILTER value', () => {
const initialState = ImmutableMap();
const state = reducer(initialState, {type: actionTypes.UPDATE_FILTER, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: 'TEST'});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: false, operator: OR, value: ['TEST']}})
test: ImmutableMap({abc: {negated: false, operator: OR, prop: 'abc', value: ['TEST']}})
}));
});

it('UPDATE_FILTER range', () => {
const initialState = ImmutableMap();
const state = reducer(initialState, {type: actionTypes.UPDATE_FILTER, meta: {name: 'test', prop: 'abc', kind: kinds.range}, payload: {min: 0, max: 1}});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {min: 0, max: 1}})
test: ImmutableMap({abc: {min: 0, max: 1, prop: 'abc'}})
}));
});

Expand Down Expand Up @@ -114,41 +129,41 @@ describe('reducer', () => {
const initialState = ImmutableMap();
const state = reducer(initialState, {type: actionTypes.SET_NEGATED, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: true});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: OR, value: []}})
test: ImmutableMap({abc: {negated: true, operator: OR, prop: 'abc', value: []}})
}));
});

it('SET_NEGATED existing no change', () => {
const initialState = ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: OR, value: []}})
test: ImmutableMap({abc: {negated: true, operator: OR, prop: 'abc', value: []}})
});
const state = reducer(initialState, {type: actionTypes.SET_NEGATED, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: true});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: OR, value: []}})
test: ImmutableMap({abc: {negated: true, operator: OR, prop: 'abc', value: []}})
}));
});

it('SET_NEGATED existing change', () => {
const initialState = ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: OR, value: ['X']}})
test: ImmutableMap({abc: {negated: true, operator: OR, prop: 'abc', value: ['X']}})
});
const state = reducer(initialState, {type: actionTypes.SET_NEGATED, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: false});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: false, operator: OR, value: ['X']}})
test: ImmutableMap({abc: {negated: false, operator: OR, prop: 'abc', value: ['X']}})
}));
});

it('SET_NEGATED throws on wrong type', () => {
const initialState = ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: OR, value: []}})
test: ImmutableMap({abc: {negated: true, operator: OR, prop: 'abc', value: []}})
});
expect(() => reducer(initialState, {type: actionTypes.SET_NEGATED, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: undefined})).toThrow(/^SET_NEGATED expects a boolean value. Received undefined$/);
expect(() => reducer(initialState, {type: actionTypes.SET_NEGATED, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: 0})).toThrow(/^SET_NEGATED expects a boolean value. Received number$/);
});

it('SET_NEGATED throws on wrong kind', () => {
const initialState = ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: OR, value: []}})
test: ImmutableMap({abc: {negated: true, operator: OR, prop: 'abc', value: []}})
});
expect(() => reducer(initialState, {type: actionTypes.SET_NEGATED, meta: {name: 'test', prop: 'abc', kind: 'wrong'}, payload: false})).toThrow(/^unexpected kind: wrong$/);
});
Expand All @@ -157,40 +172,40 @@ describe('reducer', () => {
const initialState = ImmutableMap();
const state = reducer(initialState, {type: actionTypes.SET_OPERATOR, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: AND});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: false, operator: AND, value: []}})
test: ImmutableMap({abc: {negated: false, operator: AND, prop: 'abc', value: []}})
}));
});

it('SET_OPERATOR existing no change', () => {
const initialState = ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: AND, value: []}})
test: ImmutableMap({abc: {negated: true, operator: AND, prop: 'abc', value: []}})
});
const state = reducer(initialState, {type: actionTypes.SET_OPERATOR, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: AND});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: AND, value: []}})
test: ImmutableMap({abc: {negated: true, operator: AND, prop: 'abc', value: []}})
}));
});

it('SET_OPERATOR existing change', () => {
const initialState = ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: AND, value: []}})
test: ImmutableMap({abc: {negated: true, operator: AND, prop: 'abc', value: []}})
});
const state = reducer(initialState, {type: actionTypes.SET_OPERATOR, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: OR});
expect(state).toEqual(ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: OR, value: []}})
test: ImmutableMap({abc: {negated: true, operator: OR, prop: 'abc', value: []}})
}));
});

it('SET_OPERATOR throws on wrong kind', () => {
const initialState = ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: AND, value: []}})
test: ImmutableMap({abc: {negated: true, operator: AND, prop: 'abc', value: []}})
});
expect(() => reducer(initialState, {type: actionTypes.SET_OPERATOR, meta: {name: 'test', prop: 'abc', kind: 'wrong'}, payload: OR})).toThrow(/^unexpected kind: wrong$/);
});

it('SET_OPERATOR throws on wrong value', () => {
const initialState = ImmutableMap({
test: ImmutableMap({abc: {negated: true, operator: AND, value: []}})
test: ImmutableMap({abc: {negated: true, operator: AND, prop: 'abc', value: []}})
});
expect(() => reducer(initialState, {type: actionTypes.SET_OPERATOR, meta: {name: 'test', prop: 'abc', kind: kinds.value}, payload: 'BAD'})).toThrow(/^wrong operator: BAD$/);
});
Expand Down
Loading

0 comments on commit e71c079

Please sign in to comment.