Skip to content

Commit

Permalink
feat(connector): connectNumericRefinementList (iteration 2)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandre Stanislawski committed Mar 22, 2017
1 parent 12a7935 commit bfcf860
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 93 deletions.
1 change: 1 addition & 0 deletions dev/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ search.addWidget(
templates: {
header: 'Numeric refinement list (price)',
},
//autoHideContainer: false,
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@ describe('connectNumericRefinementList', () => {
// test if isFirstRendering is true during init
expect(rendering.lastCall.args[1]).toBe(true);

const firstRenderingOptions = rendering.lastCall.args[0];
expect(firstRenderingOptions.shouldAutoHideContainer).toBe(true);
expect(firstRenderingOptions.collapsible).toBe(false);
expect(firstRenderingOptions.containerNode).toBe(container);

widget.render({
results: new SearchResults(helper.state, [{nbHits: 0}]),
state: helper.state,
Expand All @@ -56,11 +51,6 @@ describe('connectNumericRefinementList', () => {
// test that rendering has been called during init with isFirstRendering = false
expect(rendering.callCount).toBe(2);
expect(rendering.lastCall.args[1]).toBe(false);

const secondRenderingOptions = rendering.lastCall.args[0];
expect(secondRenderingOptions.shouldAutoHideContainer).toBe(true);
expect(secondRenderingOptions.collapsible).toBe(false);
expect(secondRenderingOptions.containerNode).toBe(container);
});

it('Provide a function to update the refinements at each step', () => {
Expand Down Expand Up @@ -222,4 +212,67 @@ describe('connectNumericRefinementList', () => {
toggleRefinement = renderingParameters.toggleRefinement;
});
});

it('when the state is cleared, the "no value" value should be refined', () => {
const container = document.createElement('div');
const rendering = sinon.stub();
const makeWidget = connectNumericRefinementList(rendering);
const listOptions = [
{name: 'below 10', end: 10},
{name: '10 - 20', start: 10, end: 20},
{name: 'more than 20', start: 20},
{name: '42', start: 42, end: 42},
{name: 'void'},
];
const widget = makeWidget({
container,
attributeName: 'numerics',
options: listOptions,
});

const helper = jsHelper(fakeClient);
helper.search = sinon.stub();

widget.init({
helper,
state: helper.state,
createURL: () => '#',
onHistoryChange: () => {},
});

const toggleRefinement = rendering.lastCall.args[0].toggleRefinement;
// a user selects a value in the refinement list
toggleRefinement(listOptions[0].name);

widget.render({
results: new SearchResults(helper.state, [{}]),
state: helper.state,
helper,
createURL: () => '#',
});

// No option should be selected
const expectedResults0 = [...listOptions].map(o => ({...o, isRefined: false, attributeName: 'numerics'}));
expectedResults0[0].isRefined = true;

const renderingParameters0 = rendering.lastCall.args[0];
expect(renderingParameters0.facetValues).toEqual(expectedResults0);

// All the refinements are cleared by a third party
helper.removeNumericRefinement('numerics');

widget.render({
results: new SearchResults(helper.state, [{}]),
state: helper.state,
helper,
createURL: () => '#',
});

// No option should be selected
const expectedResults1 = [...listOptions].map(o => ({...o, isRefined: false, attributeName: 'numerics'}));
expectedResults1[4].isRefined = true;

const renderingParameters1 = rendering.lastCall.args[0];
expect(renderingParameters1.facetValues).toEqual(expectedResults1);
});
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import {
bemHelper,
prepareTemplateProps,
getContainerNode,
} from '../../lib/utils.js';
import cx from 'classnames';
import find from 'lodash/find';
import includes from 'lodash/includes';
import defaultTemplates from './defaultTemplates.js';

const bem = bemHelper('ais-refinement-list');

/**
* Instantiate a list of refinements based on a facet
Expand Down Expand Up @@ -40,53 +31,20 @@ const bem = bemHelper('ais-refinement-list');
* @return {Object}
*/
const usage = `Usage:
numericRefinementList({
container,
connectNumericRefinementList(renderer)({
attributeName,
options,
[ cssClasses.{root,header,body,footer,list,item,active,label,radio,count} ],
[ templates.{header,item,footer} ],
[ transformData.{item} ],
[ autoHideContainer ],
[ collapsible=false ]
options
})`;
const connectNumericRefinementList = numericRefinementListRendering => ({
container,
attributeName,
options,
cssClasses: userCssClasses = {},
templates = defaultTemplates,
collapsible = false,
transformData,
autoHideContainer = true,
}) => {
if (!container || !attributeName || !options) {
if (!attributeName || !options) {
throw new Error(usage);
}

const containerNode = getContainerNode(container);

const cssClasses = {
root: cx(bem(null), userCssClasses.root),
header: cx(bem('header'), userCssClasses.header),
body: cx(bem('body'), userCssClasses.body),
footer: cx(bem('footer'), userCssClasses.footer),
list: cx(bem('list'), userCssClasses.list),
item: cx(bem('item'), userCssClasses.item),
label: cx(bem('label'), userCssClasses.label),
radio: cx(bem('radio'), userCssClasses.radio),
active: cx(bem('item', 'active'), userCssClasses.active),
};

return {
init({templatesConfig, helper, createURL}) {
this._templateProps = prepareTemplateProps({
transformData,
defaultTemplates,
templatesConfig,
templates,
});

init({helper, createURL, instantSearchInstance}) {
this._toggleRefinement = facetValue => {
const refinedState = refine(helper.state, attributeName, options, facetValue);
helper.setState(refinedState).search();
Expand All @@ -103,17 +61,14 @@ const connectNumericRefinementList = numericRefinementListRendering => ({
);

numericRefinementListRendering({
collapsible,
createURL: this._createURL(helper.state),
cssClasses,
facetValues,
shouldAutoHideContainer: autoHideContainer,
templateProps: this._templateProps,
noResults: true,
toggleRefinement: this._toggleRefinement,
containerNode,
instantSearchInstance,
}, true);
},
render({results, state}) {
render({results, state, instantSearchInstance}) {
const facetValues = options.map(facetValue =>
({
...facetValue,
Expand All @@ -122,15 +77,14 @@ const connectNumericRefinementList = numericRefinementListRendering => ({
})
);

const noResults = results.nbHits === 0;

numericRefinementListRendering({
collapsible,
createURL: this._createURL(state),
cssClasses,
facetValues,
shouldAutoHideContainer: autoHideContainer && results.nbHits === 0,
templateProps: this._templateProps,
noResults,
toggleRefinement: this._toggleRefinement,
containerNode,
instantSearchInstance,
}, false);
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('numericRefinementList()', () => {
helper.state.clearRefinements = sinon.stub().returns(helper.state);
helper.state.addNumericRefinement = sinon.stub().returns(helper.state);
createURL = () => '#';
widget.init({helper});
widget.init({helper, instantSearchInstance: {}});
});

it('calls twice ReactDOM.render(<RefinementList props />, container)', () => {
Expand Down Expand Up @@ -186,7 +186,7 @@ describe('numericRefinementList()', () => {
});

// The lifeccycle impose all the steps
testWidget.init({helper, createURL: () => ''});
testWidget.init({helper, createURL: () => '', instantSearchInstance: {}});

// When
testWidget.render({state, results, createURL});
Expand Down
131 changes: 107 additions & 24 deletions src/widgets/numeric-refinement-list/numeric-refinement-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@ import RefinementList from '../../components/RefinementList/RefinementList.js';

import connectNumericRefinementList from '../../connectors/numeric-refinement-list/connectNumericRefinementList.js';

import defaultTemplates from './defaultTemplates.js';

import {
bemHelper,
prepareTemplateProps,
getContainerNode,
} from '../../lib/utils.js';
import cx from 'classnames';

const bem = bemHelper('ais-refinement-list');

const renderer = ({
containerNode,
collapsible,
autoHideContainer,
cssClasses,
renderState,
transformData,
templates,
}) => ({
createURL,
instantSearchInstance,
toggleRefinement,
facetValues,
noResults,
}, isFirstRendering) => {
if (isFirstRendering) {
renderState.templateProps = prepareTemplateProps({
transformData,
defaultTemplates,
templatesConfig: instantSearchInstance.templatesConfig,
templates,
});
return;
}

ReactDOM.render(
<RefinementList
collapsible={collapsible}
createURL={createURL}
cssClasses={cssClasses}
facetValues={facetValues}
shouldAutoHideContainer={autoHideContainer && noResults}
templateProps={renderState.templateProps}
toggleRefinement={toggleRefinement}
/>,
containerNode
);
};

/**
* Instantiate a list of refinements based on a facet
* @function numericRefinementList
Expand Down Expand Up @@ -33,29 +83,62 @@ import connectNumericRefinementList from '../../connectors/numeric-refinement-li
* @param {boolean} [options.collapsible.collapsed] Initial collapsed state of a collapsible widget
* @return {Object}
*/
export default connectNumericRefinementList(defaultRendering);
const usage = `Usage:
numericRefinementList({
container,
attributeName,
options,
[ cssClasses.{root,header,body,footer,list,item,active,label,radio,count} ],
[ templates.{header,item,footer} ],
[ transformData.{item} ],
[ autoHideContainer ],
[ collapsible=false ]
})`;

function defaultRendering({
collapsible,
createURL,
cssClasses,
facetValues,
shouldAutoHideContainer,
templateProps,
toggleRefinement,
containerNode,
}, isFirstRendering) {
if (isFirstRendering) return;
ReactDOM.render(
<RefinementList
collapsible={collapsible}
createURL={createURL}
cssClasses={cssClasses}
facetValues={facetValues}
shouldAutoHideContainer={shouldAutoHideContainer}
templateProps={templateProps}
toggleRefinement={toggleRefinement}
/>,
containerNode
);
export default function numericRefinementList({
container,
attributeName,
options,
cssClasses: userCssClasses = {},
templates = defaultTemplates,
collapsible = false,
transformData,
autoHideContainer = true,
}) {
if (!container || !attributeName || !options) {
throw new Error(usage);
}

const containerNode = getContainerNode(container);

const cssClasses = {
root: cx(bem(null), userCssClasses.root),
header: cx(bem('header'), userCssClasses.header),
body: cx(bem('body'), userCssClasses.body),
footer: cx(bem('footer'), userCssClasses.footer),
list: cx(bem('list'), userCssClasses.list),
item: cx(bem('item'), userCssClasses.item),
label: cx(bem('label'), userCssClasses.label),
radio: cx(bem('radio'), userCssClasses.radio),
active: cx(bem('item', 'active'), userCssClasses.active),
};

const specializedRenderer = renderer({
containerNode,
collapsible,
autoHideContainer,
cssClasses,
renderState: {},
transformData,
templates,
});
try {
const makeNumericRefinementList = connectNumericRefinementList(specializedRenderer);
return makeNumericRefinementList({
attributeName,
options,
});
} catch (e) {
throw new Error(usage);
}
}

0 comments on commit bfcf860

Please sign in to comment.