From a3141f1f20fc0a20d4f8545707e2442ca57d31a5 Mon Sep 17 00:00:00 2001 From: Yurii Danilenko Date: Thu, 10 Jan 2019 14:21:27 +0200 Subject: [PATCH] STCOM-439: Add multiselection filter --- index.js | 1 + .../MultiSelectionFilter.js | 52 ++++++++++ .../components/MultiSelectionFilter/index.js | 1 + .../tests/MultiSelectionFilter-test.js | 96 +++++++++++++++++++ lib/SearchAndSort/readme.md | 4 + 5 files changed, 154 insertions(+) create mode 100644 lib/SearchAndSort/components/MultiSelectionFilter/MultiSelectionFilter.js create mode 100644 lib/SearchAndSort/components/MultiSelectionFilter/index.js create mode 100644 lib/SearchAndSort/components/MultiSelectionFilter/tests/MultiSelectionFilter-test.js diff --git a/index.js b/index.js index 2dd9b63fe..5ef94e780 100644 --- a/index.js +++ b/index.js @@ -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'; diff --git a/lib/SearchAndSort/components/MultiSelectionFilter/MultiSelectionFilter.js b/lib/SearchAndSort/components/MultiSelectionFilter/MultiSelectionFilter.js new file mode 100644 index 000000000..27a8258ab --- /dev/null +++ b/lib/SearchAndSort/components/MultiSelectionFilter/MultiSelectionFilter.js @@ -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 ( + + ); + } +} diff --git a/lib/SearchAndSort/components/MultiSelectionFilter/index.js b/lib/SearchAndSort/components/MultiSelectionFilter/index.js new file mode 100644 index 000000000..5aa639531 --- /dev/null +++ b/lib/SearchAndSort/components/MultiSelectionFilter/index.js @@ -0,0 +1 @@ +export { default } from './MultiSelectionFilter'; diff --git a/lib/SearchAndSort/components/MultiSelectionFilter/tests/MultiSelectionFilter-test.js b/lib/SearchAndSort/components/MultiSelectionFilter/tests/MultiSelectionFilter-test.js new file mode 100644 index 000000000..c86065976 --- /dev/null +++ b/lib/SearchAndSort/components/MultiSelectionFilter/tests/MultiSelectionFilter-test.js @@ -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( + + ); + + 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( + + ); + }); + + 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'], + }]); + }); + }); + }); +}); diff --git a/lib/SearchAndSort/readme.md b/lib/SearchAndSort/readme.md index 1833d32d1..2f9dc8614 100644 --- a/lib/SearchAndSort/readme.md +++ b/lib/SearchAndSort/readme.md @@ -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: , values: }, where name -- is a filter name and values -- filter values. \ No newline at end of file