@envive-ai/react-hooks 0.3.45 → 0.3.47

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.
Files changed (293) hide show
  1. package/dist/application/commerce-api.cjs +73 -3
  2. package/dist/application/commerce-api.d.cts +16 -1
  3. package/dist/application/commerce-api.d.ts +16 -1
  4. package/dist/application/commerce-api.js +73 -3
  5. package/dist/application/models/api/userEvent.d.cts +9 -2
  6. package/dist/application/models/api/userEvent.d.ts +9 -2
  7. package/dist/application/models/api/widgetText.d.cts +2 -1
  8. package/dist/application/models/api/widgetText.d.ts +2 -1
  9. package/dist/application/models/featureGates.cjs +2 -1
  10. package/dist/application/models/featureGates.d.cts +2 -1
  11. package/dist/application/models/featureGates.d.ts +2 -1
  12. package/dist/application/models/featureGates.js +2 -1
  13. package/dist/application/models/graphql/queries/getProductOptionsQuery.cjs +33 -0
  14. package/dist/application/models/graphql/queries/getProductOptionsQuery.d.cts +28 -0
  15. package/dist/application/models/graphql/queries/getProductOptionsQuery.d.ts +28 -0
  16. package/dist/application/models/graphql/queries/getProductOptionsQuery.js +32 -0
  17. package/dist/application/models/index.cjs +1 -1
  18. package/dist/application/models/index.js +1 -1
  19. package/dist/application/models/validators/validateGraphQLFrontendConfig.cjs +1 -1
  20. package/dist/application/models/validators/validateGraphQLFrontendConfig.js +1 -1
  21. package/dist/application/models/validators/validateResponse.cjs +2 -1
  22. package/dist/application/models/validators/validateResponse.js +2 -1
  23. package/dist/application/models/validators/validateUserEvent.cjs +3 -3
  24. package/dist/application/models/validators/validateUserEvent.js +3 -3
  25. package/dist/application/utils/elementObserver.d.cts +2 -2
  26. package/dist/application/utils/elementObserver.d.ts +2 -2
  27. package/dist/application/utils/index.cjs +2 -0
  28. package/dist/application/utils/index.d.cts +2 -1
  29. package/dist/application/utils/index.d.ts +2 -1
  30. package/dist/application/utils/index.js +2 -1
  31. package/dist/application/utils/messageFromAddToCartEvent.cjs +32 -0
  32. package/dist/application/utils/messageFromAddToCartEvent.d.cts +19 -0
  33. package/dist/application/utils/messageFromAddToCartEvent.d.ts +19 -0
  34. package/dist/application/utils/messageFromAddToCartEvent.js +31 -0
  35. package/dist/application/utils/widgetTextFromApiWidgetTextResponse.cjs +3 -2
  36. package/dist/application/utils/widgetTextFromApiWidgetTextResponse.js +3 -2
  37. package/dist/atoms/app/index.cjs +1 -1
  38. package/dist/atoms/app/index.d.cts +7 -7
  39. package/dist/atoms/app/index.d.ts +7 -7
  40. package/dist/atoms/app/index.js +1 -1
  41. package/dist/atoms/app/variant.cjs +2 -2
  42. package/dist/atoms/app/variant.d.cts +7 -7
  43. package/dist/atoms/app/variant.d.ts +7 -7
  44. package/dist/atoms/app/variant.js +2 -2
  45. package/dist/atoms/chat/chatState.d.cts +19 -19
  46. package/dist/atoms/chat/chatState.d.ts +19 -19
  47. package/dist/atoms/chat/form.d.cts +2 -2
  48. package/dist/atoms/chat/form.d.ts +2 -2
  49. package/dist/atoms/chat/index.cjs +1 -1
  50. package/dist/atoms/chat/index.d.cts +2 -2
  51. package/dist/atoms/chat/index.d.ts +2 -2
  52. package/dist/atoms/chat/index.js +1 -1
  53. package/dist/atoms/chat/lastMessage.d.cts +2 -2
  54. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  55. package/dist/atoms/chat/messageQueue.d.cts +6 -6
  56. package/dist/atoms/chat/messageQueue.d.ts +6 -6
  57. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  58. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  59. package/dist/atoms/chat/renderedWidgetRefs.d.cts +2 -2
  60. package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
  61. package/dist/atoms/chat/replies.d.cts +3 -3
  62. package/dist/atoms/chat/replies.d.ts +2 -2
  63. package/dist/atoms/chat/suggestions.d.cts +2 -2
  64. package/dist/atoms/chat/suggestions.d.ts +2 -2
  65. package/dist/atoms/envive/enviveConfig.cjs +7 -4
  66. package/dist/atoms/envive/enviveConfig.d.cts +16 -14
  67. package/dist/atoms/envive/enviveConfig.d.ts +16 -14
  68. package/dist/atoms/envive/enviveConfig.js +6 -4
  69. package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
  70. package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
  71. package/dist/atoms/org/customerService.d.cts +6 -6
  72. package/dist/atoms/org/customerService.d.ts +6 -6
  73. package/dist/atoms/org/graphqlConfig.cjs +1 -1
  74. package/dist/atoms/org/graphqlConfig.d.cts +6 -6
  75. package/dist/atoms/org/graphqlConfig.d.ts +6 -6
  76. package/dist/atoms/org/graphqlConfig.js +1 -1
  77. package/dist/atoms/org/index.cjs +1 -1
  78. package/dist/atoms/org/index.js +1 -1
  79. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  80. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  81. package/dist/atoms/org/orgAnalyticsConfig.d.cts +4 -4
  82. package/dist/atoms/org/orgAnalyticsConfig.d.ts +4 -4
  83. package/dist/atoms/search/chatSearch.cjs +1 -1
  84. package/dist/atoms/search/chatSearch.d.cts +17 -17
  85. package/dist/atoms/search/chatSearch.d.ts +17 -17
  86. package/dist/atoms/search/chatSearch.js +1 -1
  87. package/dist/atoms/search/searchAPI.cjs +1 -1
  88. package/dist/atoms/search/searchAPI.d.cts +13 -13
  89. package/dist/atoms/search/searchAPI.d.ts +13 -13
  90. package/dist/atoms/search/searchAPI.js +1 -1
  91. package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
  92. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  93. package/dist/contexts/enviveConfigContext/enviveConfigContext.cjs +6 -4
  94. package/dist/contexts/enviveConfigContext/enviveConfigContext.d.cts +3 -1
  95. package/dist/contexts/enviveConfigContext/enviveConfigContext.d.ts +3 -1
  96. package/dist/contexts/enviveConfigContext/enviveConfigContext.js +6 -4
  97. package/dist/contexts/enviveContext/enviveContext.cjs +7 -3
  98. package/dist/contexts/enviveContext/enviveContext.d.cts +5 -1
  99. package/dist/contexts/enviveContext/enviveContext.d.ts +5 -1
  100. package/dist/contexts/enviveContext/enviveContext.js +7 -3
  101. package/dist/contexts/enviveContext/types.d.cts +1 -1
  102. package/dist/contexts/enviveContext/types.d.ts +1 -1
  103. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.cjs +2 -97
  104. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.d.cts +3 -25
  105. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.d.ts +3 -25
  106. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.js +3 -97
  107. package/dist/contexts/featureFlagServiceContext/index.cjs +2 -1
  108. package/dist/contexts/featureFlagServiceContext/index.d.cts +2 -1
  109. package/dist/contexts/featureFlagServiceContext/index.d.ts +2 -1
  110. package/dist/contexts/featureFlagServiceContext/index.js +2 -1
  111. package/dist/contexts/graphqlContext/mockV3Config.cjs +1 -1
  112. package/dist/contexts/graphqlContext/mockV3Config.js +1 -1
  113. package/dist/contexts/localStorageContext/index.cjs +2 -1
  114. package/dist/contexts/localStorageContext/index.d.cts +2 -1
  115. package/dist/contexts/localStorageContext/index.d.ts +2 -1
  116. package/dist/contexts/localStorageContext/index.js +2 -1
  117. package/dist/contexts/localStorageContext/localStorageContext.cjs +5 -11
  118. package/dist/contexts/localStorageContext/localStorageContext.d.cts +3 -7
  119. package/dist/contexts/localStorageContext/localStorageContext.d.ts +3 -7
  120. package/dist/contexts/localStorageContext/localStorageContext.js +3 -8
  121. package/dist/contexts/localStorageContext/localStorageKeys.cjs +12 -0
  122. package/dist/contexts/localStorageContext/localStorageKeys.d.cts +9 -0
  123. package/dist/contexts/localStorageContext/localStorageKeys.d.ts +9 -0
  124. package/dist/contexts/localStorageContext/localStorageKeys.js +11 -0
  125. package/dist/contexts/pageContext/pageContext.cjs +2 -2
  126. package/dist/contexts/pageContext/pageContext.js +2 -2
  127. package/dist/contexts/salesAgentContext/chatAPI.cjs +15 -4
  128. package/dist/contexts/salesAgentContext/chatAPI.d.cts +4 -1
  129. package/dist/contexts/salesAgentContext/chatAPI.d.ts +4 -1
  130. package/dist/contexts/salesAgentContext/chatAPI.js +16 -5
  131. package/dist/contexts/salesAgentContext/salesAgentContext.cjs +15 -4
  132. package/dist/contexts/salesAgentContext/salesAgentContext.d.cts +2 -1
  133. package/dist/contexts/salesAgentContext/salesAgentContext.d.ts +2 -1
  134. package/dist/contexts/salesAgentContext/salesAgentContext.js +16 -5
  135. package/dist/contexts/salesAgentContext/salesAgentService.cjs +12 -4
  136. package/dist/contexts/salesAgentContext/salesAgentService.js +12 -4
  137. package/dist/contexts/searchContext/searchContext.cjs +1 -1
  138. package/dist/contexts/searchContext/searchContext.js +1 -1
  139. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
  140. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.ts +2 -2
  141. package/dist/contexts/types.cjs +1 -1
  142. package/dist/contexts/types.d.cts +3 -1
  143. package/dist/contexts/types.d.ts +3 -1
  144. package/dist/contexts/types.js +1 -1
  145. package/dist/contexts/typesV3.cjs +1 -1
  146. package/dist/contexts/typesV3.d.cts +11 -2
  147. package/dist/contexts/typesV3.d.ts +12 -3
  148. package/dist/contexts/typesV3.js +1 -1
  149. package/dist/contexts/widgetConfigContext/widgetConfigContext.cjs +1 -1
  150. package/dist/contexts/widgetConfigContext/widgetConfigContext.js +1 -1
  151. package/dist/hooks/AddToCart/addToCartErrorMessage.cjs +7 -0
  152. package/dist/hooks/AddToCart/addToCartErrorMessage.d.cts +5 -0
  153. package/dist/hooks/AddToCart/addToCartErrorMessage.d.ts +5 -0
  154. package/dist/hooks/AddToCart/addToCartErrorMessage.js +6 -0
  155. package/dist/hooks/AddToCart/createAddToCartUserEvent.cjs +20 -0
  156. package/dist/hooks/AddToCart/createAddToCartUserEvent.d.cts +17 -0
  157. package/dist/hooks/AddToCart/createAddToCartUserEvent.d.ts +17 -0
  158. package/dist/hooks/AddToCart/createAddToCartUserEvent.js +19 -0
  159. package/dist/hooks/AddToCart/index.cjs +9 -0
  160. package/dist/hooks/AddToCart/index.d.cts +5 -0
  161. package/dist/hooks/AddToCart/index.d.ts +5 -0
  162. package/dist/hooks/AddToCart/index.js +6 -0
  163. package/dist/hooks/AddToCart/parseAddToCartFunction.cjs +27 -0
  164. package/dist/hooks/AddToCart/parseAddToCartFunction.d.cts +13 -0
  165. package/dist/hooks/AddToCart/parseAddToCartFunction.d.ts +13 -0
  166. package/dist/hooks/AddToCart/parseAddToCartFunction.js +27 -0
  167. package/dist/hooks/AddToCart/useAddToCart.cjs +24 -0
  168. package/dist/hooks/AddToCart/useAddToCart.d.cts +14 -0
  169. package/dist/hooks/AddToCart/useAddToCart.d.ts +14 -0
  170. package/dist/hooks/AddToCart/useAddToCart.js +23 -0
  171. package/dist/hooks/ChatToggle/useChatToggle.cjs +1 -1
  172. package/dist/hooks/ChatToggle/useChatToggle.js +1 -1
  173. package/dist/hooks/LocalStorageOperations/useLocalStorageOperations.cjs +6 -5
  174. package/dist/hooks/LocalStorageOperations/useLocalStorageOperations.js +3 -2
  175. package/dist/hooks/PageViewedEvent/usePageViewedEvent.cjs +6 -3
  176. package/dist/hooks/PageViewedEvent/usePageViewedEvent.js +6 -3
  177. package/dist/hooks/ProductDetails/index.cjs +9 -0
  178. package/dist/hooks/ProductDetails/index.d.cts +4 -0
  179. package/dist/hooks/ProductDetails/index.d.ts +4 -0
  180. package/dist/hooks/ProductDetails/index.js +4 -0
  181. package/dist/hooks/ProductDetails/useProductDetails.cjs +53 -0
  182. package/dist/hooks/ProductDetails/useProductDetails.d.cts +20 -0
  183. package/dist/hooks/ProductDetails/useProductDetails.d.ts +20 -0
  184. package/dist/hooks/ProductDetails/useProductDetails.js +52 -0
  185. package/dist/hooks/ProductDetails/variantSelection.cjs +42 -0
  186. package/dist/hooks/ProductDetails/variantSelection.d.cts +35 -0
  187. package/dist/hooks/ProductDetails/variantSelection.d.ts +35 -0
  188. package/dist/hooks/ProductDetails/variantSelection.js +38 -0
  189. package/dist/hooks/Search/useSearch.cjs +3 -3
  190. package/dist/hooks/Search/useSearch.d.cts +1 -1
  191. package/dist/hooks/Search/useSearch.d.ts +1 -1
  192. package/dist/hooks/Search/useSearch.js +3 -3
  193. package/dist/hooks/SystemSettingsContext/useSystemSettingsContext.d.cts +2 -2
  194. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +11 -2
  195. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.cts +1 -10
  196. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.d.ts +1 -10
  197. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +11 -2
  198. package/dist/hooks/WidgetInteraction/types.cjs +13 -3
  199. package/dist/hooks/WidgetInteraction/types.d.cts +29 -4
  200. package/dist/hooks/WidgetInteraction/types.d.ts +29 -4
  201. package/dist/hooks/WidgetInteraction/types.js +13 -3
  202. package/dist/hooks/WidgetInteraction/utils.cjs +2 -2
  203. package/dist/hooks/WidgetInteraction/utils.js +2 -2
  204. package/dist/hooks/utils.d.cts +1 -1
  205. package/dist/hooks/utils.d.ts +1 -1
  206. package/dist/services/amplitudeService/amplitudeService.cjs +18 -11
  207. package/dist/services/amplitudeService/amplitudeService.d.cts +5 -2
  208. package/dist/services/amplitudeService/amplitudeService.d.ts +5 -2
  209. package/dist/services/amplitudeService/amplitudeService.js +16 -8
  210. package/dist/services/enviveConfigService/enviveConfigService.cjs +36 -4
  211. package/dist/services/enviveConfigService/enviveConfigService.d.cts +4 -1
  212. package/dist/services/enviveConfigService/enviveConfigService.d.ts +4 -1
  213. package/dist/services/enviveConfigService/enviveConfigService.js +35 -3
  214. package/dist/services/enviveConfigService/fetchGraphQLConfig.cjs +9 -8
  215. package/dist/services/enviveConfigService/fetchGraphQLConfig.js +9 -7
  216. package/dist/services/featureFlagService/index.cjs +101 -0
  217. package/dist/services/featureFlagService/index.d.cts +28 -0
  218. package/dist/services/featureFlagService/index.d.ts +28 -0
  219. package/dist/services/featureFlagService/index.js +101 -0
  220. package/dist/services/ga4ProjectionService/ga4EventSchema.cjs +8 -2
  221. package/dist/services/ga4ProjectionService/ga4EventSchema.js +8 -2
  222. package/dist/services/hardcopyService/hardcopyService.cjs +4 -3
  223. package/dist/services/hardcopyService/hardcopyService.d.cts +1 -1
  224. package/dist/services/hardcopyService/hardcopyService.d.ts +1 -1
  225. package/dist/services/hardcopyService/hardcopyService.js +4 -3
  226. package/dist/services/urlResolverWarmupService/index.cjs +54 -0
  227. package/dist/services/urlResolverWarmupService/index.d.cts +40 -0
  228. package/dist/services/urlResolverWarmupService/index.d.ts +40 -0
  229. package/dist/services/urlResolverWarmupService/index.js +54 -0
  230. package/dist/types/enviveConfig.d.cts +6 -2
  231. package/dist/types/enviveConfig.d.ts +6 -2
  232. package/dist/types/index.d.cts +2 -2
  233. package/dist/types/index.d.ts +2 -2
  234. package/package.json +18 -2
  235. package/src/application/__tests__/commerceApiAddToCartRehydration.test.ts +126 -0
  236. package/src/application/commerce-api.ts +142 -23
  237. package/src/application/models/api/userEvent.ts +8 -1
  238. package/src/application/models/api/widgetText.ts +3 -0
  239. package/src/application/models/featureGates.ts +5 -0
  240. package/src/application/models/graphql/queries/getProductOptionsQuery.ts +59 -0
  241. package/src/application/models/validators/__tests__/validateUserEvent.test.ts +41 -0
  242. package/src/application/models/validators/validateResponse.ts +1 -0
  243. package/src/application/models/validators/validateUserEvent.ts +7 -2
  244. package/src/application/utils/__tests__/messageFromAddToCartEvent.test.ts +109 -0
  245. package/src/application/utils/index.ts +1 -0
  246. package/src/application/utils/messageFromAddToCartEvent.ts +32 -0
  247. package/src/application/utils/widgetTextFromApiWidgetTextResponse.ts +1 -0
  248. package/src/atoms/app/index.ts +1 -1
  249. package/src/atoms/app/variant.ts +1 -1
  250. package/src/atoms/envive/enviveConfig.ts +6 -1
  251. package/src/atoms/org/graphqlConfig.ts +1 -1
  252. package/src/contexts/enviveConfigContext/enviveConfigContext.tsx +5 -0
  253. package/src/contexts/enviveContext/enviveContext.tsx +10 -0
  254. package/src/contexts/featureFlagServiceContext/featureFlagServiceContext.tsx +6 -173
  255. package/src/contexts/localStorageContext/localStorageContext.tsx +2 -5
  256. package/src/contexts/localStorageContext/localStorageKeys.ts +8 -0
  257. package/src/contexts/pageContext/pageContext.tsx +1 -1
  258. package/src/contexts/salesAgentContext/__tests__/chatAPI.test.tsx +63 -0
  259. package/src/contexts/salesAgentContext/chatAPI.ts +20 -1
  260. package/src/contexts/salesAgentContext/salesAgentContext.tsx +17 -0
  261. package/src/contexts/salesAgentContext/salesAgentService.ts +12 -3
  262. package/src/contexts/types.ts +2 -0
  263. package/src/contexts/typesV3.ts +14 -0
  264. package/src/hooks/AddToCart/__tests__/parseAddToCartFunction.test.ts +31 -0
  265. package/src/hooks/AddToCart/__tests__/useAddToCart.test.tsx +204 -0
  266. package/src/hooks/AddToCart/addToCartErrorMessage.ts +2 -0
  267. package/src/hooks/AddToCart/createAddToCartUserEvent.ts +25 -0
  268. package/src/hooks/AddToCart/index.ts +4 -0
  269. package/src/hooks/AddToCart/parseAddToCartFunction.ts +31 -0
  270. package/src/hooks/AddToCart/useAddToCart.ts +35 -0
  271. package/src/hooks/PageViewedEvent/__tests__/usePageViewedEvent.test.ts +4 -4
  272. package/src/hooks/PageViewedEvent/usePageViewedEvent.ts +5 -1
  273. package/src/hooks/ProductDetails/__tests__/useProductDetails.test.tsx +129 -0
  274. package/src/hooks/ProductDetails/__tests__/variantSelection.test.ts +145 -0
  275. package/src/hooks/ProductDetails/index.ts +2 -0
  276. package/src/hooks/ProductDetails/useProductDetails.ts +62 -0
  277. package/src/hooks/ProductDetails/variantSelection.ts +61 -0
  278. package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +15 -0
  279. package/src/hooks/WidgetInteraction/types.ts +43 -2
  280. package/src/hooks/WidgetInteraction/utils.ts +1 -1
  281. package/src/services/amplitudeService/__tests__/amplitudeService.test.ts +8 -8
  282. package/src/services/amplitudeService/amplitudeService.ts +43 -23
  283. package/src/services/enviveConfigService/__tests__/enviveConfigService.test.ts +208 -0
  284. package/src/services/enviveConfigService/enviveConfigService.ts +62 -2
  285. package/src/services/enviveConfigService/fetchGraphQLConfig.ts +17 -6
  286. package/src/services/featureFlagService/index.ts +176 -0
  287. package/src/services/ga4ProjectionService/__tests__/ga4ProjectionService.test.ts +36 -0
  288. package/src/services/ga4ProjectionService/ga4EventSchema.ts +7 -0
  289. package/src/services/hardcopyService/__tests__/hardcopyService.test.ts +47 -1
  290. package/src/services/hardcopyService/hardcopyService.ts +5 -0
  291. package/src/services/urlResolverWarmupService/__tests__/urlResolverWarmupService.test.ts +85 -0
  292. package/src/services/urlResolverWarmupService/index.ts +89 -0
  293. package/src/types/enviveConfig.ts +3 -0
