Skip to content

Commit e782ab8

Browse files
authored
fix(clear-all): apply excludeAttribute correctly with clearsQuery (#2935)
* fix(clear-all): apply excludeAttribute correctly This fix actually contains a rewrite of the underlying functions that will compute the list of attributes to clear. The complete required behaviour was spread both in the connectors and the utility functions. In order to simplify the code and make it easier to test, this refactoring was needed. A next step for this is to use a single connnector for current refined values and clear all widgets. * refactor(connectClearAll-test): move to jest * refactor(connectCurrentRefinedValues-test): move to jest * refactor(utils): use [].forEach
1 parent ecfd2f5 commit e782ab8

File tree

8 files changed

+579
-490
lines changed

8 files changed

+579
-490
lines changed

src/connectors/clear-all/__tests__/connectClearAll-test.js

Lines changed: 228 additions & 33 deletions
Large diffs are not rendered by default.

src/connectors/clear-all/connectClearAll.js

Lines changed: 40 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import {
22
checkRendering,
3-
getRefinements,
4-
clearRefinementsFromState,
5-
clearRefinementsAndSearch,
3+
clearRefinements,
4+
getAttributesToClear,
65
} from '../../lib/utils.js';
76

87
const usage = `Usage:
@@ -24,17 +23,6 @@ search.addWidget(
2423
Full documentation available at https://community.algolia.com/instantsearch.js/v2/connectors/connectClearAll.html
2524
`;
2625

27-
const refine = ({
28-
helper,
29-
clearAttributes,
30-
hasRefinements,
31-
clearsQuery,
32-
}) => () => {
33-
if (hasRefinements) {
34-
clearRefinementsAndSearch(helper, clearAttributes, clearsQuery);
35-
}
36-
};
37-
3826
/**
3927
* @typedef {Object} CustomClearAllWidgetOptions
4028
* @property {string[]} [excludeAttributes = []] Every attributes that should not be removed when calling `refine()`.
@@ -98,68 +86,64 @@ export default function connectClearAll(renderFn, unmountFn) {
9886
const { excludeAttributes = [], clearsQuery = false } = widgetParams;
9987

10088
return {
101-
// Provide the same function to the `renderFn` so that way the user
102-
// has to only bind it once when `isFirstRendering` for instance
103-
_refine() {},
104-
_cachedRefine() {
105-
this._refine();
106-
},
107-
10889
init({ helper, instantSearchInstance, createURL }) {
109-
this._cachedRefine = this._cachedRefine.bind(this);
110-
111-
const clearAttributes = getRefinements({}, helper.state)
112-
.map(one => one.attributeName)
113-
.filter(one => excludeAttributes.indexOf(one) === -1);
114-
115-
const hasRefinements = clearsQuery
116-
? clearAttributes.length !== 0 || helper.state.query !== ''
117-
: clearAttributes.length !== 0;
118-
const preparedCreateURL = () =>
119-
createURL(clearRefinementsFromState(helper.state, [], clearsQuery));
120-
121-
this._refine = refine({
90+
const attributesToClear = getAttributesToClear({
12291
helper,
123-
clearAttributes,
124-
hasRefinements,
125-
clearsQuery,
92+
blackList: excludeAttributes,
12693
});
12794

95+
const hasRefinements = clearsQuery
96+
? attributesToClear.length !== 0 || helper.state.query !== ''
97+
: attributesToClear.length !== 0;
98+
99+
this._refine = () => {
100+
helper
101+
.setState(
102+
clearRefinements({
103+
helper,
104+
blackList: excludeAttributes,
105+
clearsQuery,
106+
})
107+
)
108+
.search();
109+
};
110+
111+
this._createURL = () =>
112+
createURL(
113+
clearRefinements({
114+
helper,
115+
blackList: excludeAttributes,
116+
clearsQuery,
117+
})
118+
);
119+
128120
renderFn(
129121
{
130-
refine: this._cachedRefine,
122+
refine: this._refine,
131123
hasRefinements,
132-
createURL: preparedCreateURL,
124+
createURL: this._createURL,
133125
instantSearchInstance,
134126
widgetParams,
135127
},
136128
true
137129
);
138130
},
139131

140-
render({ results, state, createURL, helper, instantSearchInstance }) {
141-
const clearAttributes = getRefinements(results, state)
142-
.map(one => one.attributeName)
143-
.filter(one => excludeAttributes.indexOf(one) === -1);
144-
145-
const hasRefinements = clearsQuery
146-
? clearAttributes.length !== 0 || helper.state.query !== ''
147-
: clearAttributes.length !== 0;
148-
const preparedCreateURL = () =>
149-
createURL(clearRefinementsFromState(state, [], clearsQuery));
150-
151-
this._refine = refine({
132+
render({ helper, instantSearchInstance }) {
133+
const attributesToClear = getAttributesToClear({
152134
helper,
153-
clearAttributes,
154-
hasRefinements,
155-
clearsQuery,
135+
blackList: excludeAttributes,
156136
});
157137

138+
const hasRefinements = clearsQuery
139+
? attributesToClear.length !== 0 || helper.state.query !== ''
140+
: attributesToClear.length !== 0;
141+
158142
renderFn(
159143
{
160-
refine: this._cachedRefine,
144+
refine: this._refine,
161145
hasRefinements,
162-
createURL: preparedCreateURL,
146+
createURL: this._createURL,
163147
instantSearchInstance,
164148
widgetParams,
165149
},

src/connectors/current-refined-values/__tests__/connectCurrentRefinedValues-test.js

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
1-
import sinon from 'sinon';
21
import jsHelper from 'algoliasearch-helper';
32
const SearchResults = jsHelper.SearchResults;
43
import connectCurrentRefinedValues from '../connectCurrentRefinedValues.js';
54

65
describe('connectCurrentRefinedValues', () => {
76
it('Renders during init and render', () => {
87
const helper = jsHelper({});
9-
helper.search = sinon.stub();
8+
helper.search = () => {};
109
// test that the dummyRendering is called with the isFirstRendering
1110
// flag set accordingly
12-
const rendering = sinon.stub();
11+
const rendering = jest.fn();
1312
const makeWidget = connectCurrentRefinedValues(rendering);
1413
const widget = makeWidget({
1514
foo: 'bar', // dummy param to test `widgetParams`
1615
});
1716

1817
expect(widget.getConfiguration).toBe(undefined);
1918
// test if widget is not rendered yet at this point
20-
expect(rendering.callCount).toBe(0);
19+
expect(rendering).toHaveBeenCalledTimes(0);
2120

2221
widget.init({
2322
helper,
@@ -27,11 +26,11 @@ describe('connectCurrentRefinedValues', () => {
2726
});
2827

2928
// test that rendering has been called during init with isFirstRendering = true
30-
expect(rendering.callCount).toBe(1);
29+
expect(rendering).toHaveBeenCalledTimes(1);
3130
// test if isFirstRendering is true during init
32-
expect(rendering.lastCall.args[1]).toBe(true);
31+
expect(rendering.mock.calls[0][1]).toBe(true);
3332

34-
const firstRenderingOptions = rendering.lastCall.args[0];
33+
const firstRenderingOptions = rendering.mock.calls[0][0];
3534
expect(firstRenderingOptions.refinements).toEqual([]);
3635
expect(firstRenderingOptions.widgetParams).toEqual({
3736
foo: 'bar',
@@ -45,10 +44,10 @@ describe('connectCurrentRefinedValues', () => {
4544
});
4645

4746
// test that rendering has been called during init with isFirstRendering = false
48-
expect(rendering.callCount).toBe(2);
49-
expect(rendering.lastCall.args[1]).toBe(false);
47+
expect(rendering).toHaveBeenCalledTimes(2);
48+
expect(rendering.mock.calls[1][1]).toBe(false);
5049

51-
const secondRenderingOptions = rendering.lastCall.args[0];
50+
const secondRenderingOptions = rendering.mock.calls[0][0];
5251
expect(secondRenderingOptions.refinements).toEqual([]);
5352
expect(secondRenderingOptions.widgetParams).toEqual({
5453
foo: 'bar',
@@ -61,8 +60,8 @@ describe('connectCurrentRefinedValues', () => {
6160
const helper = jsHelper({}, '', {
6261
facets: ['myFacet'],
6362
});
64-
helper.search = sinon.stub();
65-
const rendering = sinon.stub();
63+
helper.search = () => {};
64+
const rendering = jest.fn();
6665
const makeWidget = connectCurrentRefinedValues(rendering);
6766
const widget = makeWidget();
6867

@@ -75,7 +74,7 @@ describe('connectCurrentRefinedValues', () => {
7574
onHistoryChange: () => {},
7675
});
7776

78-
const firstRenderingOptions = rendering.lastCall.args[0];
77+
const firstRenderingOptions = rendering.mock.calls[0][0];
7978
const refinements = firstRenderingOptions.refinements;
8079
expect(typeof firstRenderingOptions.refine).toBe('function');
8180
expect(refinements).toHaveLength(1);
@@ -91,7 +90,7 @@ describe('connectCurrentRefinedValues', () => {
9190
createURL: () => '#',
9291
});
9392

94-
const secondRenderingOptions = rendering.lastCall.args[0];
93+
const secondRenderingOptions = rendering.mock.calls[1][0];
9594
const otherRefinements = secondRenderingOptions.refinements;
9695
expect(typeof secondRenderingOptions.refine).toBe('function');
9796
expect(otherRefinements).toHaveLength(1);
@@ -100,14 +99,17 @@ describe('connectCurrentRefinedValues', () => {
10099
});
101100

102101
it('should clear also the search query', () => {
103-
const helper = jsHelper({}, '', {});
102+
const helper = jsHelper({}, '', {
103+
facets: ['myFacet'],
104+
});
104105
helper.search = jest.fn();
105106

106107
const rendering = jest.fn();
107108
const makeWidget = connectCurrentRefinedValues(rendering);
108109
const widget = makeWidget({ clearsQuery: true });
109110

110111
helper.setQuery('foobar');
112+
helper.toggleRefinement('myFacet', 'value');
111113
expect(helper.state.query).toBe('foobar');
112114

113115
widget.init({
@@ -119,12 +121,14 @@ describe('connectCurrentRefinedValues', () => {
119121

120122
// clear current refined values + query
121123
expect(rendering).toBeCalled();
124+
expect(helper.hasRefinements('myFacet')).toBe(true);
122125

123126
const [{ clearAllClick }] = rendering.mock.calls[0];
124127
clearAllClick();
125128

126129
expect(helper.search).toBeCalled();
127130
expect(helper.state.query).toBe('');
131+
expect(helper.hasRefinements('myFacet')).toBe(false);
128132
});
129133

130134
it('should provide the query as a refinement if clearsQuery is true', () => {

src/connectors/current-refined-values/connectCurrentRefinedValues.js

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import filter from 'lodash/filter';
1212

1313
import {
1414
getRefinements,
15-
clearRefinementsFromState,
16-
clearRefinementsAndSearch,
15+
clearRefinements,
1716
checkRendering,
1817
} from '../../lib/utils.js';
1918

@@ -187,7 +186,7 @@ export default function connectCurrentRefinedValues(renderFn, unmountFn) {
187186
}
188187

189188
const attributeNames = map(attributes, attribute => attribute.name);
190-
const restrictedTo = onlyListedAttributes ? attributeNames : [];
189+
const restrictedTo = onlyListedAttributes ? attributeNames : undefined;
191190

192191
const attributesObj = reduce(
193192
attributes,
@@ -200,16 +199,22 @@ export default function connectCurrentRefinedValues(renderFn, unmountFn) {
200199

201200
return {
202201
init({ helper, createURL, instantSearchInstance }) {
203-
this._clearRefinementsAndSearch = clearRefinementsAndSearch.bind(
204-
null,
205-
helper,
206-
restrictedTo,
207-
clearsQuery
208-
);
209-
210-
const clearAllURL = createURL(
211-
clearRefinementsFromState(helper.state, restrictedTo, clearsQuery)
212-
);
202+
this._clearRefinementsAndSearch = () => {
203+
helper
204+
.setState(
205+
clearRefinements({
206+
helper,
207+
whiteList: restrictedTo,
208+
clearsQuery,
209+
})
210+
)
211+
.search();
212+
};
213+
214+
this._createClearAllURL = () =>
215+
createURL(
216+
clearRefinements({ helper, whiteList: restrictedTo, clearsQuery })
217+
);
213218

214219
const refinements = getFilteredRefinements(
215220
{},
@@ -228,7 +233,7 @@ export default function connectCurrentRefinedValues(renderFn, unmountFn) {
228233
{
229234
attributes: attributesObj,
230235
clearAllClick: this._clearRefinementsAndSearch,
231-
clearAllURL,
236+
clearAllURL: this._createClearAllURL(),
232237
refine: _clearRefinement,
233238
createURL: _createURL,
234239
refinements,
@@ -240,10 +245,6 @@ export default function connectCurrentRefinedValues(renderFn, unmountFn) {
240245
},
241246

242247
render({ results, helper, state, createURL, instantSearchInstance }) {
243-
const clearAllURL = createURL(
244-
clearRefinementsFromState(state, restrictedTo, clearsQuery)
245-
);
246-
247248
const refinements = getFilteredRefinements(
248249
results,
249250
state,
@@ -261,7 +262,7 @@ export default function connectCurrentRefinedValues(renderFn, unmountFn) {
261262
{
262263
attributes: attributesObj,
263264
clearAllClick: this._clearRefinementsAndSearch,
264-
clearAllURL,
265+
clearAllURL: this._createClearAllURL(),
265266
refine: _clearRefinement,
266267
createURL: _createURL,
267268
refinements,

0 commit comments

Comments
 (0)