diff --git a/package.json b/package.json index 738c4ab733..c1fc32f04f 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ }, { "path": "packages/react-instantsearch/dist/umd/Connectors.min.js", - "maxSize": "41.75 kB" + "maxSize": "41.80 kB" }, { "path": "packages/react-instantsearch/dist/umd/Dom.min.js", diff --git a/packages/react-instantsearch-core/src/core/__tests__/createInstantSearchManager.derived.js b/packages/react-instantsearch-core/src/core/__tests__/createInstantSearchManager.derived.js index e905767fc2..3e1e95e4a8 100644 --- a/packages/react-instantsearch-core/src/core/__tests__/createInstantSearchManager.derived.js +++ b/packages/react-instantsearch-core/src/core/__tests__/createInstantSearchManager.derived.js @@ -47,14 +47,12 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setQuery('first query 1'), - context: {}, props: {}, }); // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setIndex('first'), - context: {}, props: { indexName: 'first', indexId: 'first', @@ -66,18 +64,16 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setPage(3), - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'first', }, }, - props: {}, }); // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setIndex('second'), - context: {}, props: { indexName: 'second', indexId: 'second', @@ -89,12 +85,11 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setQuery('second query 1'), - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'second', }, }, - props: {}, }); expect(ism.store.getState().results).toBe(null); @@ -171,14 +166,12 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setQuery('query'), - context: {}, props: {}, }); // ism.widgetsManager.registerWidget({ getSearchParameters: x => x.setIndex('first'), - context: {}, props: { indexName: 'first', indexId: 'first', @@ -190,18 +183,16 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: x => x.setIndex('third'), - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'first', }, }, - props: {}, }); // ism.widgetsManager.registerWidget({ getSearchParameters: x => x.setIndex('second'), - context: {}, props: { indexName: 'second', indexId: 'second', @@ -249,7 +240,6 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: x => x.setIndex('first'), - context: {}, props: { indexName: 'first', indexId: 'first', @@ -259,7 +249,6 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: x => x.setIndex('second'), - context: {}, props: { indexName: 'second', indexId: 'second', @@ -269,7 +258,6 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: x => x.setIndex('third'), - context: {}, props: { indexName: 'third', indexId: 'third', @@ -279,7 +267,6 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: x => x.setIndex('four'), - context: {}, props: { indexName: 'four', indexId: 'four', @@ -327,7 +314,6 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setIndex('first'), - context: {}, props: { indexName: 'first', indexId: 'first_5_hits', @@ -339,18 +325,16 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setQueryParameter('hitsPerPage', 5), - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'first_5_hits', }, }, - props: {}, }); // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setIndex('first'), - context: {}, props: { indexName: 'first', indexId: 'first_10_hits', @@ -363,12 +347,11 @@ describe('createInstantSearchManager with multi index', () => { ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setQueryParameter('hitsPerPage', 10), - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'first_10_hits', }, }, - props: {}, }); expect(ism.store.getState().results).toBe(null); @@ -415,14 +398,12 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setQuery('first query 1'), - context: {}, props: {}, }); // const unregisterFirstIndexWidget = ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setIndex('first'), - context: {}, props: { indexName: 'first', indexId: 'first', @@ -434,18 +415,16 @@ describe('createInstantSearchManager with multi index', () => { // const unregisterPaginationWidget = ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setPage(3), - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'first', }, }, - props: {}, }); // const unregisterSecondIndexWidget = ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setIndex('second'), - context: {}, props: { indexName: 'second', indexId: 'second', @@ -457,12 +436,11 @@ describe('createInstantSearchManager with multi index', () => { // const unregisterSecondSearchBoxWidget = ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setQuery('second query 1'), - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'second', }, }, - props: {}, }); expect(ism.store.getState().results).toBe(null); @@ -506,7 +484,6 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setIndex('first'), - context: {}, props: { indexName: 'first', indexId: 'first', @@ -518,18 +495,16 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setPage(3), - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'first', }, }, - props: {}, }); // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setIndex('second'), - context: {}, props: { indexName: 'second', indexId: 'second', @@ -541,12 +516,11 @@ describe('createInstantSearchManager with multi index', () => { // ism.widgetsManager.registerWidget({ getSearchParameters: params => params.setQuery('second query 2'), - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'second', }, }, - props: {}, }); await runAllMicroTasks(); diff --git a/packages/react-instantsearch-core/src/core/__tests__/createInstantSearchManager.js b/packages/react-instantsearch-core/src/core/__tests__/createInstantSearchManager.js index f409962aa1..0e7c40a520 100644 --- a/packages/react-instantsearch-core/src/core/__tests__/createInstantSearchManager.js +++ b/packages/react-instantsearch-core/src/core/__tests__/createInstantSearchManager.js @@ -1,6 +1,17 @@ +import React from 'react'; +import Adapter from 'enzyme-adapter-react-16'; +import Enzyme, { mount } from 'enzyme'; import algoliasearch from 'algoliasearch/lite'; import { SearchResults } from 'algoliasearch-helper'; import createInstantSearchManager from '../createInstantSearchManager'; +import { + InstantSearch, + Index, + SortBy, + Configure, +} from 'react-instantsearch-dom'; + +Enzyme.configure({ adapter: new Adapter() }); jest.useFakeTimers(); @@ -575,7 +586,6 @@ describe('createInstantSearchManager', () => { getSearchParameters(state) { return state.setIndex('index'); }, - context: {}, props: { indexId: 'index_with_refinement', }, @@ -588,12 +598,11 @@ describe('createInstantSearchManager', () => { getSearchParameters(state) { return state.setQuery('derived'); }, - context: { - multiIndexContext: { + props: { + indexContextValue: { targetedIndex: 'index_with_refinement', }, }, - props: {}, }); const { mainParameters, derivedParameters } = ism.getSearchParameters(); @@ -615,6 +624,126 @@ describe('createInstantSearchManager', () => { }, ]); }); + + it('expects widgets main parameters and derived parameters to be correctly calculated within a multi index context', () => { + const wrapper = mount( + + + + + + + + + + + + + + + + + ); + + const { + mainParameters, + derivedParameters, + } = wrapper.instance().aisManager.getSearchParameters(); + + expect(mainParameters).toEqual( + expect.objectContaining({ + index: 'index1', + }) + ); + + expect(derivedParameters).toEqual([ + expect.objectContaining({ + indexId: 'bestbuy', + parameters: expect.objectContaining({ + index: 'bestbuy', + }), + }), + expect.objectContaining({ + indexId: 'instant_search', + parameters: expect.objectContaining({ + index: 'instant_search', + }), + }), + expect.objectContaining({ + indexId: 'instant_search_apple', + parameters: expect.objectContaining({ + index: 'instant_search', + filters: 'brand:Apple', + }), + }), + expect.objectContaining({ + indexId: 'instant_search_samsung', + parameters: expect.objectContaining({ + index: 'instant_search', + filters: 'brand:Samsung', + }), + }), + expect.objectContaining({ + indexId: 'instant_search_microsoft', + parameters: expect.objectContaining({ + index: 'instant_search', + filters: 'brand:Microsoft', + }), + }), + ]); + }); + + it('expects widgets main parameters and derived parameters to be correctly calculated with SortBy within a multi index context', () => { + const wrapper = mount( + + + + + + + + + + ); + + const { + mainParameters, + derivedParameters, + } = wrapper.instance().aisManager.getSearchParameters(); + + expect(mainParameters).toEqual( + expect.objectContaining({ + index: 'index1', + }) + ); + + expect(derivedParameters).toEqual([ + expect.objectContaining({ + indexId: 'categories', + parameters: expect.objectContaining({ + index: 'bestbuy', + }), + }), + expect.objectContaining({ + indexId: 'products', + parameters: expect.objectContaining({ + index: 'brands', + }), + }), + ]); + }); }); describe('searchStalled', () => { diff --git a/packages/react-instantsearch-core/src/core/createInstantSearchManager.js b/packages/react-instantsearch-core/src/core/createInstantSearchManager.js index 6fd1976b34..dc77972f94 100644 --- a/packages/react-instantsearch-core/src/core/createInstantSearchManager.js +++ b/packages/react-instantsearch-core/src/core/createInstantSearchManager.js @@ -13,9 +13,13 @@ function addAlgoliaAgents(searchClient) { } } -const isMultiIndexContext = widget => hasMultipleIndices(widget.context); +const isMultiIndexContext = widget => + hasMultipleIndices({ + ais: widget.props.contextValue, + multiIndexContext: widget.props.indexContextValue, + }); const isTargetedIndexEqualIndex = (widget, indexId) => - widget.context.multiIndexContext.targetedIndex === indexId; + widget.props.indexContextValue.targetedIndex === indexId; // Relying on the `indexId` is a bit brittle to detect the `Index` widget. // Since it's a class we could rely on `instanceof` or similar. We never @@ -24,6 +28,16 @@ const isIndexWidget = widget => Boolean(widget.props.indexId); const isIndexWidgetEqualIndex = (widget, indexId) => widget.props.indexId === indexId; +const sortIndexWidgetsFirst = (firstWidget, secondWidget) => { + if (isIndexWidget(firstWidget)) { + return -1; + } + if (isIndexWidget(secondWidget)) { + return 1; + } + return 0; +}; + /** * Creates a new instance of the InstantSearchManager which controls the widgets and * trigger the search when the widgets are updated. @@ -114,6 +128,9 @@ export default function createInstantSearchManager({ return targetedIndexEqualMainIndex || subIndexEqualMainIndex; }) + // We have to sort the `Index` widgets first so the `index` parameter + // is correctly set in the `reduce` function for the following widgets + .sort(sortIndexWidgetsFirst) .reduce( (res, widget) => widget.getSearchParameters(res), sharedParameters @@ -132,9 +149,12 @@ export default function createInstantSearchManager({ return targetedIndexNotEqualMainIndex || subIndexNotEqualMainIndex; }) + // We have to sort the `Index` widgets first so the `index` parameter + // is correctly set in the `reduce` function for the following widgets + .sort(sortIndexWidgetsFirst) .reduce((indices, widget) => { const indexId = isMultiIndexContext(widget) - ? widget.context.multiIndexContext.targetedIndex + ? widget.props.indexContextValue.targetedIndex : widget.props.indexId; const widgets = indices[indexId] || []; diff --git a/packages/react-instantsearch-core/src/widgets/Index.tsx b/packages/react-instantsearch-core/src/widgets/Index.tsx index b7d60dc4f1..9d10fcce37 100644 --- a/packages/react-instantsearch-core/src/widgets/Index.tsx +++ b/packages/react-instantsearch-core/src/widgets/Index.tsx @@ -9,13 +9,13 @@ import { function getIndexContext(props: Props): IndexContext { return { - targetedIndex: props.indexId || props.indexName, + targetedIndex: props.indexId, }; } type Props = { indexName: string; - indexId?: string; + indexId: string; }; type InnerProps = Props & { contextValue: InstantSearchContext }; @@ -57,7 +57,7 @@ type State = { class Index extends Component { static propTypes = { indexName: PropTypes.string.isRequired, - indexId: PropTypes.string, + indexId: PropTypes.string.isRequired, children: PropTypes.node, }; @@ -123,11 +123,20 @@ class Index extends Component { } } -const IndexWrapper: React.FC = props => ( - - {contextValue => } - -); +const IndexWrapper: React.FC = props => { + const inferredIndexId = props.indexName; + return ( + + {contextValue => ( + + )} + + ); +}; export const IndexComponentWithoutContext = Index; export default IndexWrapper;