@envive-ai/react-widgets-v3 0.3.13 → 0.3.15-beta.0

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 (143) hide show
  1. package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.cjs +65 -0
  2. package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.js +64 -0
  3. package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.cjs +64 -0
  4. package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.js +63 -0
  5. package/dist/CXIntegration/types.cjs +2 -0
  6. package/dist/CXIntegration/types.js +2 -0
  7. package/dist/CXIntegration/utils/functions.cjs +4 -0
  8. package/dist/CXIntegration/utils/functions.js +4 -0
  9. package/dist/hocs/withBaseWidget/types.d.cts +3 -3
  10. package/dist/hocs/withBaseWidget/types.d.ts +5 -3
  11. package/dist/hocs/withBaseWidget/withBaseWidget.d.cts +2 -2
  12. package/dist/hocs/withBaseWidget/withBaseWidget.d.ts +2 -2
  13. package/dist/hooks/dist/application/models/api/widgetText.d.cts +8 -0
  14. package/dist/hooks/dist/contexts/hardcopyContext/hardcopyContext.d.cts +12 -0
  15. package/dist/hooks/dist/contexts/types.d.cts +38 -0
  16. package/dist/hooks/dist/contexts/typesV3.d.cts +239 -0
  17. package/dist/hooks/dist/services/amplitudeService/eventNames.d.cts +43 -0
  18. package/dist/hooks/dist/types/customerService.d.cts +21 -0
  19. package/dist/packages/hooks/dist/application/models/api/orgConfigResults.d.ts +1 -0
  20. package/dist/packages/hooks/dist/application/models/api/widgetText.d.ts +8 -0
  21. package/dist/packages/hooks/dist/application/models/frontendConfig.d.ts +1 -0
  22. package/dist/packages/hooks/dist/contexts/amplitudeContext/amplitudeContext.d.ts +2 -0
  23. package/dist/packages/hooks/dist/contexts/amplitudeContext/index.d.ts +2 -0
  24. package/dist/packages/hooks/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.d.ts +2 -0
  25. package/dist/packages/hooks/dist/contexts/hardcopyContext/hardcopyContext.d.ts +14 -0
  26. package/dist/packages/hooks/dist/contexts/hardcopyContext/index.d.ts +1 -0
  27. package/dist/packages/hooks/dist/contexts/types.d.ts +42 -0
  28. package/dist/packages/hooks/dist/contexts/typesV3.d.ts +239 -0
  29. package/dist/packages/hooks/dist/services/amplitudeService/amplitudeService.d.ts +1 -0
  30. package/dist/packages/hooks/dist/services/amplitudeService/eventNames.d.ts +43 -0
  31. package/dist/packages/hooks/dist/types/customerService.d.ts +21 -0
  32. package/dist/packages/widgets/dist/SearchResults/SearchResults.d.ts +3 -2
  33. package/dist/packages/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts +2 -2
  34. package/dist/packages/widgets/dist/SearchZeroState/index.d.ts +2 -1
  35. package/dist/packages/widgets/dist/SearchZeroState/types.d.ts +2 -2
  36. package/dist/packages/widgets/dist/SuggestionBar/SuggestionBar.d.ts +3 -3
  37. package/dist/packages/widgets/dist/SuggestionButtonContainer/index.d.ts +2 -0
  38. package/dist/packages/widgets/dist/SuggestionButtonContainer/types.d.ts +3 -2
  39. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/api/response.d.ts +14 -0
  40. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/api/search.d.ts +15 -0
  41. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/camelCase.d.ts +73 -0
  42. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/camelCasedPropertiesDeep.d.ts +61 -0
  43. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/internal.d.ts +25 -0
  44. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/splitWords.d.ts +35 -0
  45. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/trim.d.ts +32 -0
  46. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/utilityTypes/unknownArray.d.ts +32 -0
  47. package/dist/packages/widgets/dist/packages/hooks/dist/application/models/variantInfo/variantInfo.d.ts +1 -0
  48. package/dist/packages/widgets/dist/packages/hooks/dist/atoms/search/searchAPI.d.ts +15 -0
  49. package/dist/packages/widgets/dist/packages/hooks/dist/contexts/hardcopyContext/hardcopyContext.d.ts +1 -0
  50. package/dist/packages/widgets/dist/packages/hooks/dist/contexts/types.d.ts +63 -0
  51. package/dist/packages/widgets/dist/packages/hooks/dist/hooks/Search/useSearch.d.ts +60 -0
  52. package/dist/packages/widgets/dist/packages/hooks/dist/hooks/utils.d.ts +13 -0
  53. package/dist/packages/widgets/dist/packages/hooks/dist/types/OrgInfo.d.ts +1 -0
  54. package/dist/packages/widgets/dist/packages/hooks/dist/types/index.d.ts +1 -0
  55. package/dist/packages/widgets/dist/packages/hooks/dist/types/search-filter-types.d.ts +28 -0
  56. package/dist/packages/widgets/dist/packages/hooks/dist/types/test-types.d.ts +10 -0
  57. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
  58. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
  59. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts +3 -3
  60. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
  61. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
  62. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.cjs +29 -5
  63. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
  64. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.ts +2 -2
  65. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.js +30 -6
  66. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.cts +2 -2
  67. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts +2 -2
  68. package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +19 -3
  69. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.cts +2 -2
  70. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.ts +2 -2
  71. package/dist/widgets/ProductCardWidget/ProductCardWidget.js +20 -4
  72. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.cjs +10 -0
  73. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
  74. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts +3 -3
  75. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.js +12 -2
  76. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.cjs +15 -10
  77. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
  78. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
  79. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.js +16 -11
  80. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.cts +2 -2
  81. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
  82. package/dist/widgets/SocialProofWidget/SocialProofWidget.cjs +10 -0
  83. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
  84. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
  85. package/dist/widgets/SocialProofWidget/SocialProofWidget.js +12 -2
  86. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.cjs +11 -0
  87. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
  88. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts +2 -2
  89. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.js +12 -1
  90. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.cts +2 -2
  91. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
  92. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.cjs +23 -3
  93. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.cts +3 -3
  94. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts +3 -3
  95. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.js +24 -4
  96. package/dist/widgets/dist/SearchResults/SearchResults.d.cts +3 -2
  97. package/dist/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.cts +2 -2
  98. package/dist/widgets/dist/SearchZeroState/types.d.cts +2 -2
  99. package/dist/widgets/dist/SuggestionBar/SuggestionBar.d.cts +3 -3
  100. package/dist/widgets/dist/SuggestionButtonContainer/types.d.cts +3 -2
  101. package/dist/widgets/dist/packages/hooks/dist/application/models/api/response.d.cts +14 -0
  102. package/dist/widgets/dist/packages/hooks/dist/application/models/api/search.d.cts +15 -0
  103. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/camelCase.d.cts +73 -0
  104. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/camelCasedPropertiesDeep.d.cts +61 -0
  105. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/internal.d.cts +25 -0
  106. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/splitWords.d.cts +35 -0
  107. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/trim.d.cts +32 -0
  108. package/dist/widgets/dist/packages/hooks/dist/application/models/utilityTypes/unknownArray.d.cts +32 -0
  109. package/dist/widgets/dist/packages/hooks/dist/atoms/search/searchAPI.d.cts +14 -0
  110. package/dist/widgets/dist/packages/hooks/dist/contexts/types.d.cts +61 -0
  111. package/dist/widgets/dist/packages/hooks/dist/hooks/Search/useSearch.d.cts +60 -0
  112. package/dist/widgets/dist/packages/hooks/dist/hooks/utils.d.cts +12 -0
  113. package/dist/widgets/dist/packages/hooks/dist/types/search-filter-types.d.cts +28 -0
  114. package/dist/widgets/dist/packages/hooks/dist/types/test-types.d.cts +10 -0
  115. package/dist/widgets/utils/functions.cjs +9 -0
  116. package/dist/widgets/utils/functions.js +9 -1
  117. package/dist/widgets-v2/SearchZeroState/index.d.cts +2 -1
  118. package/dist/widgets-v2/SearchZeroState/index.d.ts +2 -1
  119. package/dist/widgets-v2/SuggestionButtonContainer/index.d.ts +1 -0
  120. package/package.json +1 -1
  121. package/src/CXIntegration/implementations/useHelpScoutUnifiedCXButton.ts +108 -0
  122. package/src/CXIntegration/implementations/useTalkdeskUnifiedCXButton.ts +94 -0
  123. package/src/CXIntegration/types.ts +2 -0
  124. package/src/CXIntegration/utils/functions.ts +8 -0
  125. package/src/hocs/withBaseWidget/__tests__/withBaseWidget.test.tsx +15 -3
  126. package/src/widgets/ChatPreviewWidget/__tests__/ChatPreviewWidget.test.tsx +114 -0
  127. package/src/widgets/FloatingChatWidget/FloatingChatWidget.tsx +56 -15
  128. package/src/widgets/FloatingChatWidget/__tests__/FloatingChatWidget.test.tsx +119 -0
  129. package/src/widgets/ProductCardWidget/ProductCardWidget.tsx +15 -3
  130. package/src/widgets/ProductCardWidget/__tests__/ProductCardWidget.test.tsx +144 -0
  131. package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx +12 -1
  132. package/src/widgets/PromptButtonCarouselWithImageWidget/__tests__/PromptButtonCarouselWithImageWidget.test.tsx +179 -0
  133. package/src/widgets/PromptCarouselWidget/PromptCarouselWidget.tsx +33 -23
  134. package/src/widgets/PromptCarouselWidget/__tests__/PromptCarouselWidget.test.tsx +150 -0
  135. package/src/widgets/SocialProofWidget/SocialProofWidget.tsx +12 -1
  136. package/src/widgets/SocialProofWidget/__tests__/SocialProofWidget.test.tsx +184 -0
  137. package/src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.tsx +12 -0
  138. package/src/widgets/TitledPromptCarouselWidget/__tests__/TitledPromptCarouselWidget.test.tsx +150 -0
  139. package/src/widgets/TypingAnimationWidget/TypingAnimationWidget.tsx +19 -2
  140. package/src/widgets/TypingAnimationWidget/__tests__/TypingAnimationWidget.test.tsx +163 -0
  141. package/src/widgets/__tests__/testUtils.tsx +63 -0
  142. package/src/widgets/__tests__/trackEventCanary.test.ts +45 -0
  143. package/src/widgets/utils/functions.ts +16 -0
