Skip to content

Commit

Permalink
fix(InstantSearch): cancel scheduled operations (#3930)
Browse files Browse the repository at this point in the history
* fix(InstantSearch): cancel scheduled render

* fix(InstantSearch): cancel scheduled search

* fix(InstantSearch): cancel scheduled stalled render

* test(utils): create controlled search client
  • Loading branch information
samouss authored and Haroenv committed Oct 23, 2019
1 parent 43a0bf8 commit 3aafbad
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 38 deletions.
4 changes: 4 additions & 0 deletions src/lib/InstantSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,10 @@ See ${createDocumentationLink({
* @return {undefined} This method does not return anything
*/
dispose() {
this.scheduleSearch.cancel();
this.scheduleRender.cancel();
clearTimeout(this._searchStalledTimer);

this.removeWidgets(this.mainIndex.getWidgets());
this.mainIndex.dispose();

Expand Down
118 changes: 80 additions & 38 deletions src/lib/__tests__/InstantSearch-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import algoliasearchHelper from 'algoliasearch-helper';
import { createSearchClient } from '../../../test/mock/createSearchClient';
import {
createSearchClient,
createControlledSearchClient,
} from '../../../test/mock/createSearchClient';
import { createWidget } from '../../../test/mock/createWidget';
import { createMutliSearchResponse } from '../../../test/mock/createAPIResponse';
import { runAllMicroTasks } from '../../../test/utils/runAllMicroTasks';
import InstantSearch from '../InstantSearch';
import version from '../version';
Expand Down Expand Up @@ -514,6 +516,82 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/instantsear
});

describe('dispose', () => {
it('cancels the scheduled search', async () => {
const search = new InstantSearch({
indexName: 'index_name',
searchClient: createSearchClient(),
});

search.addWidgets([createWidget(), createWidget()]);

search.start();

await runAllMicroTasks();

// The call to `addWidgets` schedules a new search
search.addWidgets([createWidget()]);

search.dispose();

// Without the cancel operation, the function call throws an error which
// prevents the test to complete. We can't assert that the function throws
// because we don't have access to the promise that throws in the first place.
await runAllMicroTasks();
});

it('cancels the scheduled render', async () => {
const search = new InstantSearch({
indexName: 'index_name',
searchClient: createSearchClient(),
});

search.addWidgets([createWidget(), createWidget()]);

search.start();

// We only wait for the search to schedule the render. We have now a render
// that is scheduled, it will be processed in the next microtask if not canceled.
await Promise.resolve();

search.dispose();

// Without the cancel operation, the function call throws an error which
// prevents the test to complete. We can't assert that the function throws
// because we don't have access to the promise that throws in the first place.
await runAllMicroTasks();
});

it('cancels the scheduled stalled render', async () => {
const { searches, searchClient } = createControlledSearchClient();
const search = new InstantSearch({
indexName: 'index_name',
searchClient,
});

search.addWidgets([createWidget(), createWidget()]);

search.start();

// Resolve the `search`
searches[0].resolver();

// Wait for the `render`
await runAllMicroTasks();

// Simulate a search
search.mainHelper.search();

search.dispose();

// Reaches the delay
jest.runOnlyPendingTimers();

// Without the cancel operation, the function call throws an error which
// prevents the test to complete. We can't assert that the function throws
// because we don't have access to the promise that throws in the first place.
await runAllMicroTasks();
});

it('removes the widgets from the main index', () => {
const search = new InstantSearch({
indexName: 'index_name',
Expand Down Expand Up @@ -753,30 +831,6 @@ describe('scheduleRender', () => {
});

describe('scheduleStalledRender', () => {
const createControlledSearchClient = () => {
const searches = [];
const searchClient = {
search: jest.fn(() => {
let resolver;
const promise = new Promise(resolve => {
resolver = () => resolve(createMutliSearchResponse());
});

searches.push({
promise,
resolver,
});

return promise;
}),
};

return {
searchClient,
searches,
};
};

it('calls the `render` method on the main index', async () => {
const { searches, searchClient } = createControlledSearchClient();
const search = new InstantSearch({
Expand All @@ -793,9 +847,6 @@ describe('scheduleStalledRender', () => {
// Resolve the `search`
searches[0].resolver();

// Wait for the `search`
await searches[0].promise;

// Wait for the `render`
await runAllMicroTasks();

Expand Down Expand Up @@ -829,9 +880,6 @@ describe('scheduleStalledRender', () => {
// Resolve the `search`
searches[0].resolver();

// Wait for the `search`
await searches[0].promise;

// Wait for the `render`
await runAllMicroTasks();

Expand Down Expand Up @@ -872,9 +920,6 @@ describe('scheduleStalledRender', () => {
// Resolve the `search`
searches[0].resolver();

// Wait for the `search`
await searches[0].promise;

// Wait for the `render`
await runAllMicroTasks();

Expand Down Expand Up @@ -911,9 +956,6 @@ describe('scheduleStalledRender', () => {
// Resolve the `search`
searches[1].resolver();

// Wait for the `search`
await searches[1].promise;

// Wait for the `render`
await runAllMicroTasks();

Expand Down
37 changes: 37 additions & 0 deletions test/mock/createSearchClient.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MultiResponse } from 'algoliasearch';
import { Client } from '../../src/types';
import {
createSingleSearchResponse,
Expand All @@ -19,3 +20,39 @@ export const createSearchClient = (args: Partial<Client> = {}): Client =>
),
...args,
} as Client);

type ControlledClient = {
searchClient: Client;
searches: Array<{
promise: Promise<MultiResponse>;
resolver: () => void;
}>;
};

export const createControlledSearchClient = (
args: Partial<Client> = {}
): ControlledClient => {
const searches: ControlledClient['searches'] = [];
const searchClient = createSearchClient({
search: jest.fn(() => {
let resolver: () => void;
const promise: Promise<MultiResponse> = new Promise(resolve => {
resolver = () => resolve(createMutliSearchResponse());
});

searches.push({
promise,
// @ts-ignore
resolver,
});

return promise;
}),
...args,
});

return {
searchClient,
searches,
};
};

0 comments on commit 3aafbad

Please sign in to comment.