@tambo-ai/react 0.62.0 → 0.63.0

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 (47) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/providers/__tests__/tambo-context-attachment-provider.test.d.ts +2 -0
  6. package/dist/providers/__tests__/tambo-context-attachment-provider.test.d.ts.map +1 -0
  7. package/dist/providers/__tests__/tambo-context-attachment-provider.test.js +633 -0
  8. package/dist/providers/__tests__/tambo-context-attachment-provider.test.js.map +1 -0
  9. package/dist/providers/index.d.ts +1 -0
  10. package/dist/providers/index.d.ts.map +1 -1
  11. package/dist/providers/index.js +4 -1
  12. package/dist/providers/index.js.map +1 -1
  13. package/dist/providers/tambo-context-attachment-provider.d.ts +132 -0
  14. package/dist/providers/tambo-context-attachment-provider.d.ts.map +1 -0
  15. package/dist/providers/tambo-context-attachment-provider.js +201 -0
  16. package/dist/providers/tambo-context-attachment-provider.js.map +1 -0
  17. package/dist/providers/tambo-provider.d.ts +4 -2
  18. package/dist/providers/tambo-provider.d.ts.map +1 -1
  19. package/dist/providers/tambo-provider.js +9 -4
  20. package/dist/providers/tambo-provider.js.map +1 -1
  21. package/dist/providers/tambo-stubs.d.ts.map +1 -1
  22. package/dist/providers/tambo-stubs.js +6 -2
  23. package/dist/providers/tambo-stubs.js.map +1 -1
  24. package/esm/index.d.ts +1 -1
  25. package/esm/index.d.ts.map +1 -1
  26. package/esm/index.js +1 -1
  27. package/esm/index.js.map +1 -1
  28. package/esm/providers/__tests__/tambo-context-attachment-provider.test.d.ts +2 -0
  29. package/esm/providers/__tests__/tambo-context-attachment-provider.test.d.ts.map +1 -0
  30. package/esm/providers/__tests__/tambo-context-attachment-provider.test.js +628 -0
  31. package/esm/providers/__tests__/tambo-context-attachment-provider.test.js.map +1 -0
  32. package/esm/providers/index.d.ts +1 -0
  33. package/esm/providers/index.d.ts.map +1 -1
  34. package/esm/providers/index.js +1 -0
  35. package/esm/providers/index.js.map +1 -1
  36. package/esm/providers/tambo-context-attachment-provider.d.ts +132 -0
  37. package/esm/providers/tambo-context-attachment-provider.d.ts.map +1 -0
  38. package/esm/providers/tambo-context-attachment-provider.js +164 -0
  39. package/esm/providers/tambo-context-attachment-provider.js.map +1 -0
  40. package/esm/providers/tambo-provider.d.ts +4 -2
  41. package/esm/providers/tambo-provider.d.ts.map +1 -1
  42. package/esm/providers/tambo-provider.js +9 -4
  43. package/esm/providers/tambo-provider.js.map +1 -1
  44. package/esm/providers/tambo-stubs.d.ts.map +1 -1
  45. package/esm/providers/tambo-stubs.js +6 -2
  46. package/esm/providers/tambo-stubs.js.map +1 -1
  47. package/package.json +1 -1
