Skip to content

Commit

Permalink
STCOM-439: Add multiselection filter
Browse files Browse the repository at this point in the history
  • Loading branch information
Gyv4ik authored and Yurii Danylenko committed Jan 15, 2019
1 parent fefb827 commit a3141f1
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 0 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export { default as PasswordValidationField } from './lib/PasswordValidationFiel
export { default as ProxyManager } from './lib/ProxyManager';

export { default as SearchAndSort } from './lib/SearchAndSort';
export { default as MultiSelectionFilter } from './lib/SearchAndSort/components/MultiSelectionFilter';
export { default as makeQueryFunction } from './lib/SearchAndSort/makeQueryFunction';

export { default as Settings } from './lib/Settings';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';

import { MultiSelection } from '@folio/stripes-components';

export default class MultiSelectionFilter extends React.Component {
static propTypes = {
dataOptions: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.node,
value: PropTypes.any,
})).isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
selectedValues: PropTypes.arrayOf(PropTypes.string),
};

static defaultProps = {
selectedValues: [],
}

onChangeHandler = (selectedDataOptions) => {
const {
name,
onChange,
} = this.props;

onChange({
name,
values: selectedDataOptions.map((option) => option.value),
});
};

getSelectedDataOptions() {
const {
selectedValues,
dataOptions,
} = this.props;

return selectedValues
.map((curValue) => dataOptions.find((curAvailableValue) => curAvailableValue.value === curValue));
}

render() {
return (
<MultiSelection
{...this.props}
value={this.getSelectedDataOptions()}
onChange={this.onChangeHandler}
/>
);
}
}
1 change: 1 addition & 0 deletions lib/SearchAndSort/components/MultiSelectionFilter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './MultiSelectionFilter';
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import { describe, beforeEach, it } from '@bigtest/mocha';
import { expect } from 'chai';
import sinon from 'sinon';

import { mountWithContext } from '@folio/stripes-components/tests/helpers';
import MultiSelectInteractor from '@folio/stripes-components/lib/MultiSelection/tests/interactor';

import MultiSelectionFilter from '../MultiSelectionFilter';

const multiSelectionFilter = new MultiSelectInteractor();

const dataOptions = [
{ value: 'en', label: 'English' },
{ value: 'fr', label: 'French' },
{ value: 'ge', label: 'German' },
{ value: 'it', label: 'Italian' },
];

describe('MultiSelectionFilter', () => {
const onChangeHandler = sinon.spy();

beforeEach(async () => {
await mountWithContext(
<MultiSelectionFilter
name="language"
dataOptions={dataOptions}
onChange={onChangeHandler}
/>
);

onChangeHandler.resetHistory();
});

it('should not render any selected values by default', () => {
expect(multiSelectionFilter.values()).to.deep.equal([]);
});

describe('when there are selected values', () => {
beforeEach(async () => {
await mountWithContext(
<MultiSelectionFilter
name="language"
selectedValues={['en', 'fr', 'ge']}
dataOptions={dataOptions}
onChange={onChangeHandler}
/>
);
});

it('should display selected values', () => {
expect(multiSelectionFilter.values(0).valLabel).to.equal('English');
expect(multiSelectionFilter.values(1).valLabel).to.equal('French');
expect(multiSelectionFilter.values(2).valLabel).to.equal('German');
});

describe('after click on a value delete button', () => {
beforeEach(async () => {
await multiSelectionFilter.values(0).clickRemoveButton();
});

it('should call onChange callback with filter name and rest values', () => {
expect(onChangeHandler.args[0]).to.deep.equal([{
name: 'language',
values: ['fr', 'ge'],
}]);
});
});

describe('after click on selected option', () => {
beforeEach(async () => {
await multiSelectionFilter.options(1).clickOption();
});

it('should call onChange callback with filter name and rest values', () => {
expect(onChangeHandler.args[0]).to.deep.equal([{
name: 'language',
values: ['en', 'ge']
}]);
});
});

describe('after click on unselected option', () => {
beforeEach(async () => {
await multiSelectionFilter.options(3).clickOption();
});

it('should pass filter name and all selected values to onChange callback', () => {
expect(onChangeHandler.args[0]).to.deep.equal([{
name: 'language',
values: ['en', 'fr', 'ge', 'it'],
}]);
});
});
});
});
4 changes: 4 additions & 0 deletions lib/SearchAndSort/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,7 @@ The five parameters are:
* `2`: fail if both query and filters and empty.

For backwards compatibility, `false` (or omitting the argument altogether) is equivalent to `0` , and `true` is equivalent to `1`.

## Components for filtering

Please, pay attention, there is a set of components to be used for filtering inside SearchAndSort component. Each component represents a wrapper on existing form element component e.g. MultiSelect or renders a set of elements working like one filter e.g. CheckboxFilter. After change returns data in format: {name: <String>, values: <ArrayOfObjects>}, where name -- is a filter name and values -- filter values.

0 comments on commit a3141f1

Please sign in to comment.