From 07bf79fb18e241ffd20460865cbd04cedccde420 Mon Sep 17 00:00:00 2001 From: Nicholas Labarre Date: Tue, 12 Sep 2023 10:01:25 -0400 Subject: [PATCH] feat(commerce): create PLP v2 pager controller (#3130) * wip commerce sub-package * Add commerce/ to files * Import polyfillCryptoNode * Basic boilerplate * Comment out commerce use case * Rename analytics * Adjust test descriptions * Remove version from ProductListingV2BaseParam * Remove done TODO * Move propertyId to base params * Eliminate superfluous interfaces * Use logProductListingV2Load analytics action when building mock product listing response * Remove superfluous spread * Add missing comma * Use v2 product listing API client * Update adding sub-package documentation * Add mock commerce engine * Add basic exports * Remove comment * Undo bad changes * Linting * Rename property to tracking * Update API req and response params * Adjust state + buildProductListingRequestV2 params * Simplify buildProductListingRequestV2 function * Reorder request params and remove unreachable fallback values * Remove superfluous actions * Remove version from state * Remove TODO's * Documentation updates * Renaming + docs + small fixes * Remove superfluous response properties * Remove search API client from listing v2 API client * Update imports * Remove useless jsdocs * Use Map instead of Record for labels * Un-document get sample config function and uniformize CommerceEngineConfiguration docs * Add missing jsdoc * feat(productlistings): create plpv2 interactive controller [CAPI-85] * feat(productlistings): move to proper folder [CAPI-85] * feat(commerce): export controller [CAPI-85] * feat(commerce): amend documentation [CAPI-85] * feat(productlistings): create plp v2 pager controller [CAPI-86] * feat(commerce): add controller doc [CAPI-86] * feat(commerce): cover slice case with unit test [CAPI-86] * feat(commerce): expose pager [CAPI-86] --------- Co-authored-by: fbeaudoincoveo --- packages/headless/src/commerce.index.ts | 9 ++++ .../headless-product-listing-pager.test.ts | 48 ++++++++++++++++++ .../pager/headless-product-listing-pager.ts | 50 +++++++++++++++++++ .../pagination/pagination-slice.test.ts | 19 +++++++ .../features/pagination/pagination-slice.ts | 5 ++ 5 files changed, 131 insertions(+) create mode 100644 packages/headless/src/controllers/commerce/product-listing/pager/headless-product-listing-pager.test.ts create mode 100644 packages/headless/src/controllers/commerce/product-listing/pager/headless-product-listing-pager.ts diff --git a/packages/headless/src/commerce.index.ts b/packages/headless/src/commerce.index.ts index fe1d77841c7..8b559053ed4 100644 --- a/packages/headless/src/commerce.index.ts +++ b/packages/headless/src/commerce.index.ts @@ -32,6 +32,15 @@ export type { } from './controllers/controller/headless-controller'; export {buildController} from './controllers/controller/headless-controller'; +export type { + PagerInitialState, + PagerOptions, + PagerProps, + Pager, + PagerState, +} from './controllers/commerce/product-listing/pager/headless-product-listing-pager'; +export {buildPager} from './controllers/commerce/product-listing/pager/headless-product-listing-pager'; + export type { InteractiveResult, InteractiveResultOptions, diff --git a/packages/headless/src/controllers/commerce/product-listing/pager/headless-product-listing-pager.test.ts b/packages/headless/src/controllers/commerce/product-listing/pager/headless-product-listing-pager.test.ts new file mode 100644 index 00000000000..4b3770768c4 --- /dev/null +++ b/packages/headless/src/controllers/commerce/product-listing/pager/headless-product-listing-pager.test.ts @@ -0,0 +1,48 @@ +import {fetchProductListing} from '../../../../features/product-listing/v2/product-listing-v2-actions'; +import {buildMockCommerceEngine, MockCommerceEngine} from '../../../../test'; +import { + Pager, + PagerOptions, + PagerInitialState, + buildPager, +} from './headless-product-listing-pager'; + +describe('Pager', () => { + let engine: MockCommerceEngine; + let options: PagerOptions; + let initialState: PagerInitialState; + let pager: Pager; + + function initPager() { + pager = buildPager(engine, {options, initialState}); + } + + beforeEach(() => { + options = {}; + initialState = {}; + engine = buildMockCommerceEngine(); + initPager(); + }); + + it('initializes', () => { + expect(pager).toBeTruthy(); + }); + + it('#selectPage dispatches #fetchProductListing', () => { + pager.selectPage(2); + const action = engine.findAsyncAction(fetchProductListing.pending); + expect(action).toBeTruthy(); + }); + + it('#nextPage dispatches #fetchProductListing', () => { + pager.nextPage(); + const action = engine.findAsyncAction(fetchProductListing.pending); + expect(action).toBeTruthy(); + }); + + it('#previousPage dispatches #fetchProductListing', () => { + pager.previousPage(); + const action = engine.findAsyncAction(fetchProductListing.pending); + expect(engine.actions).toContainEqual(action); + }); +}); diff --git a/packages/headless/src/controllers/commerce/product-listing/pager/headless-product-listing-pager.ts b/packages/headless/src/controllers/commerce/product-listing/pager/headless-product-listing-pager.ts new file mode 100644 index 00000000000..0a3613cfe9a --- /dev/null +++ b/packages/headless/src/controllers/commerce/product-listing/pager/headless-product-listing-pager.ts @@ -0,0 +1,50 @@ +import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine'; +import {fetchProductListing} from '../../../../features/product-listing/v2/product-listing-v2-actions'; +import { + buildCorePager, + PagerInitialState, + PagerOptions, + PagerProps, + Pager, + PagerState, +} from '../../../core/pager/headless-core-pager'; + +export type {PagerInitialState, PagerOptions, PagerProps, Pager, PagerState}; + +/** + * Creates a `Pager` controller instance for the product listing. + * + * @param engine - The headless commerce engine. + * @param props - The configurable `Pager` properties. + * @returns A `Pager` controller instance. + * */ +export function buildPager( + engine: CommerceEngine, + props: PagerProps = {} +): Pager { + const {dispatch} = engine; + const pager = buildCorePager(engine, props); + + return { + ...pager, + + get state() { + return pager.state; + }, + + selectPage(page: number) { + pager.selectPage(page); + dispatch(fetchProductListing()); + }, + + nextPage() { + pager.nextPage(); + dispatch(fetchProductListing()); + }, + + previousPage() { + pager.previousPage(); + dispatch(fetchProductListing()); + }, + }; +} diff --git a/packages/headless/src/features/pagination/pagination-slice.test.ts b/packages/headless/src/features/pagination/pagination-slice.test.ts index 16b7ca7b0d2..e9423a80d36 100644 --- a/packages/headless/src/features/pagination/pagination-slice.test.ts +++ b/packages/headless/src/features/pagination/pagination-slice.test.ts @@ -1,5 +1,6 @@ import {Action} from '@reduxjs/toolkit'; import {buildFetchProductListingResponse} from '../../test/mock-product-listing'; +import {buildFetchProductListingV2Response} from '../../test/mock-product-listing-v2'; import {buildMockSearch} from '../../test/mock-search'; import {deselectAllBreadcrumbs} from '../breadcrumb/breadcrumb-actions'; import {toggleSelectAutomaticFacetValue} from '../facets/automatic-facet-set/automatic-facet-set-actions'; @@ -22,6 +23,7 @@ import { import {change} from '../history/history-actions'; import {getHistoryInitialState} from '../history/history-state'; import {fetchProductListing} from '../product-listing/product-listing-actions'; +import {fetchProductListing as fetchProductListingV2} from '../product-listing/v2/product-listing-v2-actions'; import {logSearchboxSubmit} from '../query/query-analytics-actions'; import {restoreSearchParameters} from '../search-parameters/search-parameter-actions'; import {executeSearch} from '../search/search-actions'; @@ -194,6 +196,23 @@ describe('pagination slice', () => { ); }); + it('fetchProductListingV2.fulfilled updates totalCountFiltered to the response value', () => { + const response = buildFetchProductListingV2Response({ + pagination: { + page: 0, + perPage: 100, + totalCount: 100, + totalPages: 1, + }, + }); + const action = fetchProductListingV2.fulfilled(response, ''); + + const finalState = paginationReducer(state, action); + expect(finalState.totalCountFiltered).toBe( + response.response.pagination.totalCount + ); + }); + it('allows to restore pagination state on history change', () => { const state = getPaginationInitialState(); const expectedPagination = { diff --git a/packages/headless/src/features/pagination/pagination-slice.ts b/packages/headless/src/features/pagination/pagination-slice.ts index 8a48a4c6f9e..202fb97c3d9 100644 --- a/packages/headless/src/features/pagination/pagination-slice.ts +++ b/packages/headless/src/features/pagination/pagination-slice.ts @@ -17,6 +17,7 @@ import { } from '../facets/range-facets/numeric-facet-set/numeric-facet-actions'; import {change} from '../history/history-actions'; import {fetchProductListing} from '../product-listing/product-listing-actions'; +import {fetchProductListing as fetchProductListingV2} from '../product-listing/v2/product-listing-v2-actions'; import {restoreSearchParameters} from '../search-parameters/search-parameter-actions'; import {executeSearch} from '../search/search-actions'; import {updateActiveTab} from '../tab-set/tab-set-actions'; @@ -102,6 +103,10 @@ export const paginationReducer = createReducer( const {response} = action.payload; state.totalCountFiltered = response.pagination.totalCount; }) + .addCase(fetchProductListingV2.fulfilled, (state, action) => { + const {response} = action.payload; + state.totalCountFiltered = response.pagination.totalCount; + }) .addCase(deselectAllFacetValues, (state) => { handlePaginationReset(state); })