@envive-ai/react-widgets-v3 0.3.21 → 0.3.23
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/debug/chatEmbed.cjs +1 -1
- package/dist/debug/chatEmbed.js +1 -1
- package/dist/debug/reportIssue.cjs +1 -1
- package/dist/debug/reportIssue.js +1 -1
- package/dist/hocs/withBaseWidget/types.d.cts +5 -6
- package/dist/hocs/withBaseWidget/types.d.ts +5 -6
- package/dist/hocs/withBaseWidget/withBaseWidget.cjs +37 -43
- package/dist/hocs/withBaseWidget/withBaseWidget.d.cts +2 -2
- package/dist/hocs/withBaseWidget/withBaseWidget.d.ts +2 -2
- package/dist/hocs/withBaseWidget/withBaseWidget.js +39 -45
- 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.cjs +34 -8
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts +3 -3
- package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.js +35 -9
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.cjs +13 -5
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts +3 -3
- package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.js +13 -5
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.cjs +32 -15
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
- package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.js +35 -18
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.cjs +36 -32
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
- package/dist/widgets/FloatingChatWidget/FloatingChatWidget.js +37 -33
- package/dist/widgets/FloatingChatWidget/hooks/useAutoPopup.cjs +2 -2
- package/dist/widgets/FloatingChatWidget/hooks/useAutoPopup.js +2 -2
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.cjs +7 -1
- package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.js +7 -1
- package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +31 -15
- package/dist/widgets/ProductCardWidget/ProductCardWidget.d.cts +2 -2
- package/dist/widgets/ProductCardWidget/ProductCardWidget.d.ts +2 -2
- package/dist/widgets/ProductCardWidget/ProductCardWidget.js +33 -17
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.cjs +33 -16
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts +3 -3
- package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.js +36 -19
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.cjs +3 -12
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
- package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.js +5 -14
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.cts +2 -2
- package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
- package/dist/widgets/SocialProofWidget/SocialProofWidget.cjs +34 -17
- package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
- package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
- package/dist/widgets/SocialProofWidget/SocialProofWidget.js +37 -20
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.cjs +2 -11
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts +2 -2
- package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.js +4 -13
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.cts +2 -2
- package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.cjs +31 -14
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.cts +3 -3
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts +3 -3
- package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.js +34 -17
- 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/hooks/useGetWidgetStatus.cjs +1 -1
- package/dist/widgets/hooks/useGetWidgetStatus.js +1 -1
- package/dist/widgets/utils/functions.cjs +1 -1
- package/dist/widgets/utils/functions.js +1 -1
- package/package.json +1 -1
- package/src/hocs/withBaseWidget/__tests__/withBaseWidget.test.tsx +68 -302
- package/src/hocs/withBaseWidget/types.ts +6 -6
- package/src/hocs/withBaseWidget/withBaseWidget.tsx +46 -61
- package/src/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.tsx +33 -0
- package/src/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.tsx +9 -1
- package/src/widgets/ChatPreviewWidget/ChatPreviewWidget.tsx +34 -13
- package/src/widgets/ChatPreviewWidget/__tests__/ChatPreviewWidget.test.tsx +11 -5
- package/src/widgets/FloatingChatWidget/FloatingChatWidget.tsx +56 -38
- package/src/widgets/FloatingChatWidget/__tests__/FloatingChatWidget.test.tsx +11 -5
- package/src/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.tsx +8 -0
- package/src/widgets/ProductCardWidget/ProductCardWidget.tsx +37 -15
- package/src/widgets/ProductCardWidget/__tests__/ProductCardWidget.test.tsx +12 -8
- package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx +35 -13
- package/src/widgets/PromptButtonCarouselWithImageWidget/__tests__/PromptButtonCarouselWithImageWidget.test.tsx +19 -8
- package/src/widgets/PromptCarouselWidget/PromptCarouselWidget.tsx +1 -12
- package/src/widgets/PromptCarouselWidget/__tests__/PromptCarouselWidget.test.tsx +21 -8
- package/src/widgets/SocialProofWidget/SocialProofWidget.tsx +34 -12
- package/src/widgets/SocialProofWidget/__tests__/SocialProofWidget.test.tsx +19 -8
- package/src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.tsx +1 -12
- package/src/widgets/TitledPromptCarouselWidget/__tests__/TitledPromptCarouselWidget.test.tsx +19 -8
- package/src/widgets/TypingAnimationWidget/TypingAnimationWidget.tsx +37 -14
- package/src/widgets/TypingAnimationWidget/__tests__/TypingAnimationWidget.test.tsx +19 -8
- package/src/widgets/__tests__/trackEventCanary.test.ts +1 -1
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { withBaseWidget } from "../../hocs/withBaseWidget/withBaseWidget.js";
|
|
2
2
|
import "../../hocs/withBaseWidget/index.js";
|
|
3
3
|
import { getStringIdForText } from "../utils/functions.js";
|
|
4
|
-
import { useCallback
|
|
5
|
-
import { EnviveMetricsEventName,
|
|
4
|
+
import { useCallback } from "react";
|
|
5
|
+
import { EnviveMetricsEventName, useAmplitude } from "@envive-ai/react-hooks/contexts/amplitudeContext";
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
|
-
import { useSalesAgent } from "@envive-ai/react-hooks/contexts/salesAgentContext";
|
|
8
7
|
import { WidgetTypeV3 } from "@envive-ai/react-hooks/contexts/typesV3";
|
|
9
8
|
import { useSetAtom } from "jotai";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { useFeatureFlagService } from "@envive-ai/react-hooks/contexts/featureFlagServiceContext";
|
|
10
|
+
import { ChatElementDisplayLocationV3, FeatureGates } from "@envive-ai/react-hooks/application/models";
|
|
11
|
+
import { useSalesAgent } from "@envive-ai/react-hooks/contexts/salesAgentContext";
|
|
12
12
|
import { WidgetInteractionComponent, WidgetInteractionType } from "@envive-ai/react-hooks/hooks/WidgetInteraction/types";
|
|
13
13
|
import { PromptCarouselRows, usePromptCarouselAnalytics } from "@envive-ai/react-toolkit-v3/PromptCarousel";
|
|
14
|
+
import { useWidgetInteraction } from "@envive-ai/react-hooks/hooks/WidgetInteraction";
|
|
14
15
|
import { chatOnToggleAtom } from "@envive-ai/react-hooks/atoms/chat/chatState";
|
|
15
16
|
import { PromptButtonVariant } from "@envive-ai/react-toolkit-v3/PromptButton/types";
|
|
16
17
|
import { TypingAnimation } from "@envive-ai/react-toolkit-v3/TypingAnimation";
|
|
@@ -30,7 +31,7 @@ const mockHintText = "Ask me anything";
|
|
|
30
31
|
const TypingAnimationWidgetHandler = (props) => {
|
|
31
32
|
const { onTypedMessageSubmitted } = useSalesAgent();
|
|
32
33
|
const onToggle = useSetAtom(chatOnToggleAtom);
|
|
33
|
-
const { hardcopyContent, widgetConfig, uiConfig, isLoading
|
|
34
|
+
const { hardcopyContent, widgetConfig, uiConfig, isLoading } = props;
|
|
34
35
|
const titleLabel = hardcopyContent?.values?.titleLabel || "";
|
|
35
36
|
const headlineText = hardcopyContent?.values?.headlineText || "";
|
|
36
37
|
const animatedTextSequence = hardcopyContent?.values?.animatedTextSequence || [];
|
|
@@ -47,21 +48,15 @@ const TypingAnimationWidgetHandler = (props) => {
|
|
|
47
48
|
const showTextField = typingAnimationWidgetConfig?.showTextField !== false;
|
|
48
49
|
const logoSrc = uiConfig?.lookAndFeel?.widgetLogoSrc;
|
|
49
50
|
const hideLogo = uiConfig?.lookAndFeel?.hideWidgetLogo;
|
|
51
|
+
const voiceInputEnabled = uiConfig?.lookAndFeel?.voiceInputEnabled;
|
|
52
|
+
const { featureFlagService } = useFeatureFlagService();
|
|
53
|
+
const isVoiceInputEnabled = featureFlagService?.isFeatureGateEnabled(FeatureGates.IsVoiceInputEnabled) || voiceInputEnabled;
|
|
50
54
|
const { trackEvent } = useAmplitude();
|
|
51
55
|
const { trackWidgetInteraction } = useWidgetInteraction();
|
|
52
56
|
const { onClick: onPromptClicked, onDrag, onHover, onMouseDown, onMouseUp, onTouchStart, onTouchEnd } = usePromptCarouselAnalytics(WidgetInteractionComponent.EMBEDDED_WIDGET, (text) => {
|
|
53
57
|
const rawValues = hardcopyContent?.rawValues;
|
|
54
58
|
return getStringIdForText(rawValues, text);
|
|
55
59
|
});
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
trackEvent({
|
|
58
|
-
eventName: SpiffyMetricsEventName.ChatComponentVisible,
|
|
59
|
-
eventProps: {
|
|
60
|
-
widget_config_id: widgetConfigId,
|
|
61
|
-
widget_type: WidgetTypeV3.TypingAnimationV3
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}, [trackEvent, widgetConfigId]);
|
|
65
60
|
const handleButtonClick = useCallback((text) => {
|
|
66
61
|
const rawValues = hardcopyContent?.rawValues;
|
|
67
62
|
const stringId = getStringIdForText(rawValues, text);
|
|
@@ -96,6 +91,25 @@ const TypingAnimationWidgetHandler = (props) => {
|
|
|
96
91
|
}
|
|
97
92
|
});
|
|
98
93
|
}, [onToggle]);
|
|
94
|
+
const handleTranscriptionStarted = useCallback(() => {
|
|
95
|
+
trackWidgetInteraction({
|
|
96
|
+
eventName: EnviveMetricsEventName.WidgetInteraction,
|
|
97
|
+
trigger: {
|
|
98
|
+
widget: WidgetInteractionComponent.EMBEDDED_WIDGET,
|
|
99
|
+
widget_interaction: WidgetInteractionType.VOICE_TRANSCRIPTION_STARTED
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}, []);
|
|
103
|
+
const handleTranscriptionCompleted = useCallback((transcript) => {
|
|
104
|
+
trackWidgetInteraction({
|
|
105
|
+
eventName: EnviveMetricsEventName.WidgetInteraction,
|
|
106
|
+
trigger: {
|
|
107
|
+
widget: WidgetInteractionComponent.EMBEDDED_WIDGET,
|
|
108
|
+
widget_interaction: WidgetInteractionType.VOICE_TRANSCRIPTION_COMPLETED,
|
|
109
|
+
widget_interaction_data: { transcription: transcript }
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}, []);
|
|
99
113
|
return /* @__PURE__ */ jsx(TypingAnimation, {
|
|
100
114
|
baseProps: { id },
|
|
101
115
|
widgetContentProps: {
|
|
@@ -106,7 +120,8 @@ const TypingAnimationWidgetHandler = (props) => {
|
|
|
106
120
|
hintText: isLoading ? mockHintText : hintText,
|
|
107
121
|
textFieldAriaLabel,
|
|
108
122
|
logoSrc: logoSrc ?? void 0,
|
|
109
|
-
hideLogo: hideLogo ?? false
|
|
123
|
+
hideLogo: hideLogo ?? false,
|
|
124
|
+
voiceInputEnabled: isVoiceInputEnabled
|
|
110
125
|
},
|
|
111
126
|
widgetStyleProps: {
|
|
112
127
|
widgetVariant,
|
|
@@ -124,7 +139,9 @@ const TypingAnimationWidgetHandler = (props) => {
|
|
|
124
139
|
handleButtonMouseUp: onMouseUp,
|
|
125
140
|
handleButtonTouchStart: onTouchStart,
|
|
126
141
|
handleButtonTouchEnd: onTouchEnd,
|
|
127
|
-
handleTextFieldClick
|
|
142
|
+
handleTextFieldClick,
|
|
143
|
+
onTranscriptionStarted: handleTranscriptionStarted,
|
|
144
|
+
onTranscriptionCompleted: handleTranscriptionCompleted
|
|
128
145
|
}
|
|
129
146
|
});
|
|
130
147
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
2
2
|
|
|
3
3
|
//#region ../widgets/dist/SearchResults/SearchResultsWidget.d.ts
|
|
4
4
|
//#region src/SearchResults/SearchResultsWidget.d.ts
|
|
5
|
-
declare const SearchResultsWidget: () =>
|
|
5
|
+
declare const SearchResultsWidget: () => react_jsx_runtime1.JSX.Element;
|
|
6
6
|
//#endregion
|
|
7
7
|
|
|
8
8
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime3 from "react/jsx-runtime";
|
|
2
2
|
|
|
3
3
|
//#region ../widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts
|
|
4
4
|
//#region src/SearchZeroState/SearchZeroStateWidget.d.ts
|
|
@@ -11,7 +11,7 @@ declare const SearchZeroStateWidget: ({
|
|
|
11
11
|
initialIsOpen,
|
|
12
12
|
widgetConfigId,
|
|
13
13
|
entryPointRef
|
|
14
|
-
}: SearchZeroStateWidgetProps) =>
|
|
14
|
+
}: SearchZeroStateWidgetProps) => react_jsx_runtime3.JSX.Element;
|
|
15
15
|
//#endregion
|
|
16
16
|
|
|
17
17
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SuggestionBarLocationForMetrics } from "./types.cjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
3
3
|
import { SuggestionButtonVariant } from "@envive-ai/react-hooks/contexts/types";
|
|
4
4
|
import { Message } from "postcss";
|
|
5
5
|
|
|
@@ -30,7 +30,7 @@ declare function SuggestionBar({
|
|
|
30
30
|
buttonBorderRadius,
|
|
31
31
|
handleReply,
|
|
32
32
|
dataTestId
|
|
33
|
-
}: Readonly<SuggestionBarProps>):
|
|
33
|
+
}: Readonly<SuggestionBarProps>): react_jsx_runtime2.JSX.Element;
|
|
34
34
|
//#endregion
|
|
35
35
|
//#endregion
|
|
36
36
|
export { SuggestionBar };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
|
|
2
2
|
const require_functions = require('../utils/functions.cjs');
|
|
3
3
|
let react = require("react");
|
|
4
|
-
let __envive_ai_react_hooks_atoms_chat = require("@envive-ai/react-hooks/atoms/chat");
|
|
5
4
|
let jotai = require("jotai");
|
|
5
|
+
let __envive_ai_react_hooks_atoms_chat = require("@envive-ai/react-hooks/atoms/chat");
|
|
6
6
|
let __envive_ai_react_hooks_atoms_chat_chatState = require("@envive-ai/react-hooks/atoms/chat/chatState");
|
|
7
7
|
|
|
8
8
|
//#region src/widgets/hooks/useGetWidgetStatus.tsx
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isLoading, isProductComparison, userHasInteracted, userHasNotInteracted } from "../utils/functions.js";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
|
-
import { chatAtom, lastAssistantMessageAtom } from "@envive-ai/react-hooks/atoms/chat";
|
|
4
3
|
import { useAtomValue } from "jotai";
|
|
4
|
+
import { chatAtom, lastAssistantMessageAtom } from "@envive-ai/react-hooks/atoms/chat";
|
|
5
5
|
import { responseStreamingAtom } from "@envive-ai/react-hooks/atoms/chat/chatState";
|
|
6
6
|
|
|
7
7
|
//#region src/widgets/hooks/useGetWidgetStatus.tsx
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
|
|
2
|
-
let __envive_ai_react_hooks_atoms_chat = require("@envive-ai/react-hooks/atoms/chat");
|
|
3
2
|
let __envive_ai_react_hooks_application_models = require("@envive-ai/react-hooks/application/models");
|
|
3
|
+
let __envive_ai_react_hooks_atoms_chat = require("@envive-ai/react-hooks/atoms/chat");
|
|
4
4
|
let __envive_ai_react_hooks_atoms_atomStore = require("@envive-ai/react-hooks/atoms/atomStore");
|
|
5
5
|
|
|
6
6
|
//#region src/widgets/utils/functions.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { chatAtom, lastAssistantMessageAtom } from "@envive-ai/react-hooks/atoms/chat";
|
|
2
1
|
import { MessageRole, MessageType } from "@envive-ai/react-hooks/application/models";
|
|
2
|
+
import { chatAtom, lastAssistantMessageAtom } from "@envive-ai/react-hooks/atoms/chat";
|
|
3
3
|
import { getAtomStore } from "@envive-ai/react-hooks/atoms/atomStore";
|
|
4
4
|
|
|
5
5
|
//#region src/widgets/utils/functions.ts
|
package/package.json
CHANGED
|
@@ -1,70 +1,15 @@
|
|
|
1
1
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import { RefObject } from 'react';
|
|
3
|
-
import { SpiffyMetricsEventName } from '@envive-ai/react-hooks/contexts/amplitudeContext';
|
|
4
3
|
import { HardcopyResponse } from '@envive-ai/react-hooks/contexts/hardcopyContext';
|
|
5
4
|
import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
|
|
6
5
|
import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
|
|
7
6
|
import { BaseWidgetProps } from '../types';
|
|
8
7
|
import { withBaseWidget } from '../withBaseWidget';
|
|
9
8
|
|
|
10
|
-
// Mock IntersectionObserver
|
|
11
|
-
class MockIntersectionObserver implements IntersectionObserver {
|
|
12
|
-
observe = vi.fn();
|
|
13
|
-
|
|
14
|
-
disconnect = vi.fn();
|
|
15
|
-
|
|
16
|
-
unobserve = vi.fn();
|
|
17
|
-
|
|
18
|
-
root: Element | null = null;
|
|
19
|
-
|
|
20
|
-
rootMargin: string = '';
|
|
21
|
-
|
|
22
|
-
thresholds: ReadonlyArray<number> = [];
|
|
23
|
-
|
|
24
|
-
private callback: (entries: IntersectionObserverEntry[]) => void;
|
|
25
|
-
|
|
26
|
-
private options?: IntersectionObserverInit;
|
|
27
|
-
|
|
28
|
-
constructor(
|
|
29
|
-
callback: (entries: IntersectionObserverEntry[]) => void,
|
|
30
|
-
options?: IntersectionObserverInit,
|
|
31
|
-
) {
|
|
32
|
-
this.callback = callback;
|
|
33
|
-
this.options = options;
|
|
34
|
-
if (options) {
|
|
35
|
-
this.rootMargin = options.rootMargin || '';
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Helper method to trigger intersection
|
|
40
|
-
triggerIntersection(isIntersecting: boolean, target?: Element) {
|
|
41
|
-
const entry = {
|
|
42
|
-
isIntersecting,
|
|
43
|
-
intersectionRatio: isIntersecting ? 1 : 0,
|
|
44
|
-
boundingClientRect: {} as DOMRectReadOnly,
|
|
45
|
-
rootBounds: {} as DOMRectReadOnly,
|
|
46
|
-
target: target || ({} as Element),
|
|
47
|
-
time: Date.now(),
|
|
48
|
-
} as IntersectionObserverEntry;
|
|
49
|
-
|
|
50
|
-
this.callback([entry]);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Helper method to get options
|
|
54
|
-
getOptions() {
|
|
55
|
-
return this.options;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Required by IntersectionObserver interface but not used in tests
|
|
59
|
-
// eslint-disable-next-line class-methods-use-this
|
|
60
|
-
takeRecords(): IntersectionObserverEntry[] {
|
|
61
|
-
return [];
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
9
|
// Mock the contexts
|
|
66
10
|
const mockTrackEvent = vi.fn();
|
|
67
11
|
const mockGetHardcopy = vi.fn();
|
|
12
|
+
const mockUseTrackComponentVisibleEvent = vi.fn();
|
|
68
13
|
|
|
69
14
|
vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
|
|
70
15
|
useAmplitude: () => ({
|
|
@@ -106,6 +51,10 @@ vi.mock('@envive-ai/react-hooks/contexts/uiConfigContext', () => ({
|
|
|
106
51
|
}),
|
|
107
52
|
}));
|
|
108
53
|
|
|
54
|
+
vi.mock('@envive-ai/react-hooks/hooks/TrackComponentVisibleEvent', () => ({
|
|
55
|
+
useTrackComponentVisibleEvent: (...args: unknown[]) => mockUseTrackComponentVisibleEvent(...args),
|
|
56
|
+
}));
|
|
57
|
+
|
|
109
58
|
// Test widget component
|
|
110
59
|
interface TestWidgetProps extends BaseWidgetProps {
|
|
111
60
|
testId?: string;
|
|
@@ -145,44 +94,18 @@ const TestWidget: React.FC<TestWidgetProps> = ({
|
|
|
145
94
|
};
|
|
146
95
|
|
|
147
96
|
describe('withBaseWidget', () => {
|
|
148
|
-
let mockObserver: MockIntersectionObserver;
|
|
149
|
-
let mockObservers: MockIntersectionObserver[];
|
|
150
|
-
let originalIntersectionObserver: typeof IntersectionObserver;
|
|
151
|
-
|
|
152
97
|
beforeEach(() => {
|
|
153
98
|
vi.clearAllMocks();
|
|
154
|
-
mockObservers = [];
|
|
155
99
|
|
|
156
100
|
// Set default mock for getHardcopy to return a resolved promise
|
|
157
101
|
mockGetHardcopy.mockResolvedValue({
|
|
158
102
|
language: 'en',
|
|
159
103
|
values: {},
|
|
160
104
|
});
|
|
161
|
-
|
|
162
|
-
// Store original IntersectionObserver
|
|
163
|
-
originalIntersectionObserver = global.IntersectionObserver;
|
|
164
|
-
|
|
165
|
-
// Replace IntersectionObserver with mock class
|
|
166
|
-
global.IntersectionObserver = class extends MockIntersectionObserver {
|
|
167
|
-
constructor(
|
|
168
|
-
callback: (entries: IntersectionObserverEntry[]) => void,
|
|
169
|
-
options?: IntersectionObserverInit,
|
|
170
|
-
) {
|
|
171
|
-
super(callback, options);
|
|
172
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
173
|
-
mockObserver = this;
|
|
174
|
-
mockObservers.push(this);
|
|
175
|
-
}
|
|
176
|
-
} as unknown as typeof IntersectionObserver;
|
|
177
105
|
});
|
|
178
106
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
global.IntersectionObserver = originalIntersectionObserver;
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
describe('IntersectionObserver setup', () => {
|
|
185
|
-
it('should create an IntersectionObserver with default rootMargin', () => {
|
|
107
|
+
describe('Visibility tracking', () => {
|
|
108
|
+
it('should call useTrackComponentVisibleEvent with default event', () => {
|
|
186
109
|
const WrappedWidget = withBaseWidget(TestWidget);
|
|
187
110
|
render(
|
|
188
111
|
<WrappedWidget
|
|
@@ -191,14 +114,19 @@ describe('withBaseWidget', () => {
|
|
|
191
114
|
/>,
|
|
192
115
|
);
|
|
193
116
|
|
|
194
|
-
expect(
|
|
195
|
-
|
|
117
|
+
expect(mockUseTrackComponentVisibleEvent).toHaveBeenCalledWith(
|
|
118
|
+
expect.any(Object),
|
|
119
|
+
{
|
|
120
|
+
widget_config_id: 'test-config-1',
|
|
121
|
+
widget_type: WidgetTypeV3.PromptCarouselV3,
|
|
122
|
+
},
|
|
123
|
+
'0px',
|
|
124
|
+
true,
|
|
125
|
+
);
|
|
196
126
|
});
|
|
197
127
|
|
|
198
|
-
it('should
|
|
199
|
-
const WrappedWidget = withBaseWidget(TestWidget, {
|
|
200
|
-
rootMargin: '100px',
|
|
201
|
-
});
|
|
128
|
+
it('should pass custom rootMargin to useTrackComponentVisibleEvent', () => {
|
|
129
|
+
const WrappedWidget = withBaseWidget(TestWidget, { rootMargin: '100px' });
|
|
202
130
|
render(
|
|
203
131
|
<WrappedWidget
|
|
204
132
|
widgetConfigId="test-config-2"
|
|
@@ -206,168 +134,42 @@ describe('withBaseWidget', () => {
|
|
|
206
134
|
/>,
|
|
207
135
|
);
|
|
208
136
|
|
|
209
|
-
expect(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
widgetConfigId="test-config-3"
|
|
218
|
-
widgetType={WidgetTypeV3.PromptCarouselV3}
|
|
219
|
-
/>,
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
expect(mockObserver.observe).toHaveBeenCalled();
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe('Amplitude event tracking', () => {
|
|
227
|
-
it('should track default event when widget becomes visible', async () => {
|
|
228
|
-
const WrappedWidget = withBaseWidget(TestWidget);
|
|
229
|
-
const { container } = render(
|
|
230
|
-
<WrappedWidget
|
|
231
|
-
widgetConfigId="test-config-4"
|
|
232
|
-
widgetType={WidgetTypeV3.PromptCarouselV3}
|
|
233
|
-
/>,
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
const widgetElement = container.querySelector('[data-testid="test-widget"]');
|
|
237
|
-
|
|
238
|
-
// Trigger intersection with the actual element
|
|
239
|
-
mockObserver.triggerIntersection(true, widgetElement as Element);
|
|
240
|
-
|
|
241
|
-
await waitFor(() => {
|
|
242
|
-
expect(mockTrackEvent).toHaveBeenCalledWith({
|
|
243
|
-
eventName: SpiffyMetricsEventName.ChatComponentVisible,
|
|
244
|
-
eventProps: {
|
|
245
|
-
widget_config_id: 'test-config-4',
|
|
246
|
-
widget_type: WidgetTypeV3.PromptCarouselV3,
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('should track custom event when widget becomes visible', async () => {
|
|
253
|
-
const WrappedWidget = withBaseWidget(TestWidget, {
|
|
254
|
-
visibilityEventName: SpiffyMetricsEventName.SearchComponentVisible,
|
|
255
|
-
});
|
|
256
|
-
const { container } = render(
|
|
257
|
-
<WrappedWidget
|
|
258
|
-
widgetConfigId="test-config-5"
|
|
259
|
-
widgetType={WidgetTypeV3.ChatPreviewV3}
|
|
260
|
-
/>,
|
|
137
|
+
expect(mockUseTrackComponentVisibleEvent).toHaveBeenCalledWith(
|
|
138
|
+
expect.any(Object),
|
|
139
|
+
{
|
|
140
|
+
widget_config_id: 'test-config-2',
|
|
141
|
+
widget_type: WidgetTypeV3.ChatPreviewV3,
|
|
142
|
+
},
|
|
143
|
+
'100px',
|
|
144
|
+
true,
|
|
261
145
|
);
|
|
262
|
-
|
|
263
|
-
const widgetElement = container.querySelector('[data-testid="test-widget"]');
|
|
264
|
-
|
|
265
|
-
// Trigger intersection
|
|
266
|
-
mockObserver.triggerIntersection(true, widgetElement as Element);
|
|
267
|
-
|
|
268
|
-
await waitFor(() => {
|
|
269
|
-
expect(mockTrackEvent).toHaveBeenCalledWith({
|
|
270
|
-
eventName: SpiffyMetricsEventName.SearchComponentVisible,
|
|
271
|
-
eventProps: {
|
|
272
|
-
widget_config_id: 'test-config-5',
|
|
273
|
-
widget_type: WidgetTypeV3.ChatPreviewV3,
|
|
274
|
-
},
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
146
|
});
|
|
278
147
|
|
|
279
|
-
it('should include custom event props
|
|
148
|
+
it('should include custom event props', () => {
|
|
280
149
|
const WrappedWidget = withBaseWidget(TestWidget, {
|
|
281
150
|
visibilityEventProps: {
|
|
282
151
|
custom_prop: 'custom_value',
|
|
283
152
|
another_prop: 123,
|
|
284
153
|
},
|
|
285
154
|
});
|
|
286
|
-
|
|
155
|
+
render(
|
|
287
156
|
<WrappedWidget
|
|
288
|
-
widgetConfigId="test-config-
|
|
157
|
+
widgetConfigId="test-config-4"
|
|
289
158
|
widgetType={WidgetTypeV3.SocialProofV3}
|
|
290
159
|
/>,
|
|
291
160
|
);
|
|
292
161
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
widget_type: WidgetTypeV3.SocialProofV3,
|
|
304
|
-
custom_prop: 'custom_value',
|
|
305
|
-
another_prop: 123,
|
|
306
|
-
},
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it('should only track event once even if intersection happens multiple times', async () => {
|
|
312
|
-
const WrappedWidget = withBaseWidget(TestWidget);
|
|
313
|
-
const { container } = render(
|
|
314
|
-
<WrappedWidget
|
|
315
|
-
widgetConfigId="test-config-7"
|
|
316
|
-
widgetType={WidgetTypeV3.PromptCarouselV3}
|
|
317
|
-
/>,
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
const widgetElement = container.querySelector('[data-testid="test-widget"]');
|
|
321
|
-
|
|
322
|
-
// Trigger intersection multiple times
|
|
323
|
-
mockObserver.triggerIntersection(true, widgetElement as Element);
|
|
324
|
-
mockObserver.triggerIntersection(false, widgetElement as Element);
|
|
325
|
-
mockObserver.triggerIntersection(true, widgetElement as Element);
|
|
326
|
-
|
|
327
|
-
await waitFor(() => {
|
|
328
|
-
expect(mockTrackEvent).toHaveBeenCalledTimes(1);
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it('should disconnect observer after tracking event', async () => {
|
|
333
|
-
const WrappedWidget = withBaseWidget(TestWidget);
|
|
334
|
-
const { container } = render(
|
|
335
|
-
<WrappedWidget
|
|
336
|
-
widgetConfigId="test-config-8"
|
|
337
|
-
widgetType={WidgetTypeV3.PromptCarouselV3}
|
|
338
|
-
/>,
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
const widgetElement = container.querySelector('[data-testid="test-widget"]');
|
|
342
|
-
|
|
343
|
-
// Trigger intersection
|
|
344
|
-
mockObserver.triggerIntersection(true, widgetElement as Element);
|
|
345
|
-
|
|
346
|
-
await waitFor(() => {
|
|
347
|
-
expect(mockObserver.disconnect).toHaveBeenCalled();
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('should not track event if widget is not intersecting', async () => {
|
|
352
|
-
const WrappedWidget = withBaseWidget(TestWidget);
|
|
353
|
-
const { container } = render(
|
|
354
|
-
<WrappedWidget
|
|
355
|
-
widgetConfigId="test-config-9"
|
|
356
|
-
widgetType={WidgetTypeV3.PromptCarouselV3}
|
|
357
|
-
/>,
|
|
162
|
+
expect(mockUseTrackComponentVisibleEvent).toHaveBeenCalledWith(
|
|
163
|
+
expect.any(Object),
|
|
164
|
+
{
|
|
165
|
+
widget_config_id: 'test-config-4',
|
|
166
|
+
widget_type: WidgetTypeV3.SocialProofV3,
|
|
167
|
+
custom_prop: 'custom_value',
|
|
168
|
+
another_prop: 123,
|
|
169
|
+
},
|
|
170
|
+
'0px',
|
|
171
|
+
true,
|
|
358
172
|
);
|
|
359
|
-
|
|
360
|
-
const widgetElement = container.querySelector('[data-testid="test-widget"]');
|
|
361
|
-
|
|
362
|
-
// Trigger non-intersection
|
|
363
|
-
mockObserver.triggerIntersection(false, widgetElement as Element);
|
|
364
|
-
|
|
365
|
-
// Wait a bit to ensure no event is tracked
|
|
366
|
-
await new Promise(resolve => {
|
|
367
|
-
setTimeout(resolve, 100);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
expect(mockTrackEvent).not.toHaveBeenCalled();
|
|
371
173
|
});
|
|
372
174
|
});
|
|
373
175
|
|
|
@@ -553,7 +355,6 @@ describe('withBaseWidget', () => {
|
|
|
553
355
|
mockGetHardcopy.mockResolvedValue(mockHardcopy);
|
|
554
356
|
|
|
555
357
|
const WrappedWidget = withBaseWidget(TestWidget, {
|
|
556
|
-
visibilityEventName: SpiffyMetricsEventName.SearchComponentVisible,
|
|
557
358
|
visibilityEventProps: {
|
|
558
359
|
test_integration: true,
|
|
559
360
|
},
|
|
@@ -573,20 +374,17 @@ describe('withBaseWidget', () => {
|
|
|
573
374
|
|
|
574
375
|
const widgetElement = screen.getByTestId('test-widget');
|
|
575
376
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
},
|
|
588
|
-
});
|
|
589
|
-
});
|
|
377
|
+
expect(widgetElement).toBeInTheDocument();
|
|
378
|
+
expect(mockUseTrackComponentVisibleEvent).toHaveBeenCalledWith(
|
|
379
|
+
expect.any(Object),
|
|
380
|
+
{
|
|
381
|
+
widget_config_id: 'test-config-integration',
|
|
382
|
+
widget_type: WidgetTypeV3.PromptCarouselV3,
|
|
383
|
+
test_integration: true,
|
|
384
|
+
},
|
|
385
|
+
'0px',
|
|
386
|
+
true,
|
|
387
|
+
);
|
|
590
388
|
|
|
591
389
|
expect(screen.getByTestId('hardcopy-language')).toHaveTextContent('en');
|
|
592
390
|
expect(screen.getByTestId('hardcopy-values')).toHaveTextContent(
|
|
@@ -652,56 +450,24 @@ describe('withBaseWidget', () => {
|
|
|
652
450
|
userEvent: expect.any(Object),
|
|
653
451
|
});
|
|
654
452
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
// Trigger intersection for widget-1 using its observer
|
|
674
|
-
widget1Observer.triggerIntersection(true, widget1Element);
|
|
675
|
-
|
|
676
|
-
await waitFor(() => {
|
|
677
|
-
// Verify that an event was tracked for widget-1
|
|
678
|
-
expect(mockTrackEvent).toHaveBeenCalled();
|
|
679
|
-
const { calls } = mockTrackEvent.mock;
|
|
680
|
-
// Find any call that matches widget-1's properties
|
|
681
|
-
const widget1Call = calls.find(
|
|
682
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
683
|
-
(call: any) =>
|
|
684
|
-
call[0]?.eventProps?.widget_config_id === 'widget-1' &&
|
|
685
|
-
call[0]?.eventProps?.widget_type === WidgetTypeV3.PromptCarouselV3,
|
|
686
|
-
);
|
|
687
|
-
expect(widget1Call).toBeDefined();
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
// Verify widget-2 can also track independently
|
|
691
|
-
mockTrackEvent.mockClear();
|
|
692
|
-
widget2Observer.triggerIntersection(true, widget2Element);
|
|
693
|
-
|
|
694
|
-
await waitFor(() => {
|
|
695
|
-
expect(mockTrackEvent).toHaveBeenCalled();
|
|
696
|
-
const { calls } = mockTrackEvent.mock;
|
|
697
|
-
const widget2Call = calls.find(
|
|
698
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
699
|
-
(call: any) =>
|
|
700
|
-
call[0]?.eventProps?.widget_config_id === 'widget-2' &&
|
|
701
|
-
call[0]?.eventProps?.widget_type === WidgetTypeV3.ChatPreviewV3,
|
|
702
|
-
);
|
|
703
|
-
expect(widget2Call).toBeDefined();
|
|
704
|
-
});
|
|
453
|
+
expect(mockUseTrackComponentVisibleEvent).toHaveBeenCalledWith(
|
|
454
|
+
expect.any(Object),
|
|
455
|
+
{
|
|
456
|
+
widget_config_id: 'widget-1',
|
|
457
|
+
widget_type: WidgetTypeV3.PromptCarouselV3,
|
|
458
|
+
},
|
|
459
|
+
'0px',
|
|
460
|
+
true,
|
|
461
|
+
);
|
|
462
|
+
expect(mockUseTrackComponentVisibleEvent).toHaveBeenCalledWith(
|
|
463
|
+
expect.any(Object),
|
|
464
|
+
{
|
|
465
|
+
widget_config_id: 'widget-2',
|
|
466
|
+
widget_type: WidgetTypeV3.ChatPreviewV3,
|
|
467
|
+
},
|
|
468
|
+
'0px',
|
|
469
|
+
true,
|
|
470
|
+
);
|
|
705
471
|
});
|
|
706
472
|
});
|
|
707
473
|
});
|