@@ -1,36 +1,26 @@
1
- import {
2
- PromptCarouselWidgetV3Config,
3
- WidgetTypeV3,
4
- } from '@envive-ai/react-hooks/contexts/typesV3';
5
- import { PromptButtonVariant } from '@envive-ai/react-toolkit-v3/PromptButton/types';
6
1
  import { ChatElementDisplayLocationV3 } from '@envive-ai/react-hooks/application/models';
7
- import { useSalesAgent } from '@envive-ai/react-hooks/contexts/salesAgentContext';
8
- import { useCallback, useEffect } from 'react';
9
2
  import {
10
3
  EnviveMetricsEventName,
11
4
  SpiffyMetricsEventName,
12
5
  useAmplitude,
13
6
  } from '@envive-ai/react-hooks/contexts/amplitudeContext';
14
-
7
+ import { useSalesAgent } from '@envive-ai/react-hooks/contexts/salesAgentContext';
8
+ import {
9
+ PromptCarouselWidgetV3Config,
10
+ WidgetTypeV3,
11
+ } from '@envive-ai/react-hooks/contexts/typesV3';
15
12
  import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
13
+ import { WidgetInteractionComponent } from '@envive-ai/react-hooks/hooks/WidgetInteraction/types';
14
+ import { PromptButtonVariant } from '@envive-ai/react-toolkit-v3/PromptButton/types';
15
+ import {
16
+ PromptCarousel,
17
+ usePromptCarouselAnalytics,
18
+ } from '@envive-ai/react-toolkit-v3/PromptCarousel';
16
19
  import { AnimationSpeed } from '@envive-ai/react-toolkit-v3/PromptCarousel/types/types';
