Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 11 additions & 0 deletions packages/algoliasearch-helper/types/algoliasearch.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,17 @@ export interface CompositionClient {
}) => Promise<{
results: Array<AlgoliaSearch.SearchResponse<T>>;
}>;
searchForFacetValues?: (options: {
compositionID: string;
facetName: string;
searchForFacetValuesRequest: {
params: {
query: string;
maxFacetHits: number;
searchQuery: SearchOptions;
};
};
}) => Promise<{ results: AlgoliaSearch.SearchForFacetValuesResponse[] }>;
initIndex?: never;
addAlgoliaAgent?: DefaultSearchClient['addAlgoliaAgent'];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @jest-environment @instantsearch/testutils/jest-environment-jsdom.ts
*/
import { runTestSuites } from '@instantsearch/tests';
import * as suites from '@instantsearch/tests/shared-composition';

import instantsearch from '../index.es';
import { refinementList } from '../widgets';

import type { TestOptionsMap, TestSetupsMap } from '@instantsearch/tests';

type TestSuites = typeof suites;
const testSuites: TestSuites = suites;

const testSetups: TestSetupsMap<TestSuites, 'javascript'> = {
createSharedCompositionTests({ instantSearchOptions, widgetParams }) {
instantsearch(instantSearchOptions)
.addWidgets([
refinementList({
container: document.body.appendChild(document.createElement('div')),
...widgetParams.refinementList,
}),
])
.on('error', () => {
/*
* prevent rethrowing InstantSearch errors, so tests can be asserted.
* IRL this isn't needed, as the error doesn't stop execution.
*/
})
.start();
},
};

const testOptions: TestOptionsMap<TestSuites> = {
createSharedCompositionTests: undefined,
};

describe('Common shared composition tests (InstantSearch.js)', () => {
runTestSuites({
flavor: 'javascript',
testSuites,
testSetups,
testOptions,
});
});
17 changes: 17 additions & 0 deletions packages/instantsearch.js/src/widgets/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,23 @@ const index = (widgetParams: IndexWidgetParams): IndexWidget => {
return mainHelper.search();
};

// We use the same pattern for the `searchForFacetValues`.
helper.searchForFacetValues = (
facetName,
facetValue,
maxFacetHits,
userState
) => {
const state = helper!.state.setQueryParameters(userState!);

return mainHelper.searchForFacetValues(
facetName,
facetValue,
maxFacetHits,
state
);
};

const isolatedHelper = indexName
? helper
: algoliasearchHelper({} as SearchClient, '__empty_index__', {});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @jest-environment @instantsearch/testutils/jest-environment-jsdom.ts
*/
import { runTestSuites } from '@instantsearch/tests';
import * as suites from '@instantsearch/tests/shared-composition';
import { act, render } from '@testing-library/react';
import React from 'react';

import { InstantSearch, RefinementList } from '..';

import type { TestOptionsMap, TestSetupsMap } from '@instantsearch/tests';

type TestSuites = typeof suites;
const testSuites: TestSuites = suites;

const testSetups: TestSetupsMap<TestSuites, 'react'> = {
createSharedCompositionTests({ instantSearchOptions, widgetParams }) {
render(
<InstantSearch {...instantSearchOptions}>
<RefinementList {...widgetParams.refinementList} />
</InstantSearch>
);
},
};

const testOptions: TestOptionsMap<TestSuites> = {
createSharedCompositionTests: { act },
};

