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

feat(recommend): introduce connectTrendingItems connector #6169

Merged
merged 8 commits into from
May 3, 2024
2 changes: 1 addition & 1 deletion bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
"maxSize": "175 kB"
"maxSize": "175.25 kB"
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
Expand Down
28 changes: 28 additions & 0 deletions packages/instantsearch.js/src/__tests__/common-connectors.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
connectToggleRefinement,
connectRelatedProducts,
connectFrequentlyBoughtTogether,
connectTrendingItems,
} from '../connectors';
import instantsearch from '../index.es';
import { refinementList } from '../widgets';
Expand Down Expand Up @@ -448,6 +449,32 @@ const testSetups: TestSetupsMap<TestSuites> = {
})
.start();
},
createTrendingItemsConnectorTests({ instantSearchOptions, widgetParams }) {
const customTrendingItems = connectTrendingItems<{
container: HTMLElement;
}>((renderOptions) => {
renderOptions.widgetParams.container.innerHTML = `
<ul>${renderOptions.recommendations
.map((recommendation) => `<li>${recommendation.objectID}</li>`)
.join('')}</ul>
`;
});

instantsearch(instantSearchOptions)
.addWidgets([
customTrendingItems({
container: document.body.appendChild(document.createElement('div')),
...widgetParams,
}),
])
.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> = {
Expand All @@ -463,6 +490,7 @@ const testOptions: TestOptionsMap<TestSuites> = {
createToggleRefinementConnectorTests: undefined,
createRelatedProductsConnectorTests: undefined,
createFrequentlyBoughtTogetherConnectorTests: undefined,
createTrendingItemsConnectorTests: undefined,
};

describe('Common connector tests (InstantSearch.js)', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/instantsearch.js/src/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export { default as connectSortBy } from './sort-by/connectSortBy';
export { default as connectRatingMenu } from './rating-menu/connectRatingMenu';
export { default as connectStats } from './stats/connectStats';
export { default as connectToggleRefinement } from './toggle-refinement/connectToggleRefinement';
export { default as connectTrendingItems } from './trending-items/connectTrendingItems';
export { default as connectBreadcrumb } from './breadcrumb/connectBreadcrumb';
export { default as connectGeoSearch } from './geo-search/connectGeoSearch';
export { default as connectPoweredBy } from './powered-by/connectPoweredBy';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {
createDocumentationMessageGenerator,
checkRendering,
noop,
} from '../../lib/utils';

import type { Connector, TransformItems, Hit, BaseHit } from '../../types';
import type {
PlainSearchParameters,
RecommendResultItem,
} from 'algoliasearch-helper';

const withUsage = createDocumentationMessageGenerator({
name: 'trending-items',
connector: true,
});

export type TrendingItemsRenderState<THit extends BaseHit = BaseHit> = {
/**
* The matched recommendations from the Algolia API.
*/
recommendations: Array<Hit<THit>>;
};

export type TrendingItemsConnectorParams<THit extends BaseHit = BaseHit> = (
| {
/**
* The facet attribute to get recommendations for.
*/
facetName: string;
/**
* The facet value to get recommendations for.
*/
facetValue: string;
}
| { facetName?: never; facetValue?: never }
) & {
/**
* The number of recommendations to retrieve.
*/
maxRecommendations?: number;
/**
* The threshold for the recommendations confidence score (between 0 and 100).
*/
threshold?: number;
/**
* List of search parameters to send.
*/
fallbackParameters?: Omit<
PlainSearchParameters,
'page' | 'hitsPerPage' | 'offset' | 'length'
>;
/**
* List of search parameters to send.
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
*/
queryParameters?: Omit<
PlainSearchParameters,
'page' | 'hitsPerPage' | 'offset' | 'length'
>;
/**
* Function to transform the items passed to the templates.
*/
transformItems?: TransformItems<Hit<THit>, { results: RecommendResultItem }>;
};

export type TrendingItemsWidgetDescription<THit extends BaseHit = BaseHit> = {
$$type: 'ais.trendingItems';
renderState: TrendingItemsRenderState<THit>;
};

export type TrendingItemsConnector<THit extends BaseHit = BaseHit> = Connector<
TrendingItemsWidgetDescription<THit>,
TrendingItemsConnectorParams<THit>
>;

const connectTrendingItems: TrendingItemsConnector =
function connectTrendingItems(renderFn, unmountFn = noop) {
checkRendering(renderFn, withUsage());

return function trendingItems(widgetParams) {
const {
facetName,
facetValue,
maxRecommendations,
threshold,
fallbackParameters,
queryParameters,
transformItems = ((items) => items) as NonNullable<
TrendingItemsConnectorParams['transformItems']
>,
} = widgetParams || {};

return {
dependsOn: 'recommend',
$$type: 'ais.trendingItems',

init(initOptions) {
renderFn(
{
...this.getWidgetRenderState(initOptions),
instantSearchInstance: initOptions.instantSearchInstance,
},
true
);
},

render(renderOptions) {
const renderState = this.getWidgetRenderState(renderOptions);

renderFn(
{
...renderState,
instantSearchInstance: renderOptions.instantSearchInstance,
},
false
);
},

getRenderState(renderState) {
return renderState;
},

getWidgetRenderState({ results }) {
if (results === null || results === undefined) {
return { recommendations: [], widgetParams };
}

return {
recommendations: transformItems(results.hits, {
results: results as RecommendResultItem,
}),
widgetParams,
};
},

dispose({ state }) {
unmountFn();

return state;
},

getWidgetParameters(state) {
return state.addTrendingItems({
facetName,
facetValue,
maxRecommendations,
threshold,
fallbackParameters,
queryParameters,
$$id: this.$$id!,
});
},
};
};
};

export default connectTrendingItems;
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ const testSetups: TestSetupsMap<TestSuites> = {
</InstantSearch>
);
},
createTrendingItemsConnectorTests: () => {},
};

const testOptions: TestOptionsMap<TestSuites> = {
Expand All @@ -384,6 +385,12 @@ const testOptions: TestOptionsMap<TestSuites> = {
createToggleRefinementConnectorTests: { act },
createRelatedProductsConnectorTests: { act },
createFrequentlyBoughtTogetherConnectorTests: { act },
createTrendingItemsConnectorTests: {
act,
skippedTests: {
options: true,
},
},
};

describe('Common connector tests (React InstantSearch)', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ const testSetups = {
},
createRelatedProductsConnectorTests: () => {},
createFrequentlyBoughtTogetherConnectorTests: () => {},
createTrendingItemsConnectorTests: () => {},
};

function createCustomWidget({
Expand Down Expand Up @@ -425,6 +426,11 @@ const testOptions = {
options: true,
},
},
createTrendingItemsConnectorTests: {
skippedTests: {
options: true,
},
},
};

describe('Common connector tests (Vue InstantSearch)', () => {
Expand Down
1 change: 1 addition & 0 deletions tests/common/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './toggle-refinement';
export * from './current-refinements';
export * from './related-products';
export * from './frequently-bought-together';
export * from './trending-items';
23 changes: 23 additions & 0 deletions tests/common/connectors/trending-items/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { fakeAct } from '../../common';

import { createOptionsTests } from './options';

import type { TestOptions, TestSetup } from '../../common';
import type { TrendingItemsConnectorParams } from 'instantsearch.js/src/connectors/trending-items/connectTrendingItems';

export type TrendingItemsConnectorSetup = TestSetup<{
widgetParams: TrendingItemsConnectorParams;
}>;

export function createTrendingItemsConnectorTests(
setup: TrendingItemsConnectorSetup,
{ act = fakeAct, skippedTests = {} }: TestOptions = {}
) {
beforeAll(() => {
document.body.innerHTML = '';
});

describe('TrendingItems connector common tests', () => {
createOptionsTests(setup, { act, skippedTests });
});
}