@envive-ai/react-widgets-v3 0.3.13 → 0.3.14

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 (80) 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/withBaseWidget.d.cts +2 -2
  10. package/dist/hocs/withBaseWidget/withBaseWidget.d.ts +2 -2
  11. package/dist/packages/widgets/dist/SearchResults/SearchResults.d.ts +2 -2
  12. package/dist/packages/widgets/dist/SearchResults/SearchResultsWidget.d.ts +2 -2
  13. package/dist/packages/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts +2 -2
  14. package/dist/packages/widgets/dist/SuggestionBar/SuggestionBar.d.ts +2 -2
  15. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
  16. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts +3 -3
  17. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
  18. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts +3 -3
  19. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
  20. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
  21. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
  22. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.ts +2 -2
  23. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.cts +2 -2
  24. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts +2 -2
  25. package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +19 -3
  26. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.cts +2 -2
  27. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.ts +2 -2
  28. package/dist/widgets/ProductCardWidget/ProductCardWidget.js +20 -4
  29. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.cjs +10 -0
  30. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
  31. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts +3 -3
  32. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.js +12 -2
  33. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.cjs +2 -9
  34. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
  35. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
  36. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.js +1 -8
  37. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
  38. package/dist/widgets/SocialProofWidget/SocialProofWidget.cjs +10 -0
  39. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
  40. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
  41. package/dist/widgets/SocialProofWidget/SocialProofWidget.js +12 -2
  42. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.cjs +11 -0
  43. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
  44. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts +2 -2
  45. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.js +12 -1
  46. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.cts +2 -2
  47. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
  48. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.cjs +23 -3
  49. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.cts +3 -3
  50. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts +3 -3
  51. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.js +24 -4
  52. package/dist/widgets/dist/SearchResults/SearchResults.d.cts +2 -2
  53. package/dist/widgets/dist/SearchResults/SearchResultsWidget.d.cts +2 -2
  54. package/dist/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.cts +2 -2
  55. package/dist/widgets/dist/SuggestionBar/SuggestionBar.d.cts +2 -2
  56. package/dist/widgets/utils/functions.cjs +9 -0
  57. package/dist/widgets/utils/functions.js +9 -1
  58. package/package.json +1 -1
  59. package/src/CXIntegration/implementations/useHelpScoutUnifiedCXButton.ts +108 -0
  60. package/src/CXIntegration/implementations/useTalkdeskUnifiedCXButton.ts +94 -0
  61. package/src/CXIntegration/types.ts +2 -0
  62. package/src/CXIntegration/utils/functions.ts +8 -0
  63. package/src/hocs/withBaseWidget/__tests__/withBaseWidget.test.tsx +15 -3
  64. package/src/widgets/ChatPreviewWidget/__tests__/ChatPreviewWidget.test.tsx +114 -0
  65. package/src/widgets/FloatingChatWidget/__tests__/FloatingChatWidget.test.tsx +119 -0
  66. package/src/widgets/ProductCardWidget/ProductCardWidget.tsx +15 -3
  67. package/src/widgets/ProductCardWidget/__tests__/ProductCardWidget.test.tsx +144 -0
  68. package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx +12 -1
  69. package/src/widgets/PromptButtonCarouselWithImageWidget/__tests__/PromptButtonCarouselWithImageWidget.test.tsx +179 -0
  70. package/src/widgets/PromptCarouselWidget/PromptCarouselWidget.tsx +1 -14
  71. package/src/widgets/PromptCarouselWidget/__tests__/PromptCarouselWidget.test.tsx +150 -0
  72. package/src/widgets/SocialProofWidget/SocialProofWidget.tsx +12 -1
  73. package/src/widgets/SocialProofWidget/__tests__/SocialProofWidget.test.tsx +184 -0
  74. package/src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.tsx +12 -0
  75. package/src/widgets/TitledPromptCarouselWidget/__tests__/TitledPromptCarouselWidget.test.tsx +150 -0
  76. package/src/widgets/TypingAnimationWidget/TypingAnimationWidget.tsx +19 -2
  77. package/src/widgets/TypingAnimationWidget/__tests__/TypingAnimationWidget.test.tsx +163 -0
  78. package/src/widgets/__tests__/testUtils.tsx +63 -0
  79. package/src/widgets/__tests__/trackEventCanary.test.ts +45 -0
  80. package/src/widgets/utils/functions.ts +16 -0
