@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.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/__tests__/tambo-context-attachment-provider.test.d.ts +2 -0
- package/dist/providers/__tests__/tambo-context-attachment-provider.test.d.ts.map +1 -0
- package/dist/providers/__tests__/tambo-context-attachment-provider.test.js +633 -0
- package/dist/providers/__tests__/tambo-context-attachment-provider.test.js.map +1 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +4 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/tambo-context-attachment-provider.d.ts +132 -0
- package/dist/providers/tambo-context-attachment-provider.d.ts.map +1 -0
- package/dist/providers/tambo-context-attachment-provider.js +201 -0
- package/dist/providers/tambo-context-attachment-provider.js.map +1 -0
- package/dist/providers/tambo-provider.d.ts +4 -2
- package/dist/providers/tambo-provider.d.ts.map +1 -1
- package/dist/providers/tambo-provider.js +9 -4
- package/dist/providers/tambo-provider.js.map +1 -1
- package/dist/providers/tambo-stubs.d.ts.map +1 -1
- package/dist/providers/tambo-stubs.js +6 -2
- package/dist/providers/tambo-stubs.js.map +1 -1
- package/esm/index.d.ts +1 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -1
- package/esm/index.js.map +1 -1
- package/esm/providers/__tests__/tambo-context-attachment-provider.test.d.ts +2 -0
- package/esm/providers/__tests__/tambo-context-attachment-provider.test.d.ts.map +1 -0
- package/esm/providers/__tests__/tambo-context-attachment-provider.test.js +628 -0
- package/esm/providers/__tests__/tambo-context-attachment-provider.test.js.map +1 -0
- package/esm/providers/index.d.ts +1 -0
- package/esm/providers/index.d.ts.map +1 -1
- package/esm/providers/index.js +1 -0
- package/esm/providers/index.js.map +1 -1
- package/esm/providers/tambo-context-attachment-provider.d.ts +132 -0
- package/esm/providers/tambo-context-attachment-provider.d.ts.map +1 -0
- package/esm/providers/tambo-context-attachment-provider.js +164 -0
- package/esm/providers/tambo-context-attachment-provider.js.map +1 -0
- package/esm/providers/tambo-provider.d.ts +4 -2
- package/esm/providers/tambo-provider.d.ts.map +1 -1
- package/esm/providers/tambo-provider.js +9 -4
- package/esm/providers/tambo-provider.js.map +1 -1
- package/esm/providers/tambo-stubs.d.ts.map +1 -1
- package/esm/providers/tambo-stubs.js +6 -2
- package/esm/providers/tambo-stubs.js.map +1 -1
- 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
|