diff --git a/index.js b/index.js index 5ef94e780..6a839053d 100644 --- a/index.js +++ b/index.js @@ -26,7 +26,11 @@ export { default as PasswordValidationField } from './lib/PasswordValidationFiel export { default as ProxyManager } from './lib/ProxyManager'; export { default as SearchAndSort } from './lib/SearchAndSort'; + +export { default as CheckboxFilter } from './lib/SearchAndSort/components/CheckboxFilter'; + 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/CheckboxFilter/CheckboxFilter.js b/lib/SearchAndSort/components/CheckboxFilter/CheckboxFilter.js new file mode 100644 index 000000000..804c27473 --- /dev/null +++ b/lib/SearchAndSort/components/CheckboxFilter/CheckboxFilter.js @@ -0,0 +1,63 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { Checkbox } from '@folio/stripes-components'; + +export default class CheckboxFilter extends React.Component { + static propTypes = { + dataOptions: PropTypes.arrayOf(PropTypes.shape({ + disabled: PropTypes.bool, + label: PropTypes.node, + readOnly: PropTypes.bool, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + })).isRequired, + name: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + selectedValues: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + }; + + static defaultProps = { + selectedValues: [], + } + + createOnChangeHandler = (filterValue) => (e) => { + const { + name, + selectedValues, + onChange, + } = this.props; + + const newValues = e.target.checked + ? [...selectedValues, filterValue] + : selectedValues.filter((value) => value !== filterValue); + + onChange({ + name, + values: newValues, + }); + }; + + render() { + const { + dataOptions, + selectedValues, + } = this.props; + + return ( + dataOptions.map(({ value, label, disabled, readOnly }) => { + return ( + + ); + }) + ); + } +} diff --git a/lib/SearchAndSort/components/CheckboxFilter/index.js b/lib/SearchAndSort/components/CheckboxFilter/index.js new file mode 100644 index 000000000..370af3605 --- /dev/null +++ b/lib/SearchAndSort/components/CheckboxFilter/index.js @@ -0,0 +1 @@ +export { default } from './CheckboxFilter'; diff --git a/lib/SearchAndSort/components/CheckboxFilter/tests/CheckboxFilter-test.js b/lib/SearchAndSort/components/CheckboxFilter/tests/CheckboxFilter-test.js new file mode 100644 index 000000000..af72dcf22 --- /dev/null +++ b/lib/SearchAndSort/components/CheckboxFilter/tests/CheckboxFilter-test.js @@ -0,0 +1,119 @@ +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 CheckboxFilter from '../CheckboxFilter'; +import CheckboxFilterInteractor from './interactor'; + + +describe('CheckboxFilter', () => { + const checkboxFilter = new CheckboxFilterInteractor(); + const onChangeHandler = sinon.spy(); + + const renderComponent = (props) => { + return mountWithContext( + + ); + }; + + beforeEach(async () => { + await renderComponent(); + + onChangeHandler.resetHistory(); + }); + + it('should render all filter data options', () => { + expect(checkboxFilter.dataOptionsCount).to.equal(4); + }); + + + describe('after click on readonly data option', () => { + beforeEach(async () => { + await renderComponent({ + dataOptions: [ + { + value: 'en', + label: 'English', + readOnly: true, + }, + ] + }); + await checkboxFilter.dataOptions(0).click(); + }); + + it('should not call onChange callback', () => { + expect(onChangeHandler.called).to.equal(false); + }); + }); + + describe('after click on disabled data option', () => { + beforeEach(async () => { + await renderComponent({ + dataOptions: [ + { + value: 'en', + label: 'English', + disabled: true, + }, + ] + }); + await checkboxFilter.dataOptions(0).click(); + }); + + it('should not call onChange callback', () => { + expect(onChangeHandler.called).to.equal(false); + }); + }); + + describe('when there are selected data options', () => { + beforeEach(async () => { + await renderComponent({ + selectedValues: ['fr', 'ge'], + }); + }); + + it('should render selected data options', () => { + expect(checkboxFilter.dataOptions(1).isSelected).to.equal(true); + expect(checkboxFilter.dataOptions(2).isSelected).to.equal(true); + }); + + describe('after click on unselected data option', () => { + beforeEach(async () => { + await checkboxFilter.dataOptions(0).click(); + }); + + it('should raise filter name and new selected values to onChange callback', () => { + expect(onChangeHandler.args[0]).to.deep.equal([{ + name: 'language', + values: ['fr', 'ge', 'en'], + }]); + }); + }); + + describe('after click on selected option', () => { + beforeEach(async () => { + await checkboxFilter.dataOptions(1).click(); + }); + + it('should raise filter name with the remaining selected values to onChange callback', () => { + expect(onChangeHandler.args[0]).to.deep.equal([{ + name: 'language', + values: ['ge'], + }]); + }); + }); + }); +}); diff --git a/lib/SearchAndSort/components/CheckboxFilter/tests/interactor.js b/lib/SearchAndSort/components/CheckboxFilter/tests/interactor.js new file mode 100644 index 000000000..81bd6a34f --- /dev/null +++ b/lib/SearchAndSort/components/CheckboxFilter/tests/interactor.js @@ -0,0 +1,19 @@ +import { + count, + clickable, + interactor, + collection, + is +} from '@bigtest/interactor'; + +const DataOption = interactor(class DataOptionInteractor { + click = clickable(); + isSelected = is('[checked]'); + isDisabled = is('disabled'); + isReadOnly = is('readonly') +}); + +export default interactor(class CheckboxFilterInteractor { + dataOptionsCount = count('[data-test-checkbox-filter-data-option]'); + dataOptions = collection('[data-test-checkbox-filter-data-option]', DataOption); +});