describe('Common shared composition tests (React InstantSearch)', () => {
runTestSuites({
flavor: 'react',
testSuites,
testSetups,
testOptions,
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @jest-environment @instantsearch/testutils/jest-environment-jsdom.ts
*/
import { runTestSuites } from '@instantsearch/tests/common';
import * as testSuites from '@instantsearch/tests/shared-composition';

import { nextTick, mountApp } from '../../test/utils';
import { AisInstantSearch, AisRefinementList } from '../instantsearch';
import { renderCompat } from '../util/vue-compat';
jest.unmock('instantsearch.js/es');

const testSetups = {
async createSharedCompositionTests({ instantSearchOptions, widgetParams }) {
mountApp(
{
render: renderCompat((h) =>
h(AisInstantSearch, { props: instantSearchOptions }, [
h(AisRefinementList, { props: widgetParams.refinementList }),
])
),
},
document.body.appendChild(document.createElement('div'))
);

await nextTick();
},
};

const testOptions = {
createSharedCompositionTests: undefined,
};

describe('Common shared composition tests (Vue InstantSearch)', () => {
runTestSuites({
flavor: 'vue',
testSuites,
testSetups,
testOptions,
});
});
1 change: 1 addition & 0 deletions tests/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './connectors';
export * from './widgets';
export * from './shared';
export * from './shared-composition';
export * from './common';
25 changes: 25 additions & 0 deletions tests/common/shared-composition/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { fakeAct } from '../common';

import { createSearchableTests } from './searchable';

import type { TestOptions, TestSetup } from '../common';
import type { RefinementListWidget } from 'instantsearch.js/es/widgets/refinement-list/refinement-list';

export type SharedCompositionSetup = TestSetup<{
widgetParams: {
refinementList: Omit<Parameters<RefinementListWidget>[0], 'container'>;
};
}>;

export function createSharedCompositionTests(
setup: SharedCompositionSetup,
{ act = fakeAct, skippedTests = {}, flavor = 'javascript' }: TestOptions = {}
) {
beforeEach(() => {
document.body.innerHTML = '';
});

describe('Shared composition common tests', () => {
createSearchableTests(setup, { act, skippedTests, flavor });
});
}
102 changes: 102 additions & 0 deletions tests/common/shared-composition/searchable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
createMultiSearchResponse,
createSingleSearchResponse,
createCompositionClient,
} from '@instantsearch/mocks';
import { wait } from '@instantsearch/testutils';
import userEvent from '@testing-library/user-event';

import type { SharedCompositionSetup } from '.';
import type { TestOptions } from '../../common';

export function createSearchableTests(
setup: SharedCompositionSetup,
{ act }: Required<TestOptions>
) {
describe('searchable', () => {
test('renders search for facet values results', async () => {
const delay = 100;
const margin = 10;
const attribute = 'one';

const options = {
instantSearchOptions: {
compositionID: 'compositionID',
searchClient: createCompositionClient({
search: jest.fn(async ({ compositionID }) => {
if (!compositionID) throw new Error('Missing compositionID');
await wait(delay);
return createMultiSearchResponse(
createSingleSearchResponse({
hits: [{ objectID: '1' }],
facets: {
[attribute]: {
Samsung: 100,
Apple: 200,
},
},
page: 0,
nbPages: 20,
})
);
}) as ReturnType<typeof createCompositionClient>['search'],
searchForFacetValues: jest.fn(async ({ compositionID }) => {
if (!compositionID) throw new Error('Missing compositionID');
await wait(delay);
return {
results: [
{
facetHits: [
{
value: 'Samsung',
count: 100,
highlighted: '<em>S</em>amsung',
},
],
exhaustiveFacetsCount: true,
},
],
};
}) as ReturnType<
typeof createCompositionClient
>['searchForFacetValues'],
}),
},
widgetParams: {
refinementList: { attribute, searchable: true },
},
};

await setup(options);

// wait for the initial search
await act(() => wait(delay + margin));

expect(
Array.from(
document.querySelectorAll<HTMLDivElement>(
'.ais-RefinementList-labelText'
)
).map((el) => el.textContent)
).toEqual(['Apple', 'Samsung']);

userEvent.type(
document.querySelector(
'.ais-RefinementList-searchBox .ais-SearchBox-input'
)!,
's'
);

// wait for the search for facet values
await act(() => wait(delay + margin));

expect(
Array.from(
document.querySelectorAll<HTMLDivElement>(
'.ais-RefinementList-labelText'
)
).map((el) => el.textContent)
).toEqual(['<em>S</em>amsung']);
});
});
}