Skip to content

Commit

Permalink
feat(connector): hits-per-page-selector connector refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandre Stanislawski committed Feb 15, 2017
1 parent f727949 commit dd794e0
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 97 deletions.
9 changes: 6 additions & 3 deletions src/components/Selector.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React from 'react';

class Selector extends React.Component {
import autoHideContainer from '../decorators/autoHideContainer.js';
import headerFooter from '../decorators/headerFooter.js';

export class RawSelector extends React.Component {
componentWillMount() {
this.handleChange = this.handleChange.bind(this);
}
Expand Down Expand Up @@ -31,7 +34,7 @@ class Selector extends React.Component {
}
}

Selector.propTypes = {
RawSelector.propTypes = {
cssClasses: React.PropTypes.shape({
root: React.PropTypes.oneOfType([
React.PropTypes.string,
Expand All @@ -58,4 +61,4 @@ Selector.propTypes = {
setValue: React.PropTypes.func.isRequired,
};

export default Selector;
export default autoHideContainer(headerFooter(RawSelector));
2 changes: 1 addition & 1 deletion src/components/__tests__/Selector-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React from 'react';
import expect from 'expect';
import TestUtils from 'react-addons-test-utils';
import Selector from '../Selector';
import {RawSelector as Selector} from '../Selector';

import expectJSX from 'expect-jsx';
expect.extend(expectJSX);
Expand Down
109 changes: 109 additions & 0 deletions src/connectors/hits-per-page-selector/connectHitsPerPageSelector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
bemHelper,
getContainerNode,
} from '../../lib/utils.js';
import some from 'lodash/some';
import cx from 'classnames';

const bem = bemHelper('ais-hits-per-page-selector');

/**
* Instantiate a dropdown element to choose the number of hits to display per page
* @function hitsPerPageSelector
* @param {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget
* @param {Array} options.options Array of objects defining the different values and labels
* @param {number} options.options[0].value number of hits to display per page
* @param {string} options.options[0].label Label to display in the option
* @param {boolean} [options.autoHideContainer=false] Hide the container when no results match
* @param {Object} [options.cssClasses] CSS classes to be added
* @param {string|string[]} [options.cssClasses.root] CSS classes added to the parent `<select>`
* @param {string|string[]} [options.cssClasses.item] CSS classes added to each `<option>`
* @return {Object}
*/

const usage = `Usage:
hitsPerPageSelector({
container,
options,
[ cssClasses.{root,item}={} ],
[ autoHideContainer=false ]
})`;

const connectHitsPerPageSelector = renderHitsPerPageSelector => ({
container,
options: userOptions,
cssClasses: userCssClasses = {},
autoHideContainer = false,
} = {}) => {
let options = userOptions;

if (!container || !options) {
throw new Error(usage);
}

const containerNode = getContainerNode(container);

const cssClasses = {
root: cx(bem(null), userCssClasses.root),
item: cx(bem('item'), userCssClasses.item),
};

return {
init({helper, state}) {
const isCurrentInOptions = some(
options,
option => Number(state.hitsPerPage) === Number(option.value)
);

if (!isCurrentInOptions) {
if (state.hitsPerPage === undefined) {
if (window.console) {
window.console.log(
`[Warning][hitsPerPageSelector] hitsPerPage not defined.
You should probably use a \`hits\` widget or set the value \`hitsPerPage\`
using the searchParameters attribute of the instantsearch constructor.`
);
}
} else if (window.console) {
window.console.log(
`[Warning][hitsPerPageSelector] No option in \`options\`
with \`value: hitsPerPage\` (hitsPerPage: ${state.hitsPerPage})`
);
}

options = [{value: undefined, label: ''}].concat(options);
}

const currentValue = state.hitsPerPage;

this.setHitsPerPage = value => helper
.setQueryParameter('hitsPerPage', Number(value))
.search();

renderHitsPerPageSelector({
cssClasses,
currentValue,
options,
setValue: this.setHitsPerPage,
shouldAutoHideContainer: autoHideContainer,
containerNode,
}, true);
},

render({state, results}) {
const currentValue = state.hitsPerPage;
const hasNoResults = results.nbHits === 0;

renderHitsPerPageSelector({
cssClasses,
currentValue,
options,
setValue: this.setHitsPerPage,
shouldAutoHideContainer: autoHideContainer && hasNoResults,
containerNode,
}, false);
},
};
};

export default connectHitsPerPageSelector;
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,13 @@ describe('hitsPerPageSelector()', () => {
let props;
let helper;
let results;
let autoHideContainer;
let consoleLog;
let state;

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

hitsPerPageSelector.__Rewire__('ReactDOM', ReactDOM);
hitsPerPageSelector.__Rewire__('autoHideContainerHOC', autoHideContainer);
consoleLog = sinon.stub(window.console, 'log');

container = document.createElement('div');
Expand Down Expand Up @@ -83,14 +80,13 @@ describe('hitsPerPageSelector()', () => {
item: 'ais-hits-per-page-selector--item custom-item',
},
currentValue: 10,
shouldAutoHideContainer: true,
options: [
{value: 10, label: '10 results'},
{value: 20, label: '20 results'},
],
setValue: () => {},
};
expect(ReactDOM.render.calledTwice).toBe(true, 'ReactDOM.render called twice');
expect(ReactDOM.render.callCount).toBe(2);
expect(ReactDOM.render.firstCall.args[0]).toEqualJSX(<Selector {...props} />);
expect(ReactDOM.render.firstCall.args[1]).toEqual(container);
expect(ReactDOM.render.secondCall.args[0]).toEqualJSX(<Selector {...props} />);
Expand Down Expand Up @@ -136,7 +132,6 @@ with \`value: hitsPerPage\` (hitsPerPage: -1)`

afterEach(() => {
hitsPerPageSelector.__ResetDependency__('ReactDOM');
hitsPerPageSelector.__ResetDependency__('autoHideContainerHOC');
consoleLog.restore();
});
});
110 changes: 23 additions & 87 deletions src/widgets/hits-per-page-selector/hits-per-page-selector.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {
bemHelper,
getContainerNode,
} from '../../lib/utils.js';
import some from 'lodash/some';
import cx from 'classnames';
import autoHideContainerHOC from '../../decorators/autoHideContainer.js';
import SelectorComponent from '../../components/Selector.js';

const bem = bemHelper('ais-hits-per-page-selector');
import Selector from '../../components/Selector.js';

import connectHitsPerPageSelector from '../../connectors/hits-per-page-selector/connectHitsPerPageSelector.js';

/**
* Instantiate a dropdown element to choose the number of hits to display per page
Expand All @@ -25,83 +19,25 @@ const bem = bemHelper('ais-hits-per-page-selector');
* @return {Object}
*/

const usage = `Usage:
hitsPerPageSelector({
container,
options,
[ cssClasses.{root,item}={} ],
[ autoHideContainer=false ]
})`;
function hitsPerPageSelector({
container,
options: userOptions,
cssClasses: userCssClasses = {},
autoHideContainer = false,
} = {}) {
let options = userOptions;

if (!container || !options) {
throw new Error(usage);
}

const containerNode = getContainerNode(container);
let Selector = SelectorComponent;
if (autoHideContainer === true) {
Selector = autoHideContainerHOC(Selector);
}

const cssClasses = {
root: cx(bem(null), userCssClasses.root),
item: cx(bem('item'), userCssClasses.item),
};

return {
init({helper, state}) {
const isCurrentInOptions = some(
options,
option => Number(state.hitsPerPage) === Number(option.value)
);
export default connectHitsPerPageSelector(defaultRendering);

if (!isCurrentInOptions) {
if (state.hitsPerPage === undefined) {
if (window.console) {
window.console.log(
`[Warning][hitsPerPageSelector] hitsPerPage not defined.
You should probably use a \`hits\` widget or set the value \`hitsPerPage\`
using the searchParameters attribute of the instantsearch constructor.`
);
}
} else if (window.console) {
window.console.log(
`[Warning][hitsPerPageSelector] No option in \`options\`
with \`value: hitsPerPage\` (hitsPerPage: ${state.hitsPerPage})`
);
}

options = [{value: undefined, label: ''}].concat(options);
}

this.setHitsPerPage = value => helper
.setQueryParameter('hitsPerPage', Number(value))
.search();
},

render({state, results}) {
const currentValue = state.hitsPerPage;
const hasNoResults = results.nbHits === 0;

ReactDOM.render(
<Selector
cssClasses={cssClasses}
currentValue={currentValue}
options={options}
setValue={this.setHitsPerPage}
shouldAutoHideContainer={hasNoResults}
/>,
containerNode
);
},
};
function defaultRendering({
cssClasses,
currentValue,
options,
setValue,
shouldAutoHideContainer,
containerNode,
}, isFirstRendering) {
if (isFirstRendering) return;
ReactDOM.render(
<Selector
cssClasses={cssClasses}
currentValue={currentValue}
options={options}
setValue={setValue}
shouldAutoHideContainer={shouldAutoHideContainer}
/>,
containerNode
);
}

export default hitsPerPageSelector;

0 comments on commit dd794e0

Please sign in to comment.