From 667e9d551cf671dcae27f61058e9ce254982658d Mon Sep 17 00:00:00 2001 From: Marie-Laure Thuret Date: Thu, 2 Feb 2017 11:15:07 +0100 Subject: [PATCH] fix(StarRating): usage with filters (#1933) --- .../src/components/StarRating.js | 23 ++++++++++--------- .../src/components/StarRating.test.js | 22 +++++++++++++++++- .../src/connectors/connectRange.js | 1 - stories/StarRating.stories.js | 7 +++++- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/packages/react-instantsearch/src/components/StarRating.js b/packages/react-instantsearch/src/components/StarRating.js index d4e569005e..af6d2327d9 100644 --- a/packages/react-instantsearch/src/components/StarRating.js +++ b/packages/react-instantsearch/src/components/StarRating.js @@ -1,7 +1,7 @@ import React, {PropTypes, Component} from 'react'; import translatable from '../core/translatable'; import classNames from './classNames.js'; - +import {isEmpty} from 'lodash'; const cx = classNames('StarRating'); class StarRating extends Component { @@ -44,10 +44,12 @@ class StarRating extends Component { } } - buildItem({max, lowerBound, count, translate, createURL, isLowest}) { - const selected = lowerBound === this.props.currentRefinement.min && - max === this.props.currentRefinement.max; + buildItem({max, lowerBound, count, translate, createURL, isLastSelectableItem}) { const disabled = !count; + const isCurrentMinLower = this.props.currentRefinement.min < lowerBound; + const selected = isLastSelectableItem && isCurrentMinLower || + !disabled && lowerBound === this.props.currentRefinement.min + && max === this.props.currentRefinement.max; const icons = []; for (let icon = 0; icon < max; icon++) { @@ -65,7 +67,7 @@ class StarRating extends Component { // The last item of the list (the default item), should not // be clickable if it is selected. - const isLastAndSelect = isLowest && selected; + const isLastAndSelect = isLastSelectableItem && selected; const StarsWrapper = isLastAndSelect ? 'div' : 'a'; const onClickHandler = isLastAndSelect ? {} : { href: createURL({min: lowerBound, max}), @@ -101,13 +103,12 @@ class StarRating extends Component { render() { const {translate, refine, min, max, count, createURL, canRefine} = this.props; - const items = []; for (let i = max; i >= min; i--) { - const itemCount = count.reduce((acc, item) => { - if (item.value >= i) acc = acc + item.count; - return acc; - }, 0); + const hasCount = !isEmpty(count.filter(item => Number(item.value) === i)); + const lastSelectableItem = count.reduce((acc, item) => item.value < acc.value || !acc.value && hasCount + ? item : acc, {}); + const itemCount = count.reduce((acc, item) => item.value >= i && hasCount ? acc + item.count : acc, 0); items.push(this.buildItem({ lowerBound: i, max, @@ -115,7 +116,7 @@ class StarRating extends Component { count: itemCount, translate, createURL, - isLowest: i === min, + isLastSelectableItem: i === Number(lastSelectableItem.value), })); } return ( diff --git a/packages/react-instantsearch/src/components/StarRating.test.js b/packages/react-instantsearch/src/components/StarRating.test.js index 15e42fc1d5..3a614e2d13 100644 --- a/packages/react-instantsearch/src/components/StarRating.test.js +++ b/packages/react-instantsearch/src/components/StarRating.test.js @@ -56,7 +56,7 @@ describe('StarRating', () => { min={1} max={5} currentRefinement={{min: 1, max: 5}} - count={[{value: '1', count: 1}, + count={[{value: '1', count: 4}, {value: '2', count: 2}, {value: '3', count: 3}, {value: '4', count: 3}, @@ -139,6 +139,26 @@ describe('StarRating', () => { expect(refine.mock.calls[0][0]).toEqual({min: 1, max: 5}); wrapper.unmount(); }); + + it('the default selected range should be the lowest one with count', () => { + const wrapper = mount(starRating); + wrapper.setProps({count: [ + {value: '2', count: 2}, + {value: '3', count: 3}, + {value: '4', count: 3}, + ]}); + + const links = wrapper.find('.ais-StarRating__ratingLink'); + expect(links.first().hasClass('ais-StarRating__ratingLinkSelected')).toBe(false); + + const selected = wrapper.find('.ais-StarRating__ratingLinkSelected'); + expect(selected.find('.ais-StarRating__ratingIconEmpty').length).toEqual(3); + expect(selected.find('.ais-StarRating__ratingIcon').length).toEqual(2); + expect(selected.text()).toContain('8'); + + wrapper.unmount(); + }); + describe('Panel compatibility', () => { it('Should indicate when no more refinement', () => { const canRefine = jest.fn(); diff --git a/packages/react-instantsearch/src/connectors/connectRange.js b/packages/react-instantsearch/src/connectors/connectRange.js index 2f4bb5d3de..6ead3e1999 100644 --- a/packages/react-instantsearch/src/connectors/connectRange.js +++ b/packages/react-instantsearch/src/connectors/connectRange.js @@ -71,7 +71,6 @@ export default createConnector({ const stats = searchResults.results.getFacetByName(attributeName) ? searchResults.results.getFacetStats(attributeName) : null; - if (!stats) { return { canRefine: false, diff --git a/stories/StarRating.stories.js b/stories/StarRating.stories.js index e022049812..572ba46191 100644 --- a/stories/StarRating.stories.js +++ b/stories/StarRating.stories.js @@ -1,6 +1,6 @@ import React from 'react'; import {storiesOf} from '@kadira/storybook'; -import {StarRating, Panel, SearchBox} from '../packages/react-instantsearch/dom'; +import {StarRating, Panel, SearchBox, Configure} from '../packages/react-instantsearch/dom'; import {withKnobs, object, number} from '@kadira/storybook-addon-knobs'; import {WrapWithHits} from './util'; @@ -27,6 +27,11 @@ stories.add('default', () => +).add('with filter on rating', () => + + + + ).add('playground', () =>