diff --git a/packages/headless/src/controllers/commerce/facets/core/date/headless-commerce-date-facet.test.ts b/packages/headless/src/controllers/commerce/facets/core/date/headless-commerce-date-facet.test.ts index 5738ee08b42..9c38c15cf4f 100644 --- a/packages/headless/src/controllers/commerce/facets/core/date/headless-commerce-date-facet.test.ts +++ b/packages/headless/src/controllers/commerce/facets/core/date/headless-commerce-date-facet.test.ts @@ -1,6 +1,5 @@ import {CommerceFacetRequest} from '../../../../../features/commerce/facets/facet-set/interfaces/request'; import {FacetType} from '../../../../../features/commerce/facets/facet-set/interfaces/response'; -import {fetchProductListing} from '../../../../../features/commerce/product-listing/product-listing-actions'; import { toggleExcludeDateFacetValue, toggleSelectDateFacetValue, @@ -12,6 +11,7 @@ import {buildMockCommerceDateFacetResponse} from '../../../../../test/mock-comme import {buildMockCommerceFacetSlice} from '../../../../../test/mock-commerce-facet-slice'; import {buildMockCommerceDateFacetValue} from '../../../../../test/mock-commerce-facet-value'; import {buildMockCommerceState} from '../../../../../test/mock-commerce-state'; +import {commonOptions} from '../../../product-listing/facets/headless-product-listing-facet-options'; import { CommerceDateFacet, CommerceDateFacetOptions, @@ -45,7 +45,7 @@ describe('CommerceDateFacet', () => { beforeEach(() => { options = { facetId, - fetchResultsActionCreator: fetchProductListing, + ...commonOptions, }; state = buildMockCommerceState(); diff --git a/packages/headless/src/controllers/commerce/facets/core/headless-core-commerce-facet.test.ts b/packages/headless/src/controllers/commerce/facets/core/headless-core-commerce-facet.test.ts index 91c5907f2e4..42b3b9dc1bb 100644 --- a/packages/headless/src/controllers/commerce/facets/core/headless-core-commerce-facet.test.ts +++ b/packages/headless/src/controllers/commerce/facets/core/headless-core-commerce-facet.test.ts @@ -21,6 +21,7 @@ import {buildMockCommerceFacetSlice} from '../../../../test/mock-commerce-facet- import {buildMockCommerceRegularFacetValue} from '../../../../test/mock-commerce-facet-value'; import {buildMockCommerceState} from '../../../../test/mock-commerce-state'; import {FacetValueState} from '../../../core/facets/facet/headless-core-facet'; +import {commonOptions} from '../../product-listing/facets/headless-product-listing-facet-options'; import { buildCoreCommerceFacet, CoreCommerceFacet, @@ -52,23 +53,22 @@ describe('CoreCommerceFacet', () => { } function setFacetResponse(config: Partial = {}) { - state.productListing.facets = [ + options.facetResponseSelector = () => buildMockCommerceRegularFacetResponse({ facetId, field, type, displayName, ...config, - }), - ]; + }); } beforeEach(() => { options = { facetId, - fetchResultsActionCreator, toggleExcludeActionCreator, toggleSelectActionCreator, + ...commonOptions, }; state = buildMockCommerceState(); @@ -381,34 +381,47 @@ describe('CoreCommerceFacet', () => { expect(facet.state.displayName).toBe(displayName); }); - it('#state.values contains the same values as from the response', () => { + it('#state.values uses #facetResponseSelector', () => { const values = [buildMockCommerceRegularFacetValue()]; - const facetResponse = buildMockCommerceRegularFacetResponse({ - facetId, - values, - }); + options = { + ...options, + facetResponseSelector: () => + buildMockCommerceRegularFacetResponse({ + facetId, + values, + }), + }; + initFacet(); - state.productListing.facets = [facetResponse]; expect(facet.state.values).toBe(values); }); + it('#state.isLoading uses #isFacetLoadingResponseSelector', () => { + options = { + ...options, + isFacetLoadingResponseSelector: () => true, + }; + initFacet(); + expect(facet.state.isLoading).toBe(true); + }); + describe('#state.hasActiveValues', () => { it('when #state.values has a value with a non-idle state, returns "true"', () => { - const facetResponse = buildMockCommerceRegularFacetResponse({facetId}); - facetResponse.values = [ - buildMockCommerceRegularFacetValue({state: 'selected'}), - ]; - state.productListing.facets = [facetResponse]; + setFacetResponse({ + ...buildMockCommerceRegularFacetResponse({facetId}), + values: [buildMockCommerceRegularFacetValue({state: 'selected'})], + }); + initFacet(); expect(facet.state.hasActiveValues).toBe(true); }); it('when #state.values only has idle values, returns "false"', () => { - const facetResponse = buildMockCommerceRegularFacetResponse({facetId}); - facetResponse.values = [ - buildMockCommerceRegularFacetValue({state: 'idle'}), - ]; - state.productListing.facets = [facetResponse]; + setFacetResponse({ + ...buildMockCommerceRegularFacetResponse({facetId}), + values: [buildMockCommerceRegularFacetValue({state: 'idle'})], + }); + initFacet(); expect(facet.state.hasActiveValues).toBe(false); }); @@ -420,22 +433,26 @@ describe('CoreCommerceFacet', () => { }); it('when #moreValuesAvailable in the response is "true", returns "true"', () => { - const facetResponse = buildMockCommerceRegularFacetResponse({ - facetId, - moreValuesAvailable: true, - }); + setFacetResponse( + buildMockCommerceRegularFacetResponse({ + facetId, + moreValuesAvailable: true, + }) + ); + initFacet(); - state.productListing.facets = [facetResponse]; expect(facet.state.canShowMoreValues).toBe(true); }); it('when #moreValuesAvailable in the response is "false", returns "false"', () => { - const facetResponse = buildMockCommerceRegularFacetResponse({ - facetId, - moreValuesAvailable: false, - }); + setFacetResponse( + buildMockCommerceRegularFacetResponse({ + facetId, + moreValuesAvailable: false, + }) + ); + initFacet(); - state.productListing.facets = [facetResponse]; expect(facet.state.canShowMoreValues).toBe(false); }); }); diff --git a/packages/headless/src/controllers/commerce/facets/core/headless-core-commerce-facet.ts b/packages/headless/src/controllers/commerce/facets/core/headless-core-commerce-facet.ts index d7bc47ce34a..8759ae190ac 100644 --- a/packages/headless/src/controllers/commerce/facets/core/headless-core-commerce-facet.ts +++ b/packages/headless/src/controllers/commerce/facets/core/headless-core-commerce-facet.ts @@ -3,12 +3,9 @@ import { AsyncThunkAction, } from '@reduxjs/toolkit'; import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine'; -import { - commerceFacetResponseSelector, - isCommerceFacetLoadingResponseSelector, -} from '../../../../features/commerce/facets/facet-set/facet-set-selector'; import {commerceFacetSetReducer as commerceFacetSet} from '../../../../features/commerce/facets/facet-set/facet-set-slice'; import { + AnyFacetResponse, AnyFacetValueResponse, DateFacetValue, FacetType, @@ -74,6 +71,11 @@ export interface CoreCommerceFacetOptions { >; // eslint-disable-next-line @typescript-eslint/no-explicit-any fetchResultsActionCreator: () => AsyncThunkAction; + facetResponseSelector: ( + state: CommerceEngine['state'], + facetId: string + ) => AnyFacetResponse | undefined; + isFacetLoadingResponseSelector: (state: CommerceEngine['state']) => boolean; } export type CommerceFacetOptions = Omit< @@ -81,6 +83,8 @@ export type CommerceFacetOptions = Omit< | 'fetchResultsActionCreator' | 'toggleSelectActionCreator' | 'toggleExcludeActionCreator' + | 'facetResponseSelector' + | 'isFacetLoadingResponseSelector' >; export type CoreCommerceFacet< @@ -176,9 +180,9 @@ export function buildCoreCommerceFacet< const getRequest = () => engine.state.commerceFacetSet[facetId].request; const getResponse = () => - commerceFacetResponseSelector(engine.state, facetId)!; + props.options.facetResponseSelector(engine.state, facetId)!; const getIsLoading = () => - isCommerceFacetLoadingResponseSelector(engine.state); + props.options.isFacetLoadingResponseSelector(engine.state); const getNumberOfActiveValues = () => { return getRequest().values.filter((v) => v.state !== 'idle').length; @@ -279,7 +283,7 @@ export function buildCoreCommerceFacet< field: response.field, displayName: response.displayName, values, - isLoading: getIsLoading() ?? false, + isLoading: getIsLoading(), hasActiveValues, canShowMoreValues, canShowLessValues: computeCanShowLessValues(), diff --git a/packages/headless/src/controllers/commerce/facets/core/numeric/headless-commerce-numeric-facet.test.ts b/packages/headless/src/controllers/commerce/facets/core/numeric/headless-commerce-numeric-facet.test.ts index 58937487db9..0a3df6c9d57 100644 --- a/packages/headless/src/controllers/commerce/facets/core/numeric/headless-commerce-numeric-facet.test.ts +++ b/packages/headless/src/controllers/commerce/facets/core/numeric/headless-commerce-numeric-facet.test.ts @@ -1,6 +1,5 @@ import {CommerceFacetRequest} from '../../../../../features/commerce/facets/facet-set/interfaces/request'; import {FacetType} from '../../../../../features/commerce/facets/facet-set/interfaces/response'; -import {fetchProductListing} from '../../../../../features/commerce/product-listing/product-listing-actions'; import { toggleExcludeNumericFacetValue, toggleSelectNumericFacetValue, @@ -12,6 +11,7 @@ import {buildMockCommerceNumericFacetResponse} from '../../../../../test/mock-co import {buildMockCommerceFacetSlice} from '../../../../../test/mock-commerce-facet-slice'; import {buildMockCommerceNumericFacetValue} from '../../../../../test/mock-commerce-facet-value'; import {buildMockCommerceState} from '../../../../../test/mock-commerce-state'; +import {commonOptions} from '../../../product-listing/facets/headless-product-listing-facet-options'; import { CommerceNumericFacet, CommerceNumericFacetOptions, @@ -45,7 +45,7 @@ describe('CommerceNumericFacet', () => { beforeEach(() => { options = { facetId, - fetchResultsActionCreator: fetchProductListing, + ...commonOptions, }; state = buildMockCommerceState(); diff --git a/packages/headless/src/controllers/commerce/facets/core/regular/headless-commerce-regular-facet.test.ts b/packages/headless/src/controllers/commerce/facets/core/regular/headless-commerce-regular-facet.test.ts index 30064d6e347..0332c1b584c 100644 --- a/packages/headless/src/controllers/commerce/facets/core/regular/headless-commerce-regular-facet.test.ts +++ b/packages/headless/src/controllers/commerce/facets/core/regular/headless-commerce-regular-facet.test.ts @@ -1,5 +1,4 @@ import {CommerceFacetRequest} from '../../../../../features/commerce/facets/facet-set/interfaces/request'; -import {fetchProductListing} from '../../../../../features/commerce/product-listing/product-listing-actions'; import { toggleExcludeFacetValue, toggleSelectFacetValue, @@ -11,6 +10,7 @@ import {buildMockCommerceRegularFacetResponse} from '../../../../../test/mock-co import {buildMockCommerceFacetSlice} from '../../../../../test/mock-commerce-facet-slice'; import {buildMockCommerceRegularFacetValue} from '../../../../../test/mock-commerce-facet-value'; import {buildMockCommerceState} from '../../../../../test/mock-commerce-state'; +import {commonOptions} from '../../../product-listing/facets/headless-product-listing-facet-options'; import { CommerceRegularFacet, CommerceRegularFacetOptions, @@ -41,7 +41,7 @@ describe('CommerceRegularFacet', () => { beforeEach(() => { options = { facetId, - fetchResultsActionCreator: fetchProductListing, + ...commonOptions, }; state = buildMockCommerceState(); diff --git a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-date-facet.test.ts b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-date-facet.test.ts index c252eda7cb1..a8e164e68e6 100644 --- a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-date-facet.test.ts +++ b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-date-facet.test.ts @@ -104,4 +104,17 @@ describe('ProductListingDateFacet', () => { expectContainAction(fetchProductListing.pending); }); }); + + describe('#state', () => { + it('#state.values uses #facetResponseSelector', () => { + expect(facet.state.facetId).toEqual( + state.productListing.facets[0].facetId + ); + }); + + it('#state.isLoading uses #isFacetLoadingResponseSelector', () => { + state.productListing.isLoading = true; + expect(facet.state.isLoading).toBe(true); + }); + }); }); diff --git a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-date-facet.ts b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-date-facet.ts index e9468da6db7..bd21d2f7c37 100644 --- a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-date-facet.ts +++ b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-date-facet.ts @@ -1,5 +1,4 @@ import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine'; -import {fetchProductListing} from '../../../../features/commerce/product-listing/product-listing-actions'; import {loadReducerError} from '../../../../utils/errors'; import { CommerceDateFacet, @@ -7,6 +6,7 @@ import { } from '../../facets/core/date/headless-commerce-date-facet'; import {CommerceFacetOptions} from '../../facets/core/headless-core-commerce-facet'; import {loadProductListingReducer} from '../utils/load-product-listing-reducers'; +import {commonOptions} from './headless-product-listing-facet-options'; export function buildProductListingDateFacet( engine: CommerceEngine, @@ -18,6 +18,6 @@ export function buildProductListingDateFacet( return buildCommerceDateFacet(engine, { ...options, - fetchResultsActionCreator: fetchProductListing, + ...commonOptions, }); } diff --git a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-facet-options.ts b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-facet-options.ts new file mode 100644 index 00000000000..fa68a234f83 --- /dev/null +++ b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-facet-options.ts @@ -0,0 +1,35 @@ +import {isFacetResponse} from '../../../../features/commerce/facets/facet-set/facet-set-selector'; +import {fetchProductListing} from '../../../../features/commerce/product-listing/product-listing-actions'; +import { + CommerceFacetSetSection, + ProductListingV2Section, +} from '../../../../state/state-sections'; +import {CoreCommerceFacetOptions} from '../../facets/core/headless-core-commerce-facet'; + +const facetResponseSelector = ( + state: ProductListingV2Section & CommerceFacetSetSection, + facetId: string +) => { + const response = state.productListing.facets.find( + (response) => response.facetId === facetId + ); + if (isFacetResponse(state, response)) { + return response; + } + + return undefined; +}; + +const isFacetLoadingResponseSelector = (state: ProductListingV2Section) => + state.productListing.isLoading; + +export const commonOptions: Pick< + CoreCommerceFacetOptions, + | 'fetchResultsActionCreator' + | 'facetResponseSelector' + | 'isFacetLoadingResponseSelector' +> = { + fetchResultsActionCreator: fetchProductListing, + facetResponseSelector, + isFacetLoadingResponseSelector, +}; diff --git a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-numeric-facet.test.ts b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-numeric-facet.test.ts index 64d1efbec1a..f9a531d7e0e 100644 --- a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-numeric-facet.test.ts +++ b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-numeric-facet.test.ts @@ -104,4 +104,17 @@ describe('ProductListingNumericFacet', () => { expectContainAction(fetchProductListing.pending); }); }); + + describe('#state', () => { + it('#state.values uses #facetResponseSelector', () => { + expect(facet.state.facetId).toEqual( + state.productListing.facets[0].facetId + ); + }); + + it('#state.isLoading uses #isFacetLoadingResponseSelector', () => { + state.productListing.isLoading = true; + expect(facet.state.isLoading).toBe(true); + }); + }); }); diff --git a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-numeric-facet.ts b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-numeric-facet.ts index bc46b6551bc..68bd0f13ebc 100644 --- a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-numeric-facet.ts +++ b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-numeric-facet.ts @@ -1,5 +1,4 @@ import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine'; -import {fetchProductListing} from '../../../../features/commerce/product-listing/product-listing-actions'; import {loadReducerError} from '../../../../utils/errors'; import {CommerceFacetOptions} from '../../facets/core/headless-core-commerce-facet'; import { @@ -7,6 +6,7 @@ import { buildCommerceNumericFacet, } from '../../facets/core/numeric/headless-commerce-numeric-facet'; import {loadProductListingReducer} from '../utils/load-product-listing-reducers'; +import {commonOptions} from './headless-product-listing-facet-options'; export function buildProductListingNumericFacet( engine: CommerceEngine, @@ -18,6 +18,6 @@ export function buildProductListingNumericFacet( return buildCommerceNumericFacet(engine, { ...options, - fetchResultsActionCreator: fetchProductListing, + ...commonOptions, }); } diff --git a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-regular-facet.test.ts b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-regular-facet.test.ts index eef1a40ca39..e19b0cf9878 100644 --- a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-regular-facet.test.ts +++ b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-regular-facet.test.ts @@ -102,4 +102,17 @@ describe('ProductListingRegularFacet', () => { expectContainAction(fetchProductListing.pending); }); }); + + describe('#state', () => { + it('#state.values uses #facetResponseSelector', () => { + expect(facet.state.facetId).toEqual( + state.productListing.facets[0].facetId + ); + }); + + it('#state.isLoading uses #isFacetLoadingResponseSelector', () => { + state.productListing.isLoading = true; + expect(facet.state.isLoading).toBe(true); + }); + }); }); diff --git a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-regular-facet.ts b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-regular-facet.ts index 6d8d2367b71..4300a7d6fc6 100644 --- a/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-regular-facet.ts +++ b/packages/headless/src/controllers/commerce/product-listing/facets/headless-product-listing-regular-facet.ts @@ -1,5 +1,4 @@ import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine'; -import {fetchProductListing} from '../../../../features/commerce/product-listing/product-listing-actions'; import {loadReducerError} from '../../../../utils/errors'; import {CommerceFacetOptions} from '../../facets/core/headless-core-commerce-facet'; import { @@ -7,6 +6,7 @@ import { buildCommerceRegularFacet, } from '../../facets/core/regular/headless-commerce-regular-facet'; import {loadProductListingReducer} from '../utils/load-product-listing-reducers'; +import {commonOptions} from './headless-product-listing-facet-options'; export function buildProductListingRegularFacet( engine: CommerceEngine, @@ -18,6 +18,6 @@ export function buildProductListingRegularFacet( return buildCommerceRegularFacet(engine, { ...options, - fetchResultsActionCreator: fetchProductListing, + ...commonOptions, }); } diff --git a/packages/headless/src/controllers/commerce/search/facets/headless-search-date-facet.test.ts b/packages/headless/src/controllers/commerce/search/facets/headless-search-date-facet.test.ts index 293a9ffa5d9..53c463d275b 100644 --- a/packages/headless/src/controllers/commerce/search/facets/headless-search-date-facet.test.ts +++ b/packages/headless/src/controllers/commerce/search/facets/headless-search-date-facet.test.ts @@ -104,4 +104,17 @@ describe('SearchDateFacet', () => { expectContainAction(executeSearch.pending); }); }); + + describe('#state', () => { + it('#state.values uses #facetResponseSelector', () => { + expect(facet.state.facetId).toEqual( + state.commerceSearch.facets[0].facetId + ); + }); + + it('#state.isLoading uses #isFacetLoadingResponseSelector', () => { + state.commerceSearch.isLoading = true; + expect(facet.state.isLoading).toBe(true); + }); + }); }); diff --git a/packages/headless/src/controllers/commerce/search/facets/headless-search-date-facet.ts b/packages/headless/src/controllers/commerce/search/facets/headless-search-date-facet.ts index 331d0a3ce8e..a727231425c 100644 --- a/packages/headless/src/controllers/commerce/search/facets/headless-search-date-facet.ts +++ b/packages/headless/src/controllers/commerce/search/facets/headless-search-date-facet.ts @@ -1,5 +1,4 @@ import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine'; -import {executeSearch} from '../../../../features/commerce/search/search-actions'; import {loadReducerError} from '../../../../utils/errors'; import { CommerceDateFacet, @@ -7,6 +6,7 @@ import { } from '../../facets/core/date/headless-commerce-date-facet'; import {CommerceFacetOptions} from '../../facets/core/headless-core-commerce-facet'; import {loadSearchReducer} from '../utils/load-search-reducers'; +import {commonOptions} from './headless-search-facet-options'; export function buildSearchDateFacet( engine: CommerceEngine, @@ -18,6 +18,6 @@ export function buildSearchDateFacet( return buildCommerceDateFacet(engine, { ...options, - fetchResultsActionCreator: executeSearch, + ...commonOptions, }); } diff --git a/packages/headless/src/controllers/commerce/search/facets/headless-search-facet-options.ts b/packages/headless/src/controllers/commerce/search/facets/headless-search-facet-options.ts new file mode 100644 index 00000000000..aa5219c3f55 --- /dev/null +++ b/packages/headless/src/controllers/commerce/search/facets/headless-search-facet-options.ts @@ -0,0 +1,35 @@ +import {isFacetResponse} from '../../../../features/commerce/facets/facet-set/facet-set-selector'; +import {executeSearch} from '../../../../features/commerce/search/search-actions'; +import { + CommerceFacetSetSection, + CommerceSearchSection, +} from '../../../../state/state-sections'; +import {CoreCommerceFacetOptions} from '../../facets/core/headless-core-commerce-facet'; + +const facetResponseSelector = ( + state: CommerceSearchSection & CommerceFacetSetSection, + facetId: string +) => { + const response = state.commerceSearch.facets.find( + (response) => response.facetId === facetId + ); + if (isFacetResponse(state, response)) { + return response; + } + + return undefined; +}; + +const isFacetLoadingResponseSelector = (state: CommerceSearchSection) => + state.commerceSearch.isLoading; + +export const commonOptions: Pick< + CoreCommerceFacetOptions, + | 'fetchResultsActionCreator' + | 'facetResponseSelector' + | 'isFacetLoadingResponseSelector' +> = { + fetchResultsActionCreator: executeSearch, + facetResponseSelector, + isFacetLoadingResponseSelector, +}; diff --git a/packages/headless/src/controllers/commerce/search/facets/headless-search-numeric-facet.test.ts b/packages/headless/src/controllers/commerce/search/facets/headless-search-numeric-facet.test.ts index e4c4fe5506b..5bf409123f3 100644 --- a/packages/headless/src/controllers/commerce/search/facets/headless-search-numeric-facet.test.ts +++ b/packages/headless/src/controllers/commerce/search/facets/headless-search-numeric-facet.test.ts @@ -104,4 +104,17 @@ describe('SearchNumericFacet', () => { expectContainAction(executeSearch.pending); }); }); + + describe('#state', () => { + it('#state.values uses #facetResponseSelector', () => { + expect(facet.state.facetId).toEqual( + state.commerceSearch.facets[0].facetId + ); + }); + + it('#state.isLoading uses #isFacetLoadingResponseSelector', () => { + state.commerceSearch.isLoading = true; + expect(facet.state.isLoading).toBe(true); + }); + }); }); diff --git a/packages/headless/src/controllers/commerce/search/facets/headless-search-numeric-facet.ts b/packages/headless/src/controllers/commerce/search/facets/headless-search-numeric-facet.ts index 1d18f343ca0..ff3cf5d45df 100644 --- a/packages/headless/src/controllers/commerce/search/facets/headless-search-numeric-facet.ts +++ b/packages/headless/src/controllers/commerce/search/facets/headless-search-numeric-facet.ts @@ -1,5 +1,4 @@ import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine'; -import {executeSearch} from '../../../../features/commerce/search/search-actions'; import {loadReducerError} from '../../../../utils/errors'; import {CommerceFacetOptions} from '../../facets/core/headless-core-commerce-facet'; import { @@ -7,6 +6,7 @@ import { buildCommerceNumericFacet, } from '../../facets/core/numeric/headless-commerce-numeric-facet'; import {loadSearchReducer} from '../utils/load-search-reducers'; +import {commonOptions} from './headless-search-facet-options'; export function buildSearchNumericFacet( engine: CommerceEngine, @@ -18,6 +18,6 @@ export function buildSearchNumericFacet( return buildCommerceNumericFacet(engine, { ...options, - fetchResultsActionCreator: executeSearch, + ...commonOptions, }); } diff --git a/packages/headless/src/controllers/commerce/search/facets/headless-search-regular-facet.test.ts b/packages/headless/src/controllers/commerce/search/facets/headless-search-regular-facet.test.ts index f8db8c12665..db967d40666 100644 --- a/packages/headless/src/controllers/commerce/search/facets/headless-search-regular-facet.test.ts +++ b/packages/headless/src/controllers/commerce/search/facets/headless-search-regular-facet.test.ts @@ -102,4 +102,17 @@ describe('SearchRegularFacet', () => { expectContainAction(executeSearch.pending); }); }); + + describe('#state', () => { + it('#state.values uses #facetResponseSelector', () => { + expect(facet.state.facetId).toEqual( + state.commerceSearch.facets[0].facetId + ); + }); + + it('#state.isLoading uses #isFacetLoadingResponseSelector', () => { + state.commerceSearch.isLoading = true; + expect(facet.state.isLoading).toBe(true); + }); + }); }); diff --git a/packages/headless/src/controllers/commerce/search/facets/headless-search-regular-facet.ts b/packages/headless/src/controllers/commerce/search/facets/headless-search-regular-facet.ts index 2f99ee9b468..01fe8164fc8 100644 --- a/packages/headless/src/controllers/commerce/search/facets/headless-search-regular-facet.ts +++ b/packages/headless/src/controllers/commerce/search/facets/headless-search-regular-facet.ts @@ -1,5 +1,4 @@ import {CommerceEngine} from '../../../../app/commerce-engine/commerce-engine'; -import {executeSearch} from '../../../../features/commerce/search/search-actions'; import {loadReducerError} from '../../../../utils/errors'; import {CommerceFacetOptions} from '../../facets/core/headless-core-commerce-facet'; import { @@ -7,6 +6,7 @@ import { buildCommerceRegularFacet, } from '../../facets/core/regular/headless-commerce-regular-facet'; import {loadSearchReducer} from '../utils/load-search-reducers'; +import {commonOptions} from './headless-search-facet-options'; export function buildSearchRegularFacet( engine: CommerceEngine, @@ -18,6 +18,6 @@ export function buildSearchRegularFacet( return buildCommerceRegularFacet(engine, { ...options, - fetchResultsActionCreator: executeSearch, + ...commonOptions, }); } diff --git a/packages/headless/src/features/commerce/facets/facet-set/facet-set-selector.ts b/packages/headless/src/features/commerce/facets/facet-set/facet-set-selector.ts index b2acd426af0..7aa48a49296 100644 --- a/packages/headless/src/features/commerce/facets/facet-set/facet-set-selector.ts +++ b/packages/headless/src/features/commerce/facets/facet-set/facet-set-selector.ts @@ -1,58 +1,9 @@ -import { - CommerceFacetSetSection, - CommerceSearchSection, - ProductListingV2Section, -} from '../../../../state/state-sections'; +import {CommerceFacetSetSection} from '../../../../state/state-sections'; import {AnyFacetResponse} from './interfaces/response'; -function isFacetResponse( +export function isFacetResponse( state: CommerceFacetSetSection, response: AnyFacetResponse | undefined ): response is AnyFacetResponse { return !!response && response.facetId in state.commerceFacetSet; } - -function baseCommerceFacetResponseSelector( - state: ProductListingV2Section | CommerceSearchSection, - facetId: string -) { - const findById = (response: {facetId: string}) => - response.facetId === facetId; - - if ('productListing' in state) { - return state.productListing.facets.find(findById); - } - - if ('commerceSearch' in state) { - return state.commerceSearch.facets.find(findById); - } - - return undefined; -} - -export const commerceFacetResponseSelector = ( - state: (ProductListingV2Section | CommerceSearchSection) & - CommerceFacetSetSection, - facetId: string -) => { - const response = baseCommerceFacetResponseSelector(state, facetId); - if (isFacetResponse(state, response)) { - return response; - } - - return undefined; -}; - -export const isCommerceFacetLoadingResponseSelector = ( - state: ProductListingV2Section | CommerceSearchSection -) => { - if ('productListing' in state) { - return state.productListing.isLoading; - } - - if ('commerceSearch' in state) { - return state.commerceSearch.isLoading; - } - - return undefined; -}; diff --git a/packages/headless/src/features/facets/facet-order/facet-order-slice.test.ts b/packages/headless/src/features/facets/facet-order/facet-order-slice.test.ts index 77c986eaba8..731d9977e27 100644 --- a/packages/headless/src/features/facets/facet-order/facet-order-slice.test.ts +++ b/packages/headless/src/features/facets/facet-order/facet-order-slice.test.ts @@ -1,7 +1,13 @@ import {AnyAction} from '@reduxjs/toolkit'; +import {buildMockCommerceRegularFacetResponse} from '../../../test/mock-commerce-facet-response'; +import {buildMockCommerceRegularFacetValue} from '../../../test/mock-commerce-facet-value'; +import {buildSearchResponse} from '../../../test/mock-commerce-search'; import {buildMockFacetResponse} from '../../../test/mock-facet-response'; +import {buildFetchProductListingV2Response} from '../../../test/mock-product-listing-v2'; import {buildMockSearch} from '../../../test/mock-search'; import {buildMockSearchResponse} from '../../../test/mock-search-response'; +import {fetchProductListing} from '../../commerce/product-listing/product-listing-actions'; +import {executeSearch as executeCommerceSearch} from '../../commerce/search/search-actions'; import {change} from '../../history/history-actions'; import {getHistoryInitialState} from '../../history/history-state'; import {executeSearch} from '../../search/search-actions'; @@ -60,4 +66,37 @@ describe('facet-order slice', () => { dispatchMockHistoryChange(facetIds); expect(state).toEqual(facetIds); }); + + describe.each([ + { + actionName: '#fetchProductListing.fulfilled', + action: fetchProductListing.fulfilled, + responseBuilder: buildFetchProductListingV2Response, + }, + { + actionName: '#executeCommerceSearch.fulfilled', + action: executeCommerceSearch.fulfilled, + responseBuilder: buildSearchResponse, + }, + ])('$actionName', ({action, responseBuilder}) => { + function buildQueryAction(facetIds: string[]) { + const facetValue = buildMockCommerceRegularFacetValue({ + value: 'some-value', + }); + const response = responseBuilder(); + response.response.facets = facetIds.map((facetId) => + buildMockCommerceRegularFacetResponse({ + facetId, + values: [facetValue], + }) + ); + + return action(response, ''); + } + it('saves the facet order when a query is successful', () => { + const facetIds = ['facetA', 'facetB']; + dispatchMock(buildQueryAction(facetIds)); + expect(state).toEqual(facetIds); + }); + }); }); diff --git a/packages/headless/src/features/facets/facet-order/facet-order-slice.ts b/packages/headless/src/features/facets/facet-order/facet-order-slice.ts index 52737fd2334..0b58371d838 100644 --- a/packages/headless/src/features/facets/facet-order/facet-order-slice.ts +++ b/packages/headless/src/features/facets/facet-order/facet-order-slice.ts @@ -1,23 +1,25 @@ -import {createReducer} from '@reduxjs/toolkit'; +import {AnyAction, createReducer} from '@reduxjs/toolkit'; import {fetchProductListing} from '../../commerce/product-listing/product-listing-actions'; +import {executeSearch as executeCommerceSearch} from '../../commerce/search/search-actions'; import {change} from '../../history/history-actions'; import {executeSearch} from '../../search/search-actions'; -import {getFacetOrderInitialState} from './facet-order-state'; +import {FacetOrderState, getFacetOrderInitialState} from './facet-order-state'; export const facetOrderReducer = createReducer( getFacetOrderInitialState(), (builder) => { builder - .addCase(executeSearch.fulfilled, (_, action) => { - return action.payload.response.facets.map((facet) => facet.facetId); - }) - .addCase(fetchProductListing.fulfilled, (_, action) => { - const generateFacetId = (facet: {facetId?: string; field: string}) => - facet.facetId ?? facet.field; - return action.payload.response.facets.map(generateFacetId); - }) + .addCase(executeSearch.fulfilled, handleQueryFulfilled) + .addCase(fetchProductListing.fulfilled, handleQueryFulfilled) + .addCase(executeCommerceSearch.fulfilled, handleQueryFulfilled) .addCase(change.fulfilled, (state, action) => { return action.payload?.facetOrder ?? state; }); } ); + +function handleQueryFulfilled(_: FacetOrderState, action: AnyAction) { + return action.payload.response.facets.map( + (facet: {facetId: string}) => facet.facetId + ); +}