17
20
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
18
- import { PromptCarousel } from '@envive-ai/react-toolkit-v3/PromptCarousel';
21
+ import { useCallback, useEffect } from 'react';
19
22
  import { BaseWidgetProps, withBaseWidget } from '../../hocs/withBaseWidget';
20
-
21
- type RawWidgetString = { id: string; value: string };
22
- type RawValues = Record<string, RawWidgetString | RawWidgetString[]>;
23
-
24
- /** Finds the string_id (id) from raw widget text values for the given display text */
25
- function getStringIdForText(rawValues: RawValues | undefined, text: string): string | undefined {
26
- if (!rawValues) return undefined;
27
- for (const raw of Object.values(rawValues)) {
28
- const list = Array.isArray(raw) ? raw : [raw];
29
- const found = list.find((s: RawWidgetString) => s.value === text);
30
- if (found) return found.id;
31
- }
32
- return undefined;
33
- }
23
+ import { RawValues, getStringIdForText } from '../utils/functions';
34
24
 
35
25
  const mockButtonTexts = [
36
26
  'Loading button 1',
@@ -46,6 +36,19 @@ const PromptCarouselWidgetHandler = (props: BaseWidgetProps) => {
46
36
 
47
37
  const { hardcopyContent, widgetConfig, isLoading, widgetConfigId } = props;
48
38
 
39
+ const {
40
+ onClick: onSuggestionClick,
41
+ onDrag,
42
+ onHover,
43
+ onMouseDown,
44
+ onMouseUp,
45
+ onTouchStart,
46
+ onTouchEnd,
47
+ } = usePromptCarouselAnalytics(WidgetInteractionComponent.SUGGESTION_BAR, text => {
48
+ const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
49
+ return getStringIdForText(rawValues, text);
50
+ });
51
+
49
52
  const promptButtonTexts = (hardcopyContent?.values?.promptButtonTexts as string[]) || [];
50
53
  const buttonTexts = isLoading ? mockButtonTexts : promptButtonTexts;
51
54
 
@@ -75,6 +78,7 @@ const PromptCarouselWidgetHandler = (props: BaseWidgetProps) => {
75
78
  (text: string) => {
76
79
  const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
77
80
  const stringId = getStringIdForText(rawValues, text);
81
+ onSuggestionClick(stringId);
78
82
  trackEvent({
79
83
  eventName: EnviveMetricsEventName.WidgetTextClicked,
80
84
  eventProps: {
@@ -104,6 +108,12 @@ const PromptCarouselWidgetHandler = (props: BaseWidgetProps) => {
104
108
  promptCarouselRows={promptCarouselRows}
105
109
  animationSpeed={animationSpeed}
106
110
  handleButtonClick={handleButtonClick}
111
+ handleButtonHover={onHover}
112
+ handleButtonDrag={onDrag}
113
+ handleButtonMouseUp={onMouseUp}
114
+ handleButtonMouseDown={onMouseDown}
115
+ handleButtonTouchStart={onTouchStart}
116
+ handleButtonTouchEnd={onTouchEnd}
107
117
  promptButtonTexts={buttonTexts}
108
118
  />
109
119
  );
@@ -0,0 +1,150 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import {
3
+ EnviveMetricsEventName,
4
+ SpiffyMetricsEventName,
5
+ } from '@envive-ai/react-hooks/contexts/amplitudeContext';
6
+ import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
7
+ import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
8
+ import { PromptCarouselWidget } from '../PromptCarouselWidget';
9
+
10
+ const mockTrackEvent = vi.fn();
11
+ const mockGetHardcopy = vi.fn();
12
+
13
+ vi.mock('@envive-ai/react-toolkit-v3/PromptCarousel', () => ({
14
+ PromptCarousel: ({
15
+ promptButtonTexts,
16
+ handleButtonClick,
17
+ }: {
18
+ promptButtonTexts: string[];
19
+ handleButtonClick: (text: string) => void;
20
+ }) => (
21
+ <div data-testid="prompt-carousel-mock">
22
+ {promptButtonTexts.map(text => (
23
+ <button
24
+ key={text}
25
+ type="button"
26
+ onClick={() => handleButtonClick(text)}
27
+ >
28
+ {text}
29
+ </button>
30
+ ))}
31
+ </div>
32
+ ),
33
+ }));
34
+
35
+ vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
36
+ useAmplitude: () => ({
37
+ trackEvent: mockTrackEvent,
38
+ isReady: true,
39
+ }),
40
+ SpiffyMetricsEventName: {
41
+ ChatComponentVisible: 'Chat Component Visible',
42
+ SearchComponentVisible: 'Search Component Visible',
43
+ },
44
+ EnviveMetricsEventName: {
45
+ WidgetTextClicked: 'Widget Text Clicked',
46
+ },
47
+ }));
48
+
49
+ vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
50
+ useHardcopy: () => ({
51
+ getHardcopy: mockGetHardcopy,
52
+ }),
53
+ }));
54
+
55
+ vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
56
+ usePage: () => ({
57
+ userEvent: {
58
+ id: 'test-user-event-id',
59
+ category: UserEventCategory.PageVisit,
60
+ created_at: '2025-01-01T00:00:00Z',
61
+ event_id: 'test-event-id',
62
+ } as UserEvent,
63
+ }),
64
+ }));
65
+
66
+ vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
67
+ useWidgetConfig: () => ({
68
+ getWidgetConfig: vi.fn().mockResolvedValue({}),
69
+ }),
70
+ }));
71
+
72
+ vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
73
+ useUiConfig: () => ({
74
+ getUiConfig: vi.fn().mockResolvedValue({}),
75
+ }),
76
+ }));
77
+
78
+ vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
79
+ useSalesAgent: () => ({
80
+ onTypedMessageSubmitted: vi.fn(),
81
+ }),
82
+ }));
83
+
84
+ vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
85
+ useChatToggle: () => ({
86
+ openChat: vi.fn(),
87
+ }),
88
+ }));
89
+
90
+ describe('PromptCarouselWidget analytics', () => {
91
+ const defaultHardcopy = {
92
+ responseId: 'test-response-id',
93
+ language: 'en',
94
+ values: {
95
+ promptButtonTexts: ['Button 1', 'Button 2', 'Button 3'],
96
+ },
97
+ rawValues: {
98
+ promptButtonTexts: [
99
+ { id: 'id-1', value: 'Button 1' },
100
+ { id: 'id-2', value: 'Button 2' },
101
+ ],
102
+ },
103
+ };
104
+
105
+ beforeEach(() => {
106
+ vi.clearAllMocks();
107
+ mockGetHardcopy.mockResolvedValue(defaultHardcopy);
108
+ });
109
+
110
+ it('should track ChatComponentVisible when widget mounts', async () => {
111
+ render(<PromptCarouselWidget widgetConfigId="test-config-1" />);
112
+
113
+ await waitFor(
114
+ () => {
115
+ expect(mockTrackEvent).toHaveBeenCalledWith({
116
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
117
+ eventProps: {
118
+ widget_config_id: 'test-config-1',
119
+ widget_type: WidgetTypeV3.PromptCarouselV3,
120
+ },
121
+ });
122
+ },
123
+ { timeout: 3000 },
124
+ );
125
+ }, 5000);
126
+
127
+ it('should track WidgetTextClicked when button is clicked', async () => {
128
+ render(<PromptCarouselWidget widgetConfigId="test-config-2" />);
129
+
130
+ await waitFor(
131
+ () => {
132
+ expect(screen.getByText('Button 1')).toBeInTheDocument();
133
+ },
134
+ { timeout: 3000 },
135
+ );
136
+
137
+ mockTrackEvent.mockClear();
138
+
139
+ screen.getByText('Button 1').click();
140
+
141
+ expect(mockTrackEvent).toHaveBeenCalledWith({
142
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
143
+ eventProps: {
144
+ response_id: 'test-response-id',
145
+ string_id: 'id-1',
146
+ text: 'Button 1',
147
+ },
148
+ });
149
+ }, 5000);
150
+ });
@@ -17,6 +17,7 @@ import {
17
17
  } from '@envive-ai/react-hooks/application/models';
