Skip to content

Commit

Permalink
feat(hits-per-page-selector): New widget to change hitsPerPage
Browse files Browse the repository at this point in the history
Did a refactoring using Selector instead of IndexSelector for
both indexSelector and hitsPerPageSelector.

Closes #331
  • Loading branch information
Jerska committed Oct 27, 2015
1 parent 4837a4a commit a3e0f78
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 108 deletions.
38 changes: 0 additions & 38 deletions components/IndexSelector.js

This file was deleted.

54 changes: 54 additions & 0 deletions components/Selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
var React = require('react');

class Selector extends React.Component {
handleChange(event) {
this.props.setValue(event.target.value);
}

render() {
var {currentValue, options} = this.props;

var handleChange = this.handleChange.bind(this);

return (
<select
className={this.props.cssClasses.root}
onChange={handleChange}
value={currentValue}
>
{options.map((option) => {
return <option className={this.props.cssClasses.item} key={option.value} value={option.value}>{option.label}</option>;
})}
</select>
);
}
}

Selector.propTypes = {
cssClasses: React.PropTypes.shape({
root: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.arrayOf(React.PropTypes.string)
]),
item: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.arrayOf(React.PropTypes.string)
])
}),
currentValue: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]).isRequired,
options: React.PropTypes.arrayOf(
React.PropTypes.shape({
value: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]).isRequired,
label: React.PropTypes.string.isRequired
})
).isRequired,
setValue: React.PropTypes.func.isRequired
};

module.exports = Selector;
53 changes: 0 additions & 53 deletions components/__tests__/IndexSelector-test.js

This file was deleted.

66 changes: 66 additions & 0 deletions components/__tests__/Selector-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* eslint-env mocha */

import React from 'react';
import expect from 'expect';
import TestUtils from 'react-addons-test-utils';
import Selector from '../Selector';

import expectJSX from 'expect-jsx';
expect.extend(expectJSX);

describe('Selector', () => {
var renderer;

beforeEach(() => {
let {createRenderer} = TestUtils;
renderer = createRenderer();
});


it('should render <Selector/> with strings', () => {
var out = render({
currentValue: 'index-a',
cssClasses: {
root: 'custom-root',
item: 'custom-item'
},
options: [{value: 'index-a', label: 'Index A'}, {value: 'index-b', label: 'Index B'}]
});
expect(out).toEqualJSX(
<select
className="custom-root"
onChange={() => {}}
value="index-a"
>
<option className="custom-item" value="index-a">Index A</option>
<option className="custom-item" value="index-b">Index B</option>
</select>
);
});

it('should render <Selector/> with numbers', () => {
var out = render({
currentValue: 10,
cssClasses: {
root: 'custom-root',
item: 'custom-item'
},
options: [{value: 10, label: '10 results per page'}, {value: 20, label: '20 results per page'}]
});
expect(out).toEqualJSX(
<select
className="custom-root"
onChange={() => {}}
value={10}
>
<option className="custom-item" value={10}>10 results per page</option>
<option className="custom-item" value={20}>20 results per page</option>
</select>
);
});

function render(props = {}) {
renderer.render(<Selector {...props} />);
return renderer.getRenderOutput();
}
});
14 changes: 14 additions & 0 deletions example/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ search.addWidget(
})
);

search.addWidget(
instantsearch.widgets.hitsPerPageSelector({
container: '#hits-per-page-selector',
options: [
{value: 6, label: '6 per page'},
{value: 12, label: '12 per page'},
{value: 24, label: '24 per page'}
],
cssClasses: {
select: 'form-control'
}
})
);

