Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/instantsearch.js/src/lib/InstantSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,10 @@ See documentation: ${createDocumentationLink({
);
}

if (this.compositionID && widgets.some(isIndexWidget)) {
if (
this.compositionID &&
widgets.some((w) => isIndexWidget(w) && !w._isolated)
) {
throw new Error(
withUsage(
'The `index` widget cannot be used with a composition-based InstantSearch implementation.'
Expand Down
227 changes: 227 additions & 0 deletions packages/instantsearch.js/src/widgets/index/__tests__/index-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import {
createCompositionClient,
createSearchClient,
createSingleRecommendResponse,
createSingleSearchResponse,
Expand Down Expand Up @@ -155,6 +156,12 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge
`);
});

it('does not throw without `indexName` option when `isolated` is true', () => {
expect(() => {
index({ isolated: true });
}).not.toThrow();
});

it('is a widget', () => {
const widget = index({ indexName: 'indexName' });

Expand Down Expand Up @@ -3345,6 +3352,81 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge
});
});

describe('getWidgetUiState', () => {
it('returns the index UI state of its widgets', () => {
const instance = index({ indexName: 'instance' });
const search = instantsearch({
indexName: 'root',
searchClient: createSearchClient(),
});
search.start();
search.addWidgets([instance.addWidgets([virtualSearchBox({})])]);

search.renderState.instance.searchBox!.refine('hello');

expect(instance.getWidgetUiState({})).toEqual({
instance: {
query: 'hello',
},
});
});

it('returns the index of its widgets and child indexes', () => {
const instance = index({ indexName: 'instance' });
const search = instantsearch({
indexName: 'root',
searchClient: createSearchClient(),
});
search.start();
search.addWidgets([
instance.addWidgets([
virtualSearchBox({}),
index({ indexName: 'childInstance' }).addWidgets([
virtualPagination({}),
]),
]),
]);

search.renderState.instance.searchBox!.refine('hello');
search.renderState.childInstance.pagination!.refine(2);

expect(instance.getWidgetUiState({})).toEqual({
childInstance: {
page: 3,
},
instance: {
query: 'hello',
},
});
});

it('does not include isolated child indexes', () => {
const instance = index({ indexName: 'instance' });
const search = instantsearch({
indexName: 'root',
searchClient: createSearchClient(),
});
search.start();
search.addWidgets([
instance.addWidgets([
virtualSearchBox({}),
index({ indexName: 'childInstance', isolated: true }).addWidgets([
virtualPagination({}),
]),
]),
]);

search.renderState.instance.searchBox!.refine('hello');
search.renderState.childInstance.pagination!.refine(2);

expect(instance.getWidgetUiState({})).toEqual({
instance: {
query: 'hello',
},
});
});
});

describe('setIndexUiState', () => {
it('updates main UI state with an object', () => {
const instance = index({ indexName: 'indexName' });
Expand Down Expand Up @@ -3737,6 +3819,151 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/index-widge
});
});

describe('isolated', () => {
it('sets _isolated to true when isolated option is true', () => {
const instance = index({ isolated: true });
expect(instance._isolated).toBe(true);
expect(instance.getParent()).toBeNull();
});

it('sets _isolated to false when isolated option is false or omitted', () => {
const instance = index({ indexName: 'indexName' });
expect(instance._isolated).toBe(false);
});

it('returns correct parent for non-isolated indices', () => {
const parent = index({ indexName: 'parentIndex' });
const child = index({ indexName: 'childIndex' });
parent.addWidgets([child]);
child.init(createIndexInitOptions({ parent }));
expect(child.getParent()).toBe(parent);
});

it('returns null parent for isolated indices', () => {
const parent = index({ indexName: 'parentIndex' });
const child = index({ isolated: true });
parent.addWidgets([child]);
child.init(createIndexInitOptions({ parent }));
expect(child.getParent()).toBeNull();
});

it('does not search by default when isolated', async () => {
const search = instantsearch({
searchClient: createSearchClient(),
}).addWidgets([
index({ isolated: true }).addWidgets([virtualSearchBox({})]),
]);
search.start();

await wait(0);
expect(search.client.search).toHaveBeenCalledTimes(0);
});

it('searches by default when not isolated', async () => {
const search = instantsearch({
searchClient: createSearchClient(),
}).addWidgets([
index({ isolated: false, indexName: 'a' }).addWidgets([
virtualSearchBox({}),
]),
]);
search.start();

await wait(0);
expect(search.client.search).toHaveBeenCalledTimes(1);
expect(castToJestMock(search.client.search).mock.calls[0][0])
.toMatchInlineSnapshot(`
[
{
"indexName": "a",
"params": {
"query": "",
},
},
]
`);
});

it('searches on refine while isolated', async () => {
const search = instantsearch({
searchClient: createSearchClient(),
}).addWidgets([
index({ isolated: true, indexName: 'a' }).addWidgets([
virtualSearchBox({}),
]),
]);
search.start();

await wait(0);
expect(search.client.search).toHaveBeenCalledTimes(0);

search.renderState.a.searchBox?.refine('please search now');

expect(search.client.search).toHaveBeenCalledTimes(1);
expect(castToJestMock(search.client.search).mock.calls[0][0])
.toMatchInlineSnapshot(`
[
{
"indexName": "a",
"params": {
"query": "please search now",
},
},
]
`);
});

it('searches on refine of a child while isolated', async () => {
const search = instantsearch({
searchClient: createSearchClient(),
}).addWidgets([
index({ isolated: true }).addWidgets([
index({ indexName: 'a' }),
virtualSearchBox({}),
]),
]);
search.start();

await wait(0);
expect(search.client.search).toHaveBeenCalledTimes(0);

search.renderState[''].searchBox?.refine('please search now');

expect(search.client.search).toHaveBeenCalledTimes(1);
});

it('triggers composition when root has a compositionId', async () => {
const search = instantsearch({
searchClient: createCompositionClient(),
compositionID: 'composition-id',
}).addWidgets([
index({ isolated: true, indexName: 'a' }).addWidgets([
virtualSearchBox({}),
]),
]);
search.start();

await wait(0);
// Now called for the root, as a compositionId is set
expect(search.client.search).toHaveBeenCalledTimes(1);

search.renderState.a.searchBox?.refine('please search now');

expect(search.client.search).toHaveBeenCalledTimes(2);
expect(castToJestMock(search.client.search).mock.calls[1][0])
.toMatchInlineSnapshot(`
{
"compositionID": "a",
"requestBody": {
"params": {
"query": "please search now",
},
},
}
`);
});
});

describe('on error', () => {
it('resets the state', async () => {
const searchClient = createSearchClient();
Expand Down
Loading