diff --git a/packages/instantsearch.js/src/components/RefinementList/RefinementList.tsx b/packages/instantsearch.js/src/components/RefinementList/RefinementList.tsx index eb573b47f1..b89948e98a 100644 --- a/packages/instantsearch.js/src/components/RefinementList/RefinementList.tsx +++ b/packages/instantsearch.js/src/components/RefinementList/RefinementList.tsx @@ -68,6 +68,7 @@ export type RefinementListProps = { showMore?: boolean; toggleShowMore?: () => void; isShowingMore?: boolean; + showMoreCount?: number; hasExhaustiveItems?: boolean; canToggleShowMore?: boolean; className?: string; @@ -302,6 +303,7 @@ class RefinementList extends Component< }} data={{ isShowingMore: this.props.isShowingMore, + showMoreCount: this.props.showMoreCount, }} /> ); diff --git a/packages/instantsearch.js/src/connectors/hierarchical-menu/__tests__/connectHierarchicalMenu-test.ts b/packages/instantsearch.js/src/connectors/hierarchical-menu/__tests__/connectHierarchicalMenu-test.ts index 109db9d173..bd1cc78719 100644 --- a/packages/instantsearch.js/src/connectors/hierarchical-menu/__tests__/connectHierarchicalMenu-test.ts +++ b/packages/instantsearch.js/src/connectors/hierarchical-menu/__tests__/connectHierarchicalMenu-test.ts @@ -620,6 +620,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hierarchica attributes: ['anotherCategory', 'anotherSubCategory'], }, isShowingMore: false, + showMoreCount: 0, toggleShowMore: () => {}, canToggleShowMore: false, }, @@ -641,6 +642,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hierarchica sendEvent: expect.any(Function), widgetParams: { attributes: ['category', 'subCategory'] }, isShowingMore: false, + showMoreCount: 0, toggleShowMore: expect.any(Function), canToggleShowMore: false, }, @@ -713,6 +715,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hierarchica sendEvent: expect.any(Function), widgetParams: { attributes: ['category', 'subCategory'] }, isShowingMore: false, + showMoreCount: 0, toggleShowMore: expect.any(Function), canToggleShowMore: false, }); @@ -751,6 +754,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hierarchica createURL: expect.any(Function), widgetParams: { attributes: ['category', 'subCategory'] }, isShowingMore: false, + showMoreCount: 0, toggleShowMore: expect.any(Function), canToggleShowMore: false, }); @@ -821,6 +825,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/hierarchica createURL: expect.any(Function), widgetParams: { attributes: ['category', 'subCategory'] }, isShowingMore: false, + showMoreCount: 0, toggleShowMore: expect.any(Function), canToggleShowMore: false, }); diff --git a/packages/instantsearch.js/src/connectors/hierarchical-menu/connectHierarchicalMenu.ts b/packages/instantsearch.js/src/connectors/hierarchical-menu/connectHierarchicalMenu.ts index 68cdabc1c0..c1333def5e 100644 --- a/packages/instantsearch.js/src/connectors/hierarchical-menu/connectHierarchicalMenu.ts +++ b/packages/instantsearch.js/src/connectors/hierarchical-menu/connectHierarchicalMenu.ts @@ -113,6 +113,10 @@ export type HierarchicalMenuRenderState = { * True if the menu is displaying all the menu items. */ isShowingMore: boolean; + /** + * Total number of facet values that can be displayed for 'show more'. + */ + showMoreCount: number; /** * Toggles the number of values displayed between `limit` and `showMoreLimit`. */ @@ -222,6 +226,7 @@ const connectHierarchicalMenu: HierarchicalMenuConnector = let _refine: HierarchicalMenuRenderState['refine'] | undefined; let isShowingMore = false; + let showMoreCount = 0; function createToggleShowMore( renderOptions: RenderOptions, @@ -347,6 +352,7 @@ const connectHierarchicalMenu: HierarchicalMenuConnector = } if (results) { + const currentLimit = getLimit(); const facetValues = results.getFacetValues(hierarchicalFacetName, { sortBy, facetOrdering: sortBy === DEFAULT_SORT, @@ -363,9 +369,17 @@ const connectHierarchicalMenu: HierarchicalMenuConnector = // Because this is used for making the search of facets unable or not, it is important // to be conservative here. const hasExhaustiveItems = - (state.maxValuesPerFacet || 0) > getLimit() - ? facetItems.length <= getLimit() - : facetItems.length < getLimit(); + (state.maxValuesPerFacet || 0) > currentLimit + ? facetItems.length <= currentLimit + : facetItems.length < currentLimit; + + if (showMore) { + const showMoreTotalCount = Math.min( + showMoreLimit, + facetItems.length + ); + showMoreCount = showMoreTotalCount - currentLimit; + } canToggleShowMore = showMore && (isShowingMore || !hasExhaustiveItems); @@ -383,6 +397,7 @@ const connectHierarchicalMenu: HierarchicalMenuConnector = sendEvent, widgetParams, isShowingMore, + showMoreCount, toggleShowMore: cachedToggleShowMore, canToggleShowMore, }; diff --git a/packages/instantsearch.js/src/connectors/menu/__tests__/connectMenu-test.ts b/packages/instantsearch.js/src/connectors/menu/__tests__/connectMenu-test.ts index 4a6af23a3c..c01b6c9b08 100644 --- a/packages/instantsearch.js/src/connectors/menu/__tests__/connectMenu-test.ts +++ b/packages/instantsearch.js/src/connectors/menu/__tests__/connectMenu-test.ts @@ -628,6 +628,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/menu/js/#co sendEvent: expect.any(Function), canRefine: false, isShowingMore: false, + showMoreCount: 0, toggleShowMore: expect.any(Function), canToggleShowMore: false, widgetParams: { attribute: 'brand' }, @@ -673,6 +674,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/menu/js/#co createURL: expect.any(Function), widgetParams: { attribute: 'brand' }, isShowingMore: false, + showMoreCount: 0, toggleShowMore: expect.any(Function), canToggleShowMore: false, }, @@ -706,6 +708,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/menu/js/#co sendEvent: expect.any(Function), canRefine: false, isShowingMore: false, + showMoreCount: 0, toggleShowMore: expect.any(Function), canToggleShowMore: false, widgetParams: { attribute: 'brand' }, @@ -765,6 +768,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/menu/js/#co sendEvent: expect.any(Function), canRefine: true, isShowingMore: false, + showMoreCount: 0, toggleShowMore: expect.any(Function), canToggleShowMore: false, widgetParams: { attribute: 'brand' }, diff --git a/packages/instantsearch.js/src/connectors/menu/connectMenu.ts b/packages/instantsearch.js/src/connectors/menu/connectMenu.ts index 5eb54de735..f2773e7f4c 100644 --- a/packages/instantsearch.js/src/connectors/menu/connectMenu.ts +++ b/packages/instantsearch.js/src/connectors/menu/connectMenu.ts @@ -96,6 +96,10 @@ export type MenuRenderState = { * True if the menu is displaying all the menu items. */ isShowingMore: boolean; + /** + * Total number of facet values that can be displayed for 'show more'. + */ + showMoreCount: number; /** * Toggles the number of values displayed between `limit` and `showMore.limit`. */ @@ -182,6 +186,7 @@ const connectMenu: MenuConnector = function connectMenu( // Provide the same function to the `renderFn` so that way the user // has to only bind it once when `isFirstRendering` for instance let isShowingMore = false; + let showMoreCount = 0; let toggleShowMore = () => {}; function createToggleShowMore( renderOptions: RenderOptions, @@ -289,6 +294,7 @@ const connectMenu: MenuConnector = function connectMenu( } if (results) { + const currentLimit = getLimit(); const facetValues = results.getFacetValues(attribute, { sortBy, facetOrdering: sortBy === DEFAULT_SORT, @@ -298,12 +304,20 @@ const connectMenu: MenuConnector = function connectMenu( ? facetValues.data : []; + if (showMore) { + const showMoreTotalCount = Math.min( + showMoreLimit, + facetItems.length + ); + showMoreCount = showMoreTotalCount - currentLimit; + } + canToggleShowMore = - showMore && (isShowingMore || facetItems.length > getLimit()); + showMore && (isShowingMore || facetItems.length > currentLimit); items = transformItems( facetItems - .slice(0, getLimit()) + .slice(0, currentLimit) .map(({ name: label, escapedValue: value, path, ...item }) => ({ ...item, label, @@ -321,6 +335,7 @@ const connectMenu: MenuConnector = function connectMenu( canRefine: items.length > 0, widgetParams, isShowingMore, + showMoreCount, toggleShowMore: cachedToggleShowMore, canToggleShowMore, }; diff --git a/packages/instantsearch.js/src/connectors/refinement-list/__tests__/connectRefinementList-test.ts b/packages/instantsearch.js/src/connectors/refinement-list/__tests__/connectRefinementList-test.ts index 067b47186b..e8082f7721 100644 --- a/packages/instantsearch.js/src/connectors/refinement-list/__tests__/connectRefinementList-test.ts +++ b/packages/instantsearch.js/src/connectors/refinement-list/__tests__/connectRefinementList-test.ts @@ -1091,6 +1091,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/refinement- showMoreLimit: 2, }, isShowingMore: false, + showMoreCount: 1, canToggleShowMore: true, toggleShowMore: expect.any(Function), hasExhaustiveItems: false, @@ -1125,6 +1126,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/refinement- showMoreLimit: 2, }, isShowingMore: false, + showMoreCount: 1, canToggleShowMore: false, toggleShowMore: expect.any(Function), hasExhaustiveItems: false, @@ -1160,6 +1162,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/refinement- showMoreLimit: 2, }, isShowingMore: false, + showMoreCount: 1, canToggleShowMore: true, toggleShowMore: expect.any(Function), hasExhaustiveItems: false, @@ -1201,6 +1204,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/refinement- showMoreLimit: 2, }, isShowingMore: true, + showMoreCount: 0, canToggleShowMore: true, toggleShowMore: expect.any(Function), hasExhaustiveItems: false, @@ -2455,6 +2459,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/refinement- hasExhaustiveItems: true, isFromSearch: false, isShowingMore: false, + showMoreCount: 0, items: [], refine: expect.any(Function), searchForItems: expect.any(Function), @@ -2515,6 +2520,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/refinement- hasExhaustiveItems: true, isFromSearch: false, isShowingMore: false, + showMoreCount: 0, items: [ { count: 88, @@ -2575,6 +2581,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/refinement- hasExhaustiveItems: true, isFromSearch: false, isShowingMore: false, + showMoreCount: 0, items: [], refine: expect.any(Function), searchForItems: expect.any(Function), @@ -2618,6 +2625,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/refinement- hasExhaustiveItems: true, isFromSearch: false, isShowingMore: false, + showMoreCount: 0, items: [], refine: expect.any(Function), searchForItems: expect.any(Function), diff --git a/packages/instantsearch.js/src/connectors/refinement-list/connectRefinementList.ts b/packages/instantsearch.js/src/connectors/refinement-list/connectRefinementList.ts index 673606304c..2a586824aa 100644 --- a/packages/instantsearch.js/src/connectors/refinement-list/connectRefinementList.ts +++ b/packages/instantsearch.js/src/connectors/refinement-list/connectRefinementList.ts @@ -136,6 +136,10 @@ export type RefinementListRenderState = { * True if the menu is displaying all the menu items. */ isShowingMore: boolean; + /** + * Total number of facet values that can be displayed for 'show more'. + */ + showMoreCount: number; /** * Toggles the number of values displayed between `limit` and `showMoreLimit`. */ @@ -235,6 +239,7 @@ const connectRefinementList: RefinementListConnector = let sendEvent: RefinementListRenderState['sendEvent'] | undefined; let isShowingMore = false; + let showMoreCount = 0; // Provide the same function to the `renderFn` so that way the user // has to only bind it once when `isFirstRendering` for instance let toggleShowMore = () => {}; @@ -322,6 +327,7 @@ const connectRefinementList: RefinementListConnector = }), items: normalizedFacetValues, canToggleShowMore: false, + showMoreCount, canRefine: true, isFromSearch: true, instantSearchInstance, @@ -371,6 +377,7 @@ const connectRefinementList: RefinementListConnector = renderOptions; let items: RefinementListItem[] = []; let facetValues: SearchResults.FacetValue[] | FacetHit[] = []; + showMoreCount = 0; if (!sendEvent || !triggerRefine || !searchForFacetValues) { sendEvent = createSendEventForFacet({ @@ -415,6 +422,14 @@ const connectRefinementList: RefinementListConnector = lastResultsFromMainSearch = results; lastItemsFromMainSearch = items; + if (showMore) { + const showMoreTotalCount = Math.min( + showMoreLimit, + facetValues.length + ); + showMoreCount = showMoreTotalCount - currentLimit; + } + if (renderOptions.results) { toggleShowMore = createToggleShowMore(renderOptions, this); } @@ -443,6 +458,7 @@ const connectRefinementList: RefinementListConnector = canRefine: items.length > 0, widgetParams, isShowingMore, + showMoreCount, canToggleShowMore, toggleShowMore: cachedToggleShowMore, sendEvent, diff --git a/packages/instantsearch.js/src/widgets/hierarchical-menu/hierarchical-menu.tsx b/packages/instantsearch.js/src/widgets/hierarchical-menu/hierarchical-menu.tsx index d3d86c00d8..685cb70fe1 100644 --- a/packages/instantsearch.js/src/widgets/hierarchical-menu/hierarchical-menu.tsx +++ b/packages/instantsearch.js/src/widgets/hierarchical-menu/hierarchical-menu.tsx @@ -48,9 +48,9 @@ type HierarchicalMenuTemplates = Partial<{ cssClasses: HierarchicalMenuCSSClasses; }>; /** - * Template used for the show more text, provided with `isShowingMore` data property. + * Template used for the show more text, provided with `isShowingMore`, `showMoreCount` data properties. */ - showMoreText: Template<{ isShowingMore: boolean }>; + showMoreText: Template<{ isShowingMore: boolean; showMoreCount: number }>; }>; export type HierarchicalMenuCSSClasses = Partial<{ @@ -208,6 +208,7 @@ const renderer = refine, instantSearchInstance, isShowingMore, + showMoreCount, toggleShowMore, canToggleShowMore, }: HierarchicalMenuRenderState & @@ -233,6 +234,7 @@ const renderer = showMore={showMore} toggleShowMore={toggleShowMore} isShowingMore={isShowingMore} + showMoreCount={showMoreCount} canToggleShowMore={canToggleShowMore} />, containerNode diff --git a/packages/instantsearch.js/src/widgets/menu/__tests__/__snapshots__/menu-test.ts.snap b/packages/instantsearch.js/src/widgets/menu/__tests__/__snapshots__/menu-test.ts.snap index 8bf2991858..8212f5cc4a 100644 --- a/packages/instantsearch.js/src/widgets/menu/__tests__/__snapshots__/menu-test.ts.snap +++ b/packages/instantsearch.js/src/widgets/menu/__tests__/__snapshots__/menu-test.ts.snap @@ -41,6 +41,7 @@ exports[`menu render renders transformed items 1`] = ` ], "isShowingMore": false, "showMore": undefined, + "showMoreCount": 0, "templateProps": { "templates": { "item": [Function], @@ -96,6 +97,7 @@ exports[`menu render snapshot 1`] = ` ], "isShowingMore": false, "showMore": undefined, + "showMoreCount": 0, "templateProps": { "templates": { "item": [Function], diff --git a/packages/instantsearch.js/src/widgets/menu/menu.tsx b/packages/instantsearch.js/src/widgets/menu/menu.tsx index 51d798dafb..88dc52d26a 100644 --- a/packages/instantsearch.js/src/widgets/menu/menu.tsx +++ b/packages/instantsearch.js/src/widgets/menu/menu.tsx @@ -86,11 +86,9 @@ export type MenuTemplates = Partial<{ value: string; }>; /** - * Template used for the show more text, provided with `isShowingMore` data property. + * Template used for the show more text, provided with `isShowingMore`, `showMoreCount` data properties. */ - showMoreText: Template<{ - isShowingMore: boolean; - }>; + showMoreText: Template<{ isShowingMore: boolean; showMoreCount: number }>; }>; export type MenuComponentCSSClasses = ComponentCSSClasses; @@ -135,6 +133,7 @@ const renderer = createURL, instantSearchInstance, isShowingMore, + showMoreCount, toggleShowMore, canToggleShowMore, }: MenuRenderState & RendererOptions, @@ -164,6 +163,7 @@ const renderer = toggleRefinement={refine} toggleShowMore={toggleShowMore} isShowingMore={isShowingMore} + showMoreCount={showMoreCount} canToggleShowMore={canToggleShowMore} />, containerNode diff --git a/packages/instantsearch.js/src/widgets/refinement-list/refinement-list.tsx b/packages/instantsearch.js/src/widgets/refinement-list/refinement-list.tsx index 8afd84e3c5..59887aebb1 100644 --- a/packages/instantsearch.js/src/widgets/refinement-list/refinement-list.tsx +++ b/packages/instantsearch.js/src/widgets/refinement-list/refinement-list.tsx @@ -143,9 +143,9 @@ export type RefinementListOwnTemplates = Partial<{ */ item: Template; /** - * Template used for the show more text, provided with `isShowingMore` data property. + * Template used for the show more text, provided with `isShowingMore`, `showMoreCount` data properties. */ - showMoreText: Template<{ isShowingMore: boolean }>; + showMoreText: Template<{ isShowingMore: boolean; showMoreCount: number }>; /** * Templates to use for search for facet values when there are no results. */ @@ -243,6 +243,7 @@ const renderer = instantSearchInstance, toggleShowMore, isShowingMore, + showMoreCount, hasExhaustiveItems, canToggleShowMore, }, @@ -277,6 +278,7 @@ const renderer = showMore={showMore && !isFromSearch && items.length > 0} toggleShowMore={toggleShowMore} isShowingMore={isShowingMore} + showMoreCount={showMoreCount} hasExhaustiveItems={hasExhaustiveItems} canToggleShowMore={canToggleShowMore} />, diff --git a/packages/instantsearch.js/stories/hierarchical-menu.stories.ts b/packages/instantsearch.js/stories/hierarchical-menu.stories.ts index d2effddfeb..80cd99c3c4 100644 --- a/packages/instantsearch.js/stories/hierarchical-menu.stories.ts +++ b/packages/instantsearch.js/stories/hierarchical-menu.stories.ts @@ -134,6 +134,51 @@ storiesOf('Refinements/HierarchicalMenu', module) ]); }) ) + .add( + 'with show more and templates', + withHits(({ search, container, instantsearch }) => { + search.addWidgets([ + instantsearch.widgets.hierarchicalMenu({ + container, + attributes: [ + 'hierarchicalCategories.lvl0', + 'hierarchicalCategories.lvl1', + 'hierarchicalCategories.lvl2', + 'hierarchicalCategories.lvl3', + ], + limit: 3, + showMore: true, + showMoreLimit: 6, + templates: { + showMoreText: ({ isShowingMore }) => (isShowingMore ? '⬆️' : '⬇️'), + }, + }), + ]); + }) + ) + .add( + 'with show more templates and showMoreCount', + withHits(({ search, container, instantsearch }) => { + search.addWidgets([ + instantsearch.widgets.hierarchicalMenu({ + container, + attributes: [ + 'hierarchicalCategories.lvl0', + 'hierarchicalCategories.lvl1', + 'hierarchicalCategories.lvl2', + 'hierarchicalCategories.lvl3', + ], + limit: 3, + showMore: true, + showMoreLimit: 6, + templates: { + showMoreText: ({ isShowingMore, showMoreCount }) => + isShowingMore ? 'Show less' : `Show ${showMoreCount} more`, + }, + }), + ]); + }) + ) .add( 'with transformed items', withHits(({ search, container, instantsearch }) => { diff --git a/packages/instantsearch.js/stories/menu.stories.ts b/packages/instantsearch.js/stories/menu.stories.ts index af6761ec5b..d0b9490bc7 100644 --- a/packages/instantsearch.js/stories/menu.stories.ts +++ b/packages/instantsearch.js/stories/menu.stories.ts @@ -74,6 +74,24 @@ storiesOf('Refinements/Menu', module) ]); }) ) + .add( + 'with show more templates and showMoreCount', + withHits(({ search, container, instantsearch }) => { + search.addWidgets([ + instantsearch.widgets.menu({ + container, + attribute: 'categories', + limit: 3, + showMore: true, + showMoreLimit: 10, + templates: { + showMoreText: ({ isShowingMore, showMoreCount }) => + isShowingMore ? 'Show less' : `Show ${showMoreCount} more`, + }, + }), + ]); + }) + ) .add( 'with add/remove', withHits(({ search, container, instantsearch }) => { diff --git a/packages/instantsearch.js/stories/refinement-list.stories.ts b/packages/instantsearch.js/stories/refinement-list.stories.ts index fbf8fd1652..f5e6b1cb97 100644 --- a/packages/instantsearch.js/stories/refinement-list.stories.ts +++ b/packages/instantsearch.js/stories/refinement-list.stories.ts @@ -45,6 +45,24 @@ storiesOf('Refinements/RefinementList', module) ]); }) ) + .add( + 'with show more templates and showMoreCount', + withHits(({ search, container, instantsearch }) => { + search.addWidgets([ + instantsearch.widgets.refinementList({ + container, + attribute: 'brand', + limit: 3, + showMore: true, + showMoreLimit: 10, + templates: { + showMoreText: ({ isShowingMore, showMoreCount }) => + isShowingMore ? 'Show less' : `Show ${showMoreCount} more`, + }, + }), + ]); + }) + ) .add( 'with search inside items', withHits(({ search, container, instantsearch }) => { diff --git a/packages/react-instantsearch-hooks-web/src/__tests__/common.test.tsx b/packages/react-instantsearch-hooks-web/src/__tests__/common.test.tsx index e092b7da3b..593014e5db 100644 --- a/packages/react-instantsearch-hooks-web/src/__tests__/common.test.tsx +++ b/packages/react-instantsearch-hooks-web/src/__tests__/common.test.tsx @@ -33,6 +33,27 @@ import { import type { Hit } from 'instantsearch.js'; import type { SendEventForHits } from 'instantsearch.js/es/lib/utils'; +/** + * Converts InstantSearch.js templates into React InstantSearch Hooks translations. + * @param templates InstantSearch.js templates received in `widgetParams` + * @param map Matching between template keys and translation keys + */ +function fromTemplates( + templates: Record, + map: Record +) { + return Object.entries(map).reduce>( + (translations, [templateKey, translationKey]) => { + if (templates[templateKey] !== undefined) { + translations[translationKey] = templates[templateKey]; + } + + return translations; + }, + {} + ); +} + /** * prevent rethrowing InstantSearch errors, so tests can be asserted. * IRL this isn't needed, as the error doesn't stop execution. @@ -44,18 +65,32 @@ function GlobalErrorSwallower() { } createRefinementListTests(({ instantSearchOptions, widgetParams }) => { + const { templates, ...props } = widgetParams; + const translations = + templates && + fromTemplates(templates, { + showMoreText: 'showMoreButtonText', + }); + render( - + ); }, act); createHierarchicalMenuTests(({ instantSearchOptions, widgetParams }) => { + const { templates, ...props } = widgetParams; + const translations = + templates && + fromTemplates(templates, { + showMoreText: 'showMoreButtonText', + }); + render( - + ); @@ -73,9 +108,16 @@ createBreadcrumbTests(({ instantSearchOptions, widgetParams }) => { }, act); createMenuTests(({ instantSearchOptions, widgetParams }) => { + const { templates, ...props } = widgetParams; + const translations = + templates && + fromTemplates(templates, { + showMoreText: 'showMoreButtonText', + }); + render( - + ); diff --git a/packages/react-instantsearch-hooks-web/src/ui/HierarchicalMenu.tsx b/packages/react-instantsearch-hooks-web/src/ui/HierarchicalMenu.tsx index 688b1fa33e..b99775a25e 100644 --- a/packages/react-instantsearch-hooks-web/src/ui/HierarchicalMenu.tsx +++ b/packages/react-instantsearch-hooks-web/src/ui/HierarchicalMenu.tsx @@ -78,6 +78,7 @@ export type HierarchicalMenuProps = React.ComponentProps<'div'> & canToggleShowMore: boolean; onToggleShowMore: () => void; isShowingMore: boolean; + showMoreCount: number; translations: ShowMoreButtonTranslations; }; @@ -167,6 +168,7 @@ export function HierarchicalMenu({ canToggleShowMore, onToggleShowMore, isShowingMore, + showMoreCount, translations, ...props }: HierarchicalMenuProps) { @@ -201,6 +203,7 @@ export function HierarchicalMenu({ disabled={!canToggleShowMore} onClick={onToggleShowMore} isShowingMore={isShowingMore} + showMoreCount={showMoreCount} translations={translations} /> )} diff --git a/packages/react-instantsearch-hooks-web/src/ui/Menu.tsx b/packages/react-instantsearch-hooks-web/src/ui/Menu.tsx index 61b217d917..b66e499898 100644 --- a/packages/react-instantsearch-hooks-web/src/ui/Menu.tsx +++ b/packages/react-instantsearch-hooks-web/src/ui/Menu.tsx @@ -14,6 +14,7 @@ export type MenuProps = React.ComponentProps<'div'> & { canToggleShowMore: boolean; onToggleShowMore: () => void; isShowingMore: boolean; + showMoreCount: number; createURL: CreateURL; onRefine: (item: MenuItem) => void; translations: MenuTranslations; @@ -71,6 +72,7 @@ export function Menu({ canToggleShowMore, onToggleShowMore, isShowingMore, + showMoreCount, createURL, onRefine, translations, @@ -127,6 +129,7 @@ export function Menu({ disabled={!canToggleShowMore} onClick={onToggleShowMore} isShowingMore={isShowingMore} + showMoreCount={showMoreCount} translations={translations} /> )} diff --git a/packages/react-instantsearch-hooks-web/src/ui/RefinementList.tsx b/packages/react-instantsearch-hooks-web/src/ui/RefinementList.tsx index 037ac62312..c5d87cf73d 100644 --- a/packages/react-instantsearch-hooks-web/src/ui/RefinementList.tsx +++ b/packages/react-instantsearch-hooks-web/src/ui/RefinementList.tsx @@ -19,6 +19,7 @@ export type RefinementListProps = React.ComponentProps<'div'> & { canToggleShowMore: boolean; onToggleShowMore: () => void; isShowingMore: boolean; + showMoreCount: number; classNames?: Partial; translations: RefinementListTranslations; }; @@ -91,6 +92,7 @@ export function RefinementList({ canToggleShowMore, onToggleShowMore, isShowingMore, + showMoreCount, className, classNames = {}, translations, @@ -190,6 +192,7 @@ export function RefinementList({ disabled={!canToggleShowMore} onClick={onToggleShowMore} isShowingMore={isShowingMore} + showMoreCount={showMoreCount} translations={translations} /> )} diff --git a/packages/react-instantsearch-hooks-web/src/ui/ShowMoreButton.tsx b/packages/react-instantsearch-hooks-web/src/ui/ShowMoreButton.tsx index bee8841819..b194a6095b 100644 --- a/packages/react-instantsearch-hooks-web/src/ui/ShowMoreButton.tsx +++ b/packages/react-instantsearch-hooks-web/src/ui/ShowMoreButton.tsx @@ -2,6 +2,7 @@ import React from 'react'; type ShowMoreButtonProps = React.ComponentProps<'button'> & { isShowingMore: boolean; + showMoreCount: number; translations: ShowMoreButtonTranslations; }; @@ -10,6 +11,10 @@ export type ShowMoreButtonTextOptions = { * Whether the widget is showing more items or not. */ isShowingMore: boolean; + /** + * Total number of facet values that can be displayed for 'show more'. + */ + showMoreCount: number; }; export type ShowMoreButtonTranslations = { @@ -21,12 +26,13 @@ export type ShowMoreButtonTranslations = { export function ShowMoreButton({ isShowingMore, + showMoreCount, translations, ...props }: ShowMoreButtonProps) { return ( ); } diff --git a/packages/react-instantsearch-hooks-web/src/ui/__tests__/HierarchicalMenu.test.tsx b/packages/react-instantsearch-hooks-web/src/ui/__tests__/HierarchicalMenu.test.tsx index 42af221dd4..c40e98c8d8 100644 --- a/packages/react-instantsearch-hooks-web/src/ui/__tests__/HierarchicalMenu.test.tsx +++ b/packages/react-instantsearch-hooks-web/src/ui/__tests__/HierarchicalMenu.test.tsx @@ -59,6 +59,7 @@ describe('HierarchicalMenu', () => { onToggleShowMore: jest.fn(), canToggleShowMore: true, isShowingMore: false, + showMoreCount: 0, translations: { showMoreButtonText({ isShowingMore }: { isShowingMore: boolean }) { return isShowingMore ? 'Show less' : 'Show more'; diff --git a/packages/react-instantsearch-hooks-web/src/ui/__tests__/Menu.test.tsx b/packages/react-instantsearch-hooks-web/src/ui/__tests__/Menu.test.tsx index 91aebaaba5..c8ee1273c2 100644 --- a/packages/react-instantsearch-hooks-web/src/ui/__tests__/Menu.test.tsx +++ b/packages/react-instantsearch-hooks-web/src/ui/__tests__/Menu.test.tsx @@ -32,6 +32,7 @@ describe('Menu', () => { onToggleShowMore: jest.fn(), canToggleShowMore: true, isShowingMore: false, + showMoreCount: 0, translations: { showMoreButtonText({ isShowingMore }: { isShowingMore: boolean }) { return isShowingMore ? 'Show less' : 'Show more'; diff --git a/packages/react-instantsearch-hooks-web/src/ui/__tests__/RefinementList.test.tsx b/packages/react-instantsearch-hooks-web/src/ui/__tests__/RefinementList.test.tsx index 04a1dc5064..df82c89ee1 100644 --- a/packages/react-instantsearch-hooks-web/src/ui/__tests__/RefinementList.test.tsx +++ b/packages/react-instantsearch-hooks-web/src/ui/__tests__/RefinementList.test.tsx @@ -36,6 +36,7 @@ describe('RefinementList', () => { searchBox: null, canToggleShowMore: true, isShowingMore: false, + showMoreCount: 0, onToggleShowMore: jest.fn(), translations: { showMoreButtonText({ isShowingMore }: { isShowingMore: boolean }) { diff --git a/packages/react-instantsearch-hooks-web/src/ui/__tests__/ShowMoreButton.test.tsx b/packages/react-instantsearch-hooks-web/src/ui/__tests__/ShowMoreButton.test.tsx index 539451897d..24c2d8de51 100644 --- a/packages/react-instantsearch-hooks-web/src/ui/__tests__/ShowMoreButton.test.tsx +++ b/packages/react-instantsearch-hooks-web/src/ui/__tests__/ShowMoreButton.test.tsx @@ -19,6 +19,7 @@ describe('ShowMoreButton', () => { const { container } = render( ); @@ -34,7 +35,11 @@ describe('ShowMoreButton', () => { test('changes the button label when is showing more', () => { const { container } = render( - + ); expect(container).toMatchInlineSnapshot(` @@ -51,6 +56,7 @@ describe('ShowMoreButton', () => { const { getByRole } = render( @@ -66,6 +72,7 @@ describe('ShowMoreButton', () => { const { container, getByRole } = render( { }, }; const { getByRole, rerender } = render( - + ); expect(getByRole('button', { name: 'Display less' })).toBeInTheDocument(); rerender( - + ); expect(getByRole('button', { name: 'Display more' })).toBeInTheDocument(); }); + + test('renders show more count', () => { + const translations = { + showMoreButtonText({ + isShowingMore, + showMoreCount, + }: { + isShowingMore: boolean; + showMoreCount: number; + }) { + return isShowingMore ? 'Display less' : `Display ${showMoreCount} more`; + }, + }; + const { getByRole, rerender } = render( + + ); + + expect(getByRole('button', { name: 'Display less' })).toBeInTheDocument(); + + rerender( + + ); + + expect(getByRole('button', { name: 'Display 5 more' })).toBeInTheDocument(); + }); }); diff --git a/packages/react-instantsearch-hooks-web/src/widgets/HierarchicalMenu.tsx b/packages/react-instantsearch-hooks-web/src/widgets/HierarchicalMenu.tsx index dc7b4eab6e..f6c938f7b4 100644 --- a/packages/react-instantsearch-hooks-web/src/widgets/HierarchicalMenu.tsx +++ b/packages/react-instantsearch-hooks-web/src/widgets/HierarchicalMenu.tsx @@ -15,6 +15,7 @@ type UiProps = Pick< | 'canToggleShowMore' | 'onToggleShowMore' | 'isShowingMore' + | 'showMoreCount' | 'translations' >; @@ -45,6 +46,7 @@ export function HierarchicalMenu({ canToggleShowMore, createURL, isShowingMore, + showMoreCount, refine, toggleShowMore, } = useHierarchicalMenu( @@ -72,6 +74,7 @@ export function HierarchicalMenu({ canToggleShowMore, onToggleShowMore: toggleShowMore, isShowingMore, + showMoreCount, translations: { showMoreButtonText(options) { return options.isShowingMore ? 'Show less' : 'Show more'; diff --git a/packages/react-instantsearch-hooks-web/src/widgets/Menu.tsx b/packages/react-instantsearch-hooks-web/src/widgets/Menu.tsx index b0fa3e5aa1..d2ca7b69a8 100644 --- a/packages/react-instantsearch-hooks-web/src/widgets/Menu.tsx +++ b/packages/react-instantsearch-hooks-web/src/widgets/Menu.tsx @@ -14,6 +14,7 @@ type UiProps = Pick< | 'canToggleShowMore' | 'onToggleShowMore' | 'isShowingMore' + | 'showMoreCount' | 'translations' >; @@ -33,6 +34,7 @@ export function Menu({ const { canToggleShowMore, isShowingMore, + showMoreCount, items, refine, createURL, @@ -58,6 +60,7 @@ export function Menu({ canToggleShowMore, onToggleShowMore: toggleShowMore, isShowingMore, + showMoreCount, translations: { showMoreButtonText(options) { return options.isShowingMore ? 'Show less' : 'Show more'; diff --git a/packages/react-instantsearch-hooks-web/src/widgets/RefinementList.tsx b/packages/react-instantsearch-hooks-web/src/widgets/RefinementList.tsx index 06d2a75e86..576ef6328c 100644 --- a/packages/react-instantsearch-hooks-web/src/widgets/RefinementList.tsx +++ b/packages/react-instantsearch-hooks-web/src/widgets/RefinementList.tsx @@ -21,6 +21,7 @@ type UiProps = Pick< | 'canToggleShowMore' | 'onToggleShowMore' | 'isShowingMore' + | 'showMoreCount' | 'translations' >; @@ -60,6 +61,7 @@ export function RefinementList({ canToggleShowMore, isFromSearch, isShowingMore, + showMoreCount, items, refine, searchForItems, @@ -145,6 +147,7 @@ export function RefinementList({ canToggleShowMore, onToggleShowMore: toggleShowMore, isShowingMore, + showMoreCount, translations: { showMoreButtonText: mergedTranslations.showMoreButtonText, }, diff --git a/packages/react-instantsearch-hooks/src/connectors/__tests__/useHierarchicalMenu.test.tsx b/packages/react-instantsearch-hooks/src/connectors/__tests__/useHierarchicalMenu.test.tsx index 997c6f787e..28fdabc3bd 100644 --- a/packages/react-instantsearch-hooks/src/connectors/__tests__/useHierarchicalMenu.test.tsx +++ b/packages/react-instantsearch-hooks/src/connectors/__tests__/useHierarchicalMenu.test.tsx @@ -19,6 +19,7 @@ describe('useHierarchicalMenu', () => { canToggleShowMore: false, createURL: expect.any(Function), isShowingMore: false, + showMoreCount: 0, items: [], refine: expect.any(Function), sendEvent: expect.any(Function), @@ -33,6 +34,7 @@ describe('useHierarchicalMenu', () => { canToggleShowMore: false, createURL: expect.any(Function), isShowingMore: false, + showMoreCount: 0, items: [], refine: expect.any(Function), sendEvent: expect.any(Function), diff --git a/packages/react-instantsearch-hooks/src/connectors/__tests__/useMenu.test.tsx b/packages/react-instantsearch-hooks/src/connectors/__tests__/useMenu.test.tsx index 3ab9038f05..cf37c7138a 100644 --- a/packages/react-instantsearch-hooks/src/connectors/__tests__/useMenu.test.tsx +++ b/packages/react-instantsearch-hooks/src/connectors/__tests__/useMenu.test.tsx @@ -19,6 +19,7 @@ describe('useMenu', () => { canToggleShowMore: false, createURL: expect.any(Function), isShowingMore: false, + showMoreCount: 0, items: [], refine: expect.any(Function), sendEvent: expect.any(Function), @@ -33,6 +34,7 @@ describe('useMenu', () => { canToggleShowMore: false, createURL: expect.any(Function), isShowingMore: false, + showMoreCount: 0, items: [], refine: expect.any(Function), sendEvent: expect.any(Function), diff --git a/packages/react-instantsearch-hooks/src/connectors/__tests__/useRefinementList.test.tsx b/packages/react-instantsearch-hooks/src/connectors/__tests__/useRefinementList.test.tsx index fc79c78dc7..3a5644afea 100644 --- a/packages/react-instantsearch-hooks/src/connectors/__tests__/useRefinementList.test.tsx +++ b/packages/react-instantsearch-hooks/src/connectors/__tests__/useRefinementList.test.tsx @@ -21,6 +21,7 @@ describe('useRefinementList', () => { hasExhaustiveItems: true, isFromSearch: false, isShowingMore: false, + showMoreCount: 0, items: [], refine: expect.any(Function), searchForItems: expect.any(Function), @@ -38,6 +39,7 @@ describe('useRefinementList', () => { hasExhaustiveItems: true, isFromSearch: false, isShowingMore: false, + showMoreCount: 0, items: [], refine: expect.any(Function), searchForItems: expect.any(Function), diff --git a/packages/vue-instantsearch/src/__tests__/common.test.js b/packages/vue-instantsearch/src/__tests__/common.test.js index d6cca7b91b..c9317481c3 100644 --- a/packages/vue-instantsearch/src/__tests__/common.test.js +++ b/packages/vue-instantsearch/src/__tests__/common.test.js @@ -31,6 +31,25 @@ import { } from '../instantsearch'; jest.unmock('instantsearch.js/es'); +/** + * Converts InstantSearch.js templates into Vue InstantSearch slots. + * @param {Record} templates InstantSearch.js templates received in `widgetParams` + * @param {Record} map Matching between template keys and slots names + * @returns {Record} Vue InstantSearch slots + */ +function fromTemplates(templates, map) { + return Object.entries(map).reduce( + (translations, [templateKey, translationKey]) => { + if (templates[templateKey] !== undefined) { + return { ...translations, [translationKey]: templates[templateKey] }; + } + + return translations; + }, + {} + ); +} + /** * prevent rethrowing InstantSearch errors, so tests can be asserted. * IRL this isn't needed, as the error doesn't stop execution. @@ -46,11 +65,21 @@ const GlobalErrorSwallower = { }; createRefinementListTests(async ({ instantSearchOptions, widgetParams }) => { + const { templates, ...props } = widgetParams; + const scopedSlots = + templates && + fromTemplates(templates, { + showMoreText: 'showMoreLabel', + }); + mountApp( { render: renderCompat((h) => h(AisInstantSearch, { props: instantSearchOptions }, [ - h(AisRefinementList, { props: widgetParams }), + h(AisRefinementList, { + props, + scopedSlots, + }), h(GlobalErrorSwallower), ]) ), @@ -62,11 +91,21 @@ createRefinementListTests(async ({ instantSearchOptions, widgetParams }) => { }); createHierarchicalMenuTests(async ({ instantSearchOptions, widgetParams }) => { + const { templates, ...props } = widgetParams; + const scopedSlots = + templates && + fromTemplates(templates, { + showMoreText: 'showMoreLabel', + }); + mountApp( { render: renderCompat((h) => h(AisInstantSearch, { props: instantSearchOptions }, [ - h(AisHierarchicalMenu, { props: widgetParams }), + h(AisHierarchicalMenu, { + props, + scopedSlots, + }), h(GlobalErrorSwallower), ]) ), @@ -100,11 +139,18 @@ createBreadcrumbTests(async ({ instantSearchOptions, widgetParams }) => { }); createMenuTests(async ({ instantSearchOptions, widgetParams }) => { + const { templates, ...props } = widgetParams; + const scopedSlots = + templates && + fromTemplates(templates, { + showMoreText: 'showMoreLabel', + }); + mountApp( { render: renderCompat((h) => h(AisInstantSearch, { props: instantSearchOptions }, [ - h(AisMenu, { props: widgetParams }), + h(AisMenu, { props, scopedSlots }), h(GlobalErrorSwallower), ]) ), diff --git a/packages/vue-instantsearch/src/components/HierarchicalMenu.vue b/packages/vue-instantsearch/src/components/HierarchicalMenu.vue index 0aefe0f765..31362b5515 100644 --- a/packages/vue-instantsearch/src/components/HierarchicalMenu.vue +++ b/packages/vue-instantsearch/src/components/HierarchicalMenu.vue @@ -8,6 +8,7 @@ :can-refine="state.canRefine" :can-toggle-show-more="state.canToggleShowMore" :is-showing-more="state.isShowingMore" + :show-more-count="state.showMoreCount" :refine="state.refine" :createURL="state.createURL" :toggle-show-more="state.toggleShowMore" @@ -30,7 +31,11 @@ :disabled="!state.canToggleShowMore" @click.prevent="state.toggleShowMore" > - + {{ state.isShowingMore ? 'Show less' : 'Show more' }} diff --git a/packages/vue-instantsearch/src/components/Menu.vue b/packages/vue-instantsearch/src/components/Menu.vue index 30f07bb5e7..834a7991b8 100644 --- a/packages/vue-instantsearch/src/components/Menu.vue +++ b/packages/vue-instantsearch/src/components/Menu.vue @@ -8,6 +8,7 @@ :can-refine="state.canRefine" :can-toggle-show-more="state.canToggleShowMore" :is-showing-more="state.isShowingMore" + :show-more-count="state.showMoreCount" :refine="state.refine" :createURL="state.createURL" :toggle-show-more="state.toggleShowMore" @@ -39,7 +40,11 @@ :disabled="!state.canToggleShowMore" @click.prevent="state.toggleShowMore()" > - + {{ state.isShowingMore ? 'Show less' : 'Show more' }} diff --git a/packages/vue-instantsearch/src/components/RefinementList.vue b/packages/vue-instantsearch/src/components/RefinementList.vue index e3c880e57d..df7b895fe5 100644 --- a/packages/vue-instantsearch/src/components/RefinementList.vue +++ b/packages/vue-instantsearch/src/components/RefinementList.vue @@ -11,6 +11,7 @@ :toggle-show-more="toggleShowMore" :can-toggle-show-more="state.canToggleShowMore" :is-showing-more="state.isShowingMore" + :show-more-count="state.showMoreCount" :createURL="state.createURL" :is-from-search="state.isFromSearch" :can-refine="state.canRefine" @@ -70,7 +71,11 @@ v-if="showMore" :disabled="!state.canToggleShowMore" > - + Show {{ state.isShowingMore ? 'less' : 'more' }} diff --git a/tests/common/widgets/hierarchical-menu/index.ts b/tests/common/widgets/hierarchical-menu/index.ts index b8f34e5db6..e96dc1de06 100644 --- a/tests/common/widgets/hierarchical-menu/index.ts +++ b/tests/common/widgets/hierarchical-menu/index.ts @@ -2,10 +2,12 @@ import type { HierarchicalMenuWidget } from 'instantsearch.js/es/widgets/hierarc import type { Act, TestSetup } from '../../common'; import { fakeAct } from '../../common'; import { createOptimisticUiTests } from './optimistic-ui'; +import { createShowMoreTests } from './show-more'; type WidgetParams = Parameters[0]; export type HierarchicalMenuSetup = TestSetup<{ widgetParams: Omit; + vueSlots?: Record; }>; export function createHierarchicalMenuTests( @@ -18,5 +20,6 @@ export function createHierarchicalMenuTests( describe('HierarchicalMenu common tests', () => { createOptimisticUiTests(setup, act); + createShowMoreTests(setup, act); }); } diff --git a/tests/common/widgets/hierarchical-menu/show-more.ts b/tests/common/widgets/hierarchical-menu/show-more.ts new file mode 100644 index 0000000000..b9f9dbcb38 --- /dev/null +++ b/tests/common/widgets/hierarchical-menu/show-more.ts @@ -0,0 +1,87 @@ +import { wait } from '@instantsearch/testutils'; +import { + createMultiSearchResponse, + createSearchClient, + createSingleSearchResponse, +} from '@instantsearch/mocks'; + +import type { HierarchicalMenuSetup } from '.'; +import type { Act } from '../../common'; + +export function createShowMoreTests(setup: HierarchicalMenuSetup, act: Act) { + describe('show more', () => { + test('receives a count of facet values', async () => { + const delay = 100; + const margin = 10; + const attributes = ['brand']; + const limit = 2; + const showMoreLimit = 6; + + const options = { + instantSearchOptions: { + indexName: 'indexName', + searchClient: createSearchClient({ + search: jest.fn(async (requests) => { + await wait(delay); + return createMultiSearchResponse( + ...requests.map(() => + createSingleSearchResponse({ + facets: { + [attributes[0]]: { + Apple: 746, + Samsung: 633, + Metra: 591, + HP: 530, + 'Insignia™': 442, + GE: 394, + Sony: 350, + Incipio: 320, + KitchenAid: 318, + Whirlpool: 298, + LG: 291, + Canon: 287, + Frigidaire: 275, + Speck: 216, + OtterBox: 214, + Epson: 204, + 'Dynex™': 184, + Dell: 174, + 'Hamilton Beach': 173, + Platinum: 155, + }, + }, + }) + ) + ); + }), + }), + }, + widgetParams: { + attributes, + limit, + showMoreLimit, + showMore: true, + templates: { + // @ts-ignore + showMoreText({ isShowingMore, showMoreCount }) { + return !isShowingMore + ? `Show ${showMoreCount} more` + : 'Show top items'; + }, + }, + }, + }; + + await setup(options); + + await act(async () => { + await wait(margin + delay); + await wait(0); + }); + + expect( + document.querySelector('.ais-HierarchicalMenu-showMore') + ).toHaveTextContent(`Show ${showMoreLimit - limit} more`); + }); + }); +} diff --git a/tests/common/widgets/menu/index.ts b/tests/common/widgets/menu/index.ts index 3df854b115..4f86452de4 100644 --- a/tests/common/widgets/menu/index.ts +++ b/tests/common/widgets/menu/index.ts @@ -2,10 +2,12 @@ import type { MenuWidget } from 'instantsearch.js/es/widgets/menu/menu'; import type { Act, TestSetup } from '../../common'; import { fakeAct } from '../../common'; import { createOptimisticUiTests } from './optimistic-ui'; +import { createShowMoreTests } from './show-more'; type WidgetParams = Parameters[0]; export type MenuSetup = TestSetup<{ widgetParams: Omit; + vueSlots?: Record; }>; export function createMenuTests(setup: MenuSetup, act: Act = fakeAct) { @@ -15,5 +17,6 @@ export function createMenuTests(setup: MenuSetup, act: Act = fakeAct) { describe('Menu common tests', () => { createOptimisticUiTests(setup, act); + createShowMoreTests(setup, act); }); } diff --git a/tests/common/widgets/menu/show-more.ts b/tests/common/widgets/menu/show-more.ts new file mode 100644 index 0000000000..e4ebf8aecf --- /dev/null +++ b/tests/common/widgets/menu/show-more.ts @@ -0,0 +1,87 @@ +import { wait } from '@instantsearch/testutils'; +import { + createMultiSearchResponse, + createSearchClient, + createSingleSearchResponse, +} from '@instantsearch/mocks'; + +import type { MenuSetup } from '.'; +import type { Act } from '../../common'; + +export function createShowMoreTests(setup: MenuSetup, act: Act) { + describe('show more', () => { + test('receives a count of facet values', async () => { + const delay = 100; + const margin = 10; + const attribute = 'brand'; + const limit = 2; + const showMoreLimit = 6; + + const options = { + instantSearchOptions: { + indexName: 'indexName', + searchClient: createSearchClient({ + search: jest.fn(async (requests) => { + await wait(delay); + return createMultiSearchResponse( + ...requests.map(() => + createSingleSearchResponse({ + facets: { + [attribute]: { + Apple: 746, + Samsung: 633, + Metra: 591, + HP: 530, + 'Insignia™': 442, + GE: 394, + Sony: 350, + Incipio: 320, + KitchenAid: 318, + Whirlpool: 298, + LG: 291, + Canon: 287, + Frigidaire: 275, + Speck: 216, + OtterBox: 214, + Epson: 204, + 'Dynex™': 184, + Dell: 174, + 'Hamilton Beach': 173, + Platinum: 155, + }, + }, + }) + ) + ); + }), + }), + }, + widgetParams: { + attribute, + limit, + showMoreLimit, + showMore: true, + templates: { + // @ts-ignore + showMoreText({ isShowingMore, showMoreCount }) { + return !isShowingMore + ? `Show ${showMoreCount} more` + : 'Show top items'; + }, + }, + }, + }; + + await setup(options); + + await act(async () => { + await wait(margin + delay); + await wait(0); + }); + + expect(document.querySelector('.ais-Menu-showMore')).toHaveTextContent( + `Show ${showMoreLimit - limit} more` + ); + }); + }); +} diff --git a/tests/common/widgets/refinement-list/index.ts b/tests/common/widgets/refinement-list/index.ts index ad807fbced..75399dd771 100644 --- a/tests/common/widgets/refinement-list/index.ts +++ b/tests/common/widgets/refinement-list/index.ts @@ -2,10 +2,12 @@ import type { RefinementListWidget } from 'instantsearch.js/es/widgets/refinemen import type { TestSetup, Act } from '../../common'; import { fakeAct } from '../../common'; import { createOptimisticUiTests } from './optimistic-ui'; +import { createShowMoreTests } from './show-more'; type WidgetParams = Parameters[0]; export type RefinementListSetup = TestSetup<{ widgetParams: Omit; + vueSlots?: Record; }>; export function createRefinementListTests( @@ -18,5 +20,6 @@ export function createRefinementListTests( describe('RefinementList common tests', () => { createOptimisticUiTests(setup, act); + createShowMoreTests(setup, act); }); } diff --git a/tests/common/widgets/refinement-list/show-more.ts b/tests/common/widgets/refinement-list/show-more.ts new file mode 100644 index 0000000000..5c2e3b7683 --- /dev/null +++ b/tests/common/widgets/refinement-list/show-more.ts @@ -0,0 +1,87 @@ +import { wait } from '@instantsearch/testutils'; +import { + createMultiSearchResponse, + createSearchClient, + createSingleSearchResponse, +} from '@instantsearch/mocks'; + +import type { RefinementListSetup } from '.'; +import type { Act } from '../../common'; + +export function createShowMoreTests(setup: RefinementListSetup, act: Act) { + describe('show more', () => { + test('receives a count of facet values', async () => { + const delay = 100; + const margin = 10; + const attribute = 'brand'; + const limit = 2; + const showMoreLimit = 6; + + const options = { + instantSearchOptions: { + indexName: 'indexName', + searchClient: createSearchClient({ + search: jest.fn(async (requests) => { + await wait(delay); + return createMultiSearchResponse( + ...requests.map(() => + createSingleSearchResponse({ + facets: { + [attribute]: { + Apple: 746, + Samsung: 633, + Metra: 591, + HP: 530, + 'Insignia™': 442, + GE: 394, + Sony: 350, + Incipio: 320, + KitchenAid: 318, + Whirlpool: 298, + LG: 291, + Canon: 287, + Frigidaire: 275, + Speck: 216, + OtterBox: 214, + Epson: 204, + 'Dynex™': 184, + Dell: 174, + 'Hamilton Beach': 173, + Platinum: 155, + }, + }, + }) + ) + ); + }), + }), + }, + widgetParams: { + attribute, + limit, + showMoreLimit, + showMore: true, + templates: { + // @ts-ignore + showMoreText({ isShowingMore, showMoreCount }) { + return !isShowingMore + ? `Show ${showMoreCount} more` + : 'Show top items'; + }, + }, + }, + }; + + await setup(options); + + await act(async () => { + await wait(margin + delay); + await wait(0); + }); + + expect( + document.querySelector('.ais-RefinementList-showMore') + ).toHaveTextContent(`Show ${showMoreLimit - limit} more`); + }); + }); +}