Skip to content

Commit

Permalink
feat(RoutingManager): update state on route update (#4100)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yannick Croissant authored and Haroenv committed Oct 23, 2019
1 parent 28a8b09 commit 88f2615
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/lib/InstantSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ See ${createDocumentationLink({
});

if (this.routing) {
this._routingManager.applyStateFromRoute();
this._routingManager.applyStateFromRoute(this.routing.router.read());
}

mainHelper.search();
Expand Down
10 changes: 5 additions & 5 deletions src/lib/RoutingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ class RoutingManager {
this.instantSearchInstance = instantSearchInstance;

this.createURL = this.createURL.bind(this);
this.applyStateFromRoute = this.applyStateFromRoute.bind(this);

this.router.onUpdate(this.applyStateFromRoute);
}

public applyStateFromRoute(): void {
const currentUiState = this.stateMapping.routeToState(this.router.read());
public applyStateFromRoute(route: UiState): void {
const currentUiState = this.stateMapping.routeToState(route);

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

this.instantSearchInstance.scheduleSearch();
});

// @TODO: Update state on external route update (popState)
// this.router.onUpdate(route => {});
}

public write({ state }: { state: UiState }) {
Expand Down
162 changes: 161 additions & 1 deletion src/lib/__tests__/RoutingManager-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import qs from 'qs';
import { createSearchClient } from '../../../test/mock/createSearchClient';
import { createWidget } from '../../../test/mock/createWidget';
import { runAllMicroTasks } from '../../../test/utils/runAllMicroTasks';
import { Router, Widget, StateMapping, RouteState } from '../../types';
import { Router, Widget, UiState, StateMapping, RouteState } from '../../types';
import historyRouter from '../routers/history';
import instantsearch from '../main';

Expand Down Expand Up @@ -35,6 +35,42 @@ const createFakeStateMapping = (
...args,
});

type HistoryState = {
index: number;
entries: object[];
listeners: Array<(value: object) => void>;
};

const createFakeHistory = (
{
index = -1,
entries = [],
listeners = [],
}: HistoryState = {} as HistoryState
): any => {
const state: HistoryState = {
index,
entries,
listeners,
};

return {
subscribe(listener: () => void) {
state.listeners.push(listener);
},
push(value: object) {
state.entries.push(value);
state.index++;
},
back() {
state.index--;
listeners.forEach(listener => {
listener(state.entries[state.index]);
});
},
};
};

const createFakeSearchBox = (): Widget =>
createWidget({
render({ helper }) {
Expand Down Expand Up @@ -118,6 +154,56 @@ describe('RoutingManager', () => {
});
});

test('should update the searchParameters on router state update', done => {
const searchClient = createSearchClient();

let onRouterUpdateCallback: (args: UiState) => void;
const router = createFakeRouter({
onUpdate: fn => {
onRouterUpdateCallback = fn;
},
});

const search = instantsearch({
indexName: 'indexName',
searchClient,
routing: {
router,
},
});

const widget = {
render: jest.fn(),
getWidgetSearchParameters: jest.fn((searchParameters, { uiState }) =>
searchParameters.setQuery(uiState.query)
),
};

search.addWidget(widget);

search.start();

search.once('render', () => {
// initialization is done at this point

expect(search.mainIndex.getHelper()!.state.query).toBeUndefined();

// this simulates a router update with a uiState of {query: 'a'}
onRouterUpdateCallback({
indexName: {
query: 'a',
},
});

search.once('render', () => {
// the router update triggers a new search
// and given that the widget reads q as a query parameter
expect(search.mainIndex.getHelper()!.state.query).toEqual('a');
done();
});
});
});

test('should apply state mapping on differences after searchfunction', done => {
const searchClient = createSearchClient();

Expand Down Expand Up @@ -282,6 +368,80 @@ describe('RoutingManager', () => {
},
});
});

test('should keep the UI state up to date on router.update', async () => {
const searchClient = createSearchClient();
const stateMapping = createFakeStateMapping({});
const history = createFakeHistory();
const router = createFakeRouter({
onUpdate(fn) {
history.subscribe(state => {
fn(state);
});
},
write: jest.fn(state => {
history.push(state);
}),
});

const search = instantsearch({
indexName: 'indexName',
searchClient,
routing: {
router,
stateMapping,
},
});

const fakeSearchBox: any = createFakeSearchBox();
const fakeHitsPerPage = createFakeHitsPerPage();

search.addWidget(fakeSearchBox);
search.addWidget(fakeHitsPerPage);

search.start();

await runAllMicroTasks();

// Trigger an update - push a change
fakeSearchBox.refine('Apple');

expect(router.write).toHaveBeenCalledTimes(1);
expect(router.write).toHaveBeenLastCalledWith({
indexName: {
query: 'Apple',
},
});

// Trigger an update - push a change
fakeSearchBox.refine('Apple iPhone');

expect(router.write).toHaveBeenCalledTimes(2);
expect(router.write).toHaveBeenLastCalledWith({
indexName: {
query: 'Apple iPhone',
},
});

await runAllMicroTasks();

// Trigger an update - Apple iPhone → Apple
history.back();

await runAllMicroTasks();

// Trigger getConfiguration
search.removeWidget(fakeHitsPerPage);

await runAllMicroTasks();

expect(router.write).toHaveBeenCalledTimes(3);
expect(router.write).toHaveBeenLastCalledWith({
indexName: {
query: 'Apple',
},
});
});
});

describe('windowTitle', () => {
Expand Down

0 comments on commit 88f2615

Please sign in to comment.