@@ -1,178 +1,11 @@
1
1
  import React, { ReactNode, createContext, useContext, useMemo } from 'react';
2
- import { OrgConfigFeatureGate } from 'src/application/models/api/orgConfigResults';
3
- import { FeatureGates } from 'src/application/models/featureGates';
4
- import Logger from 'src/application/logging/logger';
2
+ import { FeatureFlagService, FullFlagValue } from 'src/services/featureFlagService';
5
3
 
6
- const FEATURE_FLAGS_STORAGE_KEY = 'spiffy-feature-flags';
7
-
8
- const logger = new Logger('featureFlagServiceContext');
9
-
10
- export interface FullFlagValue {
11
- resolved: boolean;
12
- initial: boolean | undefined;
13
- query_param_override: boolean | undefined;
14
- window_param_override: boolean | undefined;
15
- stored_override: boolean | undefined;
16
- }
17
-
18
- // This is the class that was previously implicitly used or defined elsewhere
19
- export class FeatureFlagService {
20
- private featureGates: OrgConfigFeatureGate[];
21
-
22
- constructor(featureGates: OrgConfigFeatureGate[]) {
23
- this.featureGates = featureGates;
24
- }
25
-
26
- static getQueryParamFeatureGateOverrides(featureGate: FeatureGates): boolean | undefined {
27
- const url = window.location?.href ?? '';
28
- if (url.includes('?')) {
29
- const urlObj = new URL(url);
30
- const params = new URLSearchParams(urlObj.search);
31
- const paramsObj = Object.fromEntries(params.entries());
32
- const value = paramsObj[featureGate];
33
-
34
- if (value != null) {
35
- return value === 'true';
36
- }
37
- }
38
-
39
- return undefined;
40
- }
41
-
42
- static getWindowFeatureGateOverrides(featureGate: FeatureGates): boolean | undefined {
43
- if (typeof window === 'undefined') {
44
- return undefined;
45
- }
46
- const windowOverride = window as Window & {
47
- _envive?: { featureOverrides?: Record<string, boolean> };
48
- _spiffy?: { featureOverrides?: Record<string, boolean> };
49
- };
50
- const featureOverrides =
51
- windowOverride?._envive?.featureOverrides ?? windowOverride?._spiffy?.featureOverrides;
52
- const value = featureOverrides?.[String(featureGate)];
53
- if (value != null) {
54
- return value;
55
- }
56
- return undefined;
57
- }
58
-
59
- static getStoredFeatureGateOverrides(featureGate: FeatureGates): boolean | undefined {
60
- if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
61
- return undefined;
62
- }
63
- try {
64
- const featureFlags = window.localStorage.getItem(FEATURE_FLAGS_STORAGE_KEY);
65
- if (!featureFlags) {
66
- return undefined;
67
- }
68
- const featureFlagsObj = JSON.parse(featureFlags) as Record<string, boolean>;
69
- return featureFlagsObj[featureGate];
70
- } catch (err) {
71
- logger.logError('getStoredFeatureGateOverrides: error parsing feature flags', err);
72
- return undefined;
73
- }
74
- }
75
-
76
- static getFeatureFlagOverride(featureGate: FeatureGates): boolean | undefined {
77
- const queryOverride = FeatureFlagService.getQueryParamFeatureGateOverrides(featureGate);
78
- if (queryOverride != null) {
79
- logger.logDebug(
80
- `isFeatureGateEnabled featureGate:${featureGate} overridden by URL param. New value: ${queryOverride}`,
81
- );
82
- return queryOverride;
83
- }
84
-
85
- const windowOverride = FeatureFlagService.getWindowFeatureGateOverrides(featureGate);
86
- if (windowOverride != null) {
87
- logger.logDebug(
88
- `isFeatureGateEnabled featureGate:${featureGate} overridden by window param. New value: ${windowOverride}`,
89
- );
90
- return windowOverride;
91
- }
92
-
93
- const storedOverride = FeatureFlagService.getStoredFeatureGateOverrides(featureGate);
94
- if (storedOverride != null) {
95
- logger.logDebug(
96
- `isFeatureGateEnabled featureGate:${featureGate} overridden by stored value. New value: ${storedOverride}`,
97
- );
98
- return storedOverride;
99
- }
100
-
101
- return undefined;
102
- }
103
-
104
- isFeatureGateEnabled = (featureGate: FeatureGates): boolean => {
105
- const gateValue = this.featureGates.find(gate => gate.name === featureGate);
106
-
107
- const featureFlagOverride = FeatureFlagService.getFeatureFlagOverride(featureGate);
108
- if (featureFlagOverride !== undefined) {
109
- return featureFlagOverride;
110
- }
111
-
112
- if (gateValue == null || gateValue.value == null) {
113
- logger.logDebug(
114
- `isFeatureGateEnabled featureGate:${featureGate} value is undefined - returning false`,
115
- );
116
- return false;
117
- }
118
- return gateValue.value;
119
- };
120
-
121
- // Ensure that standard overrides work as expected
122
- isClientSessionEnabled = (): boolean =>
123
- this.isFeatureGateEnabled(FeatureGates.IsClientSessionEnabled);
124
-
125
- getFeatureFlags = (): Record<string, boolean> => {
126
- return Object.fromEntries(
127
- Object.values(FeatureGates).map((featureGate: FeatureGates) => [
128
- featureGate,
129
- this.isFeatureGateEnabled(featureGate),
130
- ]),
131
- );
132
- };
133
-
134
- getFullFlagValue = (featureGate: FeatureGates): FullFlagValue => {
135
- const gateValue = this.featureGates.find(gate => gate.name === featureGate);
136
- return {
137
- resolved: this.isFeatureGateEnabled(featureGate),
138
- initial: gateValue?.value,
139
- query_param_override: FeatureFlagService.getQueryParamFeatureGateOverrides(featureGate),
140
- window_param_override: FeatureFlagService.getWindowFeatureGateOverrides(featureGate),
141
- stored_override: FeatureFlagService.getStoredFeatureGateOverrides(featureGate),
142
- };
143
- };
144
-
145
- getFullFlagValues = (): Record<string, FullFlagValue> => {
146
- const featureGates = Object.values(FeatureGates).map((featureGate: FeatureGates) => [
147
- featureGate,
148
- this.getFullFlagValue(featureGate),
149
- ]);
150
-
151
- return featureGates.length > 0
152
- ? featureGates.reduce<Record<string, FullFlagValue>>((acc, entry) => {
153
- const [name, value] = entry as [FeatureGates, FullFlagValue];
154
- if (name && value != null) {
155
- return { ...acc, [`feature_gate.${name}`]: value };
156
- }
157
- return acc;
158
- }, {})
159
- : {};
160
- };
161
-
162
- static persistFeatureGateOverrides(): void {
163
- if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
164
- return;
165
- }
166
- const overrides = Object.values(FeatureGates)
167
- .map(
168
- featureGate =>
169
- [featureGate, FeatureFlagService.getFeatureFlagOverride(featureGate)] as const,
170
- )
171
- .filter(([, value]) => value !== undefined)
172
- .reduce<Record<string, boolean>>((acc, [key, value]) => ({ ...acc, [key]: value! }), {});
173
- window.localStorage.setItem(FEATURE_FLAGS_STORAGE_KEY, JSON.stringify(overrides));
174
- }
175
- }
4
+ // The service class lives in src/services/featureFlagService (a React-free
5
+ // module the injection bundle can import pre-config); re-export it here so
6
+ // existing imports keep working.
7
+ export { FeatureFlagService };
8
+ export type { FullFlagValue };
176
9
 
