@envive-ai/react-hooks 0.3.46 → 0.3.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/commerce-api.cjs +73 -3
- package/dist/application/commerce-api.d.cts +16 -1
- package/dist/application/commerce-api.d.ts +16 -1
- package/dist/application/commerce-api.js +73 -3
- package/dist/application/models/api/userEvent.d.cts +9 -2
- package/dist/application/models/api/userEvent.d.ts +9 -2
- package/dist/application/models/featureGates.cjs +2 -1
- package/dist/application/models/featureGates.d.cts +2 -1
- package/dist/application/models/featureGates.d.ts +2 -1
- package/dist/application/models/featureGates.js +2 -1
- package/dist/application/models/graphql/queries/getProductOptionsQuery.cjs +33 -0
- package/dist/application/models/graphql/queries/getProductOptionsQuery.d.cts +28 -0
- package/dist/application/models/graphql/queries/getProductOptionsQuery.d.ts +28 -0
- package/dist/application/models/graphql/queries/getProductOptionsQuery.js +32 -0
- package/dist/application/models/validators/validateResponse.cjs +2 -1
- package/dist/application/models/validators/validateResponse.js +2 -1
- package/dist/application/models/validators/validateUserEvent.cjs +3 -3
- package/dist/application/models/validators/validateUserEvent.js +3 -3
- package/dist/application/utils/index.cjs +2 -0
- package/dist/application/utils/index.d.cts +2 -1
- package/dist/application/utils/index.d.ts +2 -1
- package/dist/application/utils/index.js +2 -1
- package/dist/application/utils/messageFromAddToCartEvent.cjs +32 -0
- package/dist/application/utils/messageFromAddToCartEvent.d.cts +19 -0
- package/dist/application/utils/messageFromAddToCartEvent.d.ts +19 -0
- package/dist/application/utils/messageFromAddToCartEvent.js +31 -0
- package/dist/atoms/app/index.cjs +1 -1
- package/dist/atoms/app/index.d.cts +7 -7
- package/dist/atoms/app/index.d.ts +7 -7
- package/dist/atoms/app/index.js +1 -1
- package/dist/atoms/app/variant.d.cts +6 -6
- package/dist/atoms/app/variant.d.ts +6 -6
- package/dist/atoms/chat/chatState.d.cts +19 -19
- package/dist/atoms/chat/chatState.d.ts +19 -19
- package/dist/atoms/chat/form.d.cts +2 -2
- package/dist/atoms/chat/form.d.ts +3 -3
- package/dist/atoms/chat/index.d.cts +2 -2
- package/dist/atoms/chat/index.d.ts +3 -3
- package/dist/atoms/chat/lastMessage.d.cts +2 -2
- package/dist/atoms/chat/lastMessage.d.ts +2 -2
- package/dist/atoms/chat/messageQueue.d.cts +6 -6
- package/dist/atoms/chat/messageQueue.d.ts +7 -7
- package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
- package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
- package/dist/atoms/chat/renderedWidgetRefs.d.cts +2 -2
- package/dist/atoms/chat/renderedWidgetRefs.d.ts +3 -3
- package/dist/atoms/chat/replies.d.cts +1 -1
- package/dist/atoms/chat/replies.d.ts +3 -3
- package/dist/atoms/chat/suggestions.d.cts +2 -2
- package/dist/atoms/chat/suggestions.d.ts +3 -3
- package/dist/atoms/envive/enviveConfig.cjs +7 -4
- package/dist/atoms/envive/enviveConfig.d.cts +16 -14
- package/dist/atoms/envive/enviveConfig.d.ts +16 -14
- package/dist/atoms/envive/enviveConfig.js +6 -4
- package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
- package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
- package/dist/atoms/org/customerService.d.cts +6 -6
- package/dist/atoms/org/customerService.d.ts +6 -6
- package/dist/atoms/org/graphqlConfig.cjs +1 -1
- package/dist/atoms/org/graphqlConfig.d.cts +6 -6
- package/dist/atoms/org/graphqlConfig.d.ts +6 -6
- package/dist/atoms/org/graphqlConfig.js +1 -1
- package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
- package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
- package/dist/atoms/org/orgAnalyticsConfig.d.cts +4 -4
- package/dist/atoms/org/orgAnalyticsConfig.d.ts +4 -4
- package/dist/atoms/search/chatSearch.d.cts +17 -17
- package/dist/atoms/search/chatSearch.d.ts +17 -17
- package/dist/atoms/search/searchAPI.d.cts +13 -13
- package/dist/atoms/search/searchAPI.d.ts +13 -13
- package/dist/atoms/search/types.d.ts +1 -1
- package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
- package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
- package/dist/contexts/enviveConfigContext/enviveConfigContext.cjs +6 -4
- package/dist/contexts/enviveConfigContext/enviveConfigContext.d.cts +3 -1
- package/dist/contexts/enviveConfigContext/enviveConfigContext.d.ts +3 -1
- package/dist/contexts/enviveConfigContext/enviveConfigContext.js +6 -4
- package/dist/contexts/enviveContext/enviveContext.cjs +7 -3
- package/dist/contexts/enviveContext/enviveContext.d.cts +5 -1
- package/dist/contexts/enviveContext/enviveContext.d.ts +5 -1
- package/dist/contexts/enviveContext/enviveContext.js +7 -3
- package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.cjs +2 -97
- package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.d.cts +3 -25
- package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.d.ts +3 -25
- package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.js +3 -97
- package/dist/contexts/featureFlagServiceContext/index.cjs +2 -1
- package/dist/contexts/featureFlagServiceContext/index.d.cts +2 -1
- package/dist/contexts/featureFlagServiceContext/index.d.ts +2 -1
- package/dist/contexts/featureFlagServiceContext/index.js +2 -1
- package/dist/contexts/graphqlContext/mockV3Config.cjs +1 -1
- package/dist/contexts/graphqlContext/mockV3Config.js +1 -1
- package/dist/contexts/localStorageContext/index.cjs +2 -1
- package/dist/contexts/localStorageContext/index.d.cts +2 -1
- package/dist/contexts/localStorageContext/index.d.ts +2 -1
- package/dist/contexts/localStorageContext/index.js +2 -1
- package/dist/contexts/localStorageContext/localStorageContext.cjs +5 -11
- package/dist/contexts/localStorageContext/localStorageContext.d.cts +3 -7
- package/dist/contexts/localStorageContext/localStorageContext.d.ts +3 -7
- package/dist/contexts/localStorageContext/localStorageContext.js +3 -8
- package/dist/contexts/localStorageContext/localStorageKeys.cjs +12 -0
- package/dist/contexts/localStorageContext/localStorageKeys.d.cts +9 -0
- package/dist/contexts/localStorageContext/localStorageKeys.d.ts +9 -0
- package/dist/contexts/localStorageContext/localStorageKeys.js +11 -0
- package/dist/contexts/salesAgentContext/chatAPI.cjs +13 -2
- package/dist/contexts/salesAgentContext/chatAPI.d.cts +4 -1
- package/dist/contexts/salesAgentContext/chatAPI.d.ts +4 -1
- package/dist/contexts/salesAgentContext/chatAPI.js +14 -3
- package/dist/contexts/salesAgentContext/salesAgentContext.cjs +12 -4
- package/dist/contexts/salesAgentContext/salesAgentContext.js +12 -4
- package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
- package/dist/contexts/types.cjs +1 -1
- package/dist/contexts/types.d.cts +3 -1
- package/dist/contexts/types.d.ts +3 -1
- package/dist/contexts/types.js +1 -1
- package/dist/contexts/typesV3.cjs +1 -1
- package/dist/contexts/typesV3.d.cts +11 -2
- package/dist/contexts/typesV3.d.ts +12 -3
- package/dist/contexts/typesV3.js +1 -1
- package/dist/hooks/AddToCart/addToCartErrorMessage.cjs +7 -0
- package/dist/hooks/AddToCart/addToCartErrorMessage.d.cts +5 -0
- package/dist/hooks/AddToCart/addToCartErrorMessage.d.ts +5 -0
- package/dist/hooks/AddToCart/addToCartErrorMessage.js +6 -0
- package/dist/hooks/AddToCart/createAddToCartUserEvent.cjs +20 -0
- package/dist/hooks/AddToCart/createAddToCartUserEvent.d.cts +17 -0
- package/dist/hooks/AddToCart/createAddToCartUserEvent.d.ts +17 -0
- package/dist/hooks/AddToCart/createAddToCartUserEvent.js +19 -0
- package/dist/hooks/AddToCart/index.cjs +9 -0
- package/dist/hooks/AddToCart/index.d.cts +5 -0
- package/dist/hooks/AddToCart/index.d.ts +5 -0
- package/dist/hooks/AddToCart/index.js +6 -0
- package/dist/hooks/AddToCart/parseAddToCartFunction.cjs +27 -0
- package/dist/hooks/AddToCart/parseAddToCartFunction.d.cts +13 -0
- package/dist/hooks/AddToCart/parseAddToCartFunction.d.ts +13 -0
- package/dist/hooks/AddToCart/parseAddToCartFunction.js +27 -0
- package/dist/hooks/AddToCart/useAddToCart.cjs +24 -0
- package/dist/hooks/AddToCart/useAddToCart.d.cts +14 -0
- package/dist/hooks/AddToCart/useAddToCart.d.ts +14 -0
- package/dist/hooks/AddToCart/useAddToCart.js +23 -0
- package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
- package/dist/hooks/LocalStorageOperations/useLocalStorageOperations.cjs +6 -5
- package/dist/hooks/LocalStorageOperations/useLocalStorageOperations.js +3 -2
- package/dist/hooks/PageViewedEvent/usePageViewedEvent.cjs +5 -2
- package/dist/hooks/PageViewedEvent/usePageViewedEvent.js +5 -2
- package/dist/hooks/ProductDetails/index.cjs +9 -0
- package/dist/hooks/ProductDetails/index.d.cts +4 -0
- package/dist/hooks/ProductDetails/index.d.ts +4 -0
- package/dist/hooks/ProductDetails/index.js +4 -0
- package/dist/hooks/ProductDetails/useProductDetails.cjs +53 -0
- package/dist/hooks/ProductDetails/useProductDetails.d.cts +20 -0
- package/dist/hooks/ProductDetails/useProductDetails.d.ts +20 -0
- package/dist/hooks/ProductDetails/useProductDetails.js +52 -0
- package/dist/hooks/ProductDetails/variantSelection.cjs +42 -0
- package/dist/hooks/ProductDetails/variantSelection.d.cts +35 -0
- package/dist/hooks/ProductDetails/variantSelection.d.ts +35 -0
- package/dist/hooks/ProductDetails/variantSelection.js +38 -0
- package/dist/hooks/Search/useSearch.d.cts +1 -1
- package/dist/hooks/Search/useSearch.d.ts +1 -1
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +11 -2
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.cts +1 -10
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.ts +1 -10
- package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +11 -2
- package/dist/hooks/WidgetInteraction/types.cjs +12 -2
- package/dist/hooks/WidgetInteraction/types.d.cts +28 -3
- package/dist/hooks/WidgetInteraction/types.d.ts +28 -3
- package/dist/hooks/WidgetInteraction/types.js +12 -2
- package/dist/hooks/utils.d.cts +1 -1
- package/dist/hooks/utils.d.ts +1 -1
- package/dist/services/amplitudeService/amplitudeService.cjs +16 -10
- package/dist/services/amplitudeService/amplitudeService.d.cts +5 -2
- package/dist/services/amplitudeService/amplitudeService.d.ts +5 -2
- package/dist/services/amplitudeService/amplitudeService.js +14 -7
- package/dist/services/enviveConfigService/enviveConfigService.cjs +3 -4
- package/dist/services/enviveConfigService/enviveConfigService.d.cts +1 -1
- package/dist/services/enviveConfigService/enviveConfigService.d.ts +1 -1
- package/dist/services/enviveConfigService/enviveConfigService.js +2 -3
- package/dist/services/enviveConfigService/fetchGraphQLConfig.cjs +8 -7
- package/dist/services/enviveConfigService/fetchGraphQLConfig.js +8 -6
- package/dist/services/featureFlagService/index.cjs +101 -0
- package/dist/services/featureFlagService/index.d.cts +28 -0
- package/dist/services/featureFlagService/index.d.ts +28 -0
- package/dist/services/featureFlagService/index.js +101 -0
- package/dist/services/ga4ProjectionService/ga4EventSchema.cjs +8 -2
- package/dist/services/ga4ProjectionService/ga4EventSchema.js +8 -2
- package/dist/services/hardcopyService/hardcopyService.d.cts +1 -1
- package/dist/services/hardcopyService/hardcopyService.d.ts +1 -1
- package/dist/services/urlResolverWarmupService/index.cjs +54 -0
- package/dist/services/urlResolverWarmupService/index.d.cts +40 -0
- package/dist/services/urlResolverWarmupService/index.d.ts +40 -0
- package/dist/services/urlResolverWarmupService/index.js +54 -0
- package/dist/types/enviveConfig.d.cts +6 -2
- package/dist/types/enviveConfig.d.ts +6 -2
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/package.json +17 -1
- package/src/application/__tests__/commerceApiAddToCartRehydration.test.ts +126 -0
- package/src/application/commerce-api.ts +142 -23
- package/src/application/models/api/userEvent.ts +8 -1
- package/src/application/models/featureGates.ts +5 -0
- package/src/application/models/graphql/queries/getProductOptionsQuery.ts +59 -0
- package/src/application/models/validators/__tests__/validateUserEvent.test.ts +41 -0
- package/src/application/models/validators/validateResponse.ts +1 -0
- package/src/application/models/validators/validateUserEvent.ts +7 -2
- package/src/application/utils/__tests__/messageFromAddToCartEvent.test.ts +109 -0
- package/src/application/utils/index.ts +1 -0
- package/src/application/utils/messageFromAddToCartEvent.ts +32 -0
- package/src/atoms/app/index.ts +1 -1
- package/src/atoms/envive/enviveConfig.ts +6 -1
- package/src/atoms/org/graphqlConfig.ts +1 -1
- package/src/contexts/enviveConfigContext/enviveConfigContext.tsx +5 -0
- package/src/contexts/enviveContext/enviveContext.tsx +10 -0
- package/src/contexts/featureFlagServiceContext/featureFlagServiceContext.tsx +6 -173
- package/src/contexts/localStorageContext/localStorageContext.tsx +2 -5
- package/src/contexts/localStorageContext/localStorageKeys.ts +8 -0
- package/src/contexts/salesAgentContext/__tests__/chatAPI.test.tsx +63 -0
- package/src/contexts/salesAgentContext/chatAPI.ts +20 -1
- package/src/contexts/salesAgentContext/salesAgentContext.tsx +10 -0
- package/src/contexts/types.ts +2 -0
- package/src/contexts/typesV3.ts +14 -0
- package/src/hooks/AddToCart/__tests__/parseAddToCartFunction.test.ts +31 -0
- package/src/hooks/AddToCart/__tests__/useAddToCart.test.tsx +204 -0
- package/src/hooks/AddToCart/addToCartErrorMessage.ts +2 -0
- package/src/hooks/AddToCart/createAddToCartUserEvent.ts +25 -0
- package/src/hooks/AddToCart/index.ts +4 -0
- package/src/hooks/AddToCart/parseAddToCartFunction.ts +31 -0
- package/src/hooks/AddToCart/useAddToCart.ts +35 -0
- package/src/hooks/PageViewedEvent/usePageViewedEvent.ts +4 -0
- package/src/hooks/ProductDetails/__tests__/useProductDetails.test.tsx +129 -0
- package/src/hooks/ProductDetails/__tests__/variantSelection.test.ts +145 -0
- package/src/hooks/ProductDetails/index.ts +2 -0
- package/src/hooks/ProductDetails/useProductDetails.ts +62 -0
- package/src/hooks/ProductDetails/variantSelection.ts +61 -0
- package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +15 -0
- package/src/hooks/WidgetInteraction/types.ts +42 -1
- package/src/services/amplitudeService/__tests__/amplitudeService.test.ts +8 -8
- package/src/services/amplitudeService/amplitudeService.ts +42 -23
- package/src/services/enviveConfigService/enviveConfigService.ts +2 -2
- package/src/services/enviveConfigService/fetchGraphQLConfig.ts +17 -6
- package/src/services/featureFlagService/index.ts +176 -0
- package/src/services/ga4ProjectionService/__tests__/ga4ProjectionService.test.ts +36 -0
- package/src/services/ga4ProjectionService/ga4EventSchema.ts +7 -0
- package/src/services/urlResolverWarmupService/__tests__/urlResolverWarmupService.test.ts +85 -0
- package/src/services/urlResolverWarmupService/index.ts +89 -0
- package/src/types/enviveConfig.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envive-ai/react-hooks",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.48",
|
|
4
4
|
"description": "React hooks for connecting to Envive AI services.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -274,6 +274,10 @@
|
|
|
274
274
|
"import": "./dist/events/index.js",
|
|
275
275
|
"require": "./dist/events/index.cjs"
|
|
276
276
|
},
|
|
277
|
+
"./hooks/AddToCart": {
|
|
278
|
+
"import": "./dist/hooks/AddToCart/index.js",
|
|
279
|
+
"require": "./dist/hooks/AddToCart/index.cjs"
|
|
280
|
+
},
|
|
277
281
|
"./hooks/AmplitudeOperations": {
|
|
278
282
|
"import": "./dist/hooks/AmplitudeOperations/index.js",
|
|
279
283
|
"require": "./dist/hooks/AmplitudeOperations/index.cjs"
|
|
@@ -350,6 +354,10 @@
|
|
|
350
354
|
"import": "./dist/hooks/PageViewedEvent/index.js",
|
|
351
355
|
"require": "./dist/hooks/PageViewedEvent/index.cjs"
|
|
352
356
|
},
|
|
357
|
+
"./hooks/ProductDetails": {
|
|
358
|
+
"import": "./dist/hooks/ProductDetails/index.js",
|
|
359
|
+
"require": "./dist/hooks/ProductDetails/index.cjs"
|
|
360
|
+
},
|
|
353
361
|
"./hooks/ProductImageUrl": {
|
|
354
362
|
"import": "./dist/hooks/ProductImageUrl/index.js",
|
|
355
363
|
"require": "./dist/hooks/ProductImageUrl/index.cjs"
|
|
@@ -422,10 +430,18 @@
|
|
|
422
430
|
"import": "./dist/services/enviveConfigService/index.js",
|
|
423
431
|
"require": "./dist/services/enviveConfigService/index.cjs"
|
|
424
432
|
},
|
|
433
|
+
"./services/featureFlagService": {
|
|
434
|
+
"import": "./dist/services/featureFlagService/index.js",
|
|
435
|
+
"require": "./dist/services/featureFlagService/index.cjs"
|
|
436
|
+
},
|
|
425
437
|
"./services/hardcopyService": {
|
|
426
438
|
"import": "./dist/services/hardcopyService/index.js",
|
|
427
439
|
"require": "./dist/services/hardcopyService/index.cjs"
|
|
428
440
|
},
|
|
441
|
+
"./services/urlResolverWarmupService": {
|
|
442
|
+
"import": "./dist/services/urlResolverWarmupService/index.js",
|
|
443
|
+
"require": "./dist/services/urlResolverWarmupService/index.cjs"
|
|
444
|
+
},
|
|
429
445
|
"./services/userIdentityService": {
|
|
430
446
|
"import": "./dist/services/userIdentityService/index.js",
|
|
431
447
|
"require": "./dist/services/userIdentityService/index.cjs"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ResponseCategory, UserEventCategory } from '@spiffy-ai/commerce-api-client';
|
|
2
|
+
import { getAtomStore } from 'src/atoms/atomStore/atomStore';
|
|
3
|
+
import { enviveConfigAtom } from 'src/atoms/envive/enviveConfig';
|
|
4
|
+
import { MessageType } from 'src/application/models';
|
|
5
|
+
import CommerceApiClient from '../commerce-api';
|
|
6
|
+
|
|
7
|
+
const { mockV1GetSessionMessages } = vi.hoisted(() => ({
|
|
8
|
+
mockV1GetSessionMessages: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock('@spiffy-ai/commerce-api-client', async (importOriginal: () => Promise<unknown>) => {
|
|
12
|
+
const actual = (await importOriginal()) as typeof import('@spiffy-ai/commerce-api-client');
|
|
13
|
+
return {
|
|
14
|
+
...actual,
|
|
15
|
+
DefaultApi: vi.fn().mockImplementation(function DefaultApi() {
|
|
16
|
+
return { v1GetSessionMessages: mockV1GetSessionMessages };
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const makeSessionResponse = (overrides: Record<string, unknown> = {}) => ({
|
|
22
|
+
responses: [],
|
|
23
|
+
suggestions: [],
|
|
24
|
+
user_events: [],
|
|
25
|
+
...overrides,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const productResponse = (id: string, title: string) => ({
|
|
29
|
+
id: `resp-${id}`,
|
|
30
|
+
created_at: '2026-07-20T00:00:00.000Z',
|
|
31
|
+
category: ResponseCategory.Product,
|
|
32
|
+
attributes: {
|
|
33
|
+
id,
|
|
34
|
+
title,
|
|
35
|
+
url: `https://example.com/${id}`,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const addToCartEvent = (eventId: string, productId: string) => ({
|
|
40
|
+
event_id: eventId,
|
|
41
|
+
created_at: '2026-07-20T00:00:01.000Z',
|
|
42
|
+
category: UserEventCategory.AddToCart,
|
|
43
|
+
attributes: {
|
|
44
|
+
product_id: productId,
|
|
45
|
+
parent_product_id: '',
|
|
46
|
+
url: '',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
vi.clearAllMocks();
|
|
52
|
+
getAtomStore().set(enviveConfigAtom, {
|
|
53
|
+
baseUrl: 'https://commerce-api.test',
|
|
54
|
+
orgLevelApiKey: 'test-key',
|
|
55
|
+
orgId: 'org-1',
|
|
56
|
+
orgShortName: 'test-org',
|
|
57
|
+
env: 'dev',
|
|
58
|
+
contextSource: 'app',
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('CommerceApiClient.getResponses — AddToCart title resolution on rehydration', () => {
|
|
63
|
+
it('resolves the title for free from a matching Product response, without a fallback fetch', async () => {
|
|
64
|
+
mockV1GetSessionMessages.mockResolvedValueOnce(
|
|
65
|
+
makeSessionResponse({
|
|
66
|
+
responses: [[productResponse('sku-tier1', 'Tier One Sweater')]],
|
|
67
|
+
user_events: [addToCartEvent('evt-tier1', 'sku-tier1')],
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
const fetchSpy = vi.spyOn(global, 'fetch');
|
|
71
|
+
|
|
72
|
+
const { messages } = await CommerceApiClient.getResponses('org-1', 'chat-1', 'user-1');
|
|
73
|
+
|
|
74
|
+
const addToCartMessage = messages.flat().find(m => m.id === 'evt-tier1');
|
|
75
|
+
expect(addToCartMessage?.type).toBe(MessageType.QueryTyped);
|
|
76
|
+
if (addToCartMessage?.type === MessageType.QueryTyped) {
|
|
77
|
+
expect(addToCartMessage.metadata.content).toBe('Add Tier One Sweater to my cart');
|
|
78
|
+
}
|
|
79
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('falls back to a cached product-details fetch when no matching response exists', async () => {
|
|
83
|
+
mockV1GetSessionMessages.mockResolvedValueOnce(
|
|
84
|
+
makeSessionResponse({
|
|
85
|
+
user_events: [addToCartEvent('evt-tier2', 'sku-tier2')],
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
89
|
+
ok: true,
|
|
90
|
+
json: async () => ({ data: { me: { productOptions: { title: 'Tier Two Boots' } } } }),
|
|
91
|
+
});
|
|
92
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
93
|
+
|
|
94
|
+
const { messages } = await CommerceApiClient.getResponses('org-1', 'chat-1', 'user-1');
|
|
95
|
+
|
|
96
|
+
const addToCartMessage = messages.flat().find(m => m.id === 'evt-tier2');
|
|
97
|
+
expect(addToCartMessage?.type).toBe(MessageType.QueryTyped);
|
|
98
|
+
if (addToCartMessage?.type === MessageType.QueryTyped) {
|
|
99
|
+
expect(addToCartMessage.metadata.content).toBe('Add Tier Two Boots to my cart');
|
|
100
|
+
}
|
|
101
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('degrades to the raw productId when neither tier resolves a title', async () => {
|
|
105
|
+
mockV1GetSessionMessages.mockResolvedValueOnce(
|
|
106
|
+
makeSessionResponse({
|
|
107
|
+
user_events: [addToCartEvent('evt-tier3', 'sku-tier3-unresolvable')],
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
vi.stubGlobal(
|
|
111
|
+
'fetch',
|
|
112
|
+
vi.fn().mockResolvedValue({
|
|
113
|
+
ok: true,
|
|
114
|
+
json: async () => ({ data: { me: { productOptions: null } } }),
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const { messages } = await CommerceApiClient.getResponses('org-1', 'chat-1', 'user-1');
|
|
119
|
+
|
|
120
|
+
const addToCartMessage = messages.flat().find(m => m.id === 'evt-tier3');
|
|
121
|
+
expect(addToCartMessage?.type).toBe(MessageType.QueryTyped);
|
|
122
|
+
if (addToCartMessage?.type === MessageType.QueryTyped) {
|
|
123
|
+
expect(addToCartMessage.metadata.content).toBe('Add sku-tier3-unresolvable to my cart');
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -28,6 +28,10 @@ import {
|
|
|
28
28
|
UserEvent,
|
|
29
29
|
} from 'src/application/models';
|
|
30
30
|
import { OrgConfig } from 'src/application/models/api/orgConfigResults';
|
|
31
|
+
import {
|
|
32
|
+
ProductDetails,
|
|
33
|
+
getProductOptionsQuery,
|
|
34
|
+
} from 'src/application/models/graphql/queries/getProductOptionsQuery';
|
|
31
35
|
import { ProductExperiment } from 'src/application/models/productExperiment';
|
|
32
36
|
import { validateOrgConfigResults } from 'src/application/models/validators/validateOrgConfigResults';
|
|
33
37
|
import { validateResponse } from 'src/application/models/validators/validateResponse';
|
|
@@ -35,6 +39,7 @@ import { validateSuggestion } from 'src/application/models/validators/validateSu
|
|
|
35
39
|
import { validateUserEvent } from 'src/application/models/validators/validateUserEvent';
|
|
36
40
|
import {
|
|
37
41
|
getQueryParam,
|
|
42
|
+
messageFromAddToCartEvent,
|
|
38
43
|
messageFromQueryEvent,
|
|
39
44
|
messageFromResponse,
|
|
40
45
|
messageFromSuggestionEvent,
|
|
@@ -146,7 +151,26 @@ class CommerceApiClient {
|
|
|
146
151
|
this.productsApi = new ProductsApi(config);
|
|
147
152
|
}
|
|
148
153
|
|
|
154
|
+
// Dedupes concurrent resolutions of the same URL: the injection bundle
|
|
155
|
+
// warms this call before React mounts, and PageProvider may request the
|
|
156
|
+
// same URL while the warm-up is still in flight.
|
|
157
|
+
private static inFlightUrlResolutions = new Map<string, Promise<unknown>>();
|
|
158
|
+
|
|
149
159
|
static resolveUrl = async (url: string) => {
|
|
160
|
+
const inFlight = CommerceApiClient.inFlightUrlResolutions.get(url);
|
|
161
|
+
if (inFlight) {
|
|
162
|
+
return inFlight;
|
|
163
|
+
}
|
|
164
|
+
const request = CommerceApiClient.resolveUrlUncached(url);
|
|
165
|
+
CommerceApiClient.inFlightUrlResolutions.set(url, request);
|
|
166
|
+
try {
|
|
167
|
+
return await request;
|
|
168
|
+
} finally {
|
|
169
|
+
CommerceApiClient.inFlightUrlResolutions.delete(url);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
private static resolveUrlUncached = async (url: string) => {
|
|
150
174
|
const atomStore = getAtomStore();
|
|
151
175
|
const orgShortName = atomStore.get(orgShortNameAtom);
|
|
152
176
|
const orgId = atomStore.get(orgIdAtom);
|
|
@@ -405,33 +429,64 @@ class CommerceApiClient {
|
|
|
405
429
|
)
|
|
406
430
|
.filter(turn => turn.length > 0);
|
|
407
431
|
|
|
408
|
-
const userMessages: Message[][] =
|
|
409
|
-
.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
432
|
+
const userMessages: Message[][] = (
|
|
433
|
+
await Promise.all(
|
|
434
|
+
userEvents.map(async event => {
|
|
435
|
+
if ([UserEventCategory.QueryTyped, UserEventCategory.Search].includes(event.category)) {
|
|
436
|
+
return [messageFromQueryEvent(event)];
|
|
437
|
+
}
|
|
413
438
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
439
|
+
if (event.category === UserEventCategory.SuggestionClicked) {
|
|
440
|
+
return [messageFromSuggestionEvent(event, suggestions)];
|
|
441
|
+
}
|
|
417
442
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
443
|
+
if (event.category === UserEventCategory.AddToCart) {
|
|
444
|
+
// Historical events never carried a client-local productTitle (it's
|
|
445
|
+
// dropped before hitting the backend), so resolve it here: first for
|
|
446
|
+
// free from a matching Product response already fetched above, then
|
|
447
|
+
// as a fallback via the cached product-details call.
|
|
448
|
+
const matchedProduct = responses
|
|
449
|
+
.flat()
|
|
450
|
+
.find(
|
|
451
|
+
response =>
|
|
452
|
+
response.category === ResponseCategory.Product &&
|
|
453
|
+
response.attributes.id === event.attributes.productId,
|
|
454
|
+
);
|
|
455
|
+
let productTitle =
|
|
456
|
+
matchedProduct?.category === ResponseCategory.Product
|
|
457
|
+
? matchedProduct.attributes.title
|
|
458
|
+
: undefined;
|
|
459
|
+
if (!productTitle) {
|
|
460
|
+
try {
|
|
461
|
+
const details = await CommerceApiClient.retrieveProductDetails(
|
|
462
|
+
event.attributes.productId,
|
|
463
|
+
);
|
|
464
|
+
productTitle = details?.title;
|
|
465
|
+
} catch {
|
|
466
|
+
productTitle = undefined;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return [messageFromAddToCartEvent(event, productTitle)];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (event.category === UserEventCategory.FormSubmitted) {
|
|
473
|
+
const formResponse = responses
|
|
474
|
+
.flat()
|
|
475
|
+
.find(
|
|
476
|
+
response =>
|
|
477
|
+
response.id === event.attributes.formResponseId &&
|
|
478
|
+
event.attributes.formType !== FormType.Escalation,
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
if (formResponse && formResponse.category === ResponseCategory.Form) {
|
|
482
|
+
return [messageFromFormSubmittedEvent(event, formResponse.attributes)];
|
|
483
|
+
}
|
|
429
484
|
}
|
|
430
|
-
}
|
|
431
485
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
486
|
+
return [];
|
|
487
|
+
}),
|
|
488
|
+
)
|
|
489
|
+
).filter((message): message is Message[] => message.length > 0);
|
|
435
490
|
|
|
436
491
|
// Sort the messages chronologically to reconstruct the chat history
|
|
437
492
|
const sortedMessages = [...assistantMessages, ...userMessages].sort(
|
|
@@ -494,6 +549,70 @@ class CommerceApiClient {
|
|
|
494
549
|
}
|
|
495
550
|
};
|
|
496
551
|
|
|
552
|
+
// Caches resolved product details by productId for the lifetime of the
|
|
553
|
+
// session: variant selection, the display of the add-to-cart chat message,
|
|
554
|
+
// and useProductDetails all resolve the same product repeatedly. Failed
|
|
555
|
+
// requests are evicted so a later call can retry.
|
|
556
|
+
private static productDetailsCache = new Map<string, Promise<ProductDetails | undefined>>();
|
|
557
|
+
|
|
558
|
+
static retrieveProductDetails = (productId: string): Promise<ProductDetails | undefined> => {
|
|
559
|
+
const cached = CommerceApiClient.productDetailsCache.get(productId);
|
|
560
|
+
if (cached) {
|
|
561
|
+
return cached;
|
|
562
|
+
}
|
|
563
|
+
const request = CommerceApiClient.retrieveProductDetailsUncached(productId).catch(err => {
|
|
564
|
+
CommerceApiClient.productDetailsCache.delete(productId);
|
|
565
|
+
throw err;
|
|
566
|
+
});
|
|
567
|
+
CommerceApiClient.productDetailsCache.set(productId, request);
|
|
568
|
+
return request;
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Retrieves variant-level product details for a single product via the GraphQL
|
|
573
|
+
* `me.productOptions` query on POST /v1/graphql. Only meaningful for products
|
|
574
|
+
* that offer variations.
|
|
575
|
+
*
|
|
576
|
+
* @param productId - The product ID to retrieve details for
|
|
577
|
+
* @returns ProductDetails, or undefined when the product has no options data
|
|
578
|
+
* @throws on network/HTTP failure or GraphQL errors
|
|
579
|
+
*/
|
|
580
|
+
private static retrieveProductDetailsUncached = async (
|
|
581
|
+
productId: string,
|
|
582
|
+
): Promise<ProductDetails | undefined> => {
|
|
583
|
+
const atomStore = getAtomStore();
|
|
584
|
+
const baseUrl = atomStore.get(baseUrlAtom);
|
|
585
|
+
const apiKey = atomStore.get(orgLevelApiKeyAtom);
|
|
586
|
+
|
|
587
|
+
try {
|
|
588
|
+
const response = await fetch(`${baseUrl}/v1/graphql`, {
|
|
589
|
+
method: 'POST',
|
|
590
|
+
headers: {
|
|
591
|
+
'Content-Type': 'application/json',
|
|
592
|
+
Authorization: `Bearer ${apiKey}`,
|
|
593
|
+
},
|
|
594
|
+
body: JSON.stringify({
|
|
595
|
+
query: getProductOptionsQuery(),
|
|
596
|
+
variables: { productId },
|
|
597
|
+
}),
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
if (!response.ok) {
|
|
601
|
+
throw new ResponseError(response, `GraphQL request failed: ${response.statusText}`);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const result = await response.json();
|
|
605
|
+
if (result.errors) {
|
|
606
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return result.data?.me?.productOptions ?? undefined;
|
|
610
|
+
} catch (err) {
|
|
611
|
+
await throwSessionRestartRequiredIf('Failed to retrieve product details', err);
|
|
612
|
+
return undefined;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
|
|
497
616
|
/**
|
|
498
617
|
*
|
|
499
618
|
* @param payload
|
|
@@ -12,7 +12,14 @@ import {
|
|
|
12
12
|
import { CamelCasedPropertiesDeep } from 'src/application/models/utilityTypes';
|
|
13
13
|
|
|
14
14
|
// TODO flatten these out or explore new typescript openapi generator fork
|
|
15
|
-
export interface PDPAttributes extends CamelCasedPropertiesDeep<ApiPDPAttributes> {
|
|
15
|
+
export interface PDPAttributes extends CamelCasedPropertiesDeep<ApiPDPAttributes> {
|
|
16
|
+
/**
|
|
17
|
+
* Client-local only: never serialized to the backend (coreUserEventToApiUserEvent
|
|
18
|
+
* hand-picks fields and ignores this one). Lets the live add-to-cart chat message
|
|
19
|
+
* show a resolved product title instead of the raw productId.
|
|
20
|
+
*/
|
|
21
|
+
productTitle?: string;
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
export interface PLPAttributes extends CamelCasedPropertiesDeep<ApiPLPAttributes> {}
|
|
18
25
|
|
|
@@ -27,6 +27,11 @@ export enum FeatureGates {
|
|
|
27
27
|
IsAiSuggestionsVariantEnabled = 'is_ai_suggestions_variant_enabled',
|
|
28
28
|
IsAiSuggestionsVariantAEnabled = 'is_ai_suggestions_variant_a_enabled',
|
|
29
29
|
IsAiSuggestionsVariantBEnabled = 'is_ai_suggestions_variant_b_enabled',
|
|
30
|
+
// Serves the agentic storefront (FullPageSalesAgentV3 welcome overlay)
|
|
31
|
+
// independently of the pdp/plp variant_b experiment. The backend serves the
|
|
32
|
+
// storefront under this gate and falls back to variant_b only until this
|
|
33
|
+
// ships, so an org can run the storefront without opting pdp/plp into variant_b.
|
|
34
|
+
IsAiSuggestionsStorefrontEnabled = 'is_ai_suggestions_storefront_enabled',
|
|
30
35
|
IsVoiceInputEnabled = 'is_voice_input_enabled',
|
|
31
36
|
// Statsig experiments / unified getWidgetConfig endpoint migration. When
|
|
32
37
|
// true, EnviveConfigService calls getWidgetConfig (experiment-aware)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Fetches variant-level product details (options, availability, pricing,
|
|
2
|
+
// images) for a single product. Only meaningful for products that offer
|
|
3
|
+
// variations — non-variant PDP surfaces should keep using the REST
|
|
4
|
+
// /v1/products/retrieve path (CommerceApiClient.retrieveProducts).
|
|
5
|
+
export const getProductOptionsQuery = () => `
|
|
6
|
+
query($productId: String!) {
|
|
7
|
+
me {
|
|
8
|
+
productOptions(productId: $productId) {
|
|
9
|
+
productId
|
|
10
|
+
virtualParentId
|
|
11
|
+
title
|
|
12
|
+
options {
|
|
13
|
+
name
|
|
14
|
+
values
|
|
15
|
+
}
|
|
16
|
+
variants {
|
|
17
|
+
variantId
|
|
18
|
+
available
|
|
19
|
+
originalPrice
|
|
20
|
+
salePrice
|
|
21
|
+
imageUrl
|
|
22
|
+
url
|
|
23
|
+
options {
|
|
24
|
+
name
|
|
25
|
+
value
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export interface ProductVariantOption {
|
|
34
|
+
name: string;
|
|
35
|
+
value: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ProductVariant {
|
|
39
|
+
variantId: string;
|
|
40
|
+
available: boolean;
|
|
41
|
+
originalPrice?: number;
|
|
42
|
+
salePrice?: number;
|
|
43
|
+
imageUrl?: string;
|
|
44
|
+
url?: string;
|
|
45
|
+
options: ProductVariantOption[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ProductOption {
|
|
49
|
+
name: string;
|
|
50
|
+
values: string[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ProductDetails {
|
|
54
|
+
productId: string;
|
|
55
|
+
virtualParentId?: string;
|
|
56
|
+
title: string;
|
|
57
|
+
options: ProductOption[];
|
|
58
|
+
variants: ProductVariant[];
|
|
59
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { UserEventCategory } from '@spiffy-ai/commerce-api-client';
|
|
2
|
+
import { validateUserEvent } from 'src/application/models/validators/validateUserEvent';
|
|
3
|
+
|
|
4
|
+
describe('validateUserEvent', () => {
|
|
5
|
+
it('preserves productId, parentProductId, and url for an AddToCart event', () => {
|
|
6
|
+
const result = validateUserEvent({
|
|
7
|
+
event_id: 'event-1',
|
|
8
|
+
created_at: '2026-07-20T00:00:00.000Z',
|
|
9
|
+
category: UserEventCategory.AddToCart,
|
|
10
|
+
attributes: {
|
|
11
|
+
product_id: 'product-1',
|
|
12
|
+
parent_product_id: 'parent-1',
|
|
13
|
+
url: 'https://example.com/product-1',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
expect(result).toEqual({
|
|
18
|
+
eventId: 'event-1',
|
|
19
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
20
|
+
category: UserEventCategory.AddToCart,
|
|
21
|
+
attributes: {
|
|
22
|
+
productId: 'product-1',
|
|
23
|
+
parentProductId: 'parent-1',
|
|
24
|
+
url: 'https://example.com/product-1',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('does not fall through to AppLoaded for an AddToCart event', () => {
|
|
30
|
+
const result = validateUserEvent({
|
|
31
|
+
event_id: 'event-1',
|
|
32
|
+
created_at: '2026-07-20T00:00:00.000Z',
|
|
33
|
+
category: UserEventCategory.AddToCart,
|
|
34
|
+
attributes: {
|
|
35
|
+
product_id: 'product-1',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(result?.category).toBe(UserEventCategory.AddToCart);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -48,6 +48,7 @@ export const validateResponse = (data: unknown): Response | undefined => {
|
|
|
48
48
|
category: ResponseCategory.Product,
|
|
49
49
|
attributes: {
|
|
50
50
|
id: data.attributes.id,
|
|
51
|
+
itemGroupId: data.attributes.item_group_id,
|
|
51
52
|
description: data.attributes.description,
|
|
52
53
|
imageUrl: data.attributes.image_url,
|
|
53
54
|
imageUrls: data.attributes.image_urls,
|
|
@@ -16,11 +16,16 @@ export const validateUserEvent = (data: unknown): UserEvent | undefined => {
|
|
|
16
16
|
return undefined;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// PdpVisit and AddToCart share the same attributes structure
|
|
20
|
+
if (
|
|
21
|
+
(data.category === UserEventCategory.PdpVisit ||
|
|
22
|
+
data.category === UserEventCategory.AddToCart) &&
|
|
23
|
+
isApiPDPAttributes(data.attributes)
|
|
24
|
+
) {
|
|
20
25
|
return {
|
|
21
26
|
eventId: data.event_id,
|
|
22
27
|
createdAt: data.created_at,
|
|
23
|
-
category:
|
|
28
|
+
category: data.category,
|
|
24
29
|
attributes: {
|
|
25
30
|
productId: data.attributes.product_id,
|
|
26
31
|
parentProductId: data.attributes.parent_product_id,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { UserEventCategory } from '@spiffy-ai/commerce-api-client';
|
|
2
|
+
import { MessageRole, MessageType, UserEvent } from 'src/application/models';
|
|
3
|
+
import { messageFromAddToCartEvent } from '../messageFromAddToCartEvent';
|
|
4
|
+
|
|
5
|
+
describe('messageFromAddToCartEvent', () => {
|
|
6
|
+
it('builds a user message from an AddToCart event', () => {
|
|
7
|
+
const event: UserEvent = {
|
|
8
|
+
eventId: 'event-1',
|
|
9
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
10
|
+
category: UserEventCategory.AddToCart,
|
|
11
|
+
attributes: {
|
|
12
|
+
productId: 'product-1',
|
|
13
|
+
parentProductId: '',
|
|
14
|
+
url: '',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
expect(messageFromAddToCartEvent(event)).toEqual({
|
|
19
|
+
id: 'event-1',
|
|
20
|
+
role: MessageRole.User,
|
|
21
|
+
type: MessageType.QueryTyped,
|
|
22
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
23
|
+
metadata: {
|
|
24
|
+
content: 'Add product-1 to my cart',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns undefined for a non-AddToCart event', () => {
|
|
30
|
+
const event: UserEvent = {
|
|
31
|
+
eventId: 'event-1',
|
|
32
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
33
|
+
category: UserEventCategory.AppLoaded,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
expect(messageFromAddToCartEvent(event)).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('prefers the explicit productTitle override parameter', () => {
|
|
40
|
+
const event: UserEvent = {
|
|
41
|
+
eventId: 'event-1',
|
|
42
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
43
|
+
category: UserEventCategory.AddToCart,
|
|
44
|
+
attributes: {
|
|
45
|
+
productId: 'product-1',
|
|
46
|
+
parentProductId: '',
|
|
47
|
+
url: '',
|
|
48
|
+
productTitle: 'Attribute Title',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
expect(messageFromAddToCartEvent(event, 'Override Title')).toEqual({
|
|
53
|
+
id: 'event-1',
|
|
54
|
+
role: MessageRole.User,
|
|
55
|
+
type: MessageType.QueryTyped,
|
|
56
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
57
|
+
metadata: {
|
|
58
|
+
content: 'Add Override Title to my cart',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('falls back to event.attributes.productTitle when no override is passed', () => {
|
|
64
|
+
const event: UserEvent = {
|
|
65
|
+
eventId: 'event-1',
|
|
66
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
67
|
+
category: UserEventCategory.AddToCart,
|
|
68
|
+
attributes: {
|
|
69
|
+
productId: 'product-1',
|
|
70
|
+
parentProductId: '',
|
|
71
|
+
url: '',
|
|
72
|
+
productTitle: 'Attribute Title',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
expect(messageFromAddToCartEvent(event)).toEqual({
|
|
77
|
+
id: 'event-1',
|
|
78
|
+
role: MessageRole.User,
|
|
79
|
+
type: MessageType.QueryTyped,
|
|
80
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
81
|
+
metadata: {
|
|
82
|
+
content: 'Add Attribute Title to my cart',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('falls back to the raw productId when neither a title override nor an attribute title is present', () => {
|
|
88
|
+
const event: UserEvent = {
|
|
89
|
+
eventId: 'event-1',
|
|
90
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
91
|
+
category: UserEventCategory.AddToCart,
|
|
92
|
+
attributes: {
|
|
93
|
+
productId: 'product-1',
|
|
94
|
+
parentProductId: '',
|
|
95
|
+
url: '',
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
expect(messageFromAddToCartEvent(event)).toEqual({
|
|
100
|
+
id: 'event-1',
|
|
101
|
+
role: MessageRole.User,
|
|
102
|
+
type: MessageType.QueryTyped,
|
|
103
|
+
createdAt: '2026-07-20T00:00:00.000Z',
|
|
104
|
+
metadata: {
|
|
105
|
+
content: 'Add product-1 to my cart',
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -6,6 +6,7 @@ export * from './divideArray';
|
|
|
6
6
|
export * from './domObserver';
|
|
7
7
|
export * from './elementObserver';
|
|
8
8
|
export * from './imageFilter';
|
|
9
|
+
export * from './messageFromAddToCartEvent';
|
|
9
10
|
export * from './messageFromFormSubmittedEvent';
|
|
10
11
|
export * from './messageFromQueryEvent';
|
|
11
12
|
export * from './messageFromResponse';
|