@@ -0,0 +1,144 @@
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 { ProductCardWidget } from '../ProductCardWidget';
9
+
10
+ const mockTrackEvent = vi.fn();
11
+ const mockGetHardcopy = vi.fn();
12
+
13
+ vi.mock('@envive-ai/react-toolkit-v3/ProductCard', () => ({
14
+ ProductCard: ({ prompts, onSelect }: { prompts: string[]; onSelect: (text: string) => void }) => (
15
+ <div data-testid="product-card-mock">
16
+ {prompts.map(text => (
17
+ <button
18
+ key={text}
19
+ type="button"
20
+ onClick={() => onSelect(text)}
21
+ >
22
+ {text}
23
+ </button>
24
+ ))}
25
+ </div>
26
+ ),
27
+ }));
28
+
29
+ vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
30
+ useAmplitude: () => ({
31
+ trackEvent: mockTrackEvent,
32
+ isReady: true,
33
+ }),
34
+ SpiffyMetricsEventName: {
35
+ ChatComponentVisible: 'Chat Component Visible',
36
+ SearchComponentVisible: 'Search Component Visible',
37
+ },
38
+ EnviveMetricsEventName: {
39
+ WidgetTextClicked: 'Widget Text Clicked',
40
+ },
41
+ }));
42
+
43
+ vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
44
+ useHardcopy: () => ({
45
+ getHardcopy: mockGetHardcopy,
46
+ }),
47
+ }));
48
+
49
+ vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
50
+ usePage: () => ({
51
+ userEvent: {
52
+ id: 'test-user-event-id',
53
+ category: UserEventCategory.PageVisit,
54
+ created_at: '2025-01-01T00:00:00Z',
55
+ event_id: 'test-event-id',
56
+ } as UserEvent,
57
+ }),
58
+ }));
59
+
60
+ vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
61
+ useWidgetConfig: () => ({
62
+ getWidgetConfig: vi.fn().mockResolvedValue({}),
63
+ }),
64
+ }));
65
+
66
+ vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
67
+ useUiConfig: () => ({
68
+ getUiConfig: vi.fn().mockResolvedValue({}),
69
+ }),
70
+ }));
71
+
72
+ vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
73
+ useSalesAgent: () => ({
74
+ onTypedMessageSubmitted: vi.fn(),
75
+ }),
76
+ }));
77
+
78
+ vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
79
+ useChatToggle: () => ({
80
+ openChat: vi.fn(),
81
+ }),
82
+ }));
83
+
84
+ describe('ProductCardWidget analytics', () => {
85
+ const defaultHardcopy = {
86
+ responseId: 'test-response-id',
87
+ language: 'en',
88
+ values: {
89
+ prompts: ['Prompt 1', 'Prompt 2', 'Prompt 3'],
90
+ },
91
+ rawValues: {
92
+ prompts: [
93
+ { id: 'id-1', value: 'Prompt 1' },
94
+ { id: 'id-2', value: 'Prompt 2' },
95
+ ],
96
+ },
97
+ };
98
+
99
+ beforeEach(() => {
100
+ vi.clearAllMocks();
101
+ mockGetHardcopy.mockResolvedValue(defaultHardcopy);
102
+ });
103
+
104
+ it('should track ChatComponentVisible when widget mounts', async () => {
105
+ render(<ProductCardWidget widgetConfigId="test-config-1" />);
106
+
107
+ await waitFor(
108
+ () => {
109
+ expect(mockTrackEvent).toHaveBeenCalledWith({
110
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
111
+ eventProps: {
112
+ widget_config_id: 'test-config-1',
113
+ widget_type: WidgetTypeV3.ProductCardV3,
114
+ },
115
+ });
116
+ },
117
+ { timeout: 3000 },
118
+ );
119
+ }, 5000);
120
+
121
+ it('should track WidgetTextClicked when prompt is selected', async () => {
122
+ render(<ProductCardWidget widgetConfigId="test-config-2" />);
123
+
124
+ await waitFor(
125
+ () => {
126
+ expect(screen.getByText('Prompt 1')).toBeInTheDocument();
127
+ },
128
+ { timeout: 3000 },
129
+ );
130
+
131
+ mockTrackEvent.mockClear();
132
+
133
+ screen.getByText('Prompt 1').click();
134
+
135
+ expect(mockTrackEvent).toHaveBeenCalledWith({
136
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
137
+ eventProps: {
138
+ response_id: 'test-response-id',
139
+ string_id: 'id-1',
140
+ text: 'Prompt 1',
141
+ },
142
+ });
143
+ }, 5000);
144
+ });
@@ -8,6 +8,7 @@ import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
8
8
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
9
9
  import { useCallback, useEffect, useMemo } from 'react';
10
10
  import {
11
+ EnviveMetricsEventName,
11
12
  SpiffyMetricsEventName,
12
13
  useAmplitude,
13
14
  } from '@envive-ai/react-hooks/contexts/amplitudeContext';
@@ -24,7 +25,7 @@ import { useAtomValue } from 'jotai';
24
25
  import { variantInfoAtom } from '@envive-ai/react-hooks/atoms/app';
25
26
  import { BaseWidgetProps } from '../../hocs/withBaseWidget/types';
26
27
  import { withBaseWidget } from '../../hocs/withBaseWidget/withBaseWidget';
27
- import { getRecentProductImageUrls } from '../utils/functions';
28
+ import { RawValues, getRecentProductImageUrls, getStringIdForText } from '../utils/functions';
28
29
 