18
18
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
19
19
  import {
20
+ EnviveMetricsEventName,
20
21
  SpiffyMetricsEventName,
21
22
  useAmplitude,
22
23
  } from '@envive-ai/react-hooks/contexts/amplitudeContext';
@@ -25,7 +26,7 @@ import { chatPreviewLoadingDataAtom } from '@envive-ai/react-hooks/atoms/widget'
25
26
  import { ChatPreviewLoading } from '@envive-ai/react-toolkit-v3/ChatPreviewLoading';
26
27
  import { BaseWidgetProps } from '../../hocs/withBaseWidget/types';
27
28
  import { withBaseWidget } from '../../hocs/withBaseWidget/withBaseWidget';
28
- import { getProductImageUrl } from '../utils/functions';
29
+ import { RawValues, getProductImageUrl, getStringIdForText } from '../utils/functions';
29
30
 
30
31
  const SocialProofWidgetHandler = (props: BaseWidgetProps) => {
31
32
  const setChatPreviewLoadingData = useSetAtom(chatPreviewLoadingDataAtom);
@@ -102,6 +103,16 @@ const SocialProofWidgetHandler = (props: BaseWidgetProps) => {
102
103
 
103
104
  const handlePrimaryButtonClick = useCallback(
104
105
  (text: string) => {
106
+ const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
107
+ const stringId = getStringIdForText(rawValues, text);
108
+ trackEvent({
109
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
110
+ eventProps: {
111
+ response_id: hardcopyContent?.responseId,
112
+ string_id: stringId,
113
+ text,
114
+ },
115
+ });
105
116
  onTypedMessageSubmitted({
106
117
  query: text,
107
118
  userTyped: false,
@@ -0,0 +1,184 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import {
3
+ EnviveMetricsEventName,
4
+ SpiffyMetricsEventName,
5
+ } from '@envive-ai/react-hooks/contexts/amplitudeContext';
6
+ import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
7
+ import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
8
+ import { SocialProofWidget } from '../SocialProofWidget';
9
+
10
+ const mockTrackEvent = vi.fn();
11
+ const mockGetHardcopy = vi.fn();
12
+
13
+ vi.mock('@envive-ai/react-toolkit-v3/SocialProof', async importOriginal => {
14
+ const actual = await importOriginal();
15
+ const MockSocialProof = ({
16
+ widgetContentProps,
17
+ widgetEventProps,
18
+ }: {
19
+ widgetContentProps: { primaryButtonText?: string };
20
+ widgetEventProps: { handlePrimaryButtonClick: (text: string) => void };
21
+ }) => (
22
+ <div data-testid="social-proof-mock">
23
+ <button
24
+ type="button"
25
+ onClick={() =>
26
+ widgetEventProps.handlePrimaryButtonClick(
27
+ widgetContentProps.primaryButtonText || 'Primary',
28
+ )
29
+ }
30
+ >
31
+ {widgetContentProps.primaryButtonText || 'Primary'}
32
+ </button>
33
+ </div>
34
+ );
35
+ return {
36
+ ...actual,
37
+ SocialProof: MockSocialProof,
38
+ };
39
+ });
40
+
41
+ vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
42
+ useAmplitude: () => ({
43
+ trackEvent: mockTrackEvent,
44
+ isReady: true,
45
+ }),
46
+ SpiffyMetricsEventName: {
47
+ ChatComponentVisible: 'Chat Component Visible',
48
+ SearchComponentVisible: 'Search Component Visible',
49
+ },
50
+ EnviveMetricsEventName: {
51
+ WidgetTextClicked: 'Widget Text Clicked',
52
+ },
53
+ }));
54
+
55
+ vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
56
+ useHardcopy: () => ({
57
+ getHardcopy: mockGetHardcopy,
58
+ }),
59
+ }));
60
+
61
+ vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
62
+ usePage: () => ({
63
+ userEvent: {
64
+ id: 'test-user-event-id',
65
+ category: UserEventCategory.PageVisit,
66
+ created_at: '2025-01-01T00:00:00Z',
67
+ event_id: 'test-event-id',
68
+ } as UserEvent,
69
+ }),
70
+ }));
71
+
72
+ vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
73
+ useWidgetConfig: () => ({
74
+ getWidgetConfig: vi.fn().mockResolvedValue({}),
75
+ }),
76
+ }));
77
+
78
+ vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
79
+ useUiConfig: () => ({
80
+ getUiConfig: vi.fn().mockResolvedValue({}),
81
+ }),
82
+ }));
83
+
84
+ vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
85
+ useSalesAgent: () => ({
86
+ onTypedMessageSubmitted: vi.fn(),
87
+ }),
88
+ }));
89
+
90
+ vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
91
+ useChatToggle: () => ({
92
+ openChat: vi.fn(),
93
+ }),
94
+ }));
95
+
96
+ vi.mock('@envive-ai/react-hooks/atoms/chat', () => ({
97
+ lastAssistantMessageAtom: {},
98
+ }));
99
+
100
+ vi.mock('@envive-ai/react-hooks/atoms/app', () => ({
101
+ variantInfoAtom: {},
102
+ }));
103
+
104
+ vi.mock('@envive-ai/react-hooks/atoms/widget', () => ({
105
+ chatPreviewLoadingDataAtom: {},
106
+ }));
107
+
108
+ vi.mock('jotai', async importOriginal => {
109
+ const actual = await importOriginal();
110
+ const variantInfo = {
111
+ variant: 'PageVisit',
112
+ productId: undefined,
113
+ plpId: undefined,
114
+ url: '',
115
+ pageVisitCategory: '',
116
+ };
117
+ const useAtomValueMock = vi.fn();
118
+ useAtomValueMock.mockReturnValueOnce([]).mockReturnValueOnce(variantInfo).mockReturnValue([]);
119
+ return {
120
+ ...actual,
121
+ useAtomValue: useAtomValueMock,
122
+ useSetAtom: vi.fn(() => vi.fn()),
123
+ };
124
+ });
125
+
126
+ describe('SocialProofWidget analytics', () => {
127
+ const defaultHardcopy = {
128
+ responseId: 'test-response-id',
129
+ language: 'en',
130
+ values: {
131
+ primaryButtonText: 'Primary Button',
132
+ titleLabel: 'Title',
133
+ },
134
+ rawValues: {
135
+ primaryButtonText: { id: 'id-1', value: 'Primary Button' },
136
+ },
137
+ };
138
+
139
+ beforeEach(() => {
140
+ vi.clearAllMocks();
141
+ mockGetHardcopy.mockResolvedValue(defaultHardcopy);
142
+ });
143
+
144
+ it('should track ChatComponentVisible when widget mounts', async () => {
145
+ render(<SocialProofWidget widgetConfigId="test-config-1" />);
146
+
147
+ await waitFor(
148
+ () => {
149
+ expect(mockTrackEvent).toHaveBeenCalledWith({
150
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
151
+ eventProps: {
152
+ widget_config_id: 'test-config-1',
153
+ widget_type: WidgetTypeV3.SocialProofV3,
154
+ },
155
+ });
156
+ },
157
+ { timeout: 3000 },
158
+ );
159
+ }, 5000);
160
+
161
+ it('should track WidgetTextClicked when primary button is clicked', async () => {
162
+ render(<SocialProofWidget widgetConfigId="test-config-2" />);
163
+
164
+ await waitFor(
165
+ () => {
166
+ expect(screen.getByText('Primary Button')).toBeInTheDocument();
167
+ },
168
+ { timeout: 3000 },
169
+ );
170
+
171
+ mockTrackEvent.mockClear();
172
+
173
+ screen.getByText('Primary Button').click();
174
+
175
+ expect(mockTrackEvent).toHaveBeenCalledWith({
176
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
177
+ eventProps: {
178
+ response_id: 'test-response-id',
179
+ string_id: 'id-1',
180
+ text: 'Primary Button',
181
+ },
182
+ });
183
+ }, 5000);
184
+ });
@@ -7,6 +7,7 @@ import { ChatElementDisplayLocationV3 } from '@envive-ai/react-hooks/application
7
7
  import { useSalesAgent } from '@envive-ai/react-hooks/contexts/salesAgentContext';
