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