@envive-ai/react-hooks 0.2.7-arthur-1 → 0.2.7-arthur-2
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/{NewOrgConfig-yptI2imS.js → NewOrgConfig-BVByiYPp.js} +2 -2
- package/dist/{NewOrgConfig-Bo1seKr6.cjs → NewOrgConfig-CInGtTV6.cjs} +2 -2
- package/dist/{SystemSettingsContext-EDpRMMt2.cjs → SystemSettingsContext-150LTxIk.cjs} +2 -2
- package/dist/{SystemSettingsContext-BY1BFgAQ.js → SystemSettingsContext-ei5B0dxO.js} +2 -2
- package/dist/{TrackComponentVisibleEvent-CgxCqrIt.cjs → TrackComponentVisibleEvent-C7-nnBks.cjs} +2 -2
- package/dist/{TrackComponentVisibleEvent-CXhKOwKQ.js → TrackComponentVisibleEvent-CuwSLbug.js} +2 -2
- package/dist/amplitudeContext-BBQ1ATA3.js +265 -0
- package/dist/amplitudeContext-C-0-DDk3.cjs +287 -0
- package/dist/{app-CflxT_xI.js → app-C_Y-57U5.js} +3 -4
- package/dist/{app-BbPSHefQ.cjs → app-XEFPotoH.cjs} +3 -4
- package/dist/application/utils/index.cjs +8 -8
- package/dist/application/utils/index.d.cts +2 -2
- package/dist/application/utils/index.d.ts +2 -2
- package/dist/application/utils/index.js +8 -8
- package/dist/{atomStore-CmZbgQHc.cjs → atomStore-8ppNkJ_n.cjs} +1 -1
- package/dist/{atomStore-DEcDhiLp.js → atomStore-BLYJ2ZoQ.js} +1 -1
- package/dist/atoms/app/index.cjs +4 -4
- package/dist/atoms/app/index.d.cts +7 -7
- package/dist/atoms/app/index.d.ts +7 -7
- package/dist/atoms/app/index.js +4 -4
- package/dist/atoms/atomStore/index.cjs +1 -1
- package/dist/atoms/atomStore/index.js +1 -1
- package/dist/atoms/chat/index.cjs +9 -9
- package/dist/atoms/chat/index.d.cts +26 -26
- package/dist/atoms/chat/index.d.ts +27 -27
- package/dist/atoms/chat/index.js +9 -9
- package/dist/atoms/globalSearch/index.d.cts +5 -5
- package/dist/atoms/globalSearch/index.d.ts +6 -6
- package/dist/atoms/org/index.cjs +1 -1
- package/dist/atoms/org/index.d.cts +16 -16
- package/dist/atoms/org/index.d.ts +16 -16
- package/dist/atoms/org/index.js +1 -1
- package/dist/atoms/search/index.cjs +11 -11
- package/dist/atoms/search/index.d.cts +1 -1
- package/dist/atoms/search/index.d.ts +1 -1
- package/dist/atoms/search/index.js +11 -11
- package/dist/atoms/search/utils.d.ts +1 -1
- package/dist/{cdnContext-CaDyQ_5p.cjs → cdnContext-Bzqk0s2M.cjs} +2 -2
- package/dist/{cdnContext-CtrIlAqX.js → cdnContext-CTC-zBtx.js} +2 -2
- package/dist/{chat-BjhQCyW_.js → chat-Bzay7QnI.js} +6 -6
- package/dist/{chat-BkPax29G.cjs → chat-Ckd1b_z_.cjs} +6 -6
- package/dist/{chatSearch-C3N3iIxu.cjs → chatSearch-Bev4ZI8Z.cjs} +3 -3
- package/dist/{chatSearch-BsYlFvpv.js → chatSearch-DNaGtQyx.js} +3 -3
- package/dist/{chatState-CXA1vF16.js → chatState-CcCvgmSM.js} +2 -2
- package/dist/{chatState-CJ52Ag_7.cjs → chatState-OkYPVghN.cjs} +2 -2
- package/dist/{commerce-api-rgj30eEp.js → commerce-api-ml5fkEuk.js} +6 -6
- package/dist/{commerce-api-DA1QGGMK.cjs → commerce-api-sQtLuwTh.cjs} +6 -6
- package/dist/contexts/amplitudeContext/index.cjs +7 -7
- package/dist/contexts/amplitudeContext/index.js +7 -7
- package/dist/contexts/cdnContext/index.cjs +3 -3
- package/dist/contexts/cdnContext/index.js +3 -3
- package/dist/contexts/chatContext/index.cjs +16 -16
- package/dist/contexts/chatContext/index.d.cts +2 -2
- package/dist/contexts/chatContext/index.d.ts +4 -4
- package/dist/contexts/chatContext/index.js +16 -16
- package/dist/contexts/enviveConfigContext/index.cjs +3 -3
- package/dist/contexts/enviveConfigContext/index.js +3 -3
- package/dist/contexts/enviveCssContext/index.cjs +6 -6
- package/dist/contexts/enviveCssContext/index.js +6 -6
- package/dist/contexts/featureFlagContext/index.cjs +4 -4
- package/dist/contexts/featureFlagContext/index.js +4 -4
- package/dist/contexts/graphqlContext/index.cjs +3 -3
- package/dist/contexts/graphqlContext/index.d.ts +1 -1
- package/dist/contexts/graphqlContext/index.js +3 -3
- package/dist/contexts/localStorageContext/index.cjs +1 -1
- package/dist/contexts/localStorageContext/index.js +1 -1
- package/dist/contexts/newOrgConfigContext/index.cjs +5 -5
- package/dist/contexts/newOrgConfigContext/index.d.ts +2 -2
- package/dist/contexts/newOrgConfigContext/index.js +5 -5
- package/dist/contexts/searchContext/index.cjs +10 -10
- package/dist/contexts/searchContext/index.js +10 -10
- package/dist/contexts/sessionStorageContext/index.cjs +1 -1
- package/dist/contexts/sessionStorageContext/index.js +1 -1
- package/dist/contexts/shopifyUrlContext/index.cjs +1 -1
- package/dist/contexts/shopifyUrlContext/index.js +1 -1
- package/dist/contexts/systemSettingsContext/index.cjs +3 -3
- package/dist/contexts/systemSettingsContext/index.d.cts +2 -2
- package/dist/contexts/systemSettingsContext/index.d.ts +2 -2
- package/dist/contexts/systemSettingsContext/index.js +3 -3
- package/dist/contexts/userIdentityContext/index.cjs +11 -11
- package/dist/contexts/userIdentityContext/index.js +11 -11
- package/dist/{enviveConfig-DZBohDpc.js → enviveConfig-DV8F12B9.js} +2 -2
- package/dist/{enviveConfig-Dv9-esGV.cjs → enviveConfig-DZHdtLsQ.cjs} +2 -2
- package/dist/{enviveConfigContext-DrDjCems.js → enviveConfigContext-BS7aNop5.js} +2 -2
- package/dist/{enviveConfigContext-D2OELZDR.cjs → enviveConfigContext-CTcHUIFP.cjs} +2 -2
- package/dist/frontendConfig-Cawh5iqv.d.ts +1 -1
- package/dist/frontendConfig-iZipB5FG.d.cts +1 -1
- package/dist/{graphqlContext-DP8T3-Kd.cjs → graphqlContext-CVbYIftg.cjs} +2 -2
- package/dist/{graphqlContext-CXQl0hq2.d.ts → graphqlContext-DgkS-UX1.d.ts} +3 -3
- package/dist/{graphqlContext-czH0kIZg.js → graphqlContext-DouNZbYo.js} +2 -2
- package/dist/hooks/AmplitudeOperations/index.cjs +9 -9
- package/dist/hooks/AmplitudeOperations/index.js +9 -9
- package/dist/hooks/AppDetails/index.cjs +7 -7
- package/dist/hooks/AppDetails/index.js +7 -7
- package/dist/hooks/CdnOperations/index.cjs +3 -3
- package/dist/hooks/CdnOperations/index.js +3 -3
- package/dist/hooks/ChatToggle/index.cjs +9 -9
- package/dist/hooks/ChatToggle/index.js +9 -9
- package/dist/hooks/ChatToggleAnalytics/index.cjs +10 -10
- package/dist/hooks/ChatToggleAnalytics/index.js +10 -10
- package/dist/hooks/GrabAndScroll/index.d.cts +2 -2
- package/dist/hooks/GrabAndScroll/index.d.ts +2 -2
- package/dist/hooks/GraphQLConfig/index.cjs +4 -4
- package/dist/hooks/GraphQLConfig/index.d.ts +1 -1
- package/dist/hooks/GraphQLConfig/index.js +4 -4
- package/dist/hooks/IdentifyUser/index.cjs +11 -11
- package/dist/hooks/IdentifyUser/index.js +11 -11
- package/dist/hooks/ImageResolver/index.cjs +2 -2
- package/dist/hooks/ImageResolver/index.js +2 -2
- package/dist/hooks/LocalStorageOperations/index.cjs +1 -1
- package/dist/hooks/LocalStorageOperations/index.js +1 -1
- package/dist/hooks/NewOrgConfig/index.cjs +6 -6
- package/dist/hooks/NewOrgConfig/index.d.ts +2 -2
- package/dist/hooks/NewOrgConfig/index.js +6 -6
- package/dist/hooks/Search/index.cjs +28 -1166
- package/dist/hooks/Search/index.d.cts +1 -1
- package/dist/hooks/Search/index.d.ts +1 -1
- package/dist/hooks/Search/index.js +26 -1165
- package/dist/hooks/SearchOperations/index.cjs +10 -10
- package/dist/hooks/SearchOperations/index.js +10 -10
- package/dist/hooks/SessionStorageOperations/index.cjs +1 -1
- package/dist/hooks/SessionStorageOperations/index.js +1 -1
- package/dist/hooks/ShopifyUrlOperations/index.cjs +1 -1
- package/dist/hooks/ShopifyUrlOperations/index.js +1 -1
- package/dist/hooks/SystemSettingsContext/index.cjs +4 -4
- package/dist/hooks/SystemSettingsContext/index.d.cts +2 -2
- package/dist/hooks/SystemSettingsContext/index.d.ts +2 -2
- package/dist/hooks/SystemSettingsContext/index.js +4 -4
- package/dist/hooks/TrackComponentVisibleEvent/index.cjs +8 -8
- package/dist/hooks/TrackComponentVisibleEvent/index.js +8 -8
- package/dist/hooks/UpdateAnalyticsProps/index.cjs +7 -7
- package/dist/hooks/UpdateAnalyticsProps/index.js +7 -7
- package/dist/{index-CUO68KG3.d.ts → index-CMJM-3zV.d.ts} +30 -30
- package/dist/{index-BSd8767K.d.cts → index-DpJzjjpi.d.cts} +30 -30
- package/dist/{localStorageContext-BPZ82q-G.js → localStorageContext-CqcSvg2H.js} +1 -1
- package/dist/{localStorageContext-NRP-CdmF.cjs → localStorageContext-DiLfSsqL.cjs} +1 -1
- package/dist/{newOrgConfigContext-Bet9CgKP.cjs → newOrgConfigContext-BIDz4ZuO.cjs} +3 -3
- package/dist/{newOrgConfigContext-I2qceBB4.d.ts → newOrgConfigContext-CKn7B2rj.d.ts} +2 -2
- package/dist/{newOrgConfigContext-Bi_dBNe5.js → newOrgConfigContext-u_9UPNcX.js} +3 -3
- package/dist/{orgAnalyticsConfig-Bm23fw4s.cjs → orgAnalyticsConfig-CGEQtAFs.cjs} +1 -1
- package/dist/{orgAnalyticsConfig-CpBmga08.js → orgAnalyticsConfig-i4jozLBB.js} +1 -1
- package/dist/{search-Csh2n66W.cjs → search-CTVX9gC6.cjs} +2 -2
- package/dist/{search-DkiqkogN.js → search-NgNrXNS9.js} +2 -2
- package/dist/{searchContext-DksJfC1s.cjs → searchContext-CnDXkawZ.cjs} +5 -5
- package/dist/{searchContext-BmgoAFMF.js → searchContext-DtRmshTA.js} +5 -5
- package/dist/{sessionStorageContext-B6FsNKjj.cjs → sessionStorageContext-1Ks_d4Z0.cjs} +1 -1
- package/dist/{sessionStorageContext-CLYCm83p.js → sessionStorageContext-CDcl7NVl.js} +1 -1
- package/dist/{shopifyUrlContext-ZOcARiMR.cjs → shopifyUrlContext-CxjV3qvH.cjs} +1 -1
- package/dist/{shopifyUrlContext-C-PkSgNC.js → shopifyUrlContext-D2btP_lY.js} +1 -1
- package/dist/{systemSettingsContext-dmE1v6w8.cjs → systemSettingsContext-BejoGzzB.cjs} +2 -2
- package/dist/{systemSettingsContext-DF0jSq9m.js → systemSettingsContext-C4dtZ0uZ.js} +2 -2
- package/dist/types-BegmH0S1.d.ts +1 -1
- package/dist/{unsupportedProductExceptions-DGENUnEA.cjs → unsupportedProductExceptions-B0yx2bHK.cjs} +1 -1
- package/dist/{unsupportedProductExceptions-uQuuelOs.js → unsupportedProductExceptions-Cs66ngs3.js} +1 -1
- package/dist/{useAmplitudeOperations-Bo6YNbTV.cjs → useAmplitudeOperations-BJXD9v2u.cjs} +2 -2
- package/dist/{useAmplitudeOperations-zIRSqmMW.js → useAmplitudeOperations-Dym0Ker8.js} +2 -2
- package/dist/{useAppDetails-B584gkCs.js → useAppDetails-Dmh16bWE.js} +4 -4
- package/dist/{useAppDetails-DczgqeLG.cjs → useAppDetails-DsAZ1xQn.cjs} +4 -4
- package/dist/{useGraphQLConfig-D_rF2Sun.cjs → useGraphQLConfig-B3DlwmGg.cjs} +2 -2
- package/dist/{useGraphQLConfig-7UxACM4n.js → useGraphQLConfig-DSRaDTdT.js} +2 -2
- package/dist/userIdentityContext-DF3atBFE.js +119 -0
- package/dist/userIdentityContext-DpQTduhF.cjs +136 -0
- package/dist/{utils-C1ErYSoW.js → utils-B7KTAEmV.js} +2 -2
- package/dist/utils-CBD4g2Nc.d.cts +1 -1
- package/dist/{utils-mqfncrhI.cjs → utils-CcC2jZRi.cjs} +2 -2
- package/package.json +3 -2
- package/src/atoms/app/index.ts +1 -1
- package/src/contexts/amplitudeContext/__tests__/amplitudeContext.test.tsx +525 -0
- package/src/contexts/amplitudeContext/amplitudeContext.tsx +5 -2
- package/src/contexts/userIdentityContext/userIdentityContext.tsx +7 -5
- package/dist/amplitudeContext-C8tT74Mi.cjs +0 -286
- package/dist/amplitudeContext-DCk6Va-j.js +0 -264
- package/dist/userIdentityContext-BqbNu7xu.cjs +0 -132
- package/dist/userIdentityContext-BxFH9FNQ.js +0 -115
- /package/dist/{AmplitudeOperations-ChZWcSsc.js → AmplitudeOperations-C-ieCm9m.js} +0 -0
- /package/dist/{AmplitudeOperations-JggIc1zD.cjs → AmplitudeOperations-p7APchq9.cjs} +0 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, waitFor, act } from "@testing-library/react";
|
|
3
|
+
import { Provider, useStore } from "jotai";
|
|
4
|
+
import {
|
|
5
|
+
AmplitudeProvider,
|
|
6
|
+
useAmplitude,
|
|
7
|
+
SpiffyMetricsEventName,
|
|
8
|
+
} from "../amplitudeContext";
|
|
9
|
+
import {
|
|
10
|
+
UserIdentityProvider,
|
|
11
|
+
useUserIdentity,
|
|
12
|
+
} from "src/contexts/userIdentityContext/userIdentityContext";
|
|
13
|
+
import { LocalStorageProvider } from "src/contexts/localStorageContext";
|
|
14
|
+
import { EnviveConfigProvider } from "src/contexts/enviveConfigContext/enviveConfigContext";
|
|
15
|
+
import { FeatureFlagServiceProvider } from "src/contexts/featureFlagServiceContext/featureFlagServiceContext";
|
|
16
|
+
import { userIdAtom } from "src/atoms/app";
|
|
17
|
+
import Logger from "src/application/logging/logger";
|
|
18
|
+
import { createInstance } from "@amplitude/analytics-browser";
|
|
19
|
+
|
|
20
|
+
// Mock the Logger to avoid console output in tests
|
|
21
|
+
// vi.spyOn(Logger, "logInfo").mockImplementation(() => {});
|
|
22
|
+
// vi.spyOn(Logger, "logWarn").mockImplementation(() => {});
|
|
23
|
+
// vi.spyOn(Logger, "logError").mockImplementation(() => {});
|
|
24
|
+
// vi.spyOn(Logger, "logDebug").mockImplementation(() => {});
|
|
25
|
+
|
|
26
|
+
// Mock Amplitude
|
|
27
|
+
const mockTrack = vi.fn();
|
|
28
|
+
const mockInit = vi.fn();
|
|
29
|
+
const mockAdd = vi.fn();
|
|
30
|
+
|
|
31
|
+
vi.mock("@amplitude/analytics-browser", () => ({
|
|
32
|
+
createInstance: vi.fn(() => ({
|
|
33
|
+
track: mockTrack,
|
|
34
|
+
init: mockInit,
|
|
35
|
+
add: mockAdd,
|
|
36
|
+
})),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
// Mock EventsDispatcher
|
|
40
|
+
vi.mock("src/events", () => ({
|
|
41
|
+
EventsDispatcher: {
|
|
42
|
+
dispatch: vi.fn(),
|
|
43
|
+
},
|
|
44
|
+
SpiffyEvent: {
|
|
45
|
+
AMPLITUDE_EVENT: "AMPLITUDE_EVENT",
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// Mock crypto.subtle.digest for insert_id generation
|
|
50
|
+
const mockDigest = vi.fn().mockResolvedValue(new Uint8Array(32).fill(0));
|
|
51
|
+
Object.defineProperty(global, "crypto", {
|
|
52
|
+
value: {
|
|
53
|
+
subtle: {
|
|
54
|
+
digest: mockDigest,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
writable: true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Component that uses the useAmplitude hook
|
|
61
|
+
const MockAmplitudeComponent: React.FC = () => {
|
|
62
|
+
const amplitude = useAmplitude();
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div data-testid="amplitude-component">
|
|
66
|
+
<div data-testid="is-ready">{amplitude.isReady.toString()}</div>
|
|
67
|
+
<button
|
|
68
|
+
data-testid="track-event-button"
|
|
69
|
+
onClick={() =>
|
|
70
|
+
amplitude.trackEvent({
|
|
71
|
+
eventName: SpiffyMetricsEventName.ChatUserMessageInput,
|
|
72
|
+
eventProps: { message: "test message" },
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
>
|
|
76
|
+
Track Event
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Component that reads userIdAtom to verify it's set
|
|
83
|
+
const AtomReaderComponent: React.FC = () => {
|
|
84
|
+
const store = useStore();
|
|
85
|
+
const [userId, setUserId] = React.useState<string>("not-set");
|
|
86
|
+
|
|
87
|
+
React.useEffect(() => {
|
|
88
|
+
const unsubscribe = store.sub(userIdAtom, () => {
|
|
89
|
+
try {
|
|
90
|
+
const value = store.get(userIdAtom);
|
|
91
|
+
if (value) {
|
|
92
|
+
setUserId(value);
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
// Still not set
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const timer = setTimeout(() => {
|
|
100
|
+
try {
|
|
101
|
+
const value = store.get(userIdAtom);
|
|
102
|
+
if (value) {
|
|
103
|
+
setUserId(value);
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// Not set yet
|
|
107
|
+
}
|
|
108
|
+
}, 50);
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
unsubscribe();
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
};
|
|
114
|
+
}, [store]);
|
|
115
|
+
|
|
116
|
+
return <div data-testid="atom-reader">{userId}</div>;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Component that uses both contexts
|
|
120
|
+
const CombinedContextComponent: React.FC = () => {
|
|
121
|
+
const userIdentity = useUserIdentity();
|
|
122
|
+
const amplitude = useAmplitude();
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div data-testid="combined-component">
|
|
126
|
+
<div data-testid="user-id-from-identity">
|
|
127
|
+
{userIdentity.getUserIdOrDefault()}
|
|
128
|
+
</div>
|
|
129
|
+
<div data-testid="amplitude-ready">{amplitude.isReady.toString()}</div>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Wrapper component with all required providers
|
|
135
|
+
const TestWrapper: React.FC<{
|
|
136
|
+
children: React.ReactNode;
|
|
137
|
+
amplitudeApiKey?: string;
|
|
138
|
+
userIdOverride?: string;
|
|
139
|
+
userIdDefault?: string;
|
|
140
|
+
}> = ({
|
|
141
|
+
children,
|
|
142
|
+
amplitudeApiKey = "test-amplitude-key",
|
|
143
|
+
userIdOverride,
|
|
144
|
+
userIdDefault,
|
|
145
|
+
}) => {
|
|
146
|
+
// Set up localStorage with user IDs if provided
|
|
147
|
+
React.useEffect(() => {
|
|
148
|
+
if (userIdOverride) {
|
|
149
|
+
localStorage.setItem("v1-spiffy-user-id-override", userIdOverride);
|
|
150
|
+
}
|
|
151
|
+
if (userIdDefault) {
|
|
152
|
+
localStorage.setItem("v1-spiffy-user-id-default", userIdDefault);
|
|
153
|
+
}
|
|
154
|
+
}, [userIdOverride, userIdDefault]);
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<Provider>
|
|
158
|
+
<EnviveConfigProvider
|
|
159
|
+
identifyingPrefix="test"
|
|
160
|
+
orgShortName="test-org"
|
|
161
|
+
amplitudeApiKey={amplitudeApiKey}
|
|
162
|
+
dataResidency="US"
|
|
163
|
+
env="test"
|
|
164
|
+
contextSource="app"
|
|
165
|
+
featureGates={[]}
|
|
166
|
+
>
|
|
167
|
+
<LocalStorageProvider>
|
|
168
|
+
<UserIdentityProvider>
|
|
169
|
+
<FeatureFlagServiceProvider featureGates={[]}>
|
|
170
|
+
<AmplitudeProvider>{children}</AmplitudeProvider>
|
|
171
|
+
</FeatureFlagServiceProvider>
|
|
172
|
+
</UserIdentityProvider>
|
|
173
|
+
</LocalStorageProvider>
|
|
174
|
+
</EnviveConfigProvider>
|
|
175
|
+
</Provider>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
describe("AmplitudeProvider with UserIdentityContext", () => {
|
|
180
|
+
beforeEach(() => {
|
|
181
|
+
vi.clearAllMocks();
|
|
182
|
+
if (typeof localStorage !== "undefined") {
|
|
183
|
+
localStorage.clear();
|
|
184
|
+
}
|
|
185
|
+
// Reset mocks
|
|
186
|
+
mockTrack.mockClear();
|
|
187
|
+
mockInit.mockClear();
|
|
188
|
+
mockAdd.mockClear();
|
|
189
|
+
mockDigest.mockResolvedValue(new Uint8Array(32).fill(0));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe("Integration with UserIdentityContext - userIdAtom", () => {
|
|
193
|
+
it("should be ready when UserIdentityContext provides a userId", async () => {
|
|
194
|
+
render(
|
|
195
|
+
<TestWrapper>
|
|
196
|
+
<CombinedContextComponent />
|
|
197
|
+
</TestWrapper>
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
await waitFor(() => {
|
|
201
|
+
const isReady = screen.getByTestId("amplitude-ready").textContent;
|
|
202
|
+
expect(isReady).toBe("true");
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should use userId from UserIdentityContext in tracking events", async () => {
|
|
207
|
+
const testUserId = "test-user-id-123";
|
|
208
|
+
render(
|
|
209
|
+
<TestWrapper userIdOverride={testUserId}>
|
|
210
|
+
<MockAmplitudeComponent />
|
|
211
|
+
</TestWrapper>
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
await waitFor(() => {
|
|
215
|
+
expect(screen.getByTestId("is-ready").textContent).toBe("true");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
await act(async () => {
|
|
219
|
+
screen.getByTestId("track-event-button").click();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await waitFor(() => {
|
|
223
|
+
expect(mockTrack).toHaveBeenCalled();
|
|
224
|
+
const callArgs = mockTrack.mock.calls[0];
|
|
225
|
+
expect(callArgs[0]).toBe("[Spiffy] Chat User Message Input");
|
|
226
|
+
expect(callArgs[1]).toHaveProperty("user.id", testUserId);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should use default userId from UserIdentityContext when override is not set", async () => {
|
|
231
|
+
const testUserId = "default-user-id-456";
|
|
232
|
+
render(
|
|
233
|
+
<TestWrapper userIdDefault={testUserId}>
|
|
234
|
+
<MockAmplitudeComponent />
|
|
235
|
+
</TestWrapper>
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
await waitFor(() => {
|
|
239
|
+
expect(screen.getByTestId("is-ready").textContent).toBe("true");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
await act(async () => {
|
|
243
|
+
screen.getByTestId("track-event-button").click();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await waitFor(() => {
|
|
247
|
+
expect(mockTrack).toHaveBeenCalled();
|
|
248
|
+
const callArgs = mockTrack.mock.calls[0];
|
|
249
|
+
expect(callArgs[1]).toHaveProperty("user.id", testUserId);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should prioritize override userId over default userId", async () => {
|
|
254
|
+
const overrideUserId = "override-user-id-789";
|
|
255
|
+
const defaultUserId = "default-user-id-012";
|
|
256
|
+
render(
|
|
257
|
+
<TestWrapper
|
|
258
|
+
userIdOverride={overrideUserId}
|
|
259
|
+
userIdDefault={defaultUserId}
|
|
260
|
+
>
|
|
261
|
+
<MockAmplitudeComponent />
|
|
262
|
+
</TestWrapper>
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
await waitFor(() => {
|
|
266
|
+
expect(screen.getByTestId("is-ready").textContent).toBe("true");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await act(async () => {
|
|
270
|
+
screen.getByTestId("track-event-button").click();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await waitFor(() => {
|
|
274
|
+
expect(mockTrack).toHaveBeenCalled();
|
|
275
|
+
const callArgs = mockTrack.mock.calls[0];
|
|
276
|
+
expect(callArgs[1]).toHaveProperty("user.id", overrideUserId);
|
|
277
|
+
expect(callArgs[1]).not.toHaveProperty("user.id", defaultUserId);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("should initialize Amplitude with userId from UserIdentityContext", async () => {
|
|
282
|
+
const testUserId = "init-user-id-345";
|
|
283
|
+
render(
|
|
284
|
+
<TestWrapper userIdOverride={testUserId}>
|
|
285
|
+
<MockAmplitudeComponent />
|
|
286
|
+
</TestWrapper>
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
await waitFor(() => {
|
|
290
|
+
expect(mockInit).toHaveBeenCalled();
|
|
291
|
+
const initArgs = mockInit.mock.calls[0];
|
|
292
|
+
expect(initArgs[1]).toBe(testUserId);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe("userIdAtom synchronization", () => {
|
|
298
|
+
it("should sync userIdAtom with UserIdentityContext userId", async () => {
|
|
299
|
+
const testUserId = "sync-user-id-678";
|
|
300
|
+
render(
|
|
301
|
+
<TestWrapper userIdOverride={testUserId}>
|
|
302
|
+
<>
|
|
303
|
+
<CombinedContextComponent />
|
|
304
|
+
<AtomReaderComponent />
|
|
305
|
+
</>
|
|
306
|
+
</TestWrapper>
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
await waitFor(() => {
|
|
310
|
+
const identityUserId = screen.getByTestId(
|
|
311
|
+
"user-id-from-identity"
|
|
312
|
+
).textContent;
|
|
313
|
+
const atomUserId = screen.getByTestId("atom-reader").textContent;
|
|
314
|
+
expect(identityUserId).toBe(testUserId);
|
|
315
|
+
expect(atomUserId).toBe(testUserId);
|
|
316
|
+
expect(identityUserId).toBe(atomUserId);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("Event tracking with UserIdentityContext userId", () => {
|
|
322
|
+
it("should include user.id in all tracked events", async () => {
|
|
323
|
+
const testUserId = "event-user-id-901";
|
|
324
|
+
render(
|
|
325
|
+
<TestWrapper userIdOverride={testUserId}>
|
|
326
|
+
<MockAmplitudeComponent />
|
|
327
|
+
</TestWrapper>
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
await waitFor(() => {
|
|
331
|
+
expect(screen.getByTestId("is-ready").textContent).toBe("true");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
await act(async () => {
|
|
335
|
+
screen.getByTestId("track-event-button").click();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await waitFor(() => {
|
|
339
|
+
expect(mockTrack).toHaveBeenCalled();
|
|
340
|
+
const callArgs = mockTrack.mock.calls[0];
|
|
341
|
+
expect(callArgs[1]).toHaveProperty("user.id", testUserId);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("should include user.id in default tracking props", async () => {
|
|
346
|
+
const testUserId = "default-props-user-id";
|
|
347
|
+
render(
|
|
348
|
+
<TestWrapper userIdOverride={testUserId}>
|
|
349
|
+
<MockAmplitudeComponent />
|
|
350
|
+
</TestWrapper>
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
await waitFor(() => {
|
|
354
|
+
expect(screen.getByTestId("is-ready").textContent).toBe("true");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
await act(async () => {
|
|
358
|
+
screen.getByTestId("track-event-button").click();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
await waitFor(() => {
|
|
362
|
+
expect(mockTrack).toHaveBeenCalled();
|
|
363
|
+
const callArgs = mockTrack.mock.calls[0];
|
|
364
|
+
const eventProps = callArgs[1];
|
|
365
|
+
expect(eventProps["user.id"]).toBe(testUserId);
|
|
366
|
+
expect(eventProps["cdp.user_id"]).toBe(null);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("should track multiple events with correct userId", async () => {
|
|
371
|
+
const testUserId = "multi-event-user-id";
|
|
372
|
+
const MultiEventComponent: React.FC = () => {
|
|
373
|
+
const amplitude = useAmplitude();
|
|
374
|
+
|
|
375
|
+
const trackMultiple = () => {
|
|
376
|
+
amplitude.trackEvent({
|
|
377
|
+
eventName: SpiffyMetricsEventName.ChatUserMessageInput,
|
|
378
|
+
eventProps: { event1: true },
|
|
379
|
+
});
|
|
380
|
+
amplitude.trackEvent({
|
|
381
|
+
eventName: SpiffyMetricsEventName.ChatSuggestionClicked,
|
|
382
|
+
eventProps: { event2: true },
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<button data-testid="track-multiple" onClick={trackMultiple}>
|
|
388
|
+
Track Multiple
|
|
389
|
+
</button>
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
render(
|
|
394
|
+
<TestWrapper userIdOverride={testUserId}>
|
|
395
|
+
<MultiEventComponent />
|
|
396
|
+
</TestWrapper>
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
await waitFor(() => {
|
|
400
|
+
expect(screen.getByTestId("track-multiple")).toBeInTheDocument();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
await act(async () => {
|
|
404
|
+
screen.getByTestId("track-multiple").click();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
await waitFor(() => {
|
|
408
|
+
expect(mockTrack).toHaveBeenCalledTimes(2);
|
|
409
|
+
mockTrack.mock.calls.forEach((call: any[]) => {
|
|
410
|
+
expect(call[1]).toHaveProperty("user.id", testUserId);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
describe("Context readiness and initialization", () => {
|
|
417
|
+
it("should wait for UserIdentityContext to be ready before becoming ready", async () => {
|
|
418
|
+
render(
|
|
419
|
+
<TestWrapper>
|
|
420
|
+
<CombinedContextComponent />
|
|
421
|
+
</TestWrapper>
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
// Both contexts should eventually be ready
|
|
425
|
+
await waitFor(() => {
|
|
426
|
+
const amplitudeReady = screen.getByTestId("amplitude-ready").textContent;
|
|
427
|
+
expect(amplitudeReady).toBe("true");
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("should initialize Amplitude client with correct userId from UserIdentityContext", async () => {
|
|
432
|
+
const testUserId = "init-client-user-id";
|
|
433
|
+
render(
|
|
434
|
+
<TestWrapper userIdOverride={testUserId}>
|
|
435
|
+
<MockAmplitudeComponent />
|
|
436
|
+
</TestWrapper>
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
await waitFor(() => {
|
|
440
|
+
expect(mockInit).toHaveBeenCalledWith(
|
|
441
|
+
"test-amplitude-key",
|
|
442
|
+
testUserId,
|
|
443
|
+
expect.objectContaining({
|
|
444
|
+
serverZone: "US",
|
|
445
|
+
})
|
|
446
|
+
);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("should not render children when not ready", async () => {
|
|
451
|
+
// Create a provider without required dependencies
|
|
452
|
+
const IncompleteWrapper: React.FC<{ children: React.ReactNode }> = ({
|
|
453
|
+
children,
|
|
454
|
+
}) => {
|
|
455
|
+
return (
|
|
456
|
+
<Provider>
|
|
457
|
+
<EnviveConfigProvider
|
|
458
|
+
identifyingPrefix="test"
|
|
459
|
+
orgShortName="test-org"
|
|
460
|
+
// Missing amplitudeApiKey
|
|
461
|
+
featureGates={[]}
|
|
462
|
+
>
|
|
463
|
+
<LocalStorageProvider>
|
|
464
|
+
<UserIdentityProvider>
|
|
465
|
+
<FeatureFlagServiceProvider featureGates={[]}>
|
|
466
|
+
<AmplitudeProvider>{children}</AmplitudeProvider>
|
|
467
|
+
</FeatureFlagServiceProvider>
|
|
468
|
+
</UserIdentityProvider>
|
|
469
|
+
</LocalStorageProvider>
|
|
470
|
+
</EnviveConfigProvider>
|
|
471
|
+
</Provider>
|
|
472
|
+
);
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const { container } = render(
|
|
476
|
+
<IncompleteWrapper>
|
|
477
|
+
<div data-testid="should-not-render">Should not render</div>
|
|
478
|
+
</IncompleteWrapper>
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
// AmplitudeProvider returns null when not ready, so children should not render
|
|
482
|
+
await waitFor(() => {
|
|
483
|
+
expect(
|
|
484
|
+
screen.queryByTestId("should-not-render")
|
|
485
|
+
).not.toBeInTheDocument();
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
describe("Error handling", () => {
|
|
491
|
+
it("should handle tracking errors gracefully when userId is available", async () => {
|
|
492
|
+
const testUserId = "error-user-id";
|
|
493
|
+
mockTrack.mockImplementationOnce(() => {
|
|
494
|
+
throw new Error("Tracking error");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
const logErrorSpy = vi.spyOn(Logger, "logError");
|
|
498
|
+
|
|
499
|
+
render(
|
|
500
|
+
<TestWrapper userIdOverride={testUserId}>
|
|
501
|
+
<MockAmplitudeComponent />
|
|
502
|
+
</TestWrapper>
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
await waitFor(() => {
|
|
506
|
+
expect(screen.getByTestId("is-ready").textContent).toBe("true");
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
await act(async () => {
|
|
510
|
+
screen.getByTestId("track-event-button").click();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
await waitFor(() => {
|
|
514
|
+
expect(logErrorSpy).toHaveBeenCalledWith(
|
|
515
|
+
"[spiffy-ai] Error tracking event",
|
|
516
|
+
expect.any(Error),
|
|
517
|
+
expect.objectContaining({
|
|
518
|
+
eventName: SpiffyMetricsEventName.ChatUserMessageInput,
|
|
519
|
+
})
|
|
520
|
+
);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
|
|
@@ -34,7 +34,6 @@ import { useEnviveConfig } from "src/contexts/enviveConfigContext/enviveConfigCo
|
|
|
34
34
|
import {
|
|
35
35
|
useFeatureFlagService,
|
|
36
36
|
} from "src/contexts/featureFlagServiceContext/featureFlagServiceContext";
|
|
37
|
-
import { useUserIdentity } from "src/contexts/userIdentityContext/userIdentityContext";
|
|
38
37
|
|
|
39
38
|
export enum SpiffyMetricsEventName {
|
|
40
39
|
BundleLoaded = "Bundle Loaded",
|
|
@@ -116,7 +115,7 @@ export const AmplitudeProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
|
116
115
|
React.useState<Record<string, unknown>>({});
|
|
117
116
|
|
|
118
117
|
const isReady = Boolean(
|
|
119
|
-
userId && featureFlagService && amplitudeApiKey
|
|
118
|
+
userId && featureFlagService && amplitudeApiKey
|
|
120
119
|
);
|
|
121
120
|
|
|
122
121
|
const getDefaultTrackingProps = useCallback((): Record<string, unknown> => {
|
|
@@ -416,6 +415,10 @@ export const AmplitudeProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
|
416
415
|
[trackEvent, isReady, setSupplementalDefaultProps]
|
|
417
416
|
);
|
|
418
417
|
|
|
418
|
+
if (!isReady) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
|
|
419
422
|
return (
|
|
420
423
|
<AmplitudeContext.Provider value={value}>
|
|
421
424
|
{children}
|
|
@@ -12,7 +12,7 @@ import CommerceApiClient from "src/application/commerce-api";
|
|
|
12
12
|
import { v4 as uuid } from "uuid";
|
|
13
13
|
import { ClientDetails } from "src/application/models/clientDetails";
|
|
14
14
|
import { useLocalStorage } from "src/contexts/localStorageContext";
|
|
15
|
-
import {
|
|
15
|
+
import { useAtom } from "jotai";
|
|
16
16
|
import { userIdAtom } from "src/atoms/app";
|
|
17
17
|
|
|
18
18
|
// Helper function from the original service
|
|
@@ -50,6 +50,7 @@ const UserIdentityContext = createContext<UserIdentityContextType | undefined>(
|
|
|
50
50
|
export const UserIdentityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
51
51
|
children,
|
|
52
52
|
}) => {
|
|
53
|
+
const [userId, setUserId] = useAtom(userIdAtom);
|
|
53
54
|
const {
|
|
54
55
|
getItem,
|
|
55
56
|
setItem,
|
|
@@ -180,6 +181,11 @@ export const UserIdentityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
|
180
181
|
isReady,
|
|
181
182
|
]
|
|
182
183
|
);
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (isReady && !userId) {
|
|
186
|
+
setUserId(getUserIdOrDefault() ?? "");
|
|
187
|
+
}
|
|
188
|
+
}, [isReady, userId, setUserId]);
|
|
183
189
|
|
|
184
190
|
return (
|
|
185
191
|
<UserIdentityContext.Provider value={value}>
|
|
@@ -190,10 +196,6 @@ export const UserIdentityProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
|
190
196
|
|
|
191
197
|
export const useUserIdentity = () => {
|
|
192
198
|
const context = useContext(UserIdentityContext);
|
|
193
|
-
const setUserId = useSetAtom(userIdAtom);
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
setUserId(context?.getUserIdOrDefault() ?? "");
|
|
196
|
-
}, [context, setUserId]);
|
|
197
199
|
if (!context) {
|
|
198
200
|
throw new Error(
|
|
199
201
|
"useUserIdentity must be used within a UserIdentityProvider"
|