diff --git a/packages/instantsearch.js/src/widgets/menu/__tests__/menu.test.tsx b/packages/instantsearch.js/src/widgets/menu/__tests__/menu.test.tsx index 611eaa40f2..97a8137457 100644 --- a/packages/instantsearch.js/src/widgets/menu/__tests__/menu.test.tsx +++ b/packages/instantsearch.js/src/widgets/menu/__tests__/menu.test.tsx @@ -397,144 +397,6 @@ describe('menu', () => { `); }); - test('renders with templates show more count', async () => { - const container = document.createElement('div'); - const searchClient = createMockedSearchClient(); - - const search = instantsearch({ - indexName: 'indexName', - searchClient, - initialUiState: { - indexName: { - menu: { - brand: 'Apple', - }, - }, - }, - }); - - search.addWidgets([ - menu({ - container, - attribute: 'brand', - limit: 3, - showMore: true, - showMoreLimit: 6, - templates: { - showMoreText({ isShowingMore, showMoreCount }) { - return isShowingMore - ? `Show top 3 items` - : `Show ${showMoreCount} more`; - }, - }, - }), - ]); - - // @MAJOR Once Hogan.js and string-based templates are removed, - // `search.start()` can be moved to the test body and the following - // assertion can go away. - expect(async () => { - search.start(); - - await wait(0); - }).not.toWarnDev(); - - await wait(0); - - expect(container).toMatchInlineSnapshot(` -
-
- - -
-
-`); - - const toggleButton = within(container).getByRole('button'); - - fireEvent.click(toggleButton); - - expect(toggleButton).toMatchInlineSnapshot(` - -`); - }); - function createMockedSearchClient() { return createSearchClient({ search: jest.fn((requests) => { diff --git a/packages/instantsearch.js/src/widgets/refinement-list/__tests__/refinement-list.test.tsx b/packages/instantsearch.js/src/widgets/refinement-list/__tests__/refinement-list.test.tsx index 709b8d8d99..6790d1e0a6 100644 --- a/packages/instantsearch.js/src/widgets/refinement-list/__tests__/refinement-list.test.tsx +++ b/packages/instantsearch.js/src/widgets/refinement-list/__tests__/refinement-list.test.tsx @@ -851,224 +851,6 @@ describe('refinementList', () => { `); }); - test('renders with templates show more count', async () => { - const container = document.createElement('div'); - const searchClient = createMockedSearchClient(); - - const search = instantsearch({ - indexName: 'indexName', - searchClient, - initialUiState: { - indexName: { - refinementList: { - brand: ['Apple'], - }, - }, - }, - }); - - search.addWidgets([ - refinementList({ - container, - attribute: 'brand', - showMore: true, - searchable: true, - limit: 2, - showMoreLimit: 6, - templates: { - showMoreText({ isShowingMore, showMoreCount }) { - return isShowingMore - ? `Show top 2 items` - : `Show ${showMoreCount} more`; - }, - }, - }), - ]); - - search.start(); - - await wait(0); - - expect(container).toMatchInlineSnapshot(` -
-
- - - -
-
-`); - - const showMoreButton = container.querySelector( - '.ais-RefinementList-showMore' - )!; - - fireEvent.click(showMoreButton); - - expect(showMoreButton).toHaveTextContent('Show top 2 items'); - }); - function createMockedSearchClient() { return createSearchClient({ search: jest.fn((requests) => { @@ -1081,23 +863,6 @@ describe('refinementList', () => { 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, }, }, }) 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 fe3f930059..fac5c6a058 100644 --- a/packages/react-instantsearch-hooks-web/src/__tests__/common.test.tsx +++ b/packages/react-instantsearch-hooks-web/src/__tests__/common.test.tsx @@ -30,6 +30,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. @@ -41,18 +62,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( - + ); @@ -70,9 +105,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/__tests__/RefinementList.test.tsx b/packages/react-instantsearch-hooks-web/src/ui/__tests__/RefinementList.test.tsx index df46d8efd7..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 @@ -682,34 +682,4 @@ describe('RefinementList', () => { getByRole('button', { name: 'Show less brands' }) ).toBeInTheDocument(); }); - - test('renders with translations show more count', () => { - const props = createProps({ - showMore: true, - translations: { - showMoreButtonText({ - isShowingMore, - showMoreCount, - }: { - isShowingMore: boolean; - showMoreCount: number; - }) { - return isShowingMore - ? `Show top 5 items` - : `Show ${showMoreCount} more`; - }, - }, - }); - const { getByRole, rerender } = render( - - ); - - expect(getByRole('button', { name: 'Show 5 more' })).toBeInTheDocument(); - - rerender(); - - expect( - getByRole('button', { name: 'Show top 5 items' }) - ).toBeInTheDocument(); - }); }); diff --git a/packages/vue-instantsearch/src/__tests__/common.test.js b/packages/vue-instantsearch/src/__tests__/common.test.js index 611bab8939..63ff324206 100644 --- a/packages/vue-instantsearch/src/__tests__/common.test.js +++ b/packages/vue-instantsearch/src/__tests__/common.test.js @@ -28,6 +28,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. @@ -43,11 +62,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), ]) ), @@ -59,11 +88,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), ]) ), @@ -94,11 +133,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/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`); + }); + }); +}