8
8
  import { useCallback, useEffect } from 'react';
9
9
  import {
10
+ EnviveMetricsEventName,
10
11
  SpiffyMetricsEventName,
11
12
  useAmplitude,
12
13
  } from '@envive-ai/react-hooks/contexts/amplitudeContext';
@@ -15,6 +16,7 @@ import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
15
16
  import { TitledPromptCarousel } from '@envive-ai/react-toolkit-v3/TitledPromptCarousel';
16
17
  import { AnimationSpeed, PromptCarouselRows } from '@envive-ai/react-toolkit-v3/PromptCarousel';
17
18
  import { BaseWidgetProps, withBaseWidget } from '../../hocs/withBaseWidget';
19
+ import { RawValues, getStringIdForText } from '../utils/functions';
18
20
 
19
21
  const mockButtonTexts = [
20
22
  'Loading button 1',
@@ -67,6 +69,16 @@ const TitledPromptCarouselWidgetHandler = (props: BaseWidgetProps) => {
67
69
 
68
70
  const handleButtonClick = useCallback(
69
71
  (text: string) => {
72
+ const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
73
+ const stringId = getStringIdForText(rawValues, text);
74
+ trackEvent({
75
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
76
+ eventProps: {
77
+ response_id: hardcopyContent?.responseId,
78
+ string_id: stringId,
79
+ text,
80
+ },
81
+ });
70
82
  onTypedMessageSubmitted({
71
83
  query: text,
72
84
  userTyped: false,
@@ -0,0 +1,150 @@
1
+ import { render, screen, waitFor } from '@testing-library/react';
2
+ import {
3
+ EnviveMetricsEventName,
4
+ SpiffyMetricsEventName,
5
+ } from '@envive-ai/react-hooks/contexts/amplitudeContext';
6
+ import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
7
+ import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
8
+ import { TitledPromptCarouselWidget } from '../TitledPromptCarouselWidget';
9
+
10
+ const mockTrackEvent = vi.fn();
11
+ const mockGetHardcopy = vi.fn();
12
+
13
+ vi.mock('@envive-ai/react-toolkit-v3/TitledPromptCarousel', () => ({
14
+ TitledPromptCarousel: ({
15
+ promptButtonTexts,
16
+ handleButtonClick,
17
+ }: {
18
+ promptButtonTexts: string[];
19
+ handleButtonClick: (text: string) => void;
20
+ }) => (
21
+ <div data-testid="titled-prompt-carousel-mock">
22
+ {promptButtonTexts.map(text => (
23
+ <button
24
+ key={text}
25
+ type="button"
26
+ onClick={() => handleButtonClick(text)}
27
+ >
28
+ {text}
29
+ </button>
30
+ ))}
31
+ </div>
32
+ ),
33
+ }));
34
+
35
+ vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
36
+ useAmplitude: () => ({
37
+ trackEvent: mockTrackEvent,
38
+ isReady: true,
39
+ }),
40
+ SpiffyMetricsEventName: {
41
+ ChatComponentVisible: 'Chat Component Visible',
42
+ SearchComponentVisible: 'Search Component Visible',
43
+ },
44
+ EnviveMetricsEventName: {
45
+ WidgetTextClicked: 'Widget Text Clicked',
46
+ },
47
+ }));
48
+
49
+ vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
50
+ useHardcopy: () => ({
51
+ getHardcopy: mockGetHardcopy,
52
+ }),
53
+ }));
54
+
55
+ vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
56
+ usePage: () => ({
57
+ userEvent: {
58
+ id: 'test-user-event-id',
59
+ category: UserEventCategory.PageVisit,
60
+ created_at: '2025-01-01T00:00:00Z',
61
+ event_id: 'test-event-id',
62
+ } as UserEvent,
63
+ }),
64
+ }));
65
+
66
+ vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
67
+ useWidgetConfig: () => ({
68
+ getWidgetConfig: vi.fn().mockResolvedValue({}),
69
+ }),
70
+ }));
71
+
72
+ vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
73
+ useUiConfig: () => ({
74
+ getUiConfig: vi.fn().mockResolvedValue({}),
75
+ }),
76
+ }));
77
+
78
+ vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
79
+ useSalesAgent: () => ({
80
+ onTypedMessageSubmitted: vi.fn(),
81
+ }),
82
+ }));
83
+
84
+ vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
85
+ useChatToggle: () => ({
86
+ openChat: vi.fn(),
87
+ }),
88
+ }));
89
+
90
+ describe('TitledPromptCarouselWidget analytics', () => {
91
+ const defaultHardcopy = {
92
+ responseId: 'test-response-id',
93
+ language: 'en',
94
+ values: {
95
+ promptButtonTexts: ['Button 1', 'Button 2', 'Button 3'],
96
+ },
97
+ rawValues: {
98
+ promptButtonTexts: [
99
+ { id: 'id-1', value: 'Button 1' },
100
+ { id: 'id-2', value: 'Button 2' },
101
+ ],
102
+ },
103
+ };
104
+
105
+ beforeEach(() => {
106
+ vi.clearAllMocks();
107
+ mockGetHardcopy.mockResolvedValue(defaultHardcopy);
108
+ });
109
+
110
+ it('should track ChatComponentVisible when widget mounts', async () => {
111
+ render(<TitledPromptCarouselWidget widgetConfigId="test-config-1" />);
112
+
113
+ await waitFor(
114
+ () => {
115
+ expect(mockTrackEvent).toHaveBeenCalledWith({
116
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
117
+ eventProps: {
118
+ widget_config_id: 'test-config-1',
119
+ widget_type: WidgetTypeV3.TitledPromptCarouselV3,
120
+ },
121
+ });
122
+ },
123
+ { timeout: 3000 },
124
+ );
125
+ }, 5000);
126
+
127
+ it('should track WidgetTextClicked when button is clicked', async () => {
128
+ render(<TitledPromptCarouselWidget widgetConfigId="test-config-2" />);
129
+
130
+ await waitFor(
131
+ () => {
132
+ expect(screen.getByText('Button 1')).toBeInTheDocument();
133
+ },
134
+ { timeout: 3000 },
135
+ );
136
+
137
+ mockTrackEvent.mockClear();
138
+
139
+ screen.getByText('Button 1').click();
140
+
141
+ expect(mockTrackEvent).toHaveBeenCalledWith({
142
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
143
+ eventProps: {
144
+ response_id: 'test-response-id',
145
+ string_id: 'id-1',
146
+ text: 'Button 1',
147
+ },
148
+ });
149
+ }, 5000);
150
+ });