search.addWidget(
instantsearch.widgets.hits({
container: '#hits',
Expand Down
10 changes: 9 additions & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ <h1>Instant search demo <small>using instantsearch.js</small></h1>
<input id="search-box" class="form-control" />
</div>
<div class="row">
<div class="col-md-8">
<div class="col-md-4">
<div id="stats"></div>
</div>
<div class="col-md-4 text-right">
<div class="form-inline">
<div class="form-group">
<label for="hits-per-page-select">Show:</label>
<span id="hits-per-page-selector"></span>
</div>
</div>
</div>
<div class="col-md-4 text-right">
<div class="form-inline">
<div class="form-group">
Expand Down
1 change: 1 addition & 0 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var instantsearch = toFactory(InstantSearch);
instantsearch.widgets = {
hierarchicalMenu: require('../widgets/hierarchical-menu/hierarchical-menu.js'),
hits: require('../widgets/hits/hits'),
hitsPerPageSelector: require('../widgets/hits-per-page-selector/hits-per-page-selector'),
indexSelector: require('../widgets/index-selector/index-selector'),
menu: require('../widgets/menu/menu.js'),
refinementList: require('../widgets/refinement-list/refinement-list.js'),
Expand Down
2 changes: 1 addition & 1 deletion lib/url-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class URLSync {
this.originalConfig = null;
this.timer = timerMaker(Date.now());
this.threshold = options.threshold || 700;
this.trackedParameters = options.trackedParameters || ['query', 'attribute:*', 'index', 'page'];
this.trackedParameters = options.trackedParameters || ['query', 'attribute:*', 'index', 'page', 'hitsPerPage'];
}

getConfiguration(currentConfiguration) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* eslint-env mocha */

import React from 'react';
import expect from 'expect';
import sinon from 'sinon';
import jsdom from 'mocha-jsdom';

import expectJSX from 'expect-jsx';
expect.extend(expectJSX);

import hitsPerPageSelector from '../hits-per-page-selector';
import Selector from '../../../components/Selector';

describe('hitsPerPageSelector()', () => {
jsdom({useEach: true});

var ReactDOM;
var container;
var options;
var cssClasses;
var widget;
var props;
var helper;
var results;
var autoHideContainer;

beforeEach(() => {
autoHideContainer = sinon.stub().returns(Selector);
ReactDOM = {render: sinon.spy()};

hitsPerPageSelector.__Rewire__('ReactDOM', ReactDOM);
hitsPerPageSelector.__Rewire__('autoHideContainer', autoHideContainer);

container = document.createElement('div');
options = [
{value: 10, label: '10 results'},
{value: 20, label: '20 results'}
];
cssClasses = {
root: 'custom-root',
item: 'custom-item'
};
widget = hitsPerPageSelector({container, options, cssClasses});
helper = {
state: {
hitsPerPage: 20
},
setQueryParameter: sinon.spy(),
search: sinon.spy()
};
results = {
hits: []
};
});

it('doesn\'t configure anything', () => {
expect(widget.getConfiguration).toEqual(undefined);
});

it('calls ReactDOM.render(<Selector props />, container)', () => {
widget.render({helper, results, state: helper.state});
props = {
cssClasses: {
root: 'ais-hits-per-page-selector custom-root',
item: 'ais-hits-per-page-selector--item custom-item'
},
currentValue: 20,
hasResults: false,
hideContainerWhenNoResults: false,
options: [
{value: 10, label: '10 results'},
{value: 20, label: '20 results'}
],
setValue: () => {}
};
expect(ReactDOM.render.calledOnce).toBe(true, 'ReactDOM.render called once');
expect(ReactDOM.render.firstCall.args[0]).toEqualJSX(<Selector {...props} />);
expect(ReactDOM.render.firstCall.args[1]).toEqual(container);
});

it('sets the underlying hitsPerPage', () => {
widget.setHitsPerPage(helper, helper.state, 10);
expect(helper.setQueryParameter.calledOnce).toBe(true, 'setQueryParameter called once');
expect(helper.search.calledOnce).toBe(true, 'search called once');
});

it('should throw if there is no name attribute in a passed object', () => {
options.length = 0;
options.push({label: 'Label without a value'});
expect(() => {
widget.init(helper.state, helper);
}).toThrow(/No option in `options` with `value: 20`/);
});

it('must include the current hitsPerPage at initialization time', () => {
helper.state.hitsPerPage = -1;
expect(() => {
widget.init(helper.state, helper);
}).toThrow(/No option in `options` with `value: -1`/);
});

afterEach(() => {
hitsPerPageSelector.__ResetDependency__('ReactDOM');
hitsPerPageSelector.__ResetDependency__('autoHideContainer');
});
});
Loading

0 comments on commit a3e0f78

Please sign in to comment.