From e4d88e0c73eb4405204e91b943082777ff4fbbb7 Mon Sep 17 00:00:00 2001 From: Vincent Voyer Date: Tue, 15 Nov 2016 16:23:36 +0100 Subject: [PATCH] fix(toggle): support negative numeric values for on/off (#1551) fixes #1537 Using stock:-2 as a facetRefinement is supported only if you escape the minus sign, otherwise it's considered as negative facet refinement (remove "-2" from facets). --- .../__tests__/currentToggle-test.js | 47 +++++ .../toggle/implementations/currentToggle.js | 194 ++++++++++-------- 2 files changed, 151 insertions(+), 90 deletions(-) diff --git a/src/widgets/toggle/implementations/__tests__/currentToggle-test.js b/src/widgets/toggle/implementations/__tests__/currentToggle-test.js index 665fd6b72e..ed5a7e3d39 100644 --- a/src/widgets/toggle/implementations/__tests__/currentToggle-test.js +++ b/src/widgets/toggle/implementations/__tests__/currentToggle-test.js @@ -187,6 +187,53 @@ describe('currentToggle()', () => { expect(ReactDOM.render.secondCall.args[0]).toEqualJSX(); }); + it('supports negative numeric off or on values', () => { + results = { + hits: [{Hello: ', world!'}], + nbHits: 1, + getFacetValues: sinon.stub().returns([ + {name: '-2', count: 2, isRefined: true}, + {name: '5', count: 1, isRefined: false}, + ]), + }; + widget = currentToggle({ + containerNode, + hasAnOffValue: true, + attributeName, + label, + cssClasses, + userValues: { + off: -2, + on: 5, + }, + RefinementList, + collapsible, + }); + widget.getConfiguration(); + widget.init({state, helper}); + widget.render({results, helper, state, createURL}); + widget.render({results, helper, state, createURL}); + + props = { + facetValues: [{ + count: 1, + isRefined: false, + name: label, + offFacetValue: {count: 2, name: label, isRefined: true}, + onFacetValue: {count: 1, name: label, isRefined: false}, + }], + shouldAutoHideContainer: false, + ...props, + }; + + expect(ReactDOM.render.firstCall.args[0]).toEqualJSX(); + expect(ReactDOM.render.secondCall.args[0]).toEqualJSX(); + + widget.toggleRefinement(helper, 'facetValueToRefine', true); + expect(helper.removeDisjunctiveFacetRefinement.calledWith(attributeName, 5)).toBe(true); + expect(helper.addDisjunctiveFacetRefinement.calledWith(attributeName, '\\-2')).toBe(true); + }); + it('without facet values', () => { results = { hits: [], diff --git a/src/widgets/toggle/implementations/currentToggle.js b/src/widgets/toggle/implementations/currentToggle.js index d817e2c161..0f7bfa6d8a 100644 --- a/src/widgets/toggle/implementations/currentToggle.js +++ b/src/widgets/toggle/implementations/currentToggle.js @@ -19,106 +19,120 @@ const currentToggle = ({ containerNode, RefinementList, cssClasses, -} = {}) => ({ - getConfiguration() { - return { - disjunctiveFacets: [attributeName], - }; - }, - toggleRefinement(helper, facetValue, isRefined) { - const on = userValues.on; - const off = userValues.off; +} = {}) => { + const on = userValues ? escapeRefinement(userValues.on) : undefined; + const off = userValues ? escapeRefinement(userValues.off) : undefined; - // Checking - if (!isRefined) { - if (hasAnOffValue) { - helper.removeDisjunctiveFacetRefinement(attributeName, off); + return { + getConfiguration() { + return { + disjunctiveFacets: [attributeName], + }; + }, + toggleRefinement(helper, facetValue, isRefined) { + // Checking + if (!isRefined) { + if (hasAnOffValue) { + helper.removeDisjunctiveFacetRefinement(attributeName, off); + } + helper.addDisjunctiveFacetRefinement(attributeName, on); + } else { + // Unchecking + helper.removeDisjunctiveFacetRefinement(attributeName, on); + if (hasAnOffValue) { + helper.addDisjunctiveFacetRefinement(attributeName, off); + } } - helper.addDisjunctiveFacetRefinement(attributeName, on); - } else { - // Unchecking - helper.removeDisjunctiveFacetRefinement(attributeName, on); - if (hasAnOffValue) { - helper.addDisjunctiveFacetRefinement(attributeName, off); - } - } - helper.search(); - }, - init({state, helper, templatesConfig}) { - this._templateProps = prepareTemplateProps({ - transformData, - defaultTemplates, - templatesConfig, - templates, - }); + helper.search(); + }, + init({state, helper, templatesConfig}) { + this._templateProps = prepareTemplateProps({ + transformData, + defaultTemplates, + templatesConfig, + templates, + }); - this.toggleRefinement = this.toggleRefinement.bind(this, helper); + this.toggleRefinement = this.toggleRefinement.bind(this, helper); - // no need to refine anything at init if no custom off values - if (!hasAnOffValue) { - return; - } + // no need to refine anything at init if no custom off values + if (!hasAnOffValue) { + return; + } - // Add filtering on the 'off' value if set - const isRefined = state.isDisjunctiveFacetRefined(attributeName, userValues.on); - if (!isRefined) { - helper.addDisjunctiveFacetRefinement(attributeName, userValues.off); - } - }, - render({helper, results, state, createURL}) { - const isRefined = helper.state.isDisjunctiveFacetRefined(attributeName, userValues.on); - const onValue = userValues.on; - const offValue = userValues.off === undefined ? false : userValues.off; - const allFacetValues = results.getFacetValues(attributeName); - const onData = find(allFacetValues, {name: onValue.toString()}); - const onFacetValue = { - name: label, - isRefined: onData !== undefined ? onData.isRefined : false, - count: onData === undefined ? null : onData.count, - }; - const offData = hasAnOffValue ? find(allFacetValues, {name: offValue.toString()}) : undefined; - const offFacetValue = { - name: label, - isRefined: offData !== undefined ? offData.isRefined : false, - count: offData === undefined ? results.nbHits : offData.count, - }; + // Add filtering on the 'off' value if set + const isRefined = state.isDisjunctiveFacetRefined(attributeName, on); + if (!isRefined) { + helper.addDisjunctiveFacetRefinement(attributeName, off); + } + }, + render({helper, results, state, createURL}) { + const isRefined = helper.state.isDisjunctiveFacetRefined(attributeName, on); + const onValue = on; + const offValue = off === undefined ? false : off; + const allFacetValues = results.getFacetValues(attributeName); + const onData = find(allFacetValues, {name: unescapeRefinement(onValue)}); + const onFacetValue = { + name: label, + isRefined: onData !== undefined ? onData.isRefined : false, + count: onData === undefined ? null : onData.count, + }; + const offData = hasAnOffValue ? find(allFacetValues, {name: unescapeRefinement(offValue)}) : undefined; + const offFacetValue = { + name: label, + isRefined: offData !== undefined ? offData.isRefined : false, + count: offData === undefined ? results.nbHits : offData.count, + }; - // what will we show by default, - // if checkbox is not checked, show: [ ] free shipping (countWhenChecked) - // if checkbox is checked, show: [x] free shipping (countWhenNotChecked) - const nextRefinement = isRefined ? offFacetValue : onFacetValue; + // what will we show by default, + // if checkbox is not checked, show: [ ] free shipping (countWhenChecked) + // if checkbox is checked, show: [x] free shipping (countWhenNotChecked) + const nextRefinement = isRefined ? offFacetValue : onFacetValue; - const facetValue = { - name: label, - isRefined, - count: nextRefinement === undefined ? null : nextRefinement.count, - onFacetValue, - offFacetValue, - }; + const facetValue = { + name: label, + isRefined, + count: nextRefinement === undefined ? null : nextRefinement.count, + onFacetValue, + offFacetValue, + }; - // Bind createURL to this specific attribute - function _createURL() { - return createURL( - state - .removeDisjunctiveFacetRefinement(attributeName, isRefined ? onValue : userValues.off) - .addDisjunctiveFacetRefinement(attributeName, isRefined ? userValues.off : onValue) + // Bind createURL to this specific attribute + function _createURL() { + return createURL( + state + .removeDisjunctiveFacetRefinement(attributeName, isRefined ? onValue : off) + .addDisjunctiveFacetRefinement(attributeName, isRefined ? off : onValue) + ); + } + + ReactDOM.render( + , + containerNode ); - } + }, + }; +}; + +function escapeRefinement(value) { + if (typeof value === 'number' && value < 0) { + value = String(value).replace('-', '\\-'); + } + + return value; +} - ReactDOM.render( - , - containerNode - ); - }, -}); +function unescapeRefinement(value) { + return String(value).replace(/^\\-/, '-'); +} export default currentToggle;