Skip to content

Commit 88f2615

Browse files
Yannick CroissantHaroenv
authored andcommitted
feat(RoutingManager): update state on route update (#4100)
1 parent 28a8b09 commit 88f2615

File tree

3 files changed

+167
-7
lines changed

3 files changed

+167
-7
lines changed

src/lib/InstantSearch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ See ${createDocumentationLink({
408408
});
409409

410410
if (this.routing) {
411-
this._routingManager.applyStateFromRoute();
411+
this._routingManager.applyStateFromRoute(this.routing.router.read());
412412
}
413413

414414
mainHelper.search();

src/lib/RoutingManager.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@ class RoutingManager {
3434
this.instantSearchInstance = instantSearchInstance;
3535

3636
this.createURL = this.createURL.bind(this);
37+
this.applyStateFromRoute = this.applyStateFromRoute.bind(this);
38+
39+
this.router.onUpdate(this.applyStateFromRoute);
3740
}
3841

39-
public applyStateFromRoute(): void {
40-
const currentUiState = this.stateMapping.routeToState(this.router.read());
42+
public applyStateFromRoute(route: UiState): void {
43+
const currentUiState = this.stateMapping.routeToState(route);
4144

4245
walk(this.instantSearchInstance.mainIndex, current => {
4346
const widgets = current.getWidgets();
@@ -59,9 +62,6 @@ class RoutingManager {
5962

6063
this.instantSearchInstance.scheduleSearch();
6164
});
62-
63-
// @TODO: Update state on external route update (popState)
64-
// this.router.onUpdate(route => {});
6565
}
6666

6767
public write({ state }: { state: UiState }) {

src/lib/__tests__/RoutingManager-test.ts

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import qs from 'qs';
44
import { createSearchClient } from '../../../test/mock/createSearchClient';
55
import { createWidget } from '../../../test/mock/createWidget';
66
import { runAllMicroTasks } from '../../../test/utils/runAllMicroTasks';
7-
import { Router, Widget, StateMapping, RouteState } from '../../types';
7+
import { Router, Widget, UiState, StateMapping, RouteState } from '../../types';
88
import historyRouter from '../routers/history';
99
import instantsearch from '../main';
1010

@@ -35,6 +35,42 @@ const createFakeStateMapping = (
3535
...args,
3636
});
3737

38+
type HistoryState = {
39+
index: number;
40+
entries: object[];
41+
listeners: Array<(value: object) => void>;
42+
};
43+
44+
const createFakeHistory = (
45+
{
46+
index = -1,
47+
entries = [],
48+
listeners = [],
49+
}: HistoryState = {} as HistoryState
50+
): any => {
51+
const state: HistoryState = {
52+
index,
53+
entries,
54+
listeners,
55+
};
56+
57+
return {
58+
subscribe(listener: () => void) {
59+
state.listeners.push(listener);
60+
},
61+
push(value: object) {
62+
state.entries.push(value);
63+
state.index++;
64+
},
65+
back() {
66+
state.index--;
67+
listeners.forEach(listener => {
68+
listener(state.entries[state.index]);
69+
});
70+
},
71+
};
72+
};
73+
3874
const createFakeSearchBox = (): Widget =>
3975
createWidget({
4076
render({ helper }) {
@@ -118,6 +154,56 @@ describe('RoutingManager', () => {
118154
});
119155
});
120156

157+
test('should update the searchParameters on router state update', done => {
158+
const searchClient = createSearchClient();
159+
160+
let onRouterUpdateCallback: (args: UiState) => void;
161+
const router = createFakeRouter({
162+
onUpdate: fn => {
163+
onRouterUpdateCallback = fn;
164+
},
165+
});
166+
167+
const search = instantsearch({
168+
indexName: 'indexName',
169+
searchClient,
170+
routing: {
171+
router,
172+
},
173+
});
174+
175+
const widget = {
176+
render: jest.fn(),
177+
getWidgetSearchParameters: jest.fn((searchParameters, { uiState }) =>
178+
searchParameters.setQuery(uiState.query)
179+
),
180+
};
181+
182+
search.addWidget(widget);
183+
184+
search.start();
185+
186+
search.once('render', () => {
187+
// initialization is done at this point
188+
189+
expect(search.mainIndex.getHelper()!.state.query).toBeUndefined();
190+
191+
// this simulates a router update with a uiState of {query: 'a'}
192+
onRouterUpdateCallback({
193+
indexName: {
194+
query: 'a',
195+
},
196+
});
197+
198+
search.once('render', () => {
199+
// the router update triggers a new search
200+
// and given that the widget reads q as a query parameter
201+
expect(search.mainIndex.getHelper()!.state.query).toEqual('a');
202+
done();
203+
});
204+
});
205+
});
206+
121207
test('should apply state mapping on differences after searchfunction', done => {
122208
const searchClient = createSearchClient();
123209

@@ -282,6 +368,80 @@ describe('RoutingManager', () => {
282368
},
283369
});
284370
});
371+
372+
test('should keep the UI state up to date on router.update', async () => {
373+
const searchClient = createSearchClient();
374+
const stateMapping = createFakeStateMapping({});
375+
const history = createFakeHistory();
376+
const router = createFakeRouter({
377+
onUpdate(fn) {
378+
history.subscribe(state => {
379+
fn(state);
380+
});
381+
},
382+
write: jest.fn(state => {
383+
history.push(state);
384+
}),
385+
});
386+
387+
const search = instantsearch({
388+
indexName: 'indexName',
389+
searchClient,
390+
routing: {
391+
router,
392+
stateMapping,
393+
},
394+
});
395+
396+
const fakeSearchBox: any = createFakeSearchBox();
397+
const fakeHitsPerPage = createFakeHitsPerPage();
398+
399+
search.addWidget(fakeSearchBox);
400+
search.addWidget(fakeHitsPerPage);
401+
402+
search.start();
403+
404+
await runAllMicroTasks();
405+
406+
// Trigger an update - push a change
407+
fakeSearchBox.refine('Apple');
408+
409+
expect(router.write).toHaveBeenCalledTimes(1);
410+
expect(router.write).toHaveBeenLastCalledWith({
411+
indexName: {
412+
query: 'Apple',
413+
},
414+
});
415+
416+
// Trigger an update - push a change
417+
fakeSearchBox.refine('Apple iPhone');
418+
419+
expect(router.write).toHaveBeenCalledTimes(2);
420+
expect(router.write).toHaveBeenLastCalledWith({
421+
indexName: {
422+
query: 'Apple iPhone',
423+
},
424+
});
425+
426+
await runAllMicroTasks();
427+
428+
// Trigger an update - Apple iPhone → Apple
429+
history.back();
430+
431+
await runAllMicroTasks();
432+
433+
// Trigger getConfiguration
434+
search.removeWidget(fakeHitsPerPage);
435+
436+
await runAllMicroTasks();
437+
438+
expect(router.write).toHaveBeenCalledTimes(3);
439+
expect(router.write).toHaveBeenLastCalledWith({
440+
indexName: {
441+
query: 'Apple',
442+
},
443+
});
444+
});
285445
});
286446

287447
describe('windowTitle', () => {

0 commit comments

Comments
 (0)