diff --git a/src/connectors/clear-refinements/__tests__/connectClearRefinements-test.js b/src/connectors/clear-refinements/__tests__/connectClearRefinements-test.js index a20580fe8f..8038c7fc43 100644 --- a/src/connectors/clear-refinements/__tests__/connectClearRefinements-test.js +++ b/src/connectors/clear-refinements/__tests__/connectClearRefinements-test.js @@ -1,4 +1,8 @@ import jsHelper, { SearchResults } from 'algoliasearch-helper'; +import { + createInitOptions, + createRenderOptions, +} from '../../../../test/mock/createWidget'; import connectClearRefinements from '../connectClearRefinements'; describe('connectClearRefinements', () => { @@ -48,10 +52,8 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin describe('Lifecycle', () => { it('renders during init and render', () => { - const helper = jsHelper({}); + const helper = jsHelper({}, 'indexName'); helper.search = () => {}; - // test that the dummyRendering is called with the isFirstRendering - // flag set accordingly const rendering = jest.fn(); const makeWidget = connectClearRefinements(rendering); const widget = makeWidget({ @@ -59,46 +61,59 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin }); expect(widget.getConfiguration).toBe(undefined); - // test if widget is not rendered yet at this point expect(rendering).toHaveBeenCalledTimes(0); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); // test that rendering has been called during init with isFirstRendering = true expect(rendering).toHaveBeenCalledTimes(1); - // test if isFirstRendering is true during init - expect(rendering.mock.calls[0][1]).toBe(true); - const firstRenderingOptions = rendering.mock.calls[0][0]; + const [ + firstRenderingOptions, + isFirstRenderAtInit, + ] = rendering.mock.calls[0]; + + expect(isFirstRenderAtInit).toBe(true); + expect(firstRenderingOptions.createURL).toBeInstanceOf(Function); + expect(firstRenderingOptions.refine).toBeInstanceOf(Function); expect(firstRenderingOptions.hasRefinements).toBe(false); expect(firstRenderingOptions.widgetParams).toEqual({ foo: 'bar', // dummy param to test `widgetParams` }); - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); // test that rendering has been called during init with isFirstRendering = false expect(rendering).toHaveBeenCalledTimes(2); - expect(rendering.mock.calls[1][1]).toBe(false); - const secondRenderingOptions = rendering.mock.calls[1][0]; + const [ + secondRenderingOptions, + isFirstRenderAtRender, + ] = rendering.mock.calls[1]; + + expect(isFirstRenderAtRender).toBe(false); + expect(secondRenderingOptions.createURL).toBeInstanceOf(Function); + expect(secondRenderingOptions.refine).toBeInstanceOf(Function); expect(secondRenderingOptions.hasRefinements).toBe(false); }); it('does not throw without the unmount function', () => { - const helper = jsHelper({}); + const helper = jsHelper({}, 'indexName'); const rendering = () => {}; const makeWidget = connectClearRefinements(rendering); const widget = makeWidget(); + expect(() => widget.dispose({ helper, state: helper.state }) ).not.toThrow(); @@ -107,7 +122,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin describe('Instance options', () => { it('provides a function to clear the refinements', () => { - const helper = jsHelper({}, '', { + const helper = jsHelper({}, 'indexName', { facets: ['myFacet'], }); helper.search = () => {}; @@ -118,39 +133,37 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin const makeWidget = connectClearRefinements(rendering); const widget = makeWidget({}); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); - - expect(helper.hasRefinements('myFacet')).toBe(true); - expect(helper.state.query).toBe('not empty'); - const initClearMethod = rendering.mock.calls[0][0].refine; - initClearMethod(); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); - expect(helper.hasRefinements('myFacet')).toBe(false); - expect(helper.state.query).toBe('not empty'); + expect(rendering.mock.calls[0][0].refine).toBeInstanceOf(Function); helper.toggleRefinement('myFacet', 'someOtherValue'); - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('myFacet')).toBe(true); expect(helper.state.query).toBe('not empty'); - const renderClearMethod = rendering.mock.calls[1][0].refine; - renderClearMethod(); + + const refine = rendering.mock.calls[1][0].refine; + refine(); + expect(helper.hasRefinements('myFacet')).toBe(false); expect(helper.state.query).toBe('not empty'); }); it('provides a function to clear the refinements and the query', () => { - const helper = jsHelper({}, '', { + const helper = jsHelper({}, 'indexName', { facets: ['myFacet'], }); helper.search = () => {}; @@ -161,38 +174,76 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin const makeWidget = connectClearRefinements(rendering); const widget = makeWidget({ excludedAttributes: [] }); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); - - expect(helper.hasRefinements('myFacet')).toBe(true); - expect(helper.state.query).toBe('a query'); - const initClearMethod = rendering.mock.calls[0][0].refine; - initClearMethod(); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); - expect(helper.hasRefinements('myFacet')).toBe(false); - expect(helper.state.query).toBe(''); + expect(rendering.mock.calls[0][0].refine).toBeInstanceOf(Function); helper.toggleRefinement('myFacet', 'someOtherValue'); helper.setQuery('another query'); - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('myFacet')).toBe(true); expect(helper.state.query).toBe('another query'); - const renderClearMethod = rendering.mock.calls[1][0].refine; - renderClearMethod(); + + const refine = rendering.mock.calls[1][0].refine; + refine(); + expect(helper.hasRefinements('myFacet')).toBe(false); expect(helper.state.query).toBe(''); }); + it('provides the same `refine` and `createURL` function references during the lifecycle', () => { + const helper = jsHelper({}, 'indexName'); + helper.search = () => {}; + + const rendering = jest.fn(); + const makeWidget = connectClearRefinements(rendering); + const widget = makeWidget({}); + + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); + + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); + + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); + + const [firstRender, secondRender, thirdRender] = rendering.mock.calls; + + expect(secondRender[0].refine).toBe(firstRender[0].refine); + expect(thirdRender[0].refine).toBe(secondRender[0].refine); + + expect(secondRender[0].createURL).toBe(firstRender[0].createURL); + expect(thirdRender[0].createURL).toBe(secondRender[0].createURL); + }); + it('gets refinements from results', () => { const helper = jsHelper({}, undefined, { facets: ['aFacet'], @@ -204,20 +255,22 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin const makeWidget = connectClearRefinements(rendering); const widget = makeWidget(); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); - expect(rendering.mock.calls[0][0].hasRefinements).toBe(true); + expect(rendering.mock.calls[0][0].hasRefinements).toBe(false); - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(rendering.mock.calls[1][0].hasRefinements).toBe(true); }); @@ -237,20 +290,22 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin excludedAttributes: [], }); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); - expect(rendering.mock.calls[0][0].hasRefinements).toBe(true); + expect(rendering.mock.calls[0][0].hasRefinements).toBe(false); - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(rendering.mock.calls[1][0].hasRefinements).toBe(true); }); @@ -267,26 +322,28 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin excludedAttributes: [], }); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); expect(rendering.mock.calls[0][0].hasRefinements).toBe(false); - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(rendering.mock.calls[1][0].hasRefinements).toBe(false); }); it('without includedAttributes or excludedAttributes and with a query has no refinements', () => { - const helper = jsHelper({}); + const helper = jsHelper({}, 'indexName'); helper.setQuery('not empty'); helper.search = () => {}; @@ -294,26 +351,28 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin const makeWidget = connectClearRefinements(rendering); const widget = makeWidget({}); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); expect(rendering.mock.calls[0][0].hasRefinements).toBe(false); - widget.render({ - results: new SearchResults(helper.state, [{}]), - state: helper.state, - helper, - createURL: () => '#', - }); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(rendering.mock.calls[1][0].hasRefinements).toBe(false); }); it('includes only includedAttributes', () => { - const helper = jsHelper({}, '', { + const helper = jsHelper({}, 'indexName', { facets: ['facet1', 'facet2'], }); helper.search = () => {}; @@ -327,30 +386,42 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin .toggleRefinement('facet2', 'value') .setQuery('not empty'); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('facet1')).toBe(true); expect(helper.hasRefinements('facet2')).toBe(true); - const refine = rendering.mock.calls[0][0].refine; + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); + + const refine = rendering.mock.calls[1][0].refine; refine(); - widget.render({ - helper, - state: helper.state, - createURL: () => '#', - }); + + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('facet1')).toBe(false); expect(helper.hasRefinements('facet2')).toBe(true); - expect(rendering.mock.calls[1][0].hasRefinements).toBe(false); + expect(rendering.mock.calls[2][0].hasRefinements).toBe(false); }); it('includes only includedAttributes (with query)', () => { - const helper = jsHelper({}, '', { + const helper = jsHelper({}, 'indexName', { facets: ['facet1'], }); helper.search = () => {}; @@ -361,30 +432,42 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin helper.toggleRefinement('facet1', 'value').setQuery('not empty'); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('facet1')).toBe(true); expect(helper.state.query).toBe('not empty'); - const refine = rendering.mock.calls[0][0].refine; + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); + + const refine = rendering.mock.calls[1][0].refine; refine(); - widget.render({ - helper, - state: helper.state, - createURL: () => '#', - }); + + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('facet1')).toBe(false); expect(helper.state.query).toBe(''); - expect(rendering.mock.calls[1][0].hasRefinements).toBe(false); + expect(rendering.mock.calls[2][0].hasRefinements).toBe(false); }); it('excludes excludedAttributes', () => { - const helper = jsHelper({}, '', { + const helper = jsHelper({}, 'indexName', { facets: ['facet1', 'facet2'], }); helper.search = () => {}; @@ -402,48 +485,66 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin { helper.setQuery('not empty'); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('facet1')).toBe(true); expect(helper.hasRefinements('facet2')).toBe(true); - const refine = rendering.mock.calls[0][0].refine; + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); + + const refine = rendering.mock.calls[1][0].refine; refine(); expect(helper.hasRefinements('facet1')).toBe(false); expect(helper.hasRefinements('facet2')).toBe(true); - expect(rendering.mock.calls[0][0].hasRefinements).toBe(true); + expect(rendering.mock.calls[1][0].hasRefinements).toBe(true); } { // facet has not been cleared and it is still refined with value helper.setQuery('not empty'); - widget.render({ - helper, - state: helper.state, - results: new SearchResults(helper.state, [{}]), - createURL: () => '#', - }); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('facet1')).toBe(false); expect(helper.hasRefinements('facet2')).toBe(true); - const refine = rendering.mock.calls[1][0].refine; + const refine = rendering.mock.calls[2][0].refine; refine(); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); + expect(helper.hasRefinements('facet1')).toBe(false); expect(helper.hasRefinements('facet2')).toBe(true); } }); - describe('transformItems is called', () => { - const helper = jsHelper({}, '', { + it('transformItems is called', () => { + const helper = jsHelper({}, 'indexName', { facets: ['facet1', 'facet2', 'facet3'], }); helper.search = () => {}; @@ -464,35 +565,47 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin .toggleRefinement('facet3', 'value') .setQuery('not empty'); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('facet1')).toBe(true); expect(helper.hasRefinements('facet2')).toBe(true); expect(helper.hasRefinements('facet3')).toBe(true); expect(helper.state.query).toBe('not empty'); - const refine = rendering.mock.calls[0][0].refine; + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); + + const refine = rendering.mock.calls[1][0].refine; refine(); - widget.render({ - helper, - state: helper.state, - createURL: () => '#', - }); + + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); expect(helper.hasRefinements('facet1')).toBe(true); expect(helper.hasRefinements('facet2')).toBe(true); expect(helper.hasRefinements('facet3')).toBe(false); expect(helper.state.query).toBe(''); - expect(rendering.mock.calls[1][0].hasRefinements).toBe(false); + expect(rendering.mock.calls[2][0].hasRefinements).toBe(false); }); describe('createURL', () => { it('consistent with the list of excludedAttributes', () => { - const helper = jsHelper({}, '', { + const helper = jsHelper({}, 'indexName', { facets: ['facet', 'otherFacet'], }); helper.search = () => {}; @@ -509,13 +622,23 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin { helper.setQuery('not empty'); - widget.init({ - helper, - state: helper.state, - createURL: opts => opts, - }); + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); + + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + createURL: state => state, + }) + ); - const { createURL, refine } = rendering.mock.calls[0][0]; + const { createURL, refine } = rendering.mock.calls[1][0]; // The state represented by the URL should be equal to a state // after refining. @@ -527,14 +650,16 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin } { - widget.render({ - helper, - state: helper.state, - results: new SearchResults(helper.state, [{}]), - createURL: () => '#', - }); + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + createURL: state => state, + }) + ); - const { createURL, refine } = rendering.mock.calls[1][0]; + const { createURL, refine } = rendering.mock.calls[2][0]; const createURLState = createURL(); refine(); @@ -546,7 +671,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin }); it('reset the page to 0', () => { - const helper = jsHelper({}, '', {}); + const helper = jsHelper({}, 'indexName', {}); helper.search = () => {}; helper.setQuery('not empty'); @@ -554,15 +679,26 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin const makeWidget = connectClearRefinements(rendering); const widget = makeWidget({}); - widget.init({ - helper, - state: helper.state, - createURL: () => '#', - }); - const clearRefinements = rendering.mock.calls[0][0].refine; + widget.init( + createInitOptions({ + helper, + state: helper.state, + }) + ); + + widget.render( + createRenderOptions({ + results: new SearchResults(helper.state, [{}]), + helper, + state: helper.state, + }) + ); + + const refine = rendering.mock.calls[1][0].refine; helper.setPage(2); - clearRefinements(); + refine(); + expect(helper.state.page).toBe(0); }); }); diff --git a/src/connectors/clear-refinements/connectClearRefinements.js b/src/connectors/clear-refinements/connectClearRefinements.js index c0ac65e925..fa2a3c4ba7 100644 --- a/src/connectors/clear-refinements/connectClearRefinements.js +++ b/src/connectors/clear-refinements/connectClearRefinements.js @@ -4,6 +4,8 @@ import { getRefinements, createDocumentationMessageGenerator, noop, + uniq, + mergeSearchParameters, } from '../../lib/utils'; const withUsage = createDocumentationMessageGenerator({ @@ -86,52 +88,23 @@ export default function connectClearRefinements(renderFn, unmountFn = noop) { transformItems = items => items, } = widgetParams; - return { - $$type: 'ais.clearRefinements', + const connectorState = { + refine: noop, + createURL: () => '', + }; - init({ helper, instantSearchInstance, createURL }) { - const attributesToClear = getAttributesToClear({ - helper, - includedAttributes, - excludedAttributes, - transformItems, - }); - const hasRefinements = attributesToClear.length > 0; - - this._refine = () => { - helper - .setState( - clearRefinements({ - helper, - attributesToClear: getAttributesToClear({ - helper, - includedAttributes, - excludedAttributes, - transformItems, - }), - }) - ) - .search(); - }; + const cachedRefine = () => connectorState.refine(); + const cachedCreateURL = () => connectorState.createURL(); - this._createURL = () => - createURL( - clearRefinements({ - helper, - attributesToClear: getAttributesToClear({ - helper, - includedAttributes, - excludedAttributes, - transformItems, - }), - }) - ); + return { + $$type: 'ais.clearRefinements', + init({ instantSearchInstance }) { renderFn( { - hasRefinements, - refine: this._refine, - createURL: this._createURL, + hasRefinements: false, + refine: cachedRefine, + createURL: cachedCreateURL, instantSearchInstance, widgetParams, }, @@ -139,20 +112,53 @@ export default function connectClearRefinements(renderFn, unmountFn = noop) { ); }, - render({ helper, instantSearchInstance }) { - const attributesToClear = getAttributesToClear({ - helper, - includedAttributes, - excludedAttributes, - transformItems, - }); - const hasRefinements = attributesToClear.length > 0; + render({ scopedResults, createURL, instantSearchInstance }) { + const attributesToClear = scopedResults.reduce( + (results, scopedResult) => { + return results.concat( + getAttributesToClear({ + scopedResult, + includedAttributes, + excludedAttributes, + transformItems, + }) + ); + }, + [] + ); + + connectorState.refine = () => { + attributesToClear.forEach(({ helper: indexHelper, items }) => { + indexHelper + .setState( + clearRefinements({ + helper: indexHelper, + attributesToClear: items, + }) + ) + .search(); + }); + }; + + connectorState.createURL = () => + createURL( + mergeSearchParameters( + ...attributesToClear.map(({ helper: indexHelper, items }) => { + return clearRefinements({ + helper: indexHelper, + attributesToClear: items, + }); + }) + ) + ); renderFn( { - hasRefinements, - refine: this._refine, - createURL: this._createURL, + hasRefinements: attributesToClear.some( + attributeToClear => attributeToClear.items.length > 0 + ), + refine: cachedRefine, + createURL: cachedCreateURL, instantSearchInstance, widgetParams, }, @@ -168,7 +174,7 @@ export default function connectClearRefinements(renderFn, unmountFn = noop) { } function getAttributesToClear({ - helper, + scopedResult, includedAttributes, excludedAttributes, transformItems, @@ -177,22 +183,31 @@ function getAttributesToClear({ includedAttributes.indexOf('query') !== -1 || excludedAttributes.indexOf('query') === -1; - return transformItems( - getRefinements(helper.lastResults || {}, helper.state, clearsQuery) - .map(refinement => refinement.attribute) - .filter( - attribute => - // If the array is empty (default case), we keep all the attributes - includedAttributes.length === 0 || - // Otherwise, only add the specified attributes - includedAttributes.indexOf(attribute) !== -1 - ) - .filter( - attribute => - // If the query is included, we ignore the default `excludedAttributes = ['query']` - (attribute === 'query' && clearsQuery) || - // Otherwise, ignore the excluded attributes - excludedAttributes.indexOf(attribute) === -1 + return { + helper: scopedResult.helper, + items: transformItems( + uniq( + getRefinements( + scopedResult.results, + scopedResult.helper.state, + clearsQuery + ) + .map(refinement => refinement.attribute) + .filter( + attribute => + // If the array is empty (default case), we keep all the attributes + includedAttributes.length === 0 || + // Otherwise, only add the specified attributes + includedAttributes.indexOf(attribute) !== -1 + ) + .filter( + attribute => + // If the query is included, we ignore the default `excludedAttributes = ['query']` + (attribute === 'query' && clearsQuery) || + // Otherwise, ignore the excluded attributes + excludedAttributes.indexOf(attribute) === -1 + ) ) - ); + ), + }; } diff --git a/src/widgets/clear-refinements/__tests__/__snapshots__/clear-refinements-test.js.snap b/src/widgets/clear-refinements/__tests__/__snapshots__/clear-refinements-test.js.snap index 808c6b3cd0..00778b0e4b 100644 --- a/src/widgets/clear-refinements/__tests__/__snapshots__/clear-refinements-test.js.snap +++ b/src/widgets/clear-refinements/__tests__/__snapshots__/clear-refinements-test.js.snap @@ -1,21 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`clearRefinements() cssClasses should add the default CSS classes 1`] = ` -Object { - "button": "ais-ClearRefinements-button", - "disabledButton": "ais-ClearRefinements-button--disabled", - "root": "ais-ClearRefinements", -} -`; - -exports[`clearRefinements() cssClasses should allow overriding CSS classes 1`] = ` -Object { - "button": "ais-ClearRefinements-button myButton myPrimaryButton", - "disabledButton": "ais-ClearRefinements-button--disabled disabled", - "root": "ais-ClearRefinements myRoot", -} -`; - exports[`clearRefinements() with refinements calls twice render(, container) 1`] = ` { const module = require.requireActual('preact-compat'); @@ -24,119 +28,87 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/clear-refin }); describe('clearRefinements()', () => { - let container; - let results; - let client; - let helper; - let createURL; - beforeEach(() => { - createURL = jest.fn().mockReturnValue('#all-cleared'); - container = document.createElement('div'); - results = {}; - client = algoliasearch('APP_ID', 'API_KEY'); - helper = { - state: { - clearRefinements: jest.fn().mockReturnThis(), - clearTags: jest.fn().mockReturnThis(), - }, - search: jest.fn(), - }; - render.mockClear(); }); describe('without refinements', () => { - beforeEach(() => { - helper.state.facetsRefinements = {}; - }); - it('calls twice render(, container)', () => { + const helper = algoliasearchHelper(createSearchClient(), 'indexName', { + facetsRefinements: {}, + }); + const container = document.createElement('div'); const widget = clearRefinements({ container, }); - widget.init({ - helper, - createURL, - instantSearchInstance: { - templatesConfig: {}, - }, - }); - widget.render({ - results, - helper, - state: helper.state, - createURL, - instantSearchInstance: {}, - }); - widget.render({ - results, - helper, - state: helper.state, - createURL, - instantSearchInstance: {}, - }); + widget.init(createInitOptions({ helper })); + widget.render(createRenderOptions({ helper, state: helper.state })); + widget.render(createRenderOptions({ helper, state: helper.state })); expect(render).toHaveBeenCalledTimes(2); - expect(render.mock.calls[0][0]).toMatchSnapshot(); - expect(render.mock.calls[0][1]).toEqual(container); + const [firstRender, secondRender] = render.mock.calls; - expect(render.mock.calls[1][0]).toMatchSnapshot(); - expect(render.mock.calls[1][1]).toEqual(container); + expect(firstRender[0]).toMatchSnapshot(); + expect(firstRender[1]).toEqual(container); + + expect(secondRender[0]).toMatchSnapshot(); + expect(secondRender[1]).toEqual(container); }); }); describe('with refinements', () => { - beforeEach(() => { - helper.state.facetsRefinements = { something: ['something'] }; - }); - it('calls twice render(, container)', () => { + const helper = algoliasearchHelper(createSearchClient(), 'indexName', { + facetsRefinements: { + facet: ['value'], + }, + }); + const container = document.createElement('div'); const widget = clearRefinements({ container, }); - widget.init({ - helper, - createURL, - instantSearchInstance: { - templatesConfig: {}, - }, - }); - widget.render({ results, helper, state: helper.state, createURL }); - widget.render({ results, helper, state: helper.state, createURL }); + + widget.init(createInitOptions({ helper })); + widget.render(createRenderOptions({ helper, state: helper.state })); + widget.render(createRenderOptions({ helper, state: helper.state })); expect(render).toHaveBeenCalledTimes(2); - expect(render.mock.calls[0][0]).toMatchSnapshot(); - expect(render.mock.calls[0][1]).toEqual(container); + const [firstRender, secondRender] = render.mock.calls; + + expect(firstRender[0]).toMatchSnapshot(); + expect(firstRender[1]).toEqual(container); - expect(render.mock.calls[1][0]).toMatchSnapshot(); - expect(render.mock.calls[1][1]).toEqual(container); + expect(secondRender[0]).toMatchSnapshot(); + expect(secondRender[1]).toEqual(container); }); }); describe('cssClasses', () => { it('should add the default CSS classes', () => { - helper = algoliasearchHelper(client, 'index_name'); + const helper = algoliasearchHelper(createSearchClient(), 'indexName'); + const container = document.createElement('div'); const widget = clearRefinements({ container, }); - widget.init({ - helper, - createURL, - instantSearchInstance: { - templatesConfig: {}, - }, - }); + widget.init(createInitOptions({ helper })); + widget.render(createRenderOptions({ helper, state: helper.state })); - widget.render({ results, helper, state: helper.state, createURL }); - expect(render.mock.calls[0][0].props.cssClasses).toMatchSnapshot(); + expect(render.mock.calls[0][0].props.cssClasses).toMatchInlineSnapshot(` + Object { + "button": "ais-ClearRefinements-button", + "disabledButton": "ais-ClearRefinements-button--disabled", + "root": "ais-ClearRefinements", + } + `); }); it('should allow overriding CSS classes', () => { + const helper = algoliasearchHelper(createSearchClient(), 'indexName'); + const container = document.createElement('div'); const widget = clearRefinements({ container, cssClasses: { @@ -145,16 +117,17 @@ describe('clearRefinements()', () => { disabledButton: ['disabled'], }, }); - widget.init({ - helper, - createURL, - instantSearchInstance: { - templatesConfig: {}, - }, - }); - widget.render({ results, helper, state: helper.state, createURL }); - expect(render.mock.calls[0][0].props.cssClasses).toMatchSnapshot(); + widget.init(createInitOptions({ helper })); + widget.render(createRenderOptions({ helper, state: helper.state })); + + expect(render.mock.calls[0][0].props.cssClasses).toMatchInlineSnapshot(` + Object { + "button": "ais-ClearRefinements-button myButton myPrimaryButton", + "disabledButton": "ais-ClearRefinements-button--disabled disabled", + "root": "ais-ClearRefinements myRoot", + } + `); }); }); }); diff --git a/stories/clear-refinements.stories.js b/stories/clear-refinements.stories.js index ae4b0e26ed..945aabf1de 100644 --- a/stories/clear-refinements.stories.js +++ b/stories/clear-refinements.stories.js @@ -88,4 +88,54 @@ storiesOf('ClearRefinements', module) }, } ) + ) + .add( + 'with multi indices', + withHits(({ search, container, instantsearch }) => { + const clearRefinementsContainer = document.createElement('div'); + const instantSearchPriceAscTitle = document.createElement('h3'); + instantSearchPriceAscTitle.innerHTML = + 'instant_search_price_asc'; + const instantSearchMediaTitle = document.createElement('h3'); + instantSearchMediaTitle.innerHTML = + 'instant_search_rating_asc'; + const refinementListContainer1 = document.createElement('div'); + const refinementListContainer2 = document.createElement('div'); + + container.appendChild(clearRefinementsContainer); + container.appendChild(instantSearchPriceAscTitle); + container.appendChild(refinementListContainer1); + container.appendChild(instantSearchMediaTitle); + container.appendChild(refinementListContainer2); + + search.addWidgets([ + instantsearch.widgets.clearRefinements({ + container: clearRefinementsContainer, + }), + + instantsearch.widgets + .index({ + indexName: 'instant_search_price_asc', + }) + .addWidgets([ + instantsearch.widgets.refinementList({ + container: refinementListContainer1, + attribute: 'brand', + limit: 3, + }), + ]), + + instantsearch.widgets + .index({ + indexName: 'instant_search_rating_asc', + }) + .addWidgets([ + instantsearch.widgets.refinementList({ + container: refinementListContainer2, + attribute: 'categories', + limit: 3, + }), + ]), + ]); + }) ); diff --git a/test/mock/createWidget.ts b/test/mock/createWidget.ts index fb5cd1eed5..c84263d565 100644 --- a/test/mock/createWidget.ts +++ b/test/mock/createWidget.ts @@ -30,6 +30,7 @@ export const createRenderOptions = ( ): RenderOptions => { const { instantSearchInstance = createInstantSearch(), ...rest } = args; const response = createMultiSearchResponse(); + const helper = args.helper || instantSearchInstance.helper!; const results = new algolisearchHelper.SearchResults( instantSearchInstance.helper!.state, response.results @@ -38,14 +39,14 @@ export const createRenderOptions = ( return { instantSearchInstance, templatesConfig: instantSearchInstance.templatesConfig, - helper: instantSearchInstance.helper!, - state: instantSearchInstance.helper!.state, + helper, + state: helper.state, results, scopedResults: [ { - indexId: instantSearchInstance.helper!.state.index, + indexId: helper.state.index, + helper, results, - helper: instantSearchInstance.helper!, }, ], searchMetadata: {