Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(routing): fix history router based on history length #5004

Merged
merged 12 commits into from
Feb 7, 2022
98 changes: 98 additions & 0 deletions src/lib/__tests__/routing/dispose-start-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { createSearchClient } from '../../../../test/mock/createSearchClient';
import { wait } from '../../../../test/utils/wait';
import historyRouter from '../../routers/history';
import instantsearch from '../../..';
import { connectSearchBox } from '../../../connectors';
import type InstantSearch from '../../InstantSearch';

/* eslint no-lone-blocks: "off" */

const writeDelay = 10;
const writeWait = 1.5 * writeDelay;

const addWidgetsAndStart = (search: InstantSearch) => {
search.addWidgets([connectSearchBox(() => {})({})]);
search.start();
};

describe('routing with `dispose` and `start`', () => {
FabienMotte marked this conversation as resolved.
Show resolved Hide resolved
test('URL is updated after a `dispose` and a `start`', async () => {
FabienMotte marked this conversation as resolved.
Show resolved Hide resolved
// -- Flow
// 1. Initial: '/'
// 2. Refine: '/?indexName[query]=Apple'
// 3. Dispose: '/'
// 4. Refine: '/'
// 5. Start: '/'
// 6. Refine: '/?indexName[query]=Apple'

const pushState = jest.spyOn(window.history, 'pushState');

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

// 1. Initial: '/'
{
addWidgetsAndStart(search);

await wait(writeWait);
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(0);
}

// 2. Refine: '/?indexName[query]=Apple'
{
search.renderState.indexName!.searchBox!.refine('Apple');

await wait(writeWait);
expect(window.location.search).toEqual(
`?${encodeURI('indexName[query]=Apple')}`
);
expect(pushState).toHaveBeenCalledTimes(1);
}

// 3. Dispose: '/'
{
search.dispose();

await wait(writeWait);
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(2);
}

// 4. Refine: '/'
{
search.renderState.indexName!.searchBox!.refine('Apple');

await wait(writeWait);
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(2);
}

// 5. Start: '/'
{
addWidgetsAndStart(search);

await wait(writeWait);
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(2);
}

// 6. Refine: '/?indexName[query]=Apple'
{
search.renderState.indexName!.searchBox!.refine('Samsung');

await wait(writeWait);
expect(window.location.search).toEqual(
`?${encodeURI('indexName[query]=Samsung')}`
);
expect(pushState).toHaveBeenCalledTimes(3);
}
});
});
74 changes: 74 additions & 0 deletions src/lib/__tests__/routing/external-influence-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { createSearchClient } from '../../../../test/mock/createSearchClient';
import { wait } from '../../../../test/utils/wait';
import historyRouter from '../../routers/history';
import instantsearch from '../../..';
import { connectSearchBox } from '../../../connectors';

/* eslint no-lone-blocks: "off" */

const writeDelay = 10;
const writeWait = 1.5 * writeDelay;

describe('routing with external influence', () => {
test('keeps on working when the URL is updated by another program', async () => {
// -- Flow
// 1. Initial: '/'
// 2. Refine: '/?indexName[query]=Apple'
// 3. External influence: '/about'
// 4. Refine: '/about?indexName[query]=Samsung'

const pushState = jest.spyOn(window.history, 'pushState');

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

// 1. Initial: '/'
{
search.addWidgets([connectSearchBox(() => {})({})]);
search.start();

await wait(writeWait);
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(0);
}

// 2. Refine: '/?indexName[query]=Apple'
{
search.renderState.indexName!.searchBox!.refine('Apple');

await wait(writeWait);
expect(window.location.search).toEqual(
`?${encodeURI('indexName[query]=Apple')}`
);
expect(pushState).toHaveBeenCalledTimes(1);
}

// 3. External influence: '/about'
{
window.history.pushState({}, '', '/about');

await wait(writeWait);
expect(window.location.pathname).toEqual('/about');
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(2);
}

// 4. Refine: '/about?indexName[query]=Samsung'
{
search.renderState.indexName!.searchBox!.refine('Samsung');

await wait(writeWait);
expect(window.location.pathname).toEqual('/about');
expect(window.location.search).toEqual(
`?${encodeURI('indexName[query]=Samsung')}`
);
}
});
});
63 changes: 63 additions & 0 deletions src/lib/__tests__/routing/modal-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { createSearchClient } from '../../../../test/mock/createSearchClient';
import { wait } from '../../../../test/utils/wait';
import historyRouter from '../../routers/history';
import instantsearch from '../../..';
import { connectSearchBox } from '../../../connectors';

