From 2611da52282aa5dd25d15548fd57183cea78df74 Mon Sep 17 00:00:00 2001 From: Samuel Vaillant Date: Thu, 11 Jul 2019 16:10:34 +0200 Subject: [PATCH] feat(index): resolve parent SearchParameters (#3937) * feat(resolveSearchParameters): implementation * feat(index): expose the parent * feat(index): resolve and shallow merge search parameters * chore(stories): remove index specific search parameters * test(index): use correct index name --- .../__tests__/resolveSearchParameters-test.ts | 64 +++++++++ src/lib/utils/index.ts | 1 + src/lib/utils/resolveSearchParameters.ts | 16 +++ src/widgets/index/__tests__/index-test.ts | 122 ++++++++++++++++++ src/widgets/index/index.ts | 20 ++- stories/index.stories.ts | 6 - 6 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 src/lib/utils/__tests__/resolveSearchParameters-test.ts create mode 100644 src/lib/utils/resolveSearchParameters.ts diff --git a/src/lib/utils/__tests__/resolveSearchParameters-test.ts b/src/lib/utils/__tests__/resolveSearchParameters-test.ts new file mode 100644 index 0000000000..ad3197d10b --- /dev/null +++ b/src/lib/utils/__tests__/resolveSearchParameters-test.ts @@ -0,0 +1,64 @@ +import { createInitOptions } from '../../../../test/mock/createWidget'; +import index from '../../../widgets/index/index'; +import resolve from '../resolveSearchParameters'; + +describe('mergeSearchParameters', () => { + describe('1 level', () => { + it('resolves the `SearchParameters` from the level 0', () => { + const level0 = index({ indexName: 'level_0_index_name' }); + + level0.init(createInitOptions()); + + const actual = resolve(level0); + + expect(actual).toEqual([level0.getHelper()!.state]); + }); + }); + + describe('2 levels', () => { + const level0 = index({ indexName: 'level_0_index_name' }); + const level1 = index({ indexName: 'level_1_index_name' }); + + level0.addWidgets([level1]); + level0.init(createInitOptions()); + + it('resolves the `SearchParameters` from the level 0', () => { + expect(resolve(level0)).toEqual([level0.getHelper()!.state]); + }); + + it('resolves the `SearchParameters` from the level 1', () => { + expect(resolve(level1)).toEqual([ + level0.getHelper()!.state, + level1.getHelper()!.state, + ]); + }); + }); + + describe('3 levels', () => { + const level0 = index({ indexName: 'level_0_index_name' }); + const level1 = index({ indexName: 'level_1_index_name' }); + const level2 = index({ indexName: 'level_2_index_name' }); + + level0.addWidgets([level1.addWidgets([level2])]); + level0.init(createInitOptions()); + + it('resolves the `SearchParameters` from the level 0', () => { + expect(resolve(level0)).toEqual([level0.getHelper()!.state]); + }); + + it('resolves the `SearchParameters` from the level 1', () => { + expect(resolve(level1)).toEqual([ + level0.getHelper()!.state, + level1.getHelper()!.state, + ]); + }); + + it('resolves the `SearchParameters` from the level 2', () => { + expect(resolve(level2)).toEqual([ + level0.getHelper()!.state, + level1.getHelper()!.state, + level2.getHelper()!.state, + ]); + }); + }); +}); diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 749db3c711..8e85dcbea6 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -22,6 +22,7 @@ export { default as escape } from './escape'; export { default as find } from './find'; export { default as findIndex } from './findIndex'; export { default as mergeDeep } from './mergeDeep'; +export { default as resolveSearchParameters } from './resolveSearchParameters'; export { warning, deprecate } from './logger'; export { createDocumentationLink, diff --git a/src/lib/utils/resolveSearchParameters.ts b/src/lib/utils/resolveSearchParameters.ts new file mode 100644 index 0000000000..c274a7a06d --- /dev/null +++ b/src/lib/utils/resolveSearchParameters.ts @@ -0,0 +1,16 @@ +import { Index } from '../../widgets/index/index'; +import { SearchParameters } from '../../types'; + +const resolveSearchParameters = (current: Index): SearchParameters[] => { + let parent = current.getParent(); + let states = [current.getHelper()!.state]; + + while (parent !== null) { + states = [parent.getHelper()!.state].concat(states); + parent = parent.getParent(); + } + + return states; +}; + +export default resolveSearchParameters; diff --git a/src/widgets/index/__tests__/index-test.ts b/src/widgets/index/__tests__/index-test.ts index e93ba2e9aa..0bd94c5d60 100644 --- a/src/widgets/index/__tests__/index-test.ts +++ b/src/widgets/index/__tests__/index-test.ts @@ -582,6 +582,108 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index/js/" ); }); + it('inherits from the parent states for the queries', () => { + const level0 = index({ indexName: 'level_0_index_name' }); + const level1 = index({ indexName: 'level_1_index_name' }); + const level2 = index({ indexName: 'level_2_index_name' }); + const searchClient = createSearchClient(); + const mainHelper = algoliasearchHelper(searchClient, '', {}); + const instantSearchInstance = createInstantSearch({ + mainHelper, + }); + + level0.addWidgets([ + createWidget({ + getConfiguration() { + return { + hitsPerPage: 5, + }; + }, + }), + + createSearchBox({ + getConfiguration() { + return { + query: 'Apple', + }; + }, + }), + + createPagination({ + getConfiguration() { + return { + page: 1, + }; + }, + }), + + level1.addWidgets([ + createSearchBox({ + getConfiguration() { + return { + query: 'Apple iPhone', + }; + }, + }), + + createPagination({ + getConfiguration() { + return { + page: 2, + }; + }, + }), + + level2.addWidgets([ + createSearchBox({ + getConfiguration() { + return { + query: 'Apple iPhone XS', + }; + }, + }), + ]), + ]), + ]); + + level0.init( + createInitOptions({ + instantSearchInstance, + }) + ); + + level0.getHelper()!.search(); + + expect(searchClient.search).toHaveBeenCalledWith( + expect.arrayContaining([ + { + indexName: 'level_0_index_name', + params: expect.objectContaining({ + hitsPerPage: 5, + query: 'Apple', + page: 1, + }), + }, + { + indexName: 'level_1_index_name', + params: expect.objectContaining({ + hitsPerPage: 5, + query: 'Apple iPhone', + page: 2, + }), + }, + { + indexName: 'level_2_index_name', + params: expect.objectContaining({ + hitsPerPage: 5, + query: 'Apple iPhone XS', + page: 2, + }), + }, + ]) + ); + }); + it('uses the internal state for the SFFV queries', () => { const instance = index({ indexName: 'index_name' }); const searchClient = createSearchClient(); @@ -839,6 +941,26 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index/js/" expect(instance.getWidgets()).toHaveLength(2); }); + it('removes the internal parent', () => { + const topLevelInstance = index({ indexName: 'top_level_index_name' }); + const subLevelInstance = index({ indexName: 'sub_level_index_name' }); + const instantSearchInstance = createInstantSearch(); + + topLevelInstance.addWidgets([subLevelInstance]); + + topLevelInstance.init( + createInitOptions({ + instantSearchInstance, + }) + ); + + expect(subLevelInstance.getHelper()).toBeDefined(); + + subLevelInstance.dispose(createDisposeOptions()); + + expect(subLevelInstance.getHelper()).toBe(null); + }); + it('removes the internal Helper', () => { const instance = index({ indexName: 'index_name' }); const instantSearchInstance = createInstantSearch(); diff --git a/src/widgets/index/index.ts b/src/widgets/index/index.ts index fed9b47492..a2b408ac70 100644 --- a/src/widgets/index/index.ts +++ b/src/widgets/index/index.ts @@ -12,6 +12,7 @@ import { } from '../../types'; import { createDocumentationMessageGenerator, + resolveSearchParameters, enhanceConfiguration, } from '../../lib/utils'; @@ -25,6 +26,7 @@ type IndexProps = { export type Index = Widget & { getHelper(): Helper | null; + getParent(): Index | null; getWidgets(): Widget[]; addWidgets(widgets: Widget[]): Index; removeWidgets(widgets: Widget[]): Index; @@ -38,6 +40,7 @@ const index = (props: IndexProps): Index => { let localWidgets: Widget[] = []; let localInstantSearchInstance: InstantSearch | null = null; + let localParent: Index | null = null; let helper: Helper | null = null; let derivedHelper: DerivedHelper | null = null; @@ -50,6 +53,10 @@ const index = (props: IndexProps): Index => { return helper; }, + getParent() { + return localParent; + }, + getWidgets() { return localWidgets; }, @@ -156,6 +163,7 @@ const index = (props: IndexProps): Index => { init({ instantSearchInstance, parent }) { localInstantSearchInstance = instantSearchInstance; + localParent = parent; // The `mainHelper` is already defined at this point. The instance is created // inside InstantSearch at the `start` method, which occurs before the `init` @@ -199,8 +207,15 @@ const index = (props: IndexProps): Index => { }; derivedHelper = mainHelper.derive(() => { - // @TODO: resolve the root and merge the SearchParameters - return helper!.state; + const parameters = resolveSearchParameters(this); + + // @TODO: replace this dummy merge with the correct function + return parameters.reduce((previous, current) => + algoliasearchHelper.SearchParameters.make({ + ...previous, + ...current, + }) + ); }); // We have to use `!` at the moment because `dervive` is not correctly typed. @@ -274,6 +289,7 @@ const index = (props: IndexProps): Index => { }); localInstantSearchInstance = null; + localParent = null; helper = null; derivedHelper!.detach(); diff --git a/stories/index.stories.ts b/stories/index.stories.ts index 915628bea3..31b94bd797 100644 --- a/stories/index.stories.ts +++ b/stories/index.stories.ts @@ -31,8 +31,6 @@ storiesOf('Index', module) .index({ indexName: 'instant_search_price_asc' }) .addWidgets([ instantsearch.widgets.configure({ - // @TODO: remove once we support inheritance of SearchParameters - attributesToSnippet: ['description'], hitsPerPage: 2, }), instantsearch.widgets.hits({ @@ -50,8 +48,6 @@ storiesOf('Index', module) .index({ indexName: 'instant_search_rating_asc' }) .addWidgets([ instantsearch.widgets.configure({ - // @TODO: remove once we support inheritance of SearchParameters - attributesToSnippet: ['description'], hitsPerPage: 1, }), instantsearch.widgets.hits({ @@ -87,8 +83,6 @@ storiesOf('Index', module) .index({ indexName: 'instant_search_price_asc' }) .addWidgets([ instantsearch.widgets.configure({ - // @TODO: remove once we support inheritance of SearchParameters - attributesToSnippet: ['description'], hitsPerPage: 2, }), instantsearch.widgets.hits({