diff --git a/src/components/Filters/FilterPanel.js b/src/components/Filters/FilterPanel.js index abd81ba76..7b57d985b 100644 --- a/src/components/Filters/FilterPanel.js +++ b/src/components/Filters/FilterPanel.js @@ -10,7 +10,7 @@ import getIcon from '../iconMap'; import { Issue } from './Issue'; import { Product } from './Product'; import React from 'react'; -import SimpleFilter from './SimpleFilter'; +import SimpleFilter from './SimpleFilter/SimpleFilter'; import { ZipCode } from './ZipCode'; import { selectViewHasFilters, diff --git a/src/components/Filters/SimpleFilter.js b/src/components/Filters/SimpleFilter.js deleted file mode 100644 index 733a00fe0..000000000 --- a/src/components/Filters/SimpleFilter.js +++ /dev/null @@ -1,53 +0,0 @@ -import './Aggregation.less'; -import AggregationItem from './AggregationItem/AggregationItem'; -import { coalesce } from '../../utils'; -import CollapsibleFilter from './CollapsibleFilter'; -import { connect } from 'react-redux'; -import MoreOrLess from './MoreOrLess'; -import PropTypes from 'prop-types'; -import React from 'react'; - -export class SimpleFilter extends React.Component { - render() { - const listComponentProps = { - fieldName: this.props.fieldName, - }; - const { desc, fieldName, options, hasChildren, title } = this.props; - - return ( - - - - ); - } -} - -export const mapStateToProps = (state, ownProps) => { - // Find all query filters that refer to the field name - const activeChildren = coalesce(state.query, ownProps.fieldName, []); - - return { - options: coalesce(state.aggs, ownProps.fieldName, []), - hasChildren: activeChildren.length > 0, - }; -}; - -// eslint-disable-next-line react-redux/prefer-separate-component-file -export default connect(mapStateToProps)(SimpleFilter); - -SimpleFilter.propTypes = { - fieldName: PropTypes.string.isRequired, - desc: PropTypes.string, - options: PropTypes.array.isRequired, - hasChildren: PropTypes.bool.isRequired, - title: PropTypes.string.isRequired, -}; diff --git a/src/components/Filters/SimpleFilter/SimpleFilter.js b/src/components/Filters/SimpleFilter/SimpleFilter.js new file mode 100644 index 000000000..6553a14e9 --- /dev/null +++ b/src/components/Filters/SimpleFilter/SimpleFilter.js @@ -0,0 +1,41 @@ +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { selectAggsState } from '../../../reducers/aggs/selectors'; +import { selectQueryState } from '../../../reducers/query/selectors'; +import { coalesce } from '../../../utils'; +import CollapsibleFilter from '../CollapsibleFilter'; +import MoreOrLess from '../MoreOrLess'; +import AggregationItem from '../AggregationItem/AggregationItem'; + +const SimpleFilter = ({ fieldName, title, desc }) => { + const aggs = useSelector(selectAggsState); + const query = useSelector(selectQueryState); + const activeChildren = coalesce(query, fieldName, []); + const options = coalesce(aggs, fieldName, []); + const hasChildren = activeChildren.length > 0; + + const listComponentProps = { fieldName }; + + return ( + + + + ); +}; + +SimpleFilter.propTypes = { + fieldName: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + desc: PropTypes.string, +}; + +export default SimpleFilter; diff --git a/src/components/Filters/SimpleFilter/SimpleFilter.spec.js b/src/components/Filters/SimpleFilter/SimpleFilter.spec.js new file mode 100644 index 000000000..564e51207 --- /dev/null +++ b/src/components/Filters/SimpleFilter/SimpleFilter.spec.js @@ -0,0 +1,86 @@ +import { testRender as render, screen } from '../../../testUtils/test-utils'; +import SimpleFilter from './SimpleFilter'; +import { merge } from '../../../testUtils/functionHelpers'; +import { defaultAggs } from '../../../reducers/aggs/aggs'; +import { defaultQuery } from '../../../reducers/query/query'; + +const renderComponent = (props, newAggsState, newQueryState) => { + merge(newAggsState, defaultAggs); + merge(newQueryState, defaultQuery); + + const data = { + aggs: newAggsState, + query: newQueryState, + }; + + return render(, { + preloadedState: data, + }); +}; + +describe('component:SimpleFilter', () => { + let props, aggs, query; + + beforeEach(() => { + props = { + fieldName: 'company_response', + desc: 'This is a description', + title: 'Title', + }; + + aggs = { + company_response: [ + { key: 'Closed with non-monetary relief', doc_count: 412732 }, + { key: 'Closed with explanation', doc_count: 345066 }, + { key: 'In progress', doc_count: 86400 }, + { key: 'Closed with monetary relief', doc_count: 244 }, + { key: 'Untimely response', doc_count: 178 }, + ], + }; + + query = {}; + }); + + describe('initial state', () => { + props = { title: 'nana', fieldName: 'company_response' }; + + test('renders without crashing', () => { + renderComponent(props, {}, {}); + expect(screen.getByRole('button')).toHaveAttribute( + 'aria-label', + `Hide ${props.title} filter`, + ); + expect(screen.getByText(props.title)).toBeInTheDocument(); + }); + + test('shows if there are any active children', () => { + query = { + company_response: ['Closed with non-monetary relief'], + }; + + renderComponent(props, aggs, query); + + expect(screen.getByRole('button')).toHaveAttribute( + 'aria-expanded', + 'true', + ); + + aggs.company_response.forEach((response) => { + expect(screen.getByText(response.key)).toBeInTheDocument(); + }); + }); + + test('hides if there are no active children', () => { + renderComponent(props, aggs, query); + + expect(screen.getByRole('button')).toHaveAttribute( + 'aria-expanded', + 'false', + ); + + aggs.company_response.forEach((response) => { + expect(screen.queryByText(response.key)).not.toBeInTheDocument(); + }); + }); + }); +}); diff --git a/src/components/Filters/__tests__/SimpleFilter.spec.js b/src/components/Filters/__tests__/SimpleFilter.spec.js deleted file mode 100644 index 64a9f24af..000000000 --- a/src/components/Filters/__tests__/SimpleFilter.spec.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import { IntlProvider } from 'react-intl'; -import ReduxSimpleFilter, { mapStateToProps } from '../SimpleFilter'; - -import renderer from 'react-test-renderer'; - -/** - * - * @param {object} initialAggs - Aggs state object - * @returns {Function} - Rendering function - */ -function setupSnapshot(initialAggs) { - const middlewares = [thunk]; - const mockStore = configureMockStore(middlewares); - const store = mockStore({ - query: {}, - aggs: { - company_response: initialAggs, - timely: 'yes', - }, - }); - - return renderer.create( - - - - - , - ); -} - -describe('initial state', () => { - it('renders without crashing', () => { - const target = setupSnapshot([{ key: 'foo', doc_count: 99 }]); - const tree = target.toJSON(); - expect(tree).toMatchSnapshot(); - }); -}); - -describe('mapStateToProps', () => { - let state, ownProps; - beforeEach(() => { - state = { - aggs: { - foo: [1, 2, 3, 4, 5, 6], - }, - query: { - foo: [1], - }, - }; - ownProps = { - fieldName: 'foo', - }; - }); - - it('shows if there are any active children', () => { - const actual = mapStateToProps(state, ownProps); - expect(actual).toEqual({ - options: [1, 2, 3, 4, 5, 6], - hasChildren: true, - }); - }); - - it('hides if there are no active children', () => { - state.query.foo = []; - - const actual = mapStateToProps(state, ownProps); - expect(actual).toEqual({ - options: [1, 2, 3, 4, 5, 6], - hasChildren: false, - }); - }); -}); diff --git a/src/components/Filters/__tests__/__snapshots__/SimpleFilter.spec.js.snap b/src/components/Filters/__tests__/__snapshots__/SimpleFilter.spec.js.snap deleted file mode 100644 index 03d4df4d6..000000000 --- a/src/components/Filters/__tests__/__snapshots__/SimpleFilter.spec.js.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`initial state renders without crashing 1`] = ` -
- -
-`; diff --git a/src/utils/index.js b/src/utils/index.js index dc1458e67..9c3a8b32c 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -103,7 +103,7 @@ export const clampDate = (val, min, max) => { * @param {object} object - the object being tested * @param {string} field - the field to check * @param {string | object} alternateValue - the value to use in absence - * @returns {string} the value to use + * @returns {string | Array | object} the value to use */ export const coalesce = (object, field, alternateValue) => { if (typeof object !== 'object') {