@envive-ai/react-widgets-v3 0.3.12 → 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.
- package/dist/CXIntegration/implementations/useEightByEightUnifiedCXButton.cjs +54 -0
- package/dist/CXIntegration/implementations/useEightByEightUnifiedCXButton.js +53 -0
- package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.cjs +65 -0
- package/dist/CXIntegration/implementations/useHelpScoutUnifiedCXButton.js +64 -0
- package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.cjs +64 -0
- package/dist/CXIntegration/implementations/useTalkdeskUnifiedCXButton.js +63 -0
- package/dist/CXIntegration/implementations/useZendeskUnifiedCXButton.cjs +2 -2
- package/dist/CXIntegration/implementations/useZendeskUnifiedCXButton.js +2 -2
- package/dist/CXIntegration/types.cjs +3 -0
- package/dist/CXIntegration/types.js +3 -0
- package/dist/CXIntegration/utils/functions.cjs +6 -0
- package/dist/CXIntegration/utils/functions.js +6 -0
- package/dist/hocs/withBaseWidget/withBaseWidget.d.cts +2 -2
- package/dist/packages/widgets/dist/SearchResults/SearchResults.d.ts +2 -2
- package/dist/packages/widgets/dist/SearchResults/SearchResultsWidget.d.ts +2 -2
- package/dist/packages/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts +2 -2
- package/dist/packages/widgets/dist/SuggestionBar/SuggestionBar.d.ts +2 -2
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts +3 -3
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.ts +2 -2
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.cts +2 -2
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.d.ts +2 -2
- package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +19 -3
- package/dist/widgets/ProductCardWidget/ProductCardWidget.d.cts +2 -2
- package/dist/widgets/ProductCardWidget/ProductCardWidget.d.ts +2 -2
- package/dist/widgets/ProductCardWidget/ProductCardWidget.js +20 -4
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.cjs +10 -0
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts +3 -3
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.js +12 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.cjs +2 -9
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.js +1 -8
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.cjs +8 -8
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.cts +2 -2
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.js +8 -8
- package/dist/widgets/SocialProofWidget/SocialProofWidget.cjs +10 -0
- package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
- package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
- package/dist/widgets/SocialProofWidget/SocialProofWidget.js +12 -2
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.cjs +11 -0
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts +2 -2
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.js +12 -1
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.cjs +8 -8
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.cts +2 -2
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.js +8 -8
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.cjs +23 -3
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.cts +3 -3
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts +3 -3
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.js +24 -4
- package/dist/widgets/dist/SearchResults/SearchResults.d.cts +2 -2
- package/dist/widgets/dist/SearchResults/SearchResultsWidget.d.cts +2 -2
- package/dist/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.cts +2 -2
- package/dist/widgets/dist/SuggestionBar/SuggestionBar.d.cts +2 -2
- package/dist/widgets/utils/functions.cjs +9 -0
- package/dist/widgets/utils/functions.js +9 -1
- package/package.json +1 -1
- package/src/CXIntegration/implementations/useEightByEightUnifiedCXButton.ts +91 -0
- package/src/CXIntegration/implementations/useHelpScoutUnifiedCXButton.ts +108 -0
- package/src/CXIntegration/implementations/useTalkdeskUnifiedCXButton.ts +94 -0
- package/src/CXIntegration/implementations/useZendeskUnifiedCXButton.ts +4 -2
- package/src/CXIntegration/types.ts +3 -0
- package/src/CXIntegration/utils/functions.ts +12 -0
- package/src/hocs/withBaseWidget/__tests__/withBaseWidget.test.tsx +15 -3
- package/src/widgets/ChatPreviewWidget/__tests__/ChatPreviewWidget.test.tsx +114 -0
- package/src/widgets/FloatingChatWidget/__tests__/FloatingChatWidget.test.tsx +119 -0
- package/src/widgets/ProductCardWidget/ProductCardWidget.tsx +15 -3
- package/src/widgets/ProductCardWidget/__tests__/ProductCardWidget.test.tsx +144 -0
- package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx +12 -1
- package/src/widgets/PromptButtonCarouselWithImageWidget/__tests__/PromptButtonCarouselWithImageWidget.test.tsx +179 -0
- package/src/widgets/PromptCarouselWidget/PromptCarouselWidget.tsx +1 -14
- package/src/widgets/PromptCarouselWidget/__tests__/PromptCarouselWidget.test.tsx +150 -0
- package/src/widgets/SocialProofFlowWidget/SocialProofFlowWidget.tsx +12 -12
- package/src/widgets/SocialProofWidget/SocialProofWidget.tsx +12 -1
- package/src/widgets/SocialProofWidget/__tests__/SocialProofWidget.test.tsx +184 -0
- package/src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.tsx +12 -0
- package/src/widgets/TitledPromptCarouselWidget/__tests__/TitledPromptCarouselWidget.test.tsx +150 -0
- package/src/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.tsx +12 -12
- package/src/widgets/TypingAnimationWidget/TypingAnimationWidget.tsx +19 -2
- package/src/widgets/TypingAnimationWidget/__tests__/TypingAnimationWidget.test.tsx +163 -0
- package/src/widgets/__tests__/testUtils.tsx +63 -0
- package/src/widgets/__tests__/trackEventCanary.test.ts +45 -0
- package/src/widgets/utils/functions.ts +16 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { SelectorFactory } from '@envive-ai/react-hooks/application/utils';
|
|
2
|
+
import { useElementObserver } from '@envive-ai/react-hooks/hooks/ElementObserver';
|
|
3
|
+
import { FLOATING_BUTTON_ID } from '../../widgets/FloatingChatWidget/constants';
|
|
4
|
+
import { CustomerServiceImplProps, UnifiedCXButton } from '../types';
|
|
5
|
+
|
|
6
|
+
interface UseTalkdeskUnifiedCXButtonProps extends CustomerServiceImplProps {}
|
|
7
|
+
|
|
8
|
+
export const useTalkdeskUnifiedCXButton = ({
|
|
9
|
+
onSwitchToAgent,
|
|
10
|
+
suppressMerchantButton,
|
|
11
|
+
}: UseTalkdeskUnifiedCXButtonProps): UnifiedCXButton => {
|
|
12
|
+
const talkdeskButton = useElementObserver(SelectorFactory.id('talkdesk-chat-widget-trigger'));
|
|
13
|
+
const talkdeskWidget = useElementObserver(SelectorFactory.id('talkdesk-chat-widget'));
|
|
14
|
+
const talkdeskContainer = useElementObserver(SelectorFactory.id('tdWebchat'));
|
|
15
|
+
const enviveFloatingButton = useElementObserver(SelectorFactory.id(FLOATING_BUTTON_ID));
|
|
16
|
+
|
|
17
|
+
const toggle = () => {
|
|
18
|
+
onSwitchToAgent();
|
|
19
|
+
if (suppressMerchantButton) {
|
|
20
|
+
enviveFloatingButton.hide();
|
|
21
|
+
}
|
|
22
|
+
talkdeskButton.show();
|
|
23
|
+
talkdeskButton.fire('click');
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const isTalkdeskButtonEnabled = () => {
|
|
27
|
+
// Check for the main Talkdesk container
|
|
28
|
+
const talkdeskMainContainer = document.getElementById('tdWebchat');
|
|
29
|
+
if (!talkdeskMainContainer) return false;
|
|
30
|
+
|
|
31
|
+
// Check for the trigger button (it's directly in the DOM, not inside an iframe)
|
|
32
|
+
const talkdeskTriggerButton = document.getElementById('talkdesk-chat-widget-trigger');
|
|
33
|
+
return !!talkdeskTriggerButton;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const isSwitchEnabled = () => isTalkdeskButtonEnabled();
|
|
37
|
+
|
|
38
|
+
// Hide Talkdesk button when it's added to DOM (if suppressMerchantButton is enabled)
|
|
39
|
+
talkdeskButton.onAdd(() => {
|
|
40
|
+
if (suppressMerchantButton) {
|
|
41
|
+
talkdeskButton.hide();
|
|
42
|
+
enviveFloatingButton.show();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Observe the widget container (NOT the button) to avoid infinite loops
|
|
47
|
+
talkdeskWidget.onAdd(el => {
|
|
48
|
+
if (el && suppressMerchantButton) {
|
|
49
|
+
const ariaHidden = el.getAttribute('aria-hidden');
|
|
50
|
+
|
|
51
|
+
// Widget is open initially (aria-hidden="false")
|
|
52
|
+
if (ariaHidden === 'false') {
|
|
53
|
+
talkdeskButton.show();
|
|
54
|
+
enviveFloatingButton.hide();
|
|
55
|
+
} else {
|
|
56
|
+
// Widget is closed initially
|
|
57
|
+
talkdeskButton.hide();
|
|
58
|
+
enviveFloatingButton.show();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Track widget visibility changes
|
|
64
|
+
talkdeskWidget.onChange(el => {
|
|
65
|
+
if (el && suppressMerchantButton) {
|
|
66
|
+
const ariaHidden = el.getAttribute('aria-hidden');
|
|
67
|
+
|
|
68
|
+
// Widget is open (aria-hidden="false")
|
|
69
|
+
if (ariaHidden === 'false') {
|
|
70
|
+
talkdeskButton.show();
|
|
71
|
+
enviveFloatingButton.hide();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Widget is closed (aria-hidden="true")
|
|
75
|
+
if (ariaHidden === 'true') {
|
|
76
|
+
talkdeskButton.hide();
|
|
77
|
+
enviveFloatingButton.show();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Cleanup when container is removed
|
|
83
|
+
talkdeskContainer.onRemove(() => {
|
|
84
|
+
if (suppressMerchantButton) {
|
|
85
|
+
talkdeskButton.hide();
|
|
86
|
+
enviveFloatingButton.show();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
toggle,
|
|
92
|
+
isSwitchEnabled,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
@@ -10,10 +10,12 @@ export const useZendeskUnifiedCXButton = ({
|
|
|
10
10
|
onCXClose,
|
|
11
11
|
suppressMerchantButton,
|
|
12
12
|
}: UseZendeskUnifiedCXButtonProps): UnifiedCXButton => {
|
|
13
|
-
const zendeskButton = useElementObserver(
|
|
13
|
+
const zendeskButton = useElementObserver(
|
|
14
|
+
SelectorFactory.chain('query|iframe#launcher @ query|button'),
|
|
15
|
+
);
|
|
14
16
|
const zendeskAlternativeIframe = useElementObserver(SelectorFactory.chain('id|webWidget'));
|
|
15
17
|
const enviveFloatingButton = useElementObserver(SelectorFactory.id(FLOATING_BUTTON_ID));
|
|
16
|
-
const zendeskIframe = useElementObserver(SelectorFactory.
|
|
18
|
+
const zendeskIframe = useElementObserver(SelectorFactory.query('iframe#launcher'));
|
|
17
19
|
|
|
18
20
|
const toggle = () => {
|
|
19
21
|
zendeskIframe.hide();
|
|
@@ -10,6 +10,9 @@ import { useReDoUnifiedCXButton } from '../implementations/useReDoUnifiedCXButto
|
|
|
10
10
|
import { useRichpanelUnifiedCXButton } from '../implementations/useRichpanelUnifiedCXButton';
|
|
11
11
|
import { useZendeskUnifiedCXButton } from '../implementations/useZendeskUnifiedCXButton';
|
|
12
12
|
import { useKustomerUnifiedCXButton } from '../implementations/useKustomerUnifiedCXButton';
|
|
13
|
+
import { useHelpScoutUnifiedCXButton } from '../implementations/useHelpScoutUnifiedCXButton';
|
|
14
|
+
import { useTalkdeskUnifiedCXButton } from '../implementations/useTalkdeskUnifiedCXButton';
|
|
15
|
+
import { useEightByEightUnifiedCXButton } from '../implementations/useEightByEightUnifiedCXButton';
|
|
13
16
|
import { useDefaultUnifiedCXButton } from '../implementations/useDefaultUnifiedCXButton';
|
|
14
17
|
|
|
15
18
|
export const findCustomerServiceImpl = (
|
|
@@ -45,6 +48,15 @@ export const findCustomerServiceImpl = (
|
|
|
45
48
|
if (provider === CustomerServiceType.zendesk) {
|
|
46
49
|
return useZendeskUnifiedCXButton;
|
|
47
50
|
}
|
|
51
|
+
if (provider === CustomerServiceType.helpscout) {
|
|
52
|
+
return useHelpScoutUnifiedCXButton;
|
|
53
|
+
}
|
|
54
|
+
if (provider === CustomerServiceType.talkdesk) {
|
|
55
|
+
return useTalkdeskUnifiedCXButton;
|
|
56
|
+
}
|
|
57
|
+
if (provider === CustomerServiceType.eightByEight) {
|
|
58
|
+
return useEightByEightUnifiedCXButton;
|
|
59
|
+
}
|
|
48
60
|
|
|
49
61
|
return useDefaultUnifiedCXButton;
|
|
50
62
|
};
|
|
@@ -66,7 +66,7 @@ class MockIntersectionObserver implements IntersectionObserver {
|
|
|
66
66
|
const mockTrackEvent = vi.fn();
|
|
67
67
|
const mockGetHardcopy = vi.fn();
|
|
68
68
|
|
|
69
|
-
vi.mock('
|
|
69
|
+
vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
|
|
70
70
|
useAmplitude: () => ({
|
|
71
71
|
trackEvent: mockTrackEvent,
|
|
72
72
|
isReady: true,
|
|
@@ -77,13 +77,13 @@ vi.mock('src/contexts/amplitudeContext/amplitudeContext', () => ({
|
|
|
77
77
|
},
|
|
78
78
|
}));
|
|
79
79
|
|
|
80
|
-
vi.mock('
|
|
80
|
+
vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
|
|
81
81
|
useHardcopy: () => ({
|
|
82
82
|
getHardcopy: mockGetHardcopy,
|
|
83
83
|
}),
|
|
84
84
|
}));
|
|
85
85
|
|
|
86
|
-
vi.mock('
|
|
86
|
+
vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
|
|
87
87
|
usePage: () => ({
|
|
88
88
|
userEvent: {
|
|
89
89
|
id: 'test-user-event-id',
|
|
@@ -94,6 +94,18 @@ vi.mock('src/contexts/pageContext', () => ({
|
|
|
94
94
|
}),
|
|
95
95
|
}));
|
|
96
96
|
|
|
97
|
+
vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
|
|
98
|
+
useWidgetConfig: () => ({
|
|
99
|
+
getWidgetConfig: vi.fn().mockResolvedValue({}),
|
|
100
|
+
}),
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
|
|
104
|
+
useUiConfig: () => ({
|
|
105
|
+
getUiConfig: vi.fn().mockResolvedValue({}),
|
|
106
|
+
}),
|
|
107
|
+
}));
|
|
108
|
+
|
|
97
109
|
// Test widget component
|
|
98
110
|
interface TestWidgetProps extends BaseWidgetProps {
|
|
99
111
|
testId?: string;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { render, waitFor } from '@testing-library/react';
|
|
2
|
+
import { SpiffyMetricsEventName } from '@envive-ai/react-hooks/contexts/amplitudeContext';
|
|
3
|
+
import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
|
|
4
|
+
import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
|
|
5
|
+
import { ChatPreviewWidget } from '../ChatPreviewWidget';
|
|
6
|
+
|
|
7
|
+
const mockTrackEvent = vi.fn();
|
|
8
|
+
const mockGetHardcopy = vi.fn();
|
|
9
|
+
|
|
10
|
+
vi.mock('@envive-ai/react-toolkit-v3/ChatPreview', () => ({
|
|
11
|
+
ChatPreview: () => <div data-testid="chat-preview-mock">Chat Preview</div>,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
|
|
15
|
+
useAmplitude: () => ({
|
|
16
|
+
trackEvent: mockTrackEvent,
|
|
17
|
+
isReady: true,
|
|
18
|
+
}),
|
|
19
|
+
SpiffyMetricsEventName: {
|
|
20
|
+
ChatComponentVisible: 'Chat Component Visible',
|
|
21
|
+
SearchComponentVisible: 'Search Component Visible',
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
|
|
26
|
+
useHardcopy: () => ({
|
|
27
|
+
getHardcopy: mockGetHardcopy,
|
|
28
|
+
}),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
|
|
32
|
+
usePage: () => ({
|
|
33
|
+
userEvent: {
|
|
34
|
+
id: 'test-user-event-id',
|
|
35
|
+
category: UserEventCategory.PageVisit,
|
|
36
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
37
|
+
event_id: 'test-event-id',
|
|
38
|
+
} as UserEvent,
|
|
39
|
+
}),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
|
|
43
|
+
useWidgetConfig: () => ({
|
|
44
|
+
getWidgetConfig: vi.fn().mockResolvedValue({}),
|
|
45
|
+
}),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
|
|
49
|
+
useUiConfig: () => ({
|
|
50
|
+
getUiConfig: vi.fn().mockResolvedValue({}),
|
|
51
|
+
}),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
|
|
55
|
+
useSalesAgent: () => ({
|
|
56
|
+
onSuggestionClicked: vi.fn(),
|
|
57
|
+
}),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
|
|
61
|
+
useChatToggle: () => ({
|
|
62
|
+
openChat: vi.fn(),
|
|
63
|
+
}),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
vi.mock('@envive-ai/react-hooks/atoms/chat', () => ({
|
|
67
|
+
chatAtom: {},
|
|
68
|
+
lastAssistantMessageAtom: {},
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
vi.mock('jotai', async importOriginal => {
|
|
72
|
+
const actual = await importOriginal();
|
|
73
|
+
return {
|
|
74
|
+
...actual,
|
|
75
|
+
useAtomValue: vi
|
|
76
|
+
.fn()
|
|
77
|
+
.mockReturnValueOnce([])
|
|
78
|
+
.mockReturnValueOnce({ messages: [], suggestions: [] })
|
|
79
|
+
.mockReturnValue([]),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('ChatPreviewWidget analytics', () => {
|
|
84
|
+
const defaultHardcopy = {
|
|
85
|
+
responseId: 'test-response-id',
|
|
86
|
+
language: 'en',
|
|
87
|
+
values: {
|
|
88
|
+
titleLabel: 'Chat Preview',
|
|
89
|
+
textFieldPlaceholderText: 'Type here',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
vi.clearAllMocks();
|
|
95
|
+
mockGetHardcopy.mockResolvedValue(defaultHardcopy);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should track ChatComponentVisible when widget mounts', async () => {
|
|
99
|
+
render(<ChatPreviewWidget widgetConfigId="test-config-1" />);
|
|
100
|
+
|
|
101
|
+
await waitFor(
|
|
102
|
+
() => {
|
|
103
|
+
expect(mockTrackEvent).toHaveBeenCalledWith({
|
|
104
|
+
eventName: SpiffyMetricsEventName.ChatComponentVisible,
|
|
105
|
+
eventProps: {
|
|
106
|
+
widget_config_id: 'test-config-1',
|
|
107
|
+
widget_type: WidgetTypeV3.ChatPreviewV3,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
{ timeout: 3000 },
|
|
112
|
+
);
|
|
113
|
+
}, 5000);
|
|
114
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { render, waitFor } from '@testing-library/react';
|
|
2
|
+
import { SpiffyMetricsEventName } from '@envive-ai/react-hooks/contexts/amplitudeContext';
|
|
3
|
+
import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
|
|
4
|
+
import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
|
|
5
|
+
import { FloatingChatWidget } from '../FloatingChatWidget';
|
|
6
|
+
|
|
7
|
+
const mockTrackEvent = vi.fn();
|
|
8
|
+
const mockGetHardcopy = vi.fn();
|
|
9
|
+
|
|
10
|
+
vi.mock('@envive-ai/react-toolkit-v3/FloatingButton', () => ({
|
|
11
|
+
FloatingButton: () => <div data-testid="floating-button">Floating Button</div>,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
|
|
15
|
+
useAmplitude: () => ({
|
|
16
|
+
trackEvent: mockTrackEvent,
|
|
17
|
+
isReady: true,
|
|
18
|
+
}),
|
|
19
|
+
SpiffyMetricsEventName: {
|
|
20
|
+
ChatComponentVisible: 'Chat Component Visible',
|
|
21
|
+
SearchComponentVisible: 'Search Component Visible',
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
|
|
26
|
+
useHardcopy: () => ({
|
|
27
|
+
getHardcopy: mockGetHardcopy,
|
|
28
|
+
}),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock('@envive-ai/react-hooks/contexts/pageContext', () => ({
|
|
32
|
+
usePage: () => ({
|
|
33
|
+
userEvent: {
|
|
34
|
+
id: 'test-user-event-id',
|
|
35
|
+
category: UserEventCategory.PageVisit,
|
|
36
|
+
created_at: '2025-01-01T00:00:00Z',
|
|
37
|
+
event_id: 'test-event-id',
|
|
38
|
+
} as UserEvent,
|
|
39
|
+
}),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
vi.mock('@envive-ai/react-hooks/contexts/widgetConfigContext', () => ({
|
|
43
|
+
useWidgetConfig: () => ({
|
|
44
|
+
getWidgetConfig: vi.fn().mockResolvedValue({}),
|
|
45
|
+
}),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
|
|
49
|
+
useUiConfig: () => ({
|
|
50
|
+
getUiConfig: vi.fn().mockResolvedValue({
|
|
51
|
+
floatingButton: { showOption: 'always', position: 'bottom-right' },
|
|
52
|
+
floatingChat: {},
|
|
53
|
+
lookAndFeel: {},
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
vi.mock('@envive-ai/react-hooks/contexts/salesAgentContext', () => ({
|
|
59
|
+
useSalesAgent: () => ({}),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
vi.mock('@envive-ai/react-hooks/hooks/ChatToggle', () => ({
|
|
63
|
+
useChatToggle: () => ({
|
|
64
|
+
isOpen: false,
|
|
65
|
+
openChat: vi.fn(),
|
|
66
|
+
closeChat: vi.fn(),
|
|
67
|
+
}),
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
vi.mock('../hooks/useFloatingButtonVisibility', () => ({
|
|
71
|
+
useFloatingButtonVisibility: () => ({
|
|
72
|
+
shouldShowFloatingButton: true,
|
|
73
|
+
}),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
vi.mock('../hooks/useAutoPopup', () => ({
|
|
77
|
+
useAutoPopup: () => {},
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
vi.mock('../../hooks/useGetWidgetStatus', () => ({
|
|
81
|
+
default: () => ({
|
|
82
|
+
userHasInteractedValue: false,
|
|
83
|
+
}),
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
vi.mock('src/debug/debugBar', () => ({
|
|
87
|
+
DebugBar: () => null,
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
vi.mock('../../CXIntegration/hooks/useUnifiedCXButton', () => ({
|
|
91
|
+
useUnifiedCXButton: () => ({
|
|
92
|
+
isSwitchEnabled: () => false,
|
|
93
|
+
toggle: vi.fn(),
|
|
94
|
+
}),
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
describe('FloatingChatWidget analytics', () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
vi.clearAllMocks();
|
|
100
|
+
mockGetHardcopy.mockResolvedValue({ language: 'en', values: {} });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should track ChatComponentVisible when floating button is shown', async () => {
|
|
104
|
+
render(<FloatingChatWidget previewButtonOnly />);
|
|
105
|
+
|
|
106
|
+
await waitFor(
|
|
107
|
+
() => {
|
|
108
|
+
expect(mockTrackEvent).toHaveBeenCalledWith({
|
|
109
|
+
eventName: SpiffyMetricsEventName.ChatComponentVisible,
|
|
110
|
+
eventProps: {
|
|
111
|
+
widget_config_id: 'floating-button',
|
|
112
|
+
widget_type: WidgetTypeV3.FloatingButtonV3,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
{ timeout: 3000 },
|
|
117
|
+
);
|
|
118
|
+
}, 5000);
|
|
119
|
+
});
|
|
@@ -2,6 +2,7 @@ import { ProductCardWidgetV3Config, WidgetTypeV3 } from '@envive-ai/react-hooks/
|
|
|
2
2
|
import { PromptButtonVariant } from '@envive-ai/react-toolkit-v3/PromptButton/types';
|
|
3
3
|
import { useCallback, useEffect } from 'react';
|
|
4
4
|
import {
|
|
5
|
+
EnviveMetricsEventName,
|
|
5
6
|
SpiffyMetricsEventName,
|
|
6
7
|
useAmplitude,
|
|
7
8
|
} from '@envive-ai/react-hooks/contexts/amplitudeContext';
|
|
@@ -11,6 +12,7 @@ import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
|
|
|
11
12
|
import { ProductCard } from '@envive-ai/react-toolkit-v3/ProductCard';
|
|
12
13
|
import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
|
|
13
14
|
import { BaseWidgetProps, withBaseWidget } from '../../hocs/withBaseWidget';
|
|
15
|
+
import { RawValues, getStringIdForText } from '../utils/functions';
|
|
14
16
|
|
|
15
17
|
const mockPrompts = [
|
|
16
18
|
'Loading prompt 1',
|
|
@@ -63,15 +65,25 @@ const ProductCardWidgetHandler = (props: BaseWidgetProps) => {
|
|
|
63
65
|
}, [trackEvent, widgetConfigId]);
|
|
64
66
|
|
|
65
67
|
const handleSelect = useCallback(
|
|
66
|
-
(
|
|
68
|
+
(text: string) => {
|
|
69
|
+
const rawValues = (hardcopyContent as { rawValues?: RawValues } | undefined)?.rawValues;
|
|
70
|
+
const stringId = getStringIdForText(rawValues, text);
|
|
71
|
+
trackEvent({
|
|
72
|
+
eventName: EnviveMetricsEventName.WidgetTextClicked,
|
|
73
|
+
eventProps: {
|
|
74
|
+
response_id: hardcopyContent?.responseId,
|
|
75
|
+
string_id: stringId,
|
|
76
|
+
text,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
67
79
|
onTypedMessageSubmitted({
|
|
68
|
-
query:
|
|
80
|
+
query: text,
|
|
69
81
|
userTyped: false,
|
|
70
82
|
displayLocation: ChatElementDisplayLocationV3.PRODUCT_CARD_PROMPT_BUTTON,
|
|
71
83
|
});
|
|
72
84
|
openChat(ChatElementDisplayLocationV3.PRODUCT_CARD_PROMPT_BUTTON);
|
|
73
85
|
},
|
|
74
|
-
[onTypedMessageSubmitted, openChat],
|
|
86
|
+
[hardcopyContent, onTypedMessageSubmitted, openChat, trackEvent],
|
|
75
87
|
);
|
|
76
88
|
|
|
77
89
|
const handleInputClick = useCallback(() => {
|
|
@@ -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
|
+
});
|
package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx
CHANGED
|
@@ -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,
|