177
10
  export interface FeatureFlagContextType {
178
11
  featureFlagService: FeatureFlagService | undefined;
@@ -1,12 +1,9 @@
1
1
  import React, { createContext, useCallback, useContext, useEffect, useMemo } from 'react';
2
2
  import Logger from 'src/application/logging/logger';
3
3
  import { LocalStorageEventListener } from 'src/application/models/localStorageEventListener';
4
+ import { LocalStorageKeys } from './localStorageKeys';
4
5
 
5
- export enum LocalStorageKeys {
6
- ChatId = 'v1-spiffy-chat-session-id',
7
- SpiffyOnOverride = 'spiffy_on',
8
- EnviveOnOverride = 'envive_on',
9
- }
6
+ export { LocalStorageKeys };
10
7
 
11
8
  interface LocalStorageContextType {
12
9
  setItem: (key: string, value: string) => void;
@@ -0,0 +1,8 @@
1
+ // Kept React-free: pre-config injection-bundle code (atoms/envive) needs
2
+ // these keys, and importing them through localStorageContext.tsx would pull
3
+ // React into the eager critical chunk.
4
+ export enum LocalStorageKeys {
5
+ ChatId = 'v1-spiffy-chat-session-id',
6
+ SpiffyOnOverride = 'spiffy_on',
7
+ EnviveOnOverride = 'envive_on',
8
+ }
@@ -176,7 +176,7 @@ export const PageProvider: React.FC<{
176
176
  pageId = variantInfo.url;
177
177
  break;
178
178
  case VariantTypeEnum.FullPageSalesAgent:
179
- pageType = 'full_page_sales_agent';
179
+ pageType = 'adaptive_storefront';
180
180
  pageId = variantInfo.url;
181
181
  break;
182
182
  default:
@@ -0,0 +1,63 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { Provider, createStore } from 'jotai';
3
+ import React from 'react';
4
+ import { Message, MessageRole, MessageType } from 'src/application/models/message';
5
+ import { messagesAtom } from 'src/atoms/chat/chatState';
6
+ import { useSalesAgentChatAPI } from '../chatAPI';
7
+
8
+ const mockTrackEvent = vi.fn();
9
+ vi.mock('../../amplitudeContext', async () => {
10
+ const actual = await vi.importActual('../../amplitudeContext');
11
+ return {
12
+ ...actual,
13
+ useAmplitude: () => ({ trackEvent: mockTrackEvent }),
14
+ };
15
+ });
16
+
17
+ vi.mock('src/hooks/WidgetInteraction', () => ({
18
+ useWidgetInteraction: () => ({ trackWidgetInteraction: vi.fn() }),
19
+ }));
20
+
21
+ const asTextMessage = (message: Message) => message as Extract<Message, { type: MessageType.Text }>;
22
+
23
+ const renderChatAPI = (store: ReturnType<typeof createStore>) => {
24
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
25
+ // The jotai Provider is typed against the root @types/react (18) while this
26
+ // package resolves @types/react 19; the cast bridges the two ReactNode types.
27
+ <Provider store={store}>{children as React.ReactElement<unknown, string>}</Provider>
28
+ );
29
+ return renderHook(() => useSalesAgentChatAPI(), { wrapper });
30
+ };
31
+
32
+ describe('useSalesAgentChatAPI.injectAssistantMessage', () => {
33
+ it('appends an assistant text message turn onto messagesAtom', () => {
34
+ const store = createStore();
35
+ const { result } = renderChatAPI(store);
36
+
37
+ result.current.injectAssistantMessage("I'm sorry, something went wrong.");
38
+
39
+ const messages = store.get(messagesAtom);
40
+ expect(messages).toHaveLength(1);
41
+ expect(messages[0]).toHaveLength(1);
42
+ expect(messages[0][0]).toMatchObject({
43
+ role: MessageRole.Assistant,
44
+ type: MessageType.Text,
45
+ metadata: { content: "I'm sorry, something went wrong." },
46
+ });
47
+ expect(messages[0][0].id).toEqual(expect.any(String));
48
+ expect(messages[0][0].createdAt).toEqual(expect.any(String));
49
+ });
50
+
51
+ it('appends subsequent messages as additional turns without clobbering prior ones', () => {
52
+ const store = createStore();
53
+ const { result } = renderChatAPI(store);
54
+
55
+ result.current.injectAssistantMessage('first');
56
+ result.current.injectAssistantMessage('second');
57
+
58
+ const messages = store.get(messagesAtom);
59
+ expect(messages).toHaveLength(2);
60
+ expect(asTextMessage(messages[0][0]).metadata).toMatchObject({ content: 'first' });
61
+ expect(asTextMessage(messages[1][0]).metadata).toMatchObject({ content: 'second' });
62
+ });
63
+ });
@@ -6,6 +6,7 @@ import Logger from 'src/application/logging/logger';
6
6
  import {
7
7
  ChatElementDisplayLocationV3,
8
8
  FormSubmittedAttributes,
9
+ Message,
9
10
  MessageRole,
10
11
  MessageType,
11
12
  Suggestion,
@@ -14,7 +15,7 @@ import {
14
15
  import { hasParsedVariantInfoAtom } from 'src/atoms/app';
15
16
  import { analyticsContextAtom } from 'src/atoms/app/variant';
16
17
  import { formSubmitAtom, replyEventCategoryAtom } from 'src/atoms/chat';
17
- import { suggestionsAtom } from 'src/atoms/chat/chatState';
18
+ import { messagesAtom, suggestionsAtom } from 'src/atoms/chat/chatState';
18
19
  import { queueUserEventAtom } from 'src/atoms/chat/messageQueue';
19
20
  import { useWidgetInteraction } from 'src/hooks/WidgetInteraction';
20
21
  import {
@@ -49,6 +50,8 @@ export interface SalesAgentChatAPI {
49
50
  displayLocation: ChatElementDisplayLocationV3;
50
51
  }) => void;
51
52
  onFormResponseSubmitted: (form: FormSubmittedAttributes) => void;
53
+ /** Injects a synthetic assistant text message into the chat transcript, e.g. to surface a failure. */
54
+ injectAssistantMessage: (content: string) => void;
52
55
  }
53
56
 
54
57
  export const useSalesAgentChatAPI = (widget?: WidgetInteractionComponent) => {
@@ -60,6 +63,7 @@ export const useSalesAgentChatAPI = (widget?: WidgetInteractionComponent) => {
60
63
  const setSuggestions = useSetAtom(suggestionsAtom);
61
64
  const setReplyEventCategory = useSetAtom(replyEventCategoryAtom);
62
65
  const setFormSubmit = useSetAtom(formSubmitAtom);
66
+ const setMessages = useSetAtom(messagesAtom);
63
67
  const { trackEvent } = useAmplitude();
64
68
  const { trackWidgetInteraction } = useWidgetInteraction();
65
69
  const contextWidget = widget || WidgetInteractionComponent.FLOATING_CHAT;
@@ -260,11 +264,26 @@ export const useSalesAgentChatAPI = (widget?: WidgetInteractionComponent) => {
260
264
  [setReplyEventCategory, setFormSubmit, trackEvent, context, queueUserEvent],
261
265
  );
262
266
 
267
+ const injectAssistantMessage = useCallback(
268
+ (content: string) => {
269
+ const message: Message = {
270
+ id: uuid(),
271
+ role: MessageRole.Assistant,
272
+ type: MessageType.Text,
273
+ createdAt: new Date().toISOString(),
274
+ metadata: { content },
275
+ };
276
+ setMessages(current => [...current, [message]]);
277
+ },
278
+ [setMessages],
279
+ );
280
+
263
281
  return {
264
282
  logPageVisit,
265
283
  logUserEvent,
266
284
  onSuggestionClicked,
267
285
  onTypedMessageSubmitted,
268
286
  onFormResponseSubmitted,
287
+ injectAssistantMessage,
269
288
  };
270
289
  };
@@ -4,12 +4,14 @@ import { ReactNode, createContext, useCallback, useEffect, useMemo, useRef, useS
4
4
  import Logger from 'src/application/logging/logger';
5
5
  import { NextMessageRequest, Suggestion, UserEvent } from 'src/application/models';
6
6
  import { Message, MessageRole, MessageType } from 'src/application/models/message';
7
+ import { messageFromAddToCartEvent } from 'src/application/utils';
7
8
  import {
8
9
  initializedAtom,
9
10
  messagesAtom,
10
11
  pendingResponseAtom,
11
12
  responseStreamingAtom,
12
13
  suggestionsAtom,
14
+ suggestionsLoadingAtom,
13
15
  } from 'src/atoms/chat/chatState';
14
16
  import {
15
17
  processUserEventAtom,
@@ -36,6 +38,9 @@ export interface SalesAgent extends SalesAgentChatAPI {
36
38
  isResponseStreaming: boolean;
37
39
  // True while the response is pending from the backend
38
40
  isPendingResponse: boolean;
41
+ // True while suggestions for the most recent message are being fetched (separate from the
42
+ // message response itself — suggestions are requested as a follow-up call once it completes)
43
+ isLoadingSuggestions: boolean;
39
44
  // True when the sales agent is initialized
40
45
  isInitialized: boolean;
41
46
  }
@@ -149,6 +154,12 @@ export const SalesAgentProvider: React.FC<SalesAgentProviderProps> = ({
149
154
  };
150
155
  setMessages(current => [...current, [newMessage]]);
151
156
  }
157
+ if (userEvent && userEvent.category === UserEventCategory.AddToCart) {
158
+ const newMessage = messageFromAddToCartEvent(userEvent);
159
+ if (newMessage) {
160
+ setMessages(current => [...current, [newMessage]]);
161
+ }
162
+ }
152
163
  if (userEvent && userEvent.category === UserEventCategory.FormSubmitted) {
153
164
  const { formResponseId, filledSchema } = userEvent.attributes;
154
165
  setMessages(prevMessagesState => {
@@ -227,6 +238,7 @@ export const useSalesAgent = (contextWidget?: WidgetInteractionComponent): Sales
227
238
  const pendingMessages = useAtomValue(userEventQueueAtom);
228
239
  const isResponseStreaming = useAtomValue(responseStreamingAtom);
229
240
  const isPendingResponse = useAtomValue(pendingResponseAtom);
241
+ const isLoadingSuggestions = useAtomValue(suggestionsLoadingAtom);
230
242
  const isInitialized = useAtomValue(initializedAtom);
231
243
  const {
232
244
  logPageVisit,
@@ -234,6 +246,7 @@ export const useSalesAgent = (contextWidget?: WidgetInteractionComponent): Sales
234
246
  onSuggestionClicked,
235
247
  onTypedMessageSubmitted,
236
248
  onFormResponseSubmitted,
249
+ injectAssistantMessage,
237
250
  } = useSalesAgentChatAPI(contextWidget);
238
251
 
239
252
  const salesAgent: SalesAgent = useMemo(() => {
@@ -244,11 +257,13 @@ export const useSalesAgent = (contextWidget?: WidgetInteractionComponent): Sales
244
257
  isInitialized,
245
258
  isResponseStreaming,
246
259
  isPendingResponse,
260
+ isLoadingSuggestions,
247
261
  logPageVisit,
248
262
  logUserEvent,
249
263
  onSuggestionClicked,
250
264
  onTypedMessageSubmitted,
251
265
  onFormResponseSubmitted,
266
+ injectAssistantMessage,
252
267
  };
253
268
  }, [
254
269
  messages,
@@ -257,11 +272,13 @@ export const useSalesAgent = (contextWidget?: WidgetInteractionComponent): Sales
257
272
  isInitialized,
258
273
  isResponseStreaming,
259
274
  isPendingResponse,
275
+ isLoadingSuggestions,
260
276
  logPageVisit,
261
277
  logUserEvent,
262
278
  onSuggestionClicked,
263
279
  onTypedMessageSubmitted,
264
280
  onFormResponseSubmitted,
281
+ injectAssistantMessage,
265
282
  ]);
266
283
  return salesAgent;
267
284
  };
@@ -22,6 +22,7 @@ import {
22
22
  requestFailureAtom,
23
23
  responseStreamingAtom,
24
24
  suggestionsAtom,
25
+ suggestionsLoadingAtom,
25
26
  } from 'src/atoms/chat/chatState';
26
27
  import { useAmplitude } from 'src/contexts/amplitudeContext';
27
28
  import { useFeatureFlagService } from 'src/contexts/featureFlagServiceContext';
@@ -242,6 +243,7 @@ export const useSalesAgentService: () => SalesAgentService = () => {
242
243
  const setSuggestions = useSetAtom(suggestionsAtom);
243
244
  const setPendingResponse = useSetAtom(pendingResponseAtom);
244
245
  const setResponseStreaming = useSetAtom(responseStreamingAtom);
246
+ const setSuggestionsLoading = useSetAtom(suggestionsLoadingAtom);
245
247
  const analyticsContext = useAtomValue(analyticsContextAtom);
246
248
  const messageInterceptor = useMessageInterceptor();
247
249
  const { trackEvent } = useAmplitude();
@@ -285,6 +287,7 @@ export const useSalesAgentService: () => SalesAgentService = () => {
285
287
  let successfulResponse: boolean | undefined;
286
288
  setPendingResponse(true);
287
289
  setSuggestions([]);
290
+ setSuggestionsLoading(true);
288
291
  const stream = CommerceApiClient.getNextResponseStreaming(payload);
289
292
  let responseAnalytics: Record<string, unknown> | undefined;
290
293
  try {
@@ -360,6 +363,7 @@ export const useSalesAgentService: () => SalesAgentService = () => {
360
363
  // logPerfMetric,
361
364
  setRequestFailure,
362
365
  setSuggestions,
366
+ setSuggestionsLoading,
363
367
  messageInterceptor,
364
368
  setMessages,
365
369
  setPendingResponse,
@@ -368,10 +372,15 @@ export const useSalesAgentService: () => SalesAgentService = () => {
368
372
 
369
373
  const getSuggestions = useCallback(
370
374
  async (payload: NextMessageRequest): Promise<void> => {
371
- const newSuggestions = await CommerceApiClient.getNextSuggestions(payload);
372
- setSuggestions(newSuggestions);
375
+ setSuggestionsLoading(true);
376
+ try {
377
+ const newSuggestions = await CommerceApiClient.getNextSuggestions(payload);
378
+ setSuggestions(newSuggestions);
379
+ } finally {
380
+ setSuggestionsLoading(false);
381
+ }
373
382
  },
374
- [setSuggestions],
383
+ [setSuggestions, setSuggestionsLoading],
375
384
  );
376
385
 
377
386
  const hydrateMessages = useCallback(async () => {
@@ -13,6 +13,7 @@ import { ChatState } from 'src/types/custservice-types';
13
13
  import { SuggestionBarLocationForMetrics } from 'src/types/suggestionBarV2-types';
14
14
  import { TestProps } from 'src/types/test-types';
15
15
  import { OrgConfigFeatureGate } from 'src/application/models/api/orgConfigResults';
16
+ import type { AddToCartCallback } from 'src/types/enviveConfig';
16
17
  import { WidgetConfigV3, WidgetTypeV3 } from './typesV3';
17
18
  import type { HardcopyResponse } from './hardcopyContext/hardcopyContext';
18
19
 
@@ -939,6 +940,7 @@ export interface EnviveConfig {
939
940
  publicKey?: string;
940
941
  featureGates?: OrgConfigFeatureGate[];
941
942
  hardcopyOverride?: Partial<Record<WidgetTypeV3, HardcopyResponse>>;
943
+ addToCart?: AddToCartCallback;
942
944
  }
943
945
 
944
946
  export type {
@@ -111,6 +111,17 @@ export const enum ButtonColorsVars {
111
111
 
112
112
  type ImageAspectRatio = '1/1' | '3/4';
113
113
 
114
+ type AddToCartConfig = {
115
+ enabled: boolean;
116
+ // Colors are CSS variable strings, e.g. 'var(--envive-colors-background-dark)'
117
+ backgroundColor?: string;
118
+ textColor?: string;
119
+ ctaText?: string;
120
+ // Merchant-supplied JS function, as a string, used to invoke their cart.
121
+ // Ignored when window._spiffy/_envive.addToCart is set - the window variable always takes precedence.
122
+ addToCartFunction?: string;
123
+ };
124
+
114
125
  type ButtonColors = {
115
126
  lightButtonBackgroundColor: string;
116
127
  lightButtonBorderColor?: string;
@@ -193,6 +204,7 @@ type LookAndFeelConfig = {
193
204
  imageAspectRatio: ImageAspectRatio;
194
205
  buttonColors: ButtonColors;
195
206
  typography: Typography;
207
+ addToCart?: AddToCartConfig;
196
208
  };
197
209
 
198
210
  export enum ConfigVersionEnum {
@@ -385,6 +397,7 @@ interface FullPageSalesAgentWidgetV3Config extends BaseWidgetConfig<WidgetTypeV3
385
397
  mobileBackgroundImage?: string;
386
398
  desktopBackgroundImage?: string;
387
399
  backgroundColor?: string;
400
+ chatBackgroundColor?: string;
388
401
  suggestionButtonType?: PromptButtonVariant;
389
402
  }
390
403
 
@@ -402,6 +415,7 @@ type WidgetConfigV3 =
402
415
  | FullPageSalesAgentWidgetV3Config;
403
416
 
404
417
  export type {
418
+ AddToCartConfig,
405
419
  ChatPreviewComparisonWidgetV3Config,
406
420
  ChatPreviewLoadingWidgetV3Config,
407
421
  ChatPreviewWidgetV3Config,
@@ -0,0 +1,31 @@
1
+ import { parseAddToCartFunction } from '../parseAddToCartFunction';
2
+
3
+ describe('parseAddToCartFunction', () => {
4
+ it('returns undefined when no string is provided', () => {
5
+ expect(parseAddToCartFunction(undefined)).toBeUndefined();
6
+ });
7
+
8
+ it('parses a function-expression string into a callable', async () => {
9
+ const fn = parseAddToCartFunction('function(params) { globalThis.__testCalls = [params]; }');
10
+
11
+ expect(fn).toBeInstanceOf(Function);
12
+ fn?.({ product_id: 'abc' });
13
+ expect((globalThis as Record<string, unknown>).__testCalls).toEqual([{ product_id: 'abc' }]);
14
+ });
15
+
16
+ it('parses an arrow-function string into a callable', () => {
17
+ const fn = parseAddToCartFunction('(params) => { globalThis.__testCalls = [params]; }');
18
+
19
+ expect(fn).toBeInstanceOf(Function);
20
+ fn?.({ product_id: 'xyz' });
21
+ expect((globalThis as Record<string, unknown>).__testCalls).toEqual([{ product_id: 'xyz' }]);
22
+ });
23
+
24
+ it('returns undefined when the string has a syntax error', () => {
25
+ expect(parseAddToCartFunction('function( { broken')).toBeUndefined();
26
+ });
27
+
28
+ it('returns undefined when the string evaluates to a non-function value', () => {
29
+ expect(parseAddToCartFunction('42')).toBeUndefined();
30
+ });
31
+ });