From 0dc9434ee910da0e37f9bac30edb145b42d51408 Mon Sep 17 00:00:00 2001 From: Nicholas Labarre Date: Thu, 27 Jun 2024 08:56:23 -0400 Subject: [PATCH] fix(commerce): populate `context.user.userAgent` with `navigatorContext` (#4113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit headless was populating the `context.user.userAgent` property with what was set through the `setUser`. With this PR, we extract the `userAgent` and the `referrer` from the relay instance and use these values instead. This makes using the headless commerce controllers less error-prone. ⚠️ This means we can remove the `setUser` action (and its sibling method on the context controller), and `setView`'s `referrer` parameter, which I do in this PR. Do you think this makes sense? Is there a use case for allowing manual control over these parameters? @samisayegh @louis-bompart @fbeaudoincoveo ? [CAPI-1091] [CAPI-1091]: https://coveord.atlassian.net/browse/CAPI-1091?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Frederic Beaudoin Co-authored-by: Louis Bompart --- .../commerce-engine-configuration.ts | 1 - .../commerce-engine/commerce-engine.test.ts | 4 - packages/headless/src/commerce.index.ts | 1 - .../commerce/context/headless-context.test.ts | 11 -- .../commerce/context/headless-context.ts | 16 --- .../features/commerce/common/actions.test.ts | 127 ++++++++++++++---- .../src/features/commerce/common/actions.ts | 24 +++- .../commerce/context/context-actions.ts | 23 +--- .../commerce/context/context-slice.test.ts | 11 +- .../commerce/context/context-slice.ts | 5 +- .../commerce/context/context-state.ts | 6 +- .../commerce/context/context-validation.ts | 14 +- ...egory-facet-search-request-builder.test.ts | 57 ++++---- ...e-category-facet-search-request-builder.ts | 10 +- .../commerce-facet-search-actions.ts | 8 +- ...gular-facet-search-request-builder.test.ts | 48 +++++-- ...ce-regular-facet-search-request-builder.ts | 10 +- .../facets/facet-set/facet-set-slice.test.ts | 6 +- .../facets/facet-set/facet-set-slice.ts | 3 +- .../pagination/pagination-slice.test.ts | 6 +- .../commerce/pagination/pagination-slice.ts | 5 +- .../product-listing-actions.ts | 18 ++- .../query-suggest/query-suggest-actions.ts | 34 ++++- .../recommendations-actions.ts | 27 +++- .../search-actions-thunk-processor.test.ts | 6 +- .../search/search-actions-thunk-processor.ts | 10 +- .../commerce/search/search-actions.ts | 12 +- .../features/commerce/sort/sort-slice.test.ts | 42 +++--- .../src/features/commerce/sort/sort-slice.ts | 5 +- .../standalone-search-box-set-actions.ts | 13 +- .../src/pages/CommerceSearchPage.tsx | 1 - 31 files changed, 340 insertions(+), 224 deletions(-) diff --git a/packages/headless/src/app/commerce-engine/commerce-engine-configuration.ts b/packages/headless/src/app/commerce-engine/commerce-engine-configuration.ts index 0ae184dc64c..b1449edbde3 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine-configuration.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine-configuration.ts @@ -57,7 +57,6 @@ export function getSampleCommerceEngineConfiguration(): CommerceEngineConfigurat currency: 'USD', view: { url: 'https://sports-dev.barca.group/browse/promotions/skis-boards/surfboards', - referrer: document.referrer, }, }, cart: { diff --git a/packages/headless/src/app/commerce-engine/commerce-engine.test.ts b/packages/headless/src/app/commerce-engine/commerce-engine.test.ts index 33b4ad9c137..57a94da8fb5 100644 --- a/packages/headless/src/app/commerce-engine/commerce-engine.test.ts +++ b/packages/headless/src/app/commerce-engine/commerce-engine.test.ts @@ -17,10 +17,6 @@ describe('buildCommerceEngine', () => { } beforeEach(() => { - Object.defineProperty(global, 'document', { - value: {referrer: 'referrer'}, - configurable: true, - }); options = { configuration: getSampleCommerceEngineConfiguration(), loggerOptions: {level: 'silent'}, diff --git a/packages/headless/src/commerce.index.ts b/packages/headless/src/commerce.index.ts index 6bed97c6712..eed1ece8b0e 100644 --- a/packages/headless/src/commerce.index.ts +++ b/packages/headless/src/commerce.index.ts @@ -57,7 +57,6 @@ export {buildController} from './controllers/controller/headless-controller'; export type { ContextOptions, - User, View, ContextProps, Context, diff --git a/packages/headless/src/controllers/commerce/context/headless-context.test.ts b/packages/headless/src/controllers/commerce/context/headless-context.test.ts index ff9934a88fc..ce1b2321e8d 100644 --- a/packages/headless/src/controllers/commerce/context/headless-context.test.ts +++ b/packages/headless/src/controllers/commerce/context/headless-context.test.ts @@ -1,6 +1,5 @@ import { setContext, - setUser, setView, } from '../../../features/commerce/context/context-actions'; import {contextReducer} from '../../../features/commerce/context/context-slice'; @@ -77,19 +76,9 @@ describe('headless commerce context', () => { ); }); - it('setUser dispatches #setUser', () => { - context.setUser({ - userAgent: 'some-user-agent', - }); - expect(setUser).toHaveBeenCalledWith( - expect.objectContaining({userAgent: 'some-user-agent'}) - ); - }); - it('setView dispatches #setView', () => { context.setView({ url: 'https://example.org', - referrer: 'https://example.org/referrer', }); expect(setView).toHaveBeenCalled(); }); diff --git a/packages/headless/src/controllers/commerce/context/headless-context.ts b/packages/headless/src/controllers/commerce/context/headless-context.ts index 06e30e2f7d1..54fa11520f0 100644 --- a/packages/headless/src/controllers/commerce/context/headless-context.ts +++ b/packages/headless/src/controllers/commerce/context/headless-context.ts @@ -3,7 +3,6 @@ import {CommerceEngine} from '../../../app/commerce-engine/commerce-engine'; import {stateKey} from '../../../app/state-key'; import { setContext, - setUser, setView, } from '../../../features/commerce/context/context-actions'; import {contextReducer as commerceContext} from '../../../features/commerce/context/context-slice'; @@ -19,17 +18,11 @@ export interface ContextOptions { language: string; country: string; currency: CurrencyCodeISO4217; - user?: User; view: View; } -export type User = { - userAgent?: string; -}; - export interface View { url: string; - referrer?: string; } export interface ContextProps { @@ -61,12 +54,6 @@ export interface Context extends Controller { */ setCurrency(currency: CurrencyCodeISO4217): void; - /** - * Sets the user. - * @param user - The new user. - */ - setUser(user: User): void; - /** * Sets the view. * @param view - The new view. @@ -83,7 +70,6 @@ export interface ContextState { language: string; country: string; currency: CurrencyCodeISO4217; - user?: User; view: View; } @@ -142,8 +128,6 @@ export function buildContext( }) ), - setUser: (user: User) => dispatch(setUser(user)), - setView: (view: View) => dispatch(setView(view)), }; } diff --git a/packages/headless/src/features/commerce/common/actions.test.ts b/packages/headless/src/features/commerce/common/actions.test.ts index fd5ac913557..c0e1d53ca1a 100644 --- a/packages/headless/src/features/commerce/common/actions.test.ts +++ b/packages/headless/src/features/commerce/common/actions.test.ts @@ -1,13 +1,15 @@ -import {Relay, createRelay} from '@coveo/relay'; +import {Meta, Relay, createRelay} from '@coveo/relay'; import {CurrencyCodeISO4217} from '@coveo/relay-event-types'; import { BaseCommerceAPIRequest, CommerceAPIRequest, } from '../../../api/commerce/common/request'; +import {NavigatorContext} from '../../../app/navigatorContextProvider'; import {buildMockCommerceFacetRequest} from '../../../test/mock-commerce-facet-request'; import {buildMockCommerceFacetSlice} from '../../../test/mock-commerce-facet-slice'; import {buildMockCommerceRegularFacetValue} from '../../../test/mock-commerce-facet-value'; import {buildMockCommerceState} from '../../../test/mock-commerce-state'; +import {buildMockNavigatorContextProvider} from '../../../test/mock-navigator-context-provider'; import {VERSION} from '../../../utils/version'; import {CommerceFacetSlice} from '../facets/facet-set/facet-set-state'; import { @@ -18,15 +20,40 @@ import {SortBy, SortCriterion, SortDirection} from '../sort/sort'; import {getCommerceSortInitialState} from '../sort/sort-state'; import * as Actions from './actions'; +jest.mock('@coveo/relay'); + describe('commerce common actions', () => { let relay: Relay; + let navigatorContext: NavigatorContext; + beforeEach(() => { - relay = createRelay({ + const mockedCreateRelay = jest + .mocked(createRelay) + .mockImplementation(() => ({ + emit: jest.fn(), + on: jest.fn(), + off: jest.fn(), + clearStorage: jest.fn(), + getMeta: jest.fn( + () => + ({ + clientId: 'client_id', + }) as Meta + ), + updateConfig: jest.fn(), + version: 'test', + })); + relay = mockedCreateRelay({ token: 'token', trackingId: 'trackingId', url: 'url', }); + navigatorContext = buildMockNavigatorContextProvider({ + userAgent: 'user_agent', + referrer: 'referrer', + })(); }); + describe('#buildBaseCommerceAPIRequest', () => { let expected: BaseCommerceAPIRequest; let state: Actions.StateNeededByQueryCommerceAPI; @@ -47,14 +74,14 @@ describe('commerce common actions', () => { language: 'en', country: 'CA', currency: 'CAD', - clientId: expect.any(String), + clientId: 'client_id', context: { user: { userAgent: 'user_agent', }, view: { url: 'https://example.com', - referrer: 'https://referrer.com', + referrer: 'referrer', }, capture: true, cart: [ @@ -79,7 +106,6 @@ describe('commerce common actions', () => { state.commerceContext.language = expected.language; state.commerceContext.country = expected.country; state.commerceContext.currency = expected.currency as CurrencyCodeISO4217; - state.commerceContext.user = expected.context.user; state.commerceContext.view = expected.context.view; state.cart.cartItems = [product.productId]; state.cart.cart = { @@ -92,7 +118,11 @@ describe('commerce common actions', () => { it('given a state with no commercePagination section, returns the expected base request', () => { delete state.commercePagination; - const request = Actions.buildBaseCommerceAPIRequest(state, relay); + const request = Actions.buildBaseCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(request).toEqual({...expected}); }); @@ -113,7 +143,11 @@ describe('commerce common actions', () => { perPage: state.commercePagination.principal.perPage, }; - const request = Actions.buildBaseCommerceAPIRequest(state, relay); + const request = Actions.buildBaseCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(request).toEqual(expectedWithPagination); }); @@ -137,7 +171,12 @@ describe('commerce common actions', () => { perPage: state.commercePagination.recommendations[slotId]!.perPage, }; - const request = Actions.buildBaseCommerceAPIRequest(state, relay, slotId); + const request = Actions.buildBaseCommerceAPIRequest( + state, + relay, + navigatorContext, + slotId + ); expect(request).toEqual(expectedWithPagination); }); @@ -160,11 +199,16 @@ describe('commerce common actions', () => { delete state.facetOrder; delete state.commerceFacetSet; - const request = Actions.buildCommerceAPIRequest(state, relay); + const request = Actions.buildCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(mockedBuildBaseCommerceAPIRequest).toHaveBeenCalledWith( state, - relay + relay, + navigatorContext ); expect(request).toEqual({ @@ -179,11 +223,16 @@ describe('commerce common actions', () => { state.facetOrder = ['facet_id']; - const request = Actions.buildCommerceAPIRequest(state, relay); + const request = Actions.buildCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(mockedBuildBaseCommerceAPIRequest).toHaveBeenCalledWith( state, - relay + relay, + navigatorContext ); expect(request).toEqual({ @@ -200,11 +249,16 @@ describe('commerce common actions', () => { facet_id: buildMockCommerceFacetSlice(), }; - const request = Actions.buildCommerceAPIRequest(state, relay); + const request = Actions.buildCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(mockedBuildBaseCommerceAPIRequest).toHaveBeenCalledWith( state, - relay + relay, + navigatorContext ); expect(request).toEqual({ @@ -218,11 +272,16 @@ describe('commerce common actions', () => { (analyticsEnabled) => { state.configuration.analytics.enabled = analyticsEnabled; - const request = Actions.buildCommerceAPIRequest(state, relay); + const request = Actions.buildCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(mockedBuildBaseCommerceAPIRequest).toHaveBeenCalledWith( state, - relay + relay, + navigatorContext ); expect(request.context.capture).toEqual(analyticsEnabled); @@ -262,11 +321,16 @@ describe('commerce common actions', () => { }; }); it('includes all non-empty facets in the #facets array of the returned request', () => { - const request = Actions.buildCommerceAPIRequest(state, relay); + const request = Actions.buildCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(mockedBuildBaseCommerceAPIRequest).toHaveBeenCalledWith( state, - relay + relay, + navigatorContext ); expect(request).toEqual({ @@ -289,11 +353,16 @@ describe('commerce common actions', () => { state.commerceFacetSet![facet3.request.facetId] = facet3; state.facetOrder.push(facet3.request.facetId); - const request = Actions.buildCommerceAPIRequest(state, relay); + const request = Actions.buildCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(mockedBuildBaseCommerceAPIRequest).toHaveBeenCalledWith( state, - relay + relay, + navigatorContext ); expect(request).toEqual({ @@ -317,11 +386,16 @@ describe('commerce common actions', () => { }, }; - const request = Actions.buildCommerceAPIRequest(state, relay); + const request = Actions.buildCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(mockedBuildBaseCommerceAPIRequest).toHaveBeenCalledWith( state, - relay + relay, + navigatorContext ); const expectedWithSort: CommerceAPIRequest = { @@ -351,11 +425,16 @@ describe('commerce common actions', () => { appliedSort: sortCriterion, }; - const request = Actions.buildCommerceAPIRequest(state, relay); + const request = Actions.buildCommerceAPIRequest( + state, + relay, + navigatorContext + ); expect(mockedBuildBaseCommerceAPIRequest).toHaveBeenCalledWith( state, - relay + relay, + navigatorContext ); const expectedWithSort: CommerceAPIRequest = { diff --git a/packages/headless/src/features/commerce/common/actions.ts b/packages/headless/src/features/commerce/common/actions.ts index af682098308..718b299fa6e 100644 --- a/packages/headless/src/features/commerce/common/actions.ts +++ b/packages/headless/src/features/commerce/common/actions.ts @@ -6,6 +6,7 @@ import { CommerceAPIRequest, } from '../../../api/commerce/common/request'; import {CommerceSuccessResponse} from '../../../api/commerce/common/response'; +import {NavigatorContext} from '../../../app/navigatorContextProvider'; import { CartSection, CommerceContextSection, @@ -35,10 +36,11 @@ export interface QueryCommerceAPIThunkReturn { export const buildCommerceAPIRequest = ( state: ListingAndSearchStateNeededByQueryCommerceAPI, - relay: Relay + relay: Relay, + navigatorContext: NavigatorContext ): CommerceAPIRequest => { return { - ...buildBaseCommerceAPIRequest(state, relay), + ...buildBaseCommerceAPIRequest(state, relay, navigatorContext), facets: getFacets(state), ...(state.commerceSort && { sort: getSort(state.commerceSort.appliedSort), @@ -49,9 +51,10 @@ export const buildCommerceAPIRequest = ( export const buildBaseCommerceAPIRequest = ( state: StateNeededByQueryCommerceAPI, relay: Relay, + navigatorContext: NavigatorContext, slotId?: string ): BaseCommerceAPIRequest => { - const {view, user, ...restOfContext} = state.commerceContext; + const {view, ...restOfContext} = state.commerceContext; return { accessToken: state.configuration.accessToken, url: state.configuration.platformUrl, @@ -60,8 +63,19 @@ export const buildBaseCommerceAPIRequest = ( ...restOfContext, clientId: relay.getMeta('').clientId, context: { - user, - view, + ...(navigatorContext.userAgent + ? { + user: { + userAgent: navigatorContext.userAgent, + }, + } + : {}), + view: { + ...view, + ...(navigatorContext.referrer + ? {referrer: navigatorContext.referrer} + : {}), + }, capture: state.configuration.analytics.enabled, cart: getProductsFromCartState(state.cart), source: getAnalyticsSource(state.configuration.analytics), diff --git a/packages/headless/src/features/commerce/context/context-actions.ts b/packages/headless/src/features/commerce/context/context-actions.ts index be4622b0158..214e6e6c9f0 100644 --- a/packages/headless/src/features/commerce/context/context-actions.ts +++ b/packages/headless/src/features/commerce/context/context-actions.ts @@ -1,22 +1,14 @@ import {CurrencyCodeISO4217} from '@coveo/relay-event-types'; import {createAction} from '@reduxjs/toolkit'; -import { - UserParams, - ViewParams, -} from '../../../api/commerce/commerce-api-params'; +import {ViewParams} from '../../../api/commerce/commerce-api-params'; import {validatePayload} from '../../../utils/validate-payload'; -import { - contextDefinition, - userDefinition, - viewDefinition, -} from './context-validation'; +import {contextDefinition, viewDefinition} from './context-validation'; export interface SetContextPayload { language: string; country: string; currency: CurrencyCodeISO4217; - user?: UserParams; - view: ViewParams; + view: SetViewPayload; } export const setContext = createAction( @@ -24,14 +16,7 @@ export const setContext = createAction( (payload: SetContextPayload) => validatePayload(payload, contextDefinition) ); -export type SetUserPayload = UserParams; - -export const setUser = createAction( - 'commerce/setUser', - (payload: SetUserPayload) => validatePayload(payload, userDefinition) -); - -export type SetViewPayload = ViewParams; +export type SetViewPayload = Pick; export const setView = createAction( 'commerce/setView', diff --git a/packages/headless/src/features/commerce/context/context-slice.test.ts b/packages/headless/src/features/commerce/context/context-slice.test.ts index 45cb9f41508..2134afd1449 100644 --- a/packages/headless/src/features/commerce/context/context-slice.test.ts +++ b/packages/headless/src/features/commerce/context/context-slice.test.ts @@ -1,4 +1,4 @@ -import {setContext, setUser, setView} from './context-actions'; +import {setContext, setView} from './context-actions'; import {contextReducer} from './context-slice'; import {CommerceContextState, getContextInitialState} from './context-state'; @@ -37,15 +37,6 @@ describe('context-slice', () => { }); }); - describe('#setUser', () => { - it('should allow to set the user', () => { - const user = { - userAgent: 'some-user-agent', - }; - expect(contextReducer(state, setUser(user)).user).toEqual(user); - }); - }); - it('should allow to set the view', () => { const view = { url: 'https://example.org', diff --git a/packages/headless/src/features/commerce/context/context-slice.ts b/packages/headless/src/features/commerce/context/context-slice.ts index 8c0eb708c24..72ee1557112 100644 --- a/packages/headless/src/features/commerce/context/context-slice.ts +++ b/packages/headless/src/features/commerce/context/context-slice.ts @@ -1,5 +1,5 @@ import {createReducer} from '@reduxjs/toolkit'; -import {setContext, setUser, setView} from './context-actions'; +import {setContext, setView} from './context-actions'; import {getContextInitialState} from './context-state'; export const contextReducer = createReducer( @@ -10,9 +10,6 @@ export const contextReducer = createReducer( .addCase(setContext, (_, {payload}) => { return payload; }) - .addCase(setUser, (state, {payload}) => { - state.user = payload; - }) .addCase(setView, (state, {payload}) => { state.view = payload; }); diff --git a/packages/headless/src/features/commerce/context/context-state.ts b/packages/headless/src/features/commerce/context/context-state.ts index 492afd53834..2ad8998ee56 100644 --- a/packages/headless/src/features/commerce/context/context-state.ts +++ b/packages/headless/src/features/commerce/context/context-state.ts @@ -1,14 +1,10 @@ import {CurrencyCodeISO4217} from '@coveo/relay-event-types'; -import { - UserParams, - ViewParams, -} from '../../../api/commerce/commerce-api-params'; +import {ViewParams} from '../../../api/commerce/commerce-api-params'; export interface CommerceContextState { language: string; country: string; currency: CurrencyCodeISO4217; - user?: UserParams; view: ViewParams; } diff --git a/packages/headless/src/features/commerce/context/context-validation.ts b/packages/headless/src/features/commerce/context/context-validation.ts index d42c9b93b6b..5942d6a7563 100644 --- a/packages/headless/src/features/commerce/context/context-validation.ts +++ b/packages/headless/src/features/commerce/context/context-validation.ts @@ -1,10 +1,6 @@ import {RecordValue, Schema, StringValue} from '@coveo/bueno'; import {CurrencyCodeISO4217} from '@coveo/relay-event-types'; -import { - nonEmptyString, - nonRequiredEmptyAllowedString, - requiredNonEmptyString, -} from '../../../utils/validate-payload'; +import {requiredNonEmptyString} from '../../../utils/validate-payload'; const currencies = Intl.supportedValuesOf('currency') as CurrencyCodeISO4217[]; @@ -16,20 +12,12 @@ const currencyDefinition = new StringValue({ export const viewDefinition = { url: requiredNonEmptyString, - referrer: nonRequiredEmptyAllowedString, -}; - -export const userDefinition = { - userAgent: nonEmptyString, }; export const contextDefinition = { language: requiredNonEmptyString, country: requiredNonEmptyString, currency: currencyDefinition, - user: new RecordValue({ - values: userDefinition, - }), view: new RecordValue({ options: {required: true}, values: viewDefinition, diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts index 6e209fc5b41..4109448bcdb 100644 --- a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts +++ b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.test.ts @@ -1,4 +1,5 @@ import {Relay, createRelay} from '@coveo/relay'; +import {NavigatorContext} from '../../../../../app/navigatorContextProvider'; import * as Actions from '../../../../../features/commerce/common/actions'; import {CommerceAppState} from '../../../../../state/commerce-app-state'; import {buildMockCategoryFacetSearch} from '../../../../../test/mock-category-facet-search'; @@ -7,12 +8,14 @@ import {buildMockCommerceFacetSlice} from '../../../../../test/mock-commerce-fac import {buildMockCategoryFacetValue} from '../../../../../test/mock-commerce-facet-value'; import {buildMockCommerceState} from '../../../../../test/mock-commerce-state'; import {buildMockFacetSearchRequestOptions} from '../../../../../test/mock-facet-search-request-options'; +import {buildMockNavigatorContextProvider} from '../../../../../test/mock-navigator-context-provider'; import {CategoryFacetValueRequest} from '../../facet-set/interfaces/request'; import {buildCategoryFacetSearchRequest} from './commerce-category-facet-search-request-builder'; describe('#buildCategoryFacetSearchRequest', () => { let state: CommerceAppState; let relay: Relay; + let navigatorContext: NavigatorContext; let facetId: string; let query: string; let buildCommerceAPIRequestMock: jest.SpyInstance; @@ -37,25 +40,28 @@ describe('#buildCategoryFacetSearchRequest', () => { ); relay = createRelay({token: 'token', url: 'url', trackingId: 'trackingId'}); + navigatorContext = buildMockNavigatorContextProvider()(); }); - it('returned object has a #facetId property whose value is the passed facet ID argument', async () => { - const request = await buildCategoryFacetSearchRequest( + it('returned object has a #facetId property whose value is the passed facet ID argument', () => { + const request = buildCategoryFacetSearchRequest( facetId, state, false, - relay + relay, + navigatorContext ); expect(request.facetId).toBe(facetId); }); - it('returned object has a #facetQuery property whose value is the facet query from state between wildcard characters', async () => { - const request = await buildCategoryFacetSearchRequest( + it('returned object has a #facetQuery property whose value is the facet query from state between wildcard characters', () => { + const request = buildCategoryFacetSearchRequest( facetId, state, false, - relay + relay, + navigatorContext ); expect(request.facetQuery).toBe( @@ -64,25 +70,27 @@ describe('#buildCategoryFacetSearchRequest', () => { }); describe('returned object #ignorePaths property', () => { - it('when the facet request has no selected value, is an empty array', async () => { - const request = await buildCategoryFacetSearchRequest( + it('when the facet request has no selected value, is an empty array', () => { + const request = buildCategoryFacetSearchRequest( facetId, state, false, - relay + relay, + navigatorContext ); expect(request.ignorePaths).toStrictEqual([]); }); - it('when the facet request has a selected value with no ancestry, is an array with a single array containing the selected value', async () => { + it('when the facet request has a selected value with no ancestry, is an array with a single array containing the selected value', () => { state.commerceFacetSet[facetId].request.values[0] = buildMockCategoryFacetValue({state: 'selected', value: 'test'}); - const request = await buildCategoryFacetSearchRequest( + const request = buildCategoryFacetSearchRequest( facetId, state, false, - relay + relay, + navigatorContext ); expect(request.ignorePaths).toStrictEqual([ @@ -95,7 +103,7 @@ describe('#buildCategoryFacetSearchRequest', () => { ]); }); - it('when the facet request has a selected value with ancestry, is an array with a single array containing the selected value and its ancestors', async () => { + it('when the facet request has a selected value with ancestry, is an array with a single array containing the selected value and its ancestors', () => { state.commerceFacetSet[facetId].request.values[0] = buildMockCategoryFacetValue({ value: 'test', @@ -111,11 +119,12 @@ describe('#buildCategoryFacetSearchRequest', () => { }), ], }); - const request = await buildCategoryFacetSearchRequest( + const request = buildCategoryFacetSearchRequest( facetId, state, false, - relay + relay, + navigatorContext ); expect(request.ignorePaths).toStrictEqual([ @@ -137,21 +146,22 @@ describe('#buildCategoryFacetSearchRequest', () => { }); }); - it('when not building a field suggestion request, returned request includes all properties returned by #buildCommerceAPIRequest, plus the #query property', async () => { + it('when not building a field suggestion request, returned request includes all properties returned by #buildCommerceAPIRequest, plus the #query property', () => { const buildCommerceAPIRequestMock = jest.spyOn( Actions, 'buildCommerceAPIRequest' ); - const request = await buildCategoryFacetSearchRequest( + const request = buildCategoryFacetSearchRequest( facetId, state, false, - relay + relay, + navigatorContext ); expect(request).toEqual({ - ...(await buildCommerceAPIRequestMock.mock.results[0].value), + ...buildCommerceAPIRequestMock.mock.results[0].value, facetId, facetQuery: `*${query}*`, ignorePaths: [], @@ -159,16 +169,17 @@ describe('#buildCategoryFacetSearchRequest', () => { }); }); - it('when building a field suggestion request, returned request includes all properties returned by #buildCommerceAPIRequest except the #facets, #page, and #sort properties', async () => { - const request = await buildCategoryFacetSearchRequest( + it('when building a field suggestion request, returned request includes all properties returned by #buildCommerceAPIRequest except the #facets, #page, and #sort properties', () => { + const request = buildCategoryFacetSearchRequest( facetId, state, true, - relay + relay, + navigatorContext ); const {facets, page, sort, ...expectedBaseRequest} = - await buildCommerceAPIRequestMock.mock.results[0].value; + buildCommerceAPIRequestMock.mock.results[0].value; expect(request).toEqual({ ...expectedBaseRequest, diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts index dbb6a5874cf..5a2a74cca79 100644 --- a/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts +++ b/packages/headless/src/features/commerce/facets/facet-search-set/category/commerce-category-facet-search-request-builder.ts @@ -1,5 +1,6 @@ import {Relay} from '@coveo/relay'; import {CategoryFacetSearchRequest} from '../../../../../api/commerce/facet-search/facet-search-request'; +import {NavigatorContext} from '../../../../../app/navigatorContextProvider'; import {buildCommerceAPIRequest} from '../../../common/actions'; import { AnyFacetRequest, @@ -7,12 +8,13 @@ import { } from '../../facet-set/interfaces/request'; import {StateNeededForCategoryFacetSearch} from './commerce-category-facet-search-state'; -export const buildCategoryFacetSearchRequest = async ( +export const buildCategoryFacetSearchRequest = ( facetId: string, state: StateNeededForCategoryFacetSearch, isFieldSuggestionsRequest: boolean, - relay: Relay -): Promise => { + relay: Relay, + navigatorContext: NavigatorContext +): CategoryFacetSearchRequest => { const baseFacetQuery = state.categoryFacetSearchSet[facetId]!.options.query; const facetQuery = `*${baseFacetQuery}*`; const categoryFacet = state.commerceFacetSet[facetId]?.request; @@ -34,7 +36,7 @@ export const buildCategoryFacetSearchRequest = async ( clientId, context, ...restOfCommerceAPIRequest - } = buildCommerceAPIRequest(state, relay); + } = buildCommerceAPIRequest(state, relay, navigatorContext); return { url, diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts b/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts index bdb2094dfac..fc1ed45ab05 100644 --- a/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts +++ b/packages/headless/src/features/commerce/facets/facet-search-set/commerce-facet-search-actions.ts @@ -38,7 +38,7 @@ const getExecuteFacetSearchThunkPayloadCreator = > => async ( facetId: string, - {getState, extra: {validatePayload, relay, apiClient}} + {getState, extra: {validatePayload, relay, navigatorContext, apiClient}} ) => { const state = getState(); validatePayload(facetId, requiredNonEmptyString); @@ -49,13 +49,15 @@ const getExecuteFacetSearchThunkPayloadCreator = facetId, state, isFieldSuggestionsRequest, - relay + relay, + navigatorContext ) : buildCategoryFacetSearchRequest( facetId, state, isFieldSuggestionsRequest, - relay + relay, + navigatorContext ); const response = await apiClient.facetSearch(await req); diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.test.ts b/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.test.ts index e171d691f29..fc3c2e496e8 100644 --- a/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.test.ts +++ b/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.test.ts @@ -1,14 +1,17 @@ import {Relay, createRelay} from '@coveo/relay'; +import {NavigatorContext} from '../../../../../app/navigatorContextProvider'; import * as Actions from '../../../../../features/commerce/common/actions'; import {CommerceAppState} from '../../../../../state/commerce-app-state'; import {buildMockCommerceState} from '../../../../../test/mock-commerce-state'; import {buildMockFacetSearch} from '../../../../../test/mock-facet-search'; import {buildMockFacetSearchRequestOptions} from '../../../../../test/mock-facet-search-request-options'; +import {buildMockNavigatorContextProvider} from '../../../../../test/mock-navigator-context-provider'; import {buildFacetSearchRequest} from './commerce-regular-facet-search-request-builder'; describe('#buildFacetSearchRequest', () => { let state: CommerceAppState; let relay: Relay; + let navigatorContext: NavigatorContext; let facetId: string; let query: string; let buildCommerceAPIRequestMock: jest.SpyInstance; @@ -29,43 +32,68 @@ describe('#buildFacetSearchRequest', () => { ); relay = createRelay({token: 'token', url: 'url', trackingId: 'trackingId'}); + navigatorContext = buildMockNavigatorContextProvider()(); }); - it('returned object has a #facetId property whose value is the passed facet ID argument', async () => { - const request = await buildFacetSearchRequest(facetId, state, false, relay); + it('returned object has a #facetId property whose value is the passed facet ID argument', () => { + const request = buildFacetSearchRequest( + facetId, + state, + false, + relay, + navigatorContext + ); expect(request.facetId).toBe(facetId); }); - it('returned object has a #facetQuery property whose value is the facet query from state between wildcard characters', async () => { - const request = await buildFacetSearchRequest(facetId, state, false, relay); + it('returned object has a #facetQuery property whose value is the facet query from state between wildcard characters', () => { + const request = buildFacetSearchRequest( + facetId, + state, + false, + relay, + navigatorContext + ); expect(request.facetQuery).toBe( `*${state.facetSearchSet[facetId].options.query}*` ); }); - it('when not building a field suggestion request, returned request includes all properties returned by #buildCommerceAPIRequest, plus the #query property', async () => { + it('when not building a field suggestion request, returned request includes all properties returned by #buildCommerceAPIRequest, plus the #query property', () => { const buildCommerceAPIRequestMock = jest.spyOn( Actions, 'buildCommerceAPIRequest' ); - const request = await buildFacetSearchRequest(facetId, state, false, relay); + const request = buildFacetSearchRequest( + facetId, + state, + false, + relay, + navigatorContext + ); expect(request).toEqual({ - ...(await buildCommerceAPIRequestMock.mock.results[0].value), + ...buildCommerceAPIRequestMock.mock.results[0].value, facetId, facetQuery: `*${query}*`, query: state.commerceQuery?.query, }); }); - it('when building a field suggestion request, returned request includes all properties returned by #buildCommerceAPIRequest except the #facets, #page, and #sort properties', async () => { - const request = await buildFacetSearchRequest(facetId, state, true, relay); + it('when building a field suggestion request, returned request includes all properties returned by #buildCommerceAPIRequest except the #facets, #page, and #sort properties', () => { + const request = buildFacetSearchRequest( + facetId, + state, + true, + relay, + navigatorContext + ); const {facets, page, sort, ...expectedBaseRequest} = - await buildCommerceAPIRequestMock.mock.results[0].value; + buildCommerceAPIRequestMock.mock.results[0].value; expect(request).toEqual({ ...expectedBaseRequest, diff --git a/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts b/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts index ec136cab283..a40f390eee6 100644 --- a/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts +++ b/packages/headless/src/features/commerce/facets/facet-search-set/regular/commerce-regular-facet-search-request-builder.ts @@ -1,14 +1,16 @@ import {Relay} from '@coveo/relay'; import {CommerceFacetSearchRequest} from '../../../../../api/commerce/facet-search/facet-search-request'; +import {NavigatorContext} from '../../../../../app/navigatorContextProvider'; import {buildCommerceAPIRequest} from '../../../common/actions'; import {StateNeededForRegularFacetSearch} from './commerce-regular-facet-search-state'; -export const buildFacetSearchRequest = async ( +export const buildFacetSearchRequest = ( facetId: string, state: StateNeededForRegularFacetSearch, isFieldSuggestionsRequest: boolean, - relay: Relay -): Promise => { + relay: Relay, + navigatorContext: NavigatorContext +): CommerceFacetSearchRequest => { const baseFacetQuery = state.facetSearchSet[facetId]!.options.query; const facetQuery = `*${baseFacetQuery}*`; let query = state.commerceQuery?.query; @@ -30,7 +32,7 @@ export const buildFacetSearchRequest = async ( clientId, context, ...restOfCommerceAPIRequest - } = buildCommerceAPIRequest(state, relay); + } = buildCommerceAPIRequest(state, relay, navigatorContext); return { url, diff --git a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts index 85f9dc7db5d..e5b1cafe4c9 100644 --- a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts +++ b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.test.ts @@ -62,7 +62,7 @@ import { updateNumericFacetValues, } from '../../../facets/range-facets/numeric-facet-set/numeric-facet-actions'; import {convertToNumericRangeRequests} from '../../../facets/range-facets/numeric-facet-set/numeric-facet-set-slice'; -import {setContext, setUser, setView} from '../../context/context-actions'; +import {setContext, setView} from '../../context/context-actions'; import {restoreProductListingParameters} from '../../product-listing-parameters/product-listing-parameters-actions'; import {fetchProductListing} from '../../product-listing/product-listing-actions'; import {restoreSearchParameters} from '../../search-parameters/search-parameters-actions'; @@ -2466,10 +2466,6 @@ describe('commerceFacetSetReducer', () => { actionName: '#setView', action: setView, }, - { - actionName: '#setUser', - action: setUser, - }, ])('$actionName', ({action}: {action: ActionCreator}) => { it('clears all facets values', () => { const facetIds = ['1', '2']; diff --git a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts index c4eb597cf45..5b1268f923f 100644 --- a/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts +++ b/packages/headless/src/features/commerce/facets/facet-set/facet-set-slice.ts @@ -40,7 +40,7 @@ import { updateNumericFacetValues, } from '../../../facets/range-facets/numeric-facet-set/numeric-facet-actions'; import {convertToNumericRangeRequests} from '../../../facets/range-facets/numeric-facet-set/numeric-facet-set-slice'; -import {setContext, setUser, setView} from '../../context/context-actions'; +import {setContext, setView} from '../../context/context-actions'; import {restoreProductListingParameters} from '../../product-listing-parameters/product-listing-parameters-actions'; import {fetchProductListing} from '../../product-listing/product-listing-actions'; import {restoreSearchParameters} from '../../search-parameters/search-parameters-actions'; @@ -411,7 +411,6 @@ export const commerceFacetSetReducer = createReducer( .addCase(deselectAllBreadcrumbs, setAllFacetValuesToIdle) .addCase(setContext, clearAllFacetValues) .addCase(setView, clearAllFacetValues) - .addCase(setUser, clearAllFacetValues) .addCase(restoreSearchParameters, restoreFromParameters) .addCase(restoreProductListingParameters, restoreFromParameters); } diff --git a/packages/headless/src/features/commerce/pagination/pagination-slice.test.ts b/packages/headless/src/features/commerce/pagination/pagination-slice.test.ts index 4171f358202..33d1ef07b64 100644 --- a/packages/headless/src/features/commerce/pagination/pagination-slice.test.ts +++ b/packages/headless/src/features/commerce/pagination/pagination-slice.test.ts @@ -11,7 +11,7 @@ import { toggleExcludeNumericFacetValue, toggleSelectNumericFacetValue, } from '../../facets/range-facets/numeric-facet-set/numeric-facet-actions'; -import {setContext, setUser, setView} from '../context/context-actions'; +import {setContext, setView} from '../context/context-actions'; import {restoreProductListingParameters} from '../product-listing-parameters/product-listing-parameters-actions'; import {fetchProductListing} from '../product-listing/product-listing-actions'; import {fetchRecommendations} from '../recommendations/recommendations-actions'; @@ -276,10 +276,6 @@ describe('pagination slice', () => { actionName: '#setView', action: setView, }, - { - actionName: '#setUser', - action: setUser, - }, ])('$actionName', ({action}) => { it('resets principal pagination', () => { state.principal.page = 5; diff --git a/packages/headless/src/features/commerce/pagination/pagination-slice.ts b/packages/headless/src/features/commerce/pagination/pagination-slice.ts index b22152aa4f3..30c711c336c 100644 --- a/packages/headless/src/features/commerce/pagination/pagination-slice.ts +++ b/packages/headless/src/features/commerce/pagination/pagination-slice.ts @@ -9,7 +9,7 @@ import { toggleExcludeNumericFacetValue, toggleSelectNumericFacetValue, } from '../../facets/range-facets/numeric-facet-set/numeric-facet-actions'; -import {setContext, setUser, setView} from '../context/context-actions'; +import {setContext, setView} from '../context/context-actions'; import {Parameters} from '../parameters/parameters-actions'; import {restoreProductListingParameters} from '../product-listing-parameters/product-listing-parameters-actions'; import {fetchProductListing} from '../product-listing/product-listing-actions'; @@ -106,8 +106,7 @@ export const paginationReducer = createReducer( .addCase(toggleSelectNumericFacetValue, handlePaginationReset) .addCase(toggleExcludeNumericFacetValue, handlePaginationReset) .addCase(setContext, handlePaginationReset) - .addCase(setView, handlePaginationReset) - .addCase(setUser, handlePaginationReset); + .addCase(setView, handlePaginationReset); } ); diff --git a/packages/headless/src/features/commerce/product-listing/product-listing-actions.ts b/packages/headless/src/features/commerce/product-listing/product-listing-actions.ts index 45ce5ce5144..b23bc034b4e 100644 --- a/packages/headless/src/features/commerce/product-listing/product-listing-actions.ts +++ b/packages/headless/src/features/commerce/product-listing/product-listing-actions.ts @@ -29,11 +29,16 @@ export const fetchProductListing = createAsyncThunk< 'commerce/productListing/fetch', async ( _action, - {getState, dispatch, rejectWithValue, extra: {apiClient, relay}} + { + getState, + dispatch, + rejectWithValue, + extra: {apiClient, relay, navigatorContext}, + } ) => { const state = getState(); const fetched = await apiClient.getProductListing( - buildCommerceAPIRequest(state, relay) + buildCommerceAPIRequest(state, relay, navigatorContext) ); if (isErrorResponse(fetched)) { @@ -55,7 +60,12 @@ export const fetchMoreProducts = createAsyncThunk< 'commerce/productListing/fetchMoreProducts', async ( _action, - {getState, dispatch, rejectWithValue, extra: {apiClient, relay}} + { + getState, + dispatch, + rejectWithValue, + extra: {apiClient, relay, navigatorContext}, + } ) => { const state = getState(); const moreProductsAvailable = moreProductsAvailableSelector(state); @@ -67,7 +77,7 @@ export const fetchMoreProducts = createAsyncThunk< const nextPageToRequest = numberOfProducts / perPage; const fetched = await apiClient.getProductListing({ - ...buildCommerceAPIRequest(state, relay), + ...buildCommerceAPIRequest(state, relay, navigatorContext), page: nextPageToRequest, }); diff --git a/packages/headless/src/features/commerce/query-suggest/query-suggest-actions.ts b/packages/headless/src/features/commerce/query-suggest/query-suggest-actions.ts index 900f6789c13..58066c0a8a5 100644 --- a/packages/headless/src/features/commerce/query-suggest/query-suggest-actions.ts +++ b/packages/headless/src/features/commerce/query-suggest/query-suggest-actions.ts @@ -7,6 +7,7 @@ import { } from '../../../api/commerce/commerce-api-client'; import {QuerySuggestRequest} from '../../../api/commerce/search/query-suggest/query-suggest-request'; import {QuerySuggestSuccessResponse} from '../../../api/commerce/search/query-suggest/query-suggest-response'; +import {NavigatorContext} from '../../../app/navigatorContextProvider'; import { CartSection, CommerceContextSection, @@ -45,11 +46,20 @@ export const fetchQuerySuggestions = createAsyncThunk< 'commerce/querySuggest/fetch', async ( payload: {id: string}, - {getState, rejectWithValue, extra: {apiClient, relay, validatePayload}} + { + getState, + rejectWithValue, + extra: {apiClient, validatePayload, relay, navigatorContext}, + } ) => { validatePayload(payload, idDefinition); const state = getState(); - const request = buildQuerySuggestRequest(payload.id, state, relay); + const request = buildQuerySuggestRequest( + payload.id, + state, + relay, + navigatorContext + ); const response = await apiClient.querySuggest(request); if (isErrorResponse(response)) { @@ -67,9 +77,10 @@ export const fetchQuerySuggestions = createAsyncThunk< export const buildQuerySuggestRequest = ( id: string, state: StateNeededByQuerySuggest, - relay: Relay + relay: Relay, + navigatorContext: NavigatorContext ): QuerySuggestRequest => { - const {view, user, ...restOfContext} = state.commerceContext; + const {view, ...restOfContext} = state.commerceContext; return { accessToken: state.configuration.accessToken, url: state.configuration.platformUrl, @@ -79,8 +90,19 @@ export const buildQuerySuggestRequest = ( ...restOfContext, clientId: relay.getMeta('').clientId, context: { - user, - view, + ...(navigatorContext.userAgent + ? { + user: { + userAgent: navigatorContext.userAgent, + }, + } + : {}), + view: { + ...view, + ...(navigatorContext.referrer + ? {referrer: navigatorContext.referrer} + : {}), + }, capture: state.configuration.analytics.enabled, cart: getProductsFromCartState(state.cart), source: getAnalyticsSource(state.configuration.analytics), diff --git a/packages/headless/src/features/commerce/recommendations/recommendations-actions.ts b/packages/headless/src/features/commerce/recommendations/recommendations-actions.ts index 0dc0e07c451..744551df20a 100644 --- a/packages/headless/src/features/commerce/recommendations/recommendations-actions.ts +++ b/packages/headless/src/features/commerce/recommendations/recommendations-actions.ts @@ -7,6 +7,7 @@ import { } from '../../../api/commerce/commerce-api-client'; import {CommerceRecommendationsRequest} from '../../../api/commerce/recommendations/recommendations-request'; import {RecommendationsCommerceSuccessResponse} from '../../../api/commerce/recommendations/recommendations-response'; +import {NavigatorContext} from '../../../app/navigatorContextProvider'; import {RecommendationsSection} from '../../../state/state-sections'; import {validatePayload} from '../../../utils/validate-payload'; import { @@ -33,9 +34,15 @@ const buildRecommendationCommerceAPIRequest = ( slotId: string, state: StateNeededByFetchRecommendations, relay: Relay, + navigatorContext: NavigatorContext, productId?: string ): CommerceRecommendationsRequest => { - const commerceAPIRequest = buildBaseCommerceAPIRequest(state, relay, slotId); + const commerceAPIRequest = buildBaseCommerceAPIRequest( + state, + relay, + navigatorContext, + slotId + ); return { ...commerceAPIRequest, context: { @@ -61,12 +68,16 @@ export const fetchRecommendations = createAsyncThunk< AsyncThunkCommerceOptions >( 'commerce/recommendations/fetch', - async (payload, {getState, rejectWithValue, extra: {apiClient, relay}}) => { + async ( + payload, + {getState, rejectWithValue, extra: {apiClient, relay, navigatorContext}} + ) => { const {slotId, productId} = payload; const request = buildRecommendationCommerceAPIRequest( slotId, getState(), relay, + navigatorContext, productId ); const fetched = await apiClient.getRecommendations(request); @@ -87,7 +98,10 @@ export const fetchMoreRecommendations = createAsyncThunk< AsyncThunkCommerceOptions >( 'commerce/recommendations/fetchMore', - async (payload, {getState, rejectWithValue, extra: {apiClient, relay}}) => { + async ( + payload, + {getState, rejectWithValue, extra: {apiClient, relay, navigatorContext}} + ) => { const slotId = payload.slotId; const state = getState(); const moreRecommendationsAvailable = moreRecommendationsAvailableSelector( @@ -103,7 +117,12 @@ export const fetchMoreRecommendations = createAsyncThunk< const nextPageToRequest = numberOfProducts / perPage; const request = { - ...buildRecommendationCommerceAPIRequest(slotId, state, relay), + ...buildRecommendationCommerceAPIRequest( + slotId, + state, + relay, + navigatorContext + ), page: nextPageToRequest, }; const fetched = await apiClient.getRecommendations(request); diff --git a/packages/headless/src/features/commerce/search/search-actions-thunk-processor.test.ts b/packages/headless/src/features/commerce/search/search-actions-thunk-processor.test.ts index e1064562930..b698b7f1267 100644 --- a/packages/headless/src/features/commerce/search/search-actions-thunk-processor.test.ts +++ b/packages/headless/src/features/commerce/search/search-actions-thunk-processor.test.ts @@ -52,7 +52,11 @@ describe('commerce AsyncSearchThunkProcessor', () => { response, duration: 123, queryExecuted: 'foo', - requestExecuted: buildCommerceAPIRequest(state, config.extra.relay), + requestExecuted: buildCommerceAPIRequest( + state, + config.extra.relay, + config.extra.navigatorContext + ), }; return processor.process(fetched); } diff --git a/packages/headless/src/features/commerce/search/search-actions-thunk-processor.ts b/packages/headless/src/features/commerce/search/search-actions-thunk-processor.ts index 40c132a1355..8dd23551597 100644 --- a/packages/headless/src/features/commerce/search/search-actions-thunk-processor.ts +++ b/packages/headless/src/features/commerce/search/search-actions-thunk-processor.ts @@ -191,7 +191,11 @@ export class AsyncSearchThunkProcessor { ); this.onUpdateQueryForCorrection(modified); const fetched = await this.fetchFromAPI({ - ...buildCommerceAPIRequest(this.getState(), this.relay), + ...buildCommerceAPIRequest( + this.getState(), + this.relay, + this.navigatorContext + ), query: modified, }); @@ -214,6 +218,10 @@ export class AsyncSearchThunkProcessor { return this.config.extra.relay; } + private get navigatorContext() { + return this.config.extra.navigatorContext; + } + private getCurrentQuery() { const state = this.getState(); return state.commerceQuery.query !== undefined diff --git a/packages/headless/src/features/commerce/search/search-actions.ts b/packages/headless/src/features/commerce/search/search-actions.ts index 06f75c6950b..8772ee8283f 100644 --- a/packages/headless/src/features/commerce/search/search-actions.ts +++ b/packages/headless/src/features/commerce/search/search-actions.ts @@ -73,9 +73,9 @@ export const executeSearch = createAsyncThunk< >('commerce/search/executeSearch', async (_action, config) => { const {getState} = config; const state = getState(); - const {relay} = config.extra; + const {relay, navigatorContext} = config.extra; - const request = buildCommerceAPIRequest(state, relay); + const request = buildCommerceAPIRequest(state, relay, navigatorContext); const query = querySelector(state); const processor = new AsyncSearchThunkProcessor< @@ -93,7 +93,7 @@ export const fetchMoreProducts = createAsyncThunk< >('commerce/search/fetchMoreProducts', async (_action, config) => { const {getState} = config; const state = getState(); - const {relay} = config.extra; + const {relay, navigatorContext} = config.extra; const moreProductsAvailable = moreProductsAvailableSelector(state); if (!moreProductsAvailable) { @@ -105,7 +105,7 @@ export const fetchMoreProducts = createAsyncThunk< const nextPageToRequest = numberOfProducts / perPage; const query = querySelector(state); - const request = buildCommerceAPIRequest(state, relay); + const request = buildCommerceAPIRequest(state, relay, navigatorContext); const processor = new AsyncSearchThunkProcessor< ReturnType @@ -152,10 +152,10 @@ export const fetchInstantProducts = createAsyncThunk< 'commerce/search/fetchInstantProducts', async (payload, {getState, dispatch, rejectWithValue, extra}) => { const state = getState(); - const {apiClient, relay} = extra; + const {apiClient, relay, navigatorContext} = extra; const {q} = payload; const fetched = await apiClient.productSuggestions({ - ...buildCommerceAPIRequest(state, relay), + ...buildCommerceAPIRequest(state, relay, navigatorContext), query: q, }); diff --git a/packages/headless/src/features/commerce/sort/sort-slice.test.ts b/packages/headless/src/features/commerce/sort/sort-slice.test.ts index 0251f9ce410..f11ea5023dc 100644 --- a/packages/headless/src/features/commerce/sort/sort-slice.test.ts +++ b/packages/headless/src/features/commerce/sort/sort-slice.test.ts @@ -1,7 +1,12 @@ import {buildSearchResponse} from '../../../test/mock-commerce-search'; import {buildFetchProductListingResponse} from '../../../test/mock-product-listing'; import {SortBy, SortDirection} from '../../sort/sort'; -import {setContext, setUser, setView} from '../context/context-actions'; +import { + setContext, + SetContextPayload, + setView, + SetViewPayload, +} from '../context/context-actions'; import {restoreProductListingParameters} from '../product-listing-parameters/product-listing-parameters-actions'; import {fetchProductListing} from '../product-listing/product-listing-actions'; import {restoreSearchParameters} from '../search-parameters/search-parameters-actions'; @@ -113,28 +118,21 @@ describe('product-listing-sort-slice', () => { }); }); - describe.each([ - { - actionName: '#setContext', - action: setContext, - }, - { - actionName: '#setView', - action: setView, - }, - { - actionName: '#setUser', - action: setUser, - }, - ])('$actionName', ({action}) => { - it('resets sort', () => { - state.appliedSort = sort; - state.availableSorts = [sort]; + it('setContext resets sort', () => { + state.appliedSort = sort; + state.availableSorts = [sort]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const finalState = sortReducer(state, action({} as any)); + const finalState = sortReducer(state, setContext({} as SetContextPayload)); - expect(finalState).toStrictEqual(getCommerceSortInitialState()); - }); + expect(finalState).toStrictEqual(getCommerceSortInitialState()); + }); + + it('setView resets sort', () => { + state.appliedSort = sort; + state.availableSorts = [sort]; + + const finalState = sortReducer(state, setView({} as SetViewPayload)); + + expect(finalState).toStrictEqual(getCommerceSortInitialState()); }); }); diff --git a/packages/headless/src/features/commerce/sort/sort-slice.ts b/packages/headless/src/features/commerce/sort/sort-slice.ts index bc251d1ff3e..5a566d32975 100644 --- a/packages/headless/src/features/commerce/sort/sort-slice.ts +++ b/packages/headless/src/features/commerce/sort/sort-slice.ts @@ -6,7 +6,7 @@ import { SortBy, SortCriterion, } from '../../sort/sort'; -import {setContext, setUser, setView} from '../context/context-actions'; +import {setContext, setView} from '../context/context-actions'; import {Parameters} from '../parameters/parameters-actions'; import {restoreProductListingParameters} from '../product-listing-parameters/product-listing-parameters-actions'; import {fetchProductListing} from '../product-listing/product-listing-actions'; @@ -28,8 +28,7 @@ export const sortReducer = createReducer( .addCase(restoreSearchParameters, handleRestoreParameters) .addCase(restoreProductListingParameters, handleRestoreParameters) .addCase(setContext, getCommerceSortInitialState) - .addCase(setView, getCommerceSortInitialState) - .addCase(setUser, getCommerceSortInitialState); + .addCase(setView, getCommerceSortInitialState); } ); diff --git a/packages/headless/src/features/commerce/standalone-search-box-set/standalone-search-box-set-actions.ts b/packages/headless/src/features/commerce/standalone-search-box-set/standalone-search-box-set-actions.ts index fd61751a805..39527db20dc 100644 --- a/packages/headless/src/features/commerce/standalone-search-box-set/standalone-search-box-set-actions.ts +++ b/packages/headless/src/features/commerce/standalone-search-box-set/standalone-search-box-set-actions.ts @@ -7,6 +7,7 @@ import { } from '../../../api/commerce/commerce-api-client'; import {CommerceSearchRequest} from '../../../api/commerce/search/request'; import {isRedirectTrigger} from '../../../api/common/trigger'; +import {NavigatorContext} from '../../../app/navigatorContextProvider'; import { CartSection, CommerceContextSection, @@ -75,10 +76,13 @@ export const fetchRedirectUrl = createAsyncThunk< AsyncThunkCommerceOptions >( 'commerce/standaloneSearchBox/fetchRedirect', - async (payload, {getState, rejectWithValue, extra: {apiClient, relay}}) => { + async ( + payload, + {getState, rejectWithValue, extra: {apiClient, relay, navigatorContext}} + ) => { validatePayload(payload, {id: new StringValue({emptyAllowed: false})}); const state = getState(); - const request = buildPlanRequest(state, relay); + const request = buildPlanRequest(state, relay, navigatorContext); const response = await apiClient.plan(request); if (isErrorResponse(response)) { return rejectWithValue(response.error); @@ -93,10 +97,11 @@ export const fetchRedirectUrl = createAsyncThunk< export const buildPlanRequest = ( state: StateNeededForRedirect, - relay: Relay + relay: Relay, + navigatorContext: NavigatorContext ): CommerceSearchRequest => { return { query: state.commerceQuery.query, - ...buildBaseCommerceAPIRequest(state, relay), + ...buildBaseCommerceAPIRequest(state, relay, navigatorContext), }; }; diff --git a/packages/samples/atomic-react/src/pages/CommerceSearchPage.tsx b/packages/samples/atomic-react/src/pages/CommerceSearchPage.tsx index 3b9b3d25acb..cf8aa8abdbd 100644 --- a/packages/samples/atomic-react/src/pages/CommerceSearchPage.tsx +++ b/packages/samples/atomic-react/src/pages/CommerceSearchPage.tsx @@ -41,7 +41,6 @@ export const CommerceSearchPage = () => { currency: 'USD', view: { url: 'https://sports-dev.barca.group/commerce-search', - referrer: document.referrer, }, }, cart: {