Skip to content

Commit

Permalink
fix(createURL): support multi-index (#4082)
Browse files Browse the repository at this point in the history
* fix(createURL): support multi-index

* test(InstantSearch): use search box connector

* test(InstantSearch): mock `router.createURL` implementation

* refactor(index): store `createURL` on the index

* style(InstantSearch): remove unused import
  • Loading branch information
francoischalifour authored and Haroenv committed Oct 23, 2019
1 parent 2c05a01 commit 179a6e5
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 53 deletions.
17 changes: 4 additions & 13 deletions src/lib/InstantSearch.ts
@@ -1,8 +1,4 @@
import algoliasearchHelper, {
AlgoliaSearchHelper,
SearchParameters,
PlainSearchParameters,
} from 'algoliasearch-helper';
import algoliasearchHelper, { AlgoliaSearchHelper } from 'algoliasearch-helper';
import { Client as AlgoliaSearchClient } from 'algoliasearch';
import EventEmitter from 'events';
import index, { Index } from '../widgets/index/index';
Expand Down Expand Up @@ -142,8 +138,7 @@ class InstantSearch extends EventEmitter {
public _isSearchStalled: boolean;
public _initialUiState: UiState;
public _searchFunction?: InstantSearchOptions['searchFunction'];
public _createURL?: (params: SearchParameters) => string;
public _createAbsoluteURL?: (params: SearchParameters) => string;
public _createURL?(nextState: UiState): string;
public _mainHelperSearch?: AlgoliaSearchHelper['search'];
public routing?: Routing;

Expand Down Expand Up @@ -350,14 +345,12 @@ See ${createDocumentationLink({
instantSearchInstance: this,
});
this._createURL = routingManager.createURL.bind(routingManager);
this._createAbsoluteURL = this._createURL;
// We don't use `addWidgets` because we have to ensure that `RoutingManager`
// is the last widget added. Otherwise we have an issue with the `routing`.
// https://github.com/algolia/instantsearch.js/pull/3149
this.mainIndex.getWidgets().push(routingManager);
} else {
this._createURL = defaultCreateURL;
this._createAbsoluteURL = defaultCreateURL;
}

// This Helper is used for the queries, we don't care about its state. The
Expand Down Expand Up @@ -483,16 +476,14 @@ See ${createDocumentationLink({
// const nextUiState = this.mainIndex.getWidgetState({});
};

public createURL(params: PlainSearchParameters): string {
public createURL(nextState: UiState = {}): string {
if (!this._createURL) {
throw new Error(
withUsage('The `start` method needs to be called before `createURL`.')
);
}

return this._createURL(
this.mainIndex.getHelper()!.state.setQueryParameters(params)
);
return this._createURL(nextState);
}

public refresh() {
Expand Down
12 changes: 8 additions & 4 deletions src/lib/RoutingManager.ts
Expand Up @@ -173,10 +173,14 @@ class RoutingManager implements Widget {
}
}

public createURL(state: SearchParameters): string {
const uiState = this.getAllUiStates({
searchParameters: state,
});
public createURL(nextState: UiState): string {
const uiState = Object.keys(nextState).reduce(
(acc, indexId) => ({
...acc,
[indexId]: nextState[indexId],
}),
this.instantSearchInstance.mainIndex.getWidgetState({})
);

const route = this.stateMapping.stateToRoute(uiState);

Expand Down
115 changes: 86 additions & 29 deletions src/lib/__tests__/InstantSearch-test.js
@@ -1,13 +1,16 @@
import algoliasearchHelper from 'algoliasearch-helper';
import InstantSearch from '../InstantSearch';
import version from '../version';
import connectSearchBox from '../../connectors/search-box/connectSearchBox';
import connectPagination from '../../connectors/pagination/connectPagination';
import index from '../../widgets/index/index';
import { noop, warning } from '../utils';
import {
createSearchClient,
createControlledSearchClient,
} from '../../../test/mock/createSearchClient';
import { createWidget } from '../../../test/mock/createWidget';
import { runAllMicroTasks } from '../../../test/utils/runAllMicroTasks';
import InstantSearch from '../InstantSearch';
import version from '../version';
import { warning } from '../utils';

jest.useFakeTimers();

Expand Down Expand Up @@ -979,9 +982,9 @@ describe('createURL', () => {
createURL: jest.fn(() => '#'),
});

it('returns the URL for the main index state', () => {
it('at top-level returns the default URL for the main index state', () => {
const router = createRouter();
router.createURL.mockImplementation(() => 'http://algolia.com');
router.createURL.mockImplementation(() => 'https://algolia.com');

const search = new InstantSearch({
indexName: 'indexName',
Expand All @@ -991,56 +994,110 @@ describe('createURL', () => {
},
});

search.addWidget(
createWidget({
getWidgetState() {
return {
query: 'Apple',
};
},
})
search.start();

expect(search.createURL()).toBe('https://algolia.com');
});

it('at top-level returns a custom URL for the main index state', () => {
const router = createRouter();
router.createURL.mockImplementation(() => 'https://algolia.com');

const search = new InstantSearch({
indexName: 'indexName',
searchClient: createSearchClient(),
routing: {
router,
},
});

search.addWidget(connectSearchBox(noop)({}));
search.start();

expect(search.createURL({ indexName: { query: 'Apple' } })).toBe(
'https://algolia.com'
);
});

it('returns the default URL for the main index state', () => {
const router = createRouter();
const search = new InstantSearch({
indexName: 'indexName',
searchClient: createSearchClient(),
initialUiState: {
indexName: {
query: 'Apple',
},
},
routing: {
router,
},
});

search.addWidget(connectSearchBox(noop)({}));
search.start();
search.createURL();

expect(search.createURL()).toBe('http://algolia.com');
expect(router.createURL).toHaveBeenCalledWith({
indexName: {
query: 'Apple',
},
});
});

it('returns the URL with the given `SearchParameters` applied', () => {
it('returns the URL for nested index states', async () => {
const router = createRouter();
router.createURL.mockImplementation(() => 'http://algolia.com');

const search = new InstantSearch({
indexName: 'indexName',
searchClient: createSearchClient(),
initialUiState: {
indexName: {
query: 'Google',
},
indexNameLvl1: {
query: 'Samsung',
},
indexNameLvl2: {
query: 'Google',
},
},
routing: {
router,
},
});

search.addWidget(
createWidget({
getWidgetState(_, { searchParameters }) {
return {
query: 'Apple',
page: searchParameters.page,
};
},
})
);
search.addWidgets([
connectSearchBox(noop)({}),
index({ indexName: 'indexNameLvl1' }).addWidgets([
connectSearchBox(noop)({}),
index({ indexName: 'indexNameLvl2' }).addWidgets([
connectSearchBox(noop)({}),
connectPagination(noop)({}),
createWidget({
render({ helper, createURL }) {
createURL(helper.state.setPage(3).setQuery('Apple'));
},
}),
]),
]),
]);

search.start();

expect(search.createURL({ page: 5 })).toBe('http://algolia.com');
// We need to run all micro tasks for the `render` method of the last
// widget to be called and its `createURL` to be triggered.
await runAllMicroTasks();

expect(router.createURL).toHaveBeenCalledWith({
indexName: {
query: 'Google',
},
indexNameLvl1: {
query: 'Samsung',
},
indexNameLvl2: {
query: 'Apple',
page: 5,
page: 4,
},
});
});
Expand Down
6 changes: 3 additions & 3 deletions src/widgets/index/__tests__/index-test.ts
Expand Up @@ -250,7 +250,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index/js/"
helper: instance.getHelper(),
state: instance.getHelper()!.state,
templatesConfig: instantSearchInstance.templatesConfig,
createURL: instantSearchInstance._createAbsoluteURL,
createURL: expect.any(Function),
});
});
});
Expand Down Expand Up @@ -903,7 +903,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index/js/"
helper: instance.getHelper(),
state: instance.getHelper()!.state,
templatesConfig: instantSearchInstance.templatesConfig,
createURL: instantSearchInstance._createAbsoluteURL,
createURL: expect.any(Function),
});
});
});
Expand Down Expand Up @@ -1789,7 +1789,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index/js/"
state: expect.any(algoliasearchHelper.SearchParameters),
helper: instance.getHelper(),
templatesConfig: instantSearchInstance.templatesConfig,
createURL: instantSearchInstance._createAbsoluteURL,
createURL: expect.any(Function),
searchMetadata: {
isSearchStalled: instantSearchInstance._isSearchStalled,
},
Expand Down
14 changes: 11 additions & 3 deletions src/widgets/index/index.ts
Expand Up @@ -150,6 +150,14 @@ const index = (props: IndexProps): Index => {
let derivedHelper: DerivedHelper | null = null;
let isFirstRender = true;

const createURL = (nextState: SearchParameters) =>
localInstantSearchInstance!._createURL!({
[indexId]: getLocalWidgetsState(localWidgets, {
searchParameters: nextState,
helper: helper!,
}),
});

return {
$$type: 'ais.index',

Expand Down Expand Up @@ -228,7 +236,7 @@ const index = (props: IndexProps): Index => {
instantSearchInstance: localInstantSearchInstance,
state: helper!.state,
templatesConfig: localInstantSearchInstance.templatesConfig,
createURL: localInstantSearchInstance._createAbsoluteURL!,
createURL,
});
}
});
Expand Down Expand Up @@ -366,7 +374,7 @@ const index = (props: IndexProps): Index => {
instantSearchInstance,
state: helper!.state,
templatesConfig: instantSearchInstance.templatesConfig,
createURL: instantSearchInstance._createAbsoluteURL!,
createURL,
});
}
});
Expand Down Expand Up @@ -404,7 +412,7 @@ const index = (props: IndexProps): Index => {
scopedResults: resolveScopedResultsFromIndex(this),
state: derivedHelper!.lastResults._state,
templatesConfig: instantSearchInstance.templatesConfig,
createURL: instantSearchInstance._createAbsoluteURL!,
createURL,
searchMetadata: {
isSearchStalled: instantSearchInstance._isSearchStalled,
},
Expand Down
1 change: 0 additions & 1 deletion test/mock/createInstantSearch.ts
Expand Up @@ -38,7 +38,6 @@ export const createInstantSearch = (
_searchStalledTimer: null,
_initialUiState: {},
_createURL: jest.fn(() => '#'),
_createAbsoluteURL: jest.fn(() => '#'),
onStateChange: jest.fn(),
createURL: jest.fn(() => '#'),
routing: undefined,
Expand Down

0 comments on commit 179a6e5

Please sign in to comment.