/* eslint no-lone-blocks: "off" */

const writeDelay = 10;
const writeWait = 1.5 * writeDelay;

describe('routing with no navigation', () => {
test('cleans the URL when InstantSearch is disposed within the same page', async () => {
// -- Flow
// 1. Initial: '/'
// 2. Refine: '/?indexName[query]=Apple'
// 3. Dispose: '/'

const pushState = jest.spyOn(window.history, 'pushState');

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

// 1. Initial: '/'
{
search.addWidgets([connectSearchBox(() => {})({})]);
search.start();

await wait(writeWait);
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(0);
}

// 2. Refine: '/?indexName[query]=Apple'
{
search.renderState.indexName!.searchBox!.refine('Apple');

await wait(writeWait);
expect(window.location.pathname).toEqual('/');
expect(window.location.search).toEqual(
`?${encodeURI('indexName[query]=Apple')}`
);
expect(pushState).toHaveBeenCalledTimes(1);
}

// 3. Dispose: '/'
{
search.dispose();

await wait(writeWait);
expect(window.location.pathname).toEqual('/');
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(2);
}
});
});
96 changes: 96 additions & 0 deletions src/lib/__tests__/routing/spa-debounced-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { createSearchClient } from '../../../../test/mock/createSearchClient';
import { wait } from '../../../../test/utils/wait';
import historyRouter from '../../routers/history';
import instantsearch from '../../..';
import { connectSearchBox } from '../../../connectors';

/* eslint no-lone-blocks: "off" */

const writeDelay = 10;
const writeWait = 1.5 * writeDelay;

describe('routing with debounced third-party client-side router', () => {
test('does not clean the URL after navigating', async () => {
// -- Flow
// 1. Initial: '/'
// 2. Refine: '/?indexName[query]=Apple'
// 3. Dispose: '/'
// 4. Route change: '/about'
// 5. Back: '/'
// 6. Back: '/?indexName[query]=Apple'

const pushState = jest.spyOn(window.history, 'pushState');

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

// 1. Initial: '/'
{
search.addWidgets([connectSearchBox(() => {})({})]);
search.start();

await wait(writeWait);
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(0);
}

// 2. Refine: '/?indexName[query]=Apple'
{
search.renderState.indexName!.searchBox!.refine('Apple');

await wait(writeWait);
expect(window.location.search).toEqual(
`?${encodeURI('indexName[query]=Apple')}`
);
expect(pushState).toHaveBeenCalledTimes(1);
}

// 3. Dispose: '/'
{
search.dispose();

await wait(writeWait);
expect(window.location.pathname).toEqual('/');
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(2);
}

// 4. Route change: '/about'
{
window.history.pushState({}, '', '/about');

await wait(writeWait);
expect(window.location.pathname).toEqual('/about');
expect(window.location.search).toEqual('');
expect(pushState).toHaveBeenCalledTimes(3);
}

// 5. Back: '/'
{
window.history.back();

await wait(writeWait);
expect(window.location.pathname).toEqual('/');
expect(window.location.search).toEqual('');
}

// 6. Back: '/?indexName[query]=Apple'
{
window.history.back();

await wait(writeWait);
expect(window.location.pathname).toEqual('/');
expect(window.location.search).toEqual(
`?${encodeURI('indexName[query]=Apple')}`
);
expect(pushState).toHaveBeenCalledTimes(3);
}
});
});
Loading