@envive-ai/react-hooks 0.2.7-arthur-1 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/{TrackComponentVisibleEvent-CXhKOwKQ.js → TrackComponentVisibleEvent-qNE0dIL1.js} +2 -2
  2. package/dist/{TrackComponentVisibleEvent-CgxCqrIt.cjs → TrackComponentVisibleEvent-tDRnrJi3.cjs} +2 -2
  3. package/dist/amplitudeContext-C6LHpqP_.cjs +290 -0
  4. package/dist/amplitudeContext-rEjGf57q.js +268 -0
  5. package/dist/application/utils/index.cjs +2 -2
  6. package/dist/application/utils/index.js +2 -2
  7. package/dist/atoms/app/index.d.cts +7 -7
  8. package/dist/atoms/app/index.d.ts +1 -1
  9. package/dist/atoms/chat/index.cjs +3 -3
  10. package/dist/atoms/chat/index.d.cts +26 -26
  11. package/dist/atoms/chat/index.d.ts +1 -1
  12. package/dist/atoms/chat/index.js +3 -3
  13. package/dist/atoms/globalSearch/index.d.cts +6 -6
  14. package/dist/atoms/globalSearch/index.d.ts +5 -5
  15. package/dist/atoms/org/index.d.cts +16 -16
  16. package/dist/atoms/org/index.d.ts +16 -16
  17. package/dist/atoms/search/index.cjs +5 -5
  18. package/dist/atoms/search/index.js +5 -5
  19. package/dist/atoms/search/utils.d.ts +1 -1
  20. package/dist/{cdnContext-CtrIlAqX.js → cdnContext-B8zWuGGT.js} +1 -1
  21. package/dist/{cdnContext-CaDyQ_5p.cjs → cdnContext-DzifgoNo.cjs} +1 -1
  22. package/dist/{chat-BjhQCyW_.js → chat-DChvXHjz.js} +3 -3
  23. package/dist/{chat-BkPax29G.cjs → chat-L_N0qaqs.cjs} +3 -3
  24. package/dist/{chatSearch-C3N3iIxu.cjs → chatSearch-B5whqPLW.cjs} +3 -3
  25. package/dist/{chatSearch-BsYlFvpv.js → chatSearch-so-qeiEL.js} +3 -3
  26. package/dist/{chatState-CJ52Ag_7.cjs → chatState-D9_aA1_h.cjs} +2 -2
  27. package/dist/{chatState-CXA1vF16.js → chatState-Dl5lyuKC.js} +2 -2
  28. package/dist/{commerce-api-rgj30eEp.js → commerce-api-DEFd5HUH.js} +2 -2
  29. package/dist/{commerce-api-DA1QGGMK.cjs → commerce-api-DqVD4NH8.cjs} +2 -2
  30. package/dist/contexts/amplitudeContext/index.cjs +2 -2
  31. package/dist/contexts/amplitudeContext/index.js +2 -2
  32. package/dist/contexts/cdnContext/index.cjs +1 -1
  33. package/dist/contexts/cdnContext/index.js +1 -1
  34. package/dist/contexts/chatContext/index.cjs +7 -7
  35. package/dist/contexts/chatContext/index.d.ts +4 -4
  36. package/dist/contexts/chatContext/index.js +7 -7
  37. package/dist/contexts/systemSettingsContext/index.d.cts +2 -2
  38. package/dist/contexts/systemSettingsContext/index.d.ts +4 -4
  39. package/dist/contexts/userIdentityContext/index.cjs +4 -4
  40. package/dist/contexts/userIdentityContext/index.js +4 -4
  41. package/dist/frontendConfig-Cawh5iqv.d.ts +1 -1
  42. package/dist/frontendConfig-iZipB5FG.d.cts +1 -1
  43. package/dist/hooks/AmplitudeOperations/index.cjs +3 -3
  44. package/dist/hooks/AmplitudeOperations/index.js +3 -3
  45. package/dist/hooks/CdnOperations/index.cjs +1 -1
  46. package/dist/hooks/CdnOperations/index.js +1 -1
  47. package/dist/hooks/ChatToggle/index.cjs +3 -3
  48. package/dist/hooks/ChatToggle/index.js +3 -3
  49. package/dist/hooks/ChatToggleAnalytics/index.cjs +4 -4
  50. package/dist/hooks/ChatToggleAnalytics/index.js +4 -4
  51. package/dist/hooks/GrabAndScroll/index.d.cts +2 -2
  52. package/dist/hooks/IdentifyUser/index.cjs +4 -4
  53. package/dist/hooks/IdentifyUser/index.js +4 -4
  54. package/dist/hooks/Search/index.cjs +19 -1157
  55. package/dist/hooks/Search/index.js +17 -1156
  56. package/dist/hooks/SystemSettingsContext/index.d.ts +2 -2
  57. package/dist/hooks/TrackComponentVisibleEvent/index.cjs +2 -2
  58. package/dist/hooks/TrackComponentVisibleEvent/index.js +2 -2
  59. package/dist/hooks/UpdateAnalyticsProps/index.cjs +1 -1
  60. package/dist/hooks/UpdateAnalyticsProps/index.js +1 -1
  61. package/dist/{search-Csh2n66W.cjs → search-CneZkauD.cjs} +2 -2
  62. package/dist/{search-DkiqkogN.js → search-DbZeJYdg.js} +2 -2
  63. package/dist/{useAmplitudeOperations-zIRSqmMW.js → useAmplitudeOperations-B9HjVe6G.js} +2 -2
  64. package/dist/{useAmplitudeOperations-Bo6YNbTV.cjs → useAmplitudeOperations-BdvxAzuI.cjs} +2 -2
  65. package/dist/userIdentityContext-CPztaX6-.js +126 -0
  66. package/dist/userIdentityContext-DdySlQBz.cjs +143 -0
  67. package/dist/{utils-C1ErYSoW.js → utils-B4PvsKVY.js} +2 -2
  68. package/dist/{utils-mqfncrhI.cjs → utils-BRVP7Mnu.cjs} +2 -2
  69. package/dist/utils-CBD4g2Nc.d.cts +1 -1
  70. package/dist/utils-QKFAbPT6.d.ts +1 -1
  71. package/package.json +3 -2
  72. package/src/contexts/amplitudeContext/__tests__/amplitudeContext.test.tsx +525 -0
  73. package/src/contexts/amplitudeContext/amplitudeContext.tsx +6 -2
  74. package/src/contexts/userIdentityContext/__tests__/userIdentityContext.test.tsx +0 -162
  75. package/src/contexts/userIdentityContext/userIdentityContext.tsx +13 -4
  76. package/dist/amplitudeContext-C8tT74Mi.cjs +0 -286
  77. package/dist/amplitudeContext-DCk6Va-j.js +0 -264
  78. package/dist/userIdentityContext-BqbNu7xu.cjs +0 -132
  79. package/dist/userIdentityContext-BxFH9FNQ.js +0 -115
  80. /package/dist/{AmplitudeOperations-ChZWcSsc.js → AmplitudeOperations-C-ieCm9m.js} +0 -0
  81. /package/dist/{AmplitudeOperations-JggIc1zD.cjs → AmplitudeOperations-p7APchq9.cjs} +0 -0
  82. /package/dist/{amplitudeContext-BItT9HmT.js → amplitudeContext-DbicJUzl.js} +0 -0
  83. /package/dist/{amplitudeContext-DPtyVv3Q.cjs → amplitudeContext-q3mggFSE.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 && userId
118
+ userId && featureFlagService && amplitudeApiKey
120
119
  );
121
120
 
122
121
  const getDefaultTrackingProps = useCallback((): Record<string, unknown> => {
@@ -416,6 +415,11 @@ export const AmplitudeProvider: React.FC<{ children: React.ReactNode }> = ({
416
415
  [trackEvent, isReady, setSupplementalDefaultProps]
417
416
  );
418
417
 
418
+ if (!isReady) {
419
+ console.log("AmplitudeProvider is not ready", isReady, userId, featureFlagService, amplitudeApiKey);
420
+ return null;
421
+ }
422
+
419
423
  return (
420
424
  <AmplitudeContext.Provider value={value}>
421
425
  {children}