29
30
  const PromptButtonCarouselWithImageWidgetHandler = (props: BaseWidgetProps) => {
30
31
  const { onTypedMessageSubmitted } = useSalesAgent();
@@ -60,6 +61,16 @@ const PromptButtonCarouselWithImageWidgetHandler = (props: BaseWidgetProps) => {
60
61
 
61
62
  const handlePromptButtonClick = useCallback(
62
63
  (text: string) => {
64
+ const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
65
+ const stringId = getStringIdForText(rawValues, text);
66
+ trackEvent({
67
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
68
+ eventProps: {
69
+ response_id: hardcopyContent?.responseId,
70
+ string_id: stringId,
71
+ text,
72
+ },
73
+ });
63
74
  onTypedMessageSubmitted({
64
75
  query: text,
65
76
  userTyped: false,
@@ -0,0 +1,179 @@
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 { PromptButtonCarouselWithImageWidget } from '../PromptButtonCarouselWithImageWidget';
9
+
10
+ const mockTrackEvent = vi.fn();
11
+ const mockGetHardcopy = vi.fn();
12
+
13
+ vi.mock('@envive-ai/react-toolkit-v3/PromptButtonCarouselWithImage', () => ({
14
+ PromptButtonCarouselWithImage: ({
15
+ promptButtonsTexts = [],
16
+ handlePromptButtonClick,
17
+ }: {
18
+ promptButtonsTexts?: string[];
19
+ handlePromptButtonClick: (text: string) => void;
20
+ }) => (
21
+ <div data-testid="prompt-button-carousel-mock">
22
+ {(promptButtonsTexts ?? []).map(text => (
23
+ <button
24
+ key={text}
25
+ type="button"
26
+ onClick={() => handlePromptButtonClick(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
+ vi.mock('@envive-ai/react-hooks/atoms/chat', () => ({
91
+ lastAssistantMessageAtom: {},
92
+ }));
93
+
94
+ vi.mock('@envive-ai/react-hooks/atoms/app', () => ({
95
+ variantInfoAtom: {},
96
+ }));
97
+
98
+ vi.mock('jotai', async importOriginal => {
99
+ const actual = await importOriginal();
100
+ const variantInfo = {
101
+ variant: 'PageVisit',
102
+ productId: undefined,
103
+ plpId: undefined,
104
+ url: '',
105
+ pageVisitCategory: '',
106
+ };
107
+ const useAtomValueMock = vi.fn();
108
+ let callCount = 0;
109
+ useAtomValueMock.mockImplementation(() => {
110
+ callCount += 1;
111
+ return callCount % 2 === 0 ? [] : variantInfo;
112
+ });
113
+ return {
114
+ ...actual,
115
+ useAtomValue: useAtomValueMock,
116
+ };
117
+ });
118
+
119
+ describe('PromptButtonCarouselWithImageWidget analytics', () => {
120
+ const defaultHardcopy = {
121
+ responseId: 'test-response-id',
122
+ language: 'en',
123
+ values: {
124
+ promptButtonsTexts: ['Button 1', 'Button 2', 'Button 3'],
125
+ },
126
+ rawValues: {
127
+ promptButtonsTexts: [
128
+ { id: 'id-1', value: 'Button 1' },
129
+ { id: 'id-2', value: 'Button 2' },
130
+ ],
131
+ },
132
+ };
133
+
134
+ beforeEach(() => {
135
+ vi.clearAllMocks();
136
+ mockGetHardcopy.mockResolvedValue(defaultHardcopy);
137
+ });
138
+
139
+ it('should track ChatComponentVisible when widget mounts', async () => {
140
+ render(<PromptButtonCarouselWithImageWidget widgetConfigId="test-config-1" />);
141
+
142
+ await waitFor(
143
+ () => {
144
+ expect(mockTrackEvent).toHaveBeenCalledWith({
145
+ eventName: SpiffyMetricsEventName.ChatComponentVisible,
146
+ eventProps: {
147
+ widget_config_id: 'test-config-1',
148
+ widget_type: WidgetTypeV3.PromptButtonCarouselWithImageV3,
149
+ },
150
+ });
151
+ },
152
+ { timeout: 3000 },
153
+ );
154
+ }, 5000);
155
+
156
+ it('should track WidgetTextClicked when button is clicked', async () => {
157
+ render(<PromptButtonCarouselWithImageWidget widgetConfigId="test-config-2" />);
158
+
159
+ await waitFor(
160
+ () => {
161
+ expect(screen.getByText('Button 1')).toBeInTheDocument();
162
+ },
163
+ { timeout: 3000 },
164
+ );
165
+
166
+ mockTrackEvent.mockClear();
167
+
168
+ screen.getByText('Button 1').click();
169
+
170
+ expect(mockTrackEvent).toHaveBeenCalledWith({
171
+ eventName: EnviveMetricsEventName.WidgetTextClicked,
172
+ eventProps: {
173
+ response_id: 'test-response-id',
174
+ string_id: 'id-1',
175
+ text: 'Button 1',
176
+ },
177
+ });
178
+ }, 5000);
179
+ });
@@ -17,20 +17,7 @@ import { AnimationSpeed } from '@envive-ai/react-toolkit-v3/PromptCarousel/types
17
17
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
18
18
  import { PromptCarousel } from '@envive-ai/react-toolkit-v3/PromptCarousel';
19
19
  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
- }
20
+ import { RawValues, getStringIdForText } from '../utils/functions';
34
21
 
35
22
  const mockButtonTexts = [
36
23
  'Loading button 1',
@@ -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,