Skip to content
This repository has been archived by the owner on Dec 30, 2022. It is now read-only.

Commit

Permalink
fix(StarRatings): always show the stars below (#929)
Browse files Browse the repository at this point in the history
* fix(StarRating): update the computation of rows

* refactor(stories): update StarRating to stories

* fix(StarRating): avoid to throw an error when only min is defined

* feat(StarRating): add test for handle min / max smaller than count min & max

* fix(StarRating): handle min above max
  • Loading branch information
samouss authored Feb 5, 2018
1 parent 08917b6 commit 22bf93a
Show file tree
Hide file tree
Showing 4 changed files with 566 additions and 75 deletions.
75 changes: 41 additions & 34 deletions packages/react-instantsearch/src/components/StarRating.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import translatable from '../core/translatable';
import classNames from './classNames.js';
import { isEmpty } from 'lodash';
const cx = classNames('StarRating');

class StarRating extends Component {
Expand Down Expand Up @@ -128,39 +127,47 @@ 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 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,
refine,
count: itemCount,
translate,
createURL,
isLastSelectableItem: i === Number(lastSelectableItem.value),
})
const { min, max, translate, count, createURL, canRefine } = this.props;

// min & max are always set when there is a results, otherwise it means
// that we don't want to render anything since we don't have any values.
const limitMin = min !== undefined && min >= 0 ? min : 0;
const limitMax = max !== undefined && max >= 0 ? max : -1;
const inclusiveLength = limitMax - limitMin + 1;
const safeInclusiveLength = Math.max(inclusiveLength, 0);

const values = count
.map(item => ({ ...item, value: parseFloat(item.value) }))
.filter(item => item.value >= limitMin && item.value <= limitMax);

const range = new Array(safeInclusiveLength)
.fill(null)
.map((_, index) => {
const element = values.find(item => item.value === limitMax - index);
const placeholder = { value: limitMax - index, count: 0, total: 0 };

return element || placeholder;
})
.reduce(
(acc, item, index) =>
acc.concat({
...item,
total: index === 0 ? item.count : acc[index - 1].total + item.count,
}),
[]
);
}

const items = range.map((item, index) =>
this.buildItem({
lowerBound: item.value,
count: item.total,
isLastSelectableItem: range.length - 1 === index,
max: limitMax,
translate,
createURL,
})
);

return <div {...cx('root', !canRefine && 'noRefinement')}>{items}</div>;
}
}
Expand Down
154 changes: 126 additions & 28 deletions packages/react-instantsearch/src/components/StarRating.test.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import PropTypes from 'prop-types';
/* eslint-env jest, jasmine */

import React from 'react';
import PropTypes from 'prop-types';
import renderer from 'react-test-renderer';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });

import StarRating from './StarRating';

Enzyme.configure({ adapter: new Adapter() });

describe('StarRating', () => {
it('supports passing max/min values', () => {
const tree = renderer
Expand All @@ -33,6 +31,29 @@ describe('StarRating', () => {
expect(tree).toMatchSnapshot();
});

it('supports passing max/min values smaller than count max & min', () => {
const tree = renderer
.create(
<StarRating
createURL={() => '#'}
refine={() => null}
min={2}
max={4}
currentRefinement={{}}
count={[
{ value: '1', count: 1 },
{ value: '2', count: 2 },
{ value: '3', count: 3 },
{ value: '4', count: 4 },
{ value: '5', count: 5 },
]}
canRefine={true}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

it('applies translations', () => {
const tree = renderer
.create(
Expand All @@ -59,6 +80,106 @@ describe('StarRating', () => {
expect(tree).toMatchSnapshot();
});

it('expect to not throw when only min is defined', () => {
expect(() => {
renderer.create(
<StarRating
createURL={() => '#'}
refine={() => null}
translations={{
ratingLabel: ' & Up',
}}
min={3}
currentRefinement={{ min: 1, max: 5 }}
count={[
{ value: '1', count: 1 },
{ value: '2', count: 2 },
{ value: '3', count: 3 },
{ value: '4', count: 4 },
{ value: '5', count: 5 },
]}
canRefine={true}
/>
);
}).not.toThrow();
});

it('expect to not throw when only max is defined', () => {
expect(() => {
renderer.create(
<StarRating
createURL={() => '#'}
refine={() => null}
translations={{
ratingLabel: ' & Up',
}}
max={3}
currentRefinement={{ min: 1, max: 5 }}
count={[
{ value: '1', count: 1 },
{ value: '2', count: 2 },
{ value: '3', count: 3 },
{ value: '4', count: 4 },
{ value: '5', count: 5 },
]}
canRefine={true}
/>
);
}).not.toThrow();
});

it('expect to render from from 0 when min is negative', () => {
const tree = renderer
.create(
<StarRating
createURL={() => '#'}
refine={() => null}
translations={{
ratingLabel: ' & Up',
}}
min={-5}
max={5}
currentRefinement={{ min: 1, max: 5 }}
count={[
{ value: '1', count: 1 },
{ value: '2', count: 2 },
{ value: '3', count: 3 },
{ value: '4', count: 4 },
{ value: '5', count: 5 },
]}
canRefine={true}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

it('expect to render nothing when min is higher than max', () => {
const tree = renderer
.create(
<StarRating
createURL={() => '#'}
refine={() => null}
translations={{
ratingLabel: ' & Up',
}}
min={5}
max={3}
currentRefinement={{ min: 1, max: 5 }}
count={[
{ value: '1', count: 1 },
{ value: '2', count: 2 },
{ value: '3', count: 3 },
{ value: '4', count: 4 },
{ value: '5', count: 5 },
]}
canRefine={true}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
});

const refine = jest.fn();
const createURL = jest.fn();
const starRating = (
Expand Down Expand Up @@ -153,29 +274,6 @@ describe('StarRating', () => {
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')).toHaveLength(3);
expect(selected.find('.ais-StarRating__ratingIcon')).toHaveLength(2);
expect(selected.text()).toContain('8');

wrapper.unmount();
});

describe('Panel compatibility', () => {
it('Should indicate when no more refinement', () => {
const canRefine = jest.fn();
Expand Down
Loading

0 comments on commit 22bf93a

Please sign in to comment.