@@ -0,0 +1,628 @@
1
+ import { act, renderHook, waitFor } from "@testing-library/react";
2
+ import React from "react";
3
+ import { TamboContextAttachmentProvider, useTamboContextAttachment, } from "../tambo-context-attachment-provider";
4
+ import { TamboContextHelpersProvider, useTamboContextHelpers, } from "../tambo-context-helpers-provider";
5
+ /**
6
+ * Test suite for TamboContextAttachmentProvider
7
+ *
8
+ * Tests the context attachment feature which allows:
9
+ * - Visual context badges above message input
10
+ * - Automatic context helper registration/unregistration
11
+ * - Custom suggestions that override auto-generated ones
12
+ * - Dynamic context data customization via getContextHelperData
13
+ */
14
+ describe("TamboContextAttachmentProvider", () => {
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ });
18
+ /**
19
+ * Base wrapper with both TamboContextAttachmentProvider and TamboContextHelpersProvider
20
+ * since context attachments need access to context helpers API
21
+ * @param getContextHelperData - Optional custom function to get context helper data
22
+ * @returns A React component that wraps children with the necessary providers
23
+ */
24
+ const createWrapper = (getContextHelperData) => {
25
+ const Wrapper = ({ children }) => (React.createElement(TamboContextHelpersProvider, null,
26
+ React.createElement(TamboContextAttachmentProvider, { getContextHelperData: getContextHelperData }, children)));
27
+ Wrapper.displayName = "TestWrapper";
28
+ return Wrapper;
29
+ };
30
+ describe("Hook Access", () => {
31
+ /**
32
+ * Hook should provide all expected API functions when used within provider
33
+ */
34
+ it("should provide context attachment API functions", () => {
35
+ const { result } = renderHook(() => useTamboContextAttachment(), {
36
+ wrapper: createWrapper(),
37
+ });
38
+ expect(result.current).toHaveProperty("attachments");
39
+ expect(result.current).toHaveProperty("addContextAttachment");
40
+ expect(result.current).toHaveProperty("removeContextAttachment");
41
+ expect(result.current).toHaveProperty("clearContextAttachments");
42
+ expect(result.current).toHaveProperty("customSuggestions");
43
+ expect(result.current).toHaveProperty("setCustomSuggestions");
44
+ });
45
+ /**
46
+ * Hook should throw error when used outside of provider
47
+ */
48
+ it("should throw error when used outside provider", () => {
49
+ // Suppress console.error for this test
50
+ const consoleErrorSpy = jest
51
+ .spyOn(console, "error")
52
+ .mockImplementation(() => { });
53
+ expect(() => {
54
+ renderHook(() => useTamboContextAttachment());
55
+ }).toThrow("useTamboContextAttachment must be used within a TamboContextAttachmentProvider");
56
+ consoleErrorSpy.mockRestore();
57
+ });
58
+ });
59
+ describe("Adding Context Attachments", () => {
60
+ /**
61
+ * Should add a context attachment with auto-generated ID
62
+ */
63
+ it("should add a context attachment", () => {
64
+ const { result } = renderHook(() => useTamboContextAttachment(), {
65
+ wrapper: createWrapper(),
66
+ });
67
+ act(() => {
68
+ result.current.addContextAttachment({
69
+ name: "Button.tsx",
70
+ metadata: { filePath: "/src/Button.tsx" },
71
+ });
72
+ });
73
+ expect(result.current.attachments).toHaveLength(1);
74
+ expect(result.current.attachments[0]).toMatchObject({
75
+ name: "Button.tsx",
76
+ metadata: { filePath: "/src/Button.tsx" },
77
+ });
78
+ expect(result.current.attachments[0].id).toBeDefined();
79
+ });
80
+ /**
81
+ * Should add multiple different attachments
82
+ */
83
+ it("should add multiple context attachments", () => {
84
+ const { result } = renderHook(() => useTamboContextAttachment(), {
85
+ wrapper: createWrapper(),
86
+ });
87
+ act(() => {
88
+ result.current.addContextAttachment({
89
+ name: "Button.tsx",
90
+ });
91
+ result.current.addContextAttachment({
92
+ name: "Card.tsx",
93
+ });
94
+ });
95
+ expect(result.current.attachments).toHaveLength(2);
96
+ expect(result.current.attachments[0].name).toBe("Button.tsx");
97
+ expect(result.current.attachments[1].name).toBe("Card.tsx");
98
+ });
99
+ /**
100
+ * Should prevent duplicates with the same name
101
+ */
102
+ it("should prevent duplicate attachments with same name", () => {
103
+ const { result } = renderHook(() => useTamboContextAttachment(), {
104
+ wrapper: createWrapper(),
105
+ });
106
+ act(() => {
107
+ result.current.addContextAttachment({
108
+ name: "Button.tsx",
109
+ });
110
+ result.current.addContextAttachment({
111
+ name: "Button.tsx",
112
+ });
113
+ });
114
+ expect(result.current.attachments).toHaveLength(1);
115
+ });
116
+ /**
117
+ * Should support optional icon property
118
+ */
119
+ it("should support icon property", () => {
120
+ const { result } = renderHook(() => useTamboContextAttachment(), {
121
+ wrapper: createWrapper(),
122
+ });
123
+ const icon = React.createElement("span", null, "\uD83D\uDCC4");
124
+ act(() => {
125
+ result.current.addContextAttachment({
126
+ name: "File.txt",
127
+ icon,
128
+ });
129
+ });
130
+ expect(result.current.attachments[0].icon).toBe(icon);
131
+ });
132
+ });
133
+ describe("Removing Context Attachments", () => {
134
+ /**
135
+ * Should remove a specific attachment by ID
136
+ */
137
+ it("should remove context attachment by ID", () => {
138
+ const { result } = renderHook(() => useTamboContextAttachment(), {
139
+ wrapper: createWrapper(),
140
+ });
141
+ act(() => {
142
+ result.current.addContextAttachment({
143
+ name: "Button.tsx",
144
+ });
145
+ });
146
+ expect(result.current.attachments).toHaveLength(1);
147
+ const attachmentId = result.current.attachments[0].id;
148
+ act(() => {
149
+ result.current.removeContextAttachment(attachmentId);
150
+ });
151
+ expect(result.current.attachments).toHaveLength(0);
152
+ });
153
+ /**
154
+ * Should only remove the specified attachment when multiple exist
155
+ */
156
+ it("should only remove specified attachment", () => {
157
+ const { result } = renderHook(() => useTamboContextAttachment(), {
158
+ wrapper: createWrapper(),
159
+ });
160
+ act(() => {
161
+ result.current.addContextAttachment({ name: "First.tsx" });
162
+ result.current.addContextAttachment({ name: "Second.tsx" });
163
+ });
164
+ expect(result.current.attachments).toHaveLength(2);
165
+ const firstId = result.current.attachments[0].id;
166
+ act(() => {
167
+ result.current.removeContextAttachment(firstId);
168
+ });
169
+ expect(result.current.attachments).toHaveLength(1);
170
+ expect(result.current.attachments[0].name).toBe("Second.tsx");
171
+ });
172
+ /**
173
+ * Should handle removing non-existent attachment gracefully
174
+ */
175
+ it("should handle removing non-existent attachment gracefully", () => {
176
+ const { result } = renderHook(() => useTamboContextAttachment(), {
177
+ wrapper: createWrapper(),
178
+ });
179
+ expect(() => {
180
+ act(() => {
181
+ result.current.removeContextAttachment("non-existent-id");
182
+ });
183
+ }).not.toThrow();
184
+ });
185
+ });
186
+ describe("Clearing All Attachments", () => {
187
+ /**
188
+ * Should clear all attachments at once
189
+ */
190
+ it("should clear all context attachments", () => {
191
+ const { result } = renderHook(() => useTamboContextAttachment(), {
192
+ wrapper: createWrapper(),
193
+ });
194
+ act(() => {
195
+ result.current.addContextAttachment({ name: "First.tsx" });
196
+ result.current.addContextAttachment({ name: "Second.tsx" });
197
+ result.current.addContextAttachment({ name: "Third.tsx" });
198
+ });
199
+ expect(result.current.attachments).toHaveLength(3);
200
+ act(() => {
201
+ result.current.clearContextAttachments();
202
+ });
203
+ expect(result.current.attachments).toHaveLength(0);
204
+ });
205
+ /**
206
+ * Should handle clearing when no attachments exist
207
+ */
208
+ it("should handle clearing when no attachments exist", () => {
209
+ const { result } = renderHook(() => useTamboContextAttachment(), {
210
+ wrapper: createWrapper(),
211
+ });
212
+ expect(() => {
213
+ act(() => {
214
+ result.current.clearContextAttachments();
215
+ });
216
+ }).not.toThrow();
217
+ expect(result.current.attachments).toHaveLength(0);
218
+ });
219
+ });
220
+ describe("Context Helpers Integration", () => {
221
+ /**
222
+ * Should automatically register context helpers when attachments are added
223
+ */
224
+ it("should register context helpers for attachments", async () => {
225
+ const wrapper = ({ children }) => (React.createElement(TamboContextHelpersProvider, null,
226
+ React.createElement(TamboContextAttachmentProvider, null, children)));
227
+ const { result } = renderHook(() => ({
228
+ attachment: useTamboContextAttachment(),
229
+ helpers: useTamboContextHelpers(),
230
+ }), { wrapper });
231
+ // Add attachment
232
+ act(() => {
233
+ result.current.attachment.addContextAttachment({
234
+ name: "Button.tsx",
235
+ metadata: { filePath: "/src/Button.tsx" },
236
+ });
237
+ });
238
+ // Wait for effect to run
239
+ await waitFor(async () => {
240
+ const contexts = await result.current.helpers.getAdditionalContext();
241
+ expect(contexts.length).toBeGreaterThan(0);
242
+ });
243
+ const contexts = await result.current.helpers.getAdditionalContext();
244
+ const attachmentContext = contexts.find((c) => c.name.includes(result.current.attachment.attachments[0].id));
245
+ expect(attachmentContext).toBeDefined();
246
+ expect(attachmentContext?.context).toHaveProperty("selectedComponent");
247
+ });
248
+ /**
249
+ * Should unregister context helpers when attachments are removed
250
+ */
251
+ it("should unregister context helpers when attachments are removed", async () => {
252
+ const wrapper = ({ children }) => (React.createElement(TamboContextHelpersProvider, null,
253
+ React.createElement(TamboContextAttachmentProvider, null, children)));
254
+ const { result } = renderHook(() => ({
255
+ attachment: useTamboContextAttachment(),
256
+ helpers: useTamboContextHelpers(),
257
+ }), { wrapper });
258
+ // Add attachment
259
+ act(() => {
260
+ result.current.attachment.addContextAttachment({
261
+ name: "Button.tsx",
262
+ });
263
+ });
264
+ // Wait for context helper to be registered
265
+ await waitFor(async () => {
266
+ const contexts = await result.current.helpers.getAdditionalContext();
267
+ expect(contexts.length).toBeGreaterThan(0);
268
+ });
269
+ const initialContexts = await result.current.helpers.getAdditionalContext();
270
+ const initialCount = initialContexts.length;
271
+ const attachmentId = result.current.attachment.attachments[0].id;
272
+ // Remove attachment
273
+ act(() => {
274
+ result.current.attachment.removeContextAttachment(attachmentId);
275
+ });
276
+ // Wait for context helper to be unregistered
277
+ await waitFor(async () => {
278
+ const contexts = await result.current.helpers.getAdditionalContext();
279
+ expect(contexts.length).toBeLessThan(initialCount);
280
+ });
281
+ });
282
+ /**
283
+ * Should use default context data structure when no custom function provided
284
+ */
285
+ it("should use default context data structure", async () => {
286
+ const wrapper = ({ children }) => (React.createElement(TamboContextHelpersProvider, null,
287
+ React.createElement(TamboContextAttachmentProvider, null, children)));
288
+ const { result } = renderHook(() => ({
289
+ attachment: useTamboContextAttachment(),
290
+ helpers: useTamboContextHelpers(),
291
+ }), { wrapper });
292
+ act(() => {
293
+ result.current.attachment.addContextAttachment({
294
+ name: "Button.tsx",
295
+ metadata: { filePath: "/src/Button.tsx", type: "component" },
296
+ });
297
+ });
298
+ await waitFor(async () => {
299
+ const contexts = await result.current.helpers.getAdditionalContext();
300
+ expect(contexts.length).toBeGreaterThan(0);
301
+ });
302
+ const contexts = await result.current.helpers.getAdditionalContext();
303
+ const attachmentContext = contexts.find((c) => c.name.includes(result.current.attachment.attachments[0].id));
304
+ expect(attachmentContext?.context).toMatchObject({
305
+ selectedComponent: {
306
+ name: "Button.tsx",
307
+ instruction: expect.stringContaining("Tambo interactable component"),
308
+ filePath: "/src/Button.tsx",
309
+ type: "component",
310
+ },
311
+ });
312
+ });
313
+ /**
314
+ * Should use custom getContextHelperData function when provided
315
+ */
316
+ it("should use custom getContextHelperData function", async () => {
317
+ const customGetContextHelperData = jest.fn(async (context) => ({
318
+ selectedFile: {
319
+ name: context.name,
320
+ path: context.metadata?.filePath,
321
+ customField: "custom value",
322
+ },
323
+ }));
324
+ const wrapper = ({ children }) => (React.createElement(TamboContextHelpersProvider, null,
325
+ React.createElement(TamboContextAttachmentProvider, { getContextHelperData: customGetContextHelperData }, children)));
326
+ const { result } = renderHook(() => ({
327
+ attachment: useTamboContextAttachment(),
328
+ helpers: useTamboContextHelpers(),
329
+ }), { wrapper });
330
+ act(() => {
331
+ result.current.attachment.addContextAttachment({
332
+ name: "Button.tsx",
333
+ metadata: { filePath: "/src/Button.tsx" },
334
+ });
335
+ });
336
+ // Wait for context helper to be registered and called
337
+ await waitFor(async () => {
338
+ const contexts = await result.current.helpers.getAdditionalContext();
339
+ expect(contexts.length).toBeGreaterThan(0);
340
+ }, { timeout: 2000 });
341
+ expect(customGetContextHelperData).toHaveBeenCalled();
342
+ const contexts = await result.current.helpers.getAdditionalContext();
343
+ const attachmentContext = contexts.find((c) => c.name.includes(result.current.attachment.attachments[0].id));
344
+ expect(attachmentContext?.context).toEqual({
345
+ selectedFile: {
346
+ name: "Button.tsx",
347
+ path: "/src/Button.tsx",
348
+ customField: "custom value",
349
+ },
350
+ });
351
+ });
352
+ /**
353
+ * Should update context helpers when getContextHelperData function changes
354
+ * This tests the bug fix for stale closure issue
355
+ */
356
+ it("should update existing context helpers when getContextHelperData changes", async () => {
357
+ // Create a wrapper component with state to manage the function
358
+ const TestWrapper = ({ children }) => {
359
+ const [version, setVersion] = React.useState("v1");
360
+ const getContextHelperData = React.useCallback(async (context) => ({
361
+ version,
362
+ name: context.name,
363
+ }), [version]);
364
+ // Expose setVersion for the test
365
+ TestWrapper.setVersion = setVersion;
366
+ return (React.createElement(TamboContextHelpersProvider, null,
367
+ React.createElement(TamboContextAttachmentProvider, { getContextHelperData: getContextHelperData }, children)));
368
+ };
369
+ const { result } = renderHook(() => ({
370
+ attachment: useTamboContextAttachment(),
371
+ helpers: useTamboContextHelpers(),
372
+ }), { wrapper: TestWrapper });
373
+ // Add attachment with first version
374
+ act(() => {
375
+ result.current.attachment.addContextAttachment({
376
+ name: "Button.tsx",
377
+ });
378
+ });
379
+ // Wait for context helper to be registered
380
+ await waitFor(async () => {
381
+ const contexts = await result.current.helpers.getAdditionalContext();
382
+ expect(contexts.length).toBeGreaterThan(0);
383
+ }, { timeout: 2000 });
384
+ // Verify v1 context
385
+ let contexts = await result.current.helpers.getAdditionalContext();
386
+ let attachmentContext = contexts.find((c) => c.name.includes(result.current.attachment.attachments[0].id));
387
+ expect(attachmentContext?.context).toMatchObject({ version: "v1" });
388
+ // Change the version which will trigger a new getContextHelperData function
389
+ act(() => {
390
+ TestWrapper.setVersion("v2");
391
+ });
392
+ // Wait for context to update to v2
393
+ await waitFor(async () => {
394
+ const contexts = await result.current.helpers.getAdditionalContext();
395
+ const context = contexts.find((c) => c.name.includes(result.current.attachment.attachments[0].id));
396
+ return context?.context.version === "v2";
397
+ }, { timeout: 2000 });
398
+ // Verify v2 context - should be updated, not stale
399
+ contexts = await result.current.helpers.getAdditionalContext();
400
+ attachmentContext = contexts.find((c) => c.name.includes(result.current.attachment.attachments[0].id));
401
+ expect(attachmentContext?.context).toMatchObject({ version: "v2" });
402
+ });
403
+ /**
404
+ * Should handle errors in getContextHelperData gracefully
405
+ */
406
+ it("should handle errors in getContextHelperData gracefully", async () => {
407
+ const consoleErrorSpy = jest
408
+ .spyOn(console, "error")
409
+ .mockImplementation(() => { });
410
+ const errorGetData = jest.fn(async () => {
411
+ throw new Error("Custom data error");
412
+ });
413
+ const wrapper = ({ children }) => (React.createElement(TamboContextHelpersProvider, null,
414
+ React.createElement(TamboContextAttachmentProvider, { getContextHelperData: errorGetData }, children)));
415
+ const { result } = renderHook(() => ({
416
+ attachment: useTamboContextAttachment(),
417
+ helpers: useTamboContextHelpers(),
418
+ }), { wrapper });
419
+ act(() => {
420
+ result.current.attachment.addContextAttachment({
421
+ name: "Button.tsx",
422
+ });
423
+ });
424
+ // Wait for effect to run and try to call the function
425
+ await waitFor(() => {
426
+ expect(result.current.attachment.attachments.length).toBeGreaterThan(0);
427
+ }, { timeout: 2000 });
428
+ // Try to get contexts, which will trigger the error
429
+ await result.current.helpers.getAdditionalContext();
430
+ expect(errorGetData).toHaveBeenCalled();
431
+ expect(consoleErrorSpy).toHaveBeenCalled();
432
+ consoleErrorSpy.mockRestore();
433
+ });
434
+ });
435
+ describe("Custom Suggestions", () => {
436
+ /**
437
+ * Should start with null custom suggestions
438
+ */
439
+ it("should initialize with null custom suggestions", () => {
440
+ const { result } = renderHook(() => useTamboContextAttachment(), {
441
+ wrapper: createWrapper(),
442
+ });
443
+ expect(result.current.customSuggestions).toBeNull();
444
+ });
445
+ /**
446
+ * Should set custom suggestions
447
+ */
448
+ it("should set custom suggestions", () => {
449
+ const { result } = renderHook(() => useTamboContextAttachment(), {
450
+ wrapper: createWrapper(),
451
+ });
452
+ const suggestions = [
453
+ {
454
+ id: "1",
455
+ title: "Edit component",
456
+ detailedSuggestion: "Modify the Button component",
457
+ messageId: "",
458
+ },
459
+ {
460
+ id: "2",
461
+ title: "Add feature",
462
+ detailedSuggestion: "Add a new feature",
463
+ messageId: "",
464
+ },
465
+ ];
466
+ act(() => {
467
+ result.current.setCustomSuggestions(suggestions);
468
+ });
469
+ expect(result.current.customSuggestions).toEqual(suggestions);
470
+ });
471
+ /**
472
+ * Should clear custom suggestions by setting to null
473
+ */
474
+ it("should clear custom suggestions", () => {
475
+ const { result } = renderHook(() => useTamboContextAttachment(), {
476
+ wrapper: createWrapper(),
477
+ });
478
+ const suggestions = [
479
+ {
480
+ id: "1",
481
+ title: "Test",
482
+ detailedSuggestion: "Test suggestion",
483
+ messageId: "",
484
+ },
485
+ ];
486
+ act(() => {
487
+ result.current.setCustomSuggestions(suggestions);
488
+ });
489
+ expect(result.current.customSuggestions).toEqual(suggestions);
490
+ act(() => {
491
+ result.current.setCustomSuggestions(null);
492
+ });
493
+ expect(result.current.customSuggestions).toBeNull();
494
+ });
495
+ /**
496
+ * Should update custom suggestions when changed
497
+ */
498
+ it("should update custom suggestions", () => {
499
+ const { result } = renderHook(() => useTamboContextAttachment(), {
500
+ wrapper: createWrapper(),
501
+ });
502
+ const firstSuggestions = [
503
+ {
504
+ id: "1",
505
+ title: "First",
506
+ detailedSuggestion: "First suggestion",
507
+ messageId: "",
508
+ },
509
+ ];
510
+ const secondSuggestions = [
511
+ {
512
+ id: "2",
513
+ title: "Second",
514
+ detailedSuggestion: "Second suggestion",
515
+ messageId: "",
516
+ },
517
+ ];
518
+ act(() => {
519
+ result.current.setCustomSuggestions(firstSuggestions);
520
+ });
521
+ expect(result.current.customSuggestions).toEqual(firstSuggestions);
522
+ act(() => {
523
+ result.current.setCustomSuggestions(secondSuggestions);
524
+ });
525
+ expect(result.current.customSuggestions).toEqual(secondSuggestions);
526
+ });
527
+ });
528
+ describe("Combined Workflows", () => {
529
+ /**
530
+ * Should handle adding attachment and setting custom suggestions together
531
+ */
532
+ it("should handle attachment and custom suggestions together", () => {
533
+ const { result } = renderHook(() => useTamboContextAttachment(), {
534
+ wrapper: createWrapper(),
535
+ });
536
+ const suggestions = [
537
+ {
538
+ id: "1",
539
+ title: "Edit file",
540
+ detailedSuggestion: "Edit this file",
541
+ messageId: "",
542
+ },
543
+ ];
544
+ act(() => {
545
+ result.current.addContextAttachment({
546
+ name: "Button.tsx",
547
+ metadata: { filePath: "/src/Button.tsx" },
548
+ });
549
+ result.current.setCustomSuggestions(suggestions);
550
+ });
551
+ expect(result.current.attachments).toHaveLength(1);
552
+ expect(result.current.customSuggestions).toEqual(suggestions);
553
+ });
554
+ /**
555
+ * Should clear suggestions when clearing attachments
556
+ */
557
+ it("should independently manage attachments and suggestions", () => {
558
+ const { result } = renderHook(() => useTamboContextAttachment(), {
559
+ wrapper: createWrapper(),
560
+ });
561
+ const suggestions = [
562
+ {
563
+ id: "1",
564
+ title: "Test",
565
+ detailedSuggestion: "Test suggestion",
566
+ messageId: "",
567
+ },
568
+ ];
569
+ act(() => {
570
+ result.current.addContextAttachment({ name: "File.tsx" });
571
+ result.current.setCustomSuggestions(suggestions);
572
+ });
573
+ // Clear attachments
574
+ act(() => {
575
+ result.current.clearContextAttachments();
576
+ });
577
+ // Suggestions should remain
578
+ expect(result.current.attachments).toHaveLength(0);
579
+ expect(result.current.customSuggestions).toEqual(suggestions);
580
+ // Can clear suggestions separately
581
+ act(() => {
582
+ result.current.setCustomSuggestions(null);
583
+ });
584
+ expect(result.current.customSuggestions).toBeNull();
585
+ });
586
+ });
587
+ describe("Cleanup", () => {
588
+ /**
589
+ * Should cleanup context helpers on unmount
590
+ */
591
+ it("should cleanup context helpers on unmount", async () => {
592
+ // Use a shared provider wrapper so context helpers persist
593
+ const SharedWrapper = ({ children }) => (React.createElement(TamboContextHelpersProvider, null, children));
594
+ // First render with attachment provider
595
+ const { result: attachmentResult, unmount } = renderHook(() => ({
596
+ attachment: useTamboContextAttachment(),
597
+ helpers: useTamboContextHelpers(),
598
+ }), {
599
+ wrapper: ({ children }) => (React.createElement(SharedWrapper, null,
600
+ React.createElement(TamboContextAttachmentProvider, null, children))),
601
+ });
602
+ // Add attachment
603
+ act(() => {
604
+ attachmentResult.current.attachment.addContextAttachment({
605
+ name: "Button.tsx",
606
+ });
607
+ });
608
+ const attachmentId = attachmentResult.current.attachment.attachments[0].id;
609
+ // Wait for context helper to be registered
610
+ await waitFor(async () => {
611
+ const contexts = await attachmentResult.current.helpers.getAdditionalContext();
612
+ const hasContext = contexts.some((c) => c.name.includes(attachmentId));
613
+ expect(hasContext).toBe(true);
614
+ });
615
+ // Unmount the attachment provider
616
+ unmount();
617
+ // Create new hook to check cleanup
618
+ const { result: helpersResult } = renderHook(() => useTamboContextHelpers(), { wrapper: SharedWrapper });
619
+ // Wait a bit for cleanup effect to run
620
+ await new Promise((resolve) => setTimeout(resolve, 100));
621
+ const contexts = await helpersResult.current.getAdditionalContext();
622
+ // Should not contain the attachment context after unmount
623
+ const hasAttachmentContext = contexts.some((c) => c.name.includes(attachmentId));
624
+ expect(hasAttachmentContext).toBe(false);
625
+ });
626
+ });
627
+ });
628
+ //# sourceMappingURL=tambo-context-attachment-provider.test.js.map