@tambo-ai/react 0.68.0 → 0.69.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 (326) hide show
  1. package/README.md +1 -1
  2. package/dist/context-helpers/context-helpers.test.js +16 -4
  3. package/dist/context-helpers/context-helpers.test.js.map +1 -1
  4. package/dist/context-helpers/current-interactables-context-helper.d.ts +2 -2
  5. package/dist/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
  6. package/dist/context-helpers/current-interactables-context-helper.js +31 -15
  7. package/dist/context-helpers/current-interactables-context-helper.js.map +1 -1
  8. package/dist/context-helpers/registry.d.ts +2 -2
  9. package/dist/context-helpers/registry.d.ts.map +1 -1
  10. package/dist/context-helpers/registry.js.map +1 -1
  11. package/dist/context-helpers/types.d.ts +2 -2
  12. package/dist/context-helpers/types.d.ts.map +1 -1
  13. package/dist/context-helpers/types.js.map +1 -1
  14. package/dist/hooks/use-message-images.test.js +174 -37
  15. package/dist/hooks/use-message-images.test.js.map +1 -1
  16. package/dist/hooks/use-tambo-voice.d.ts +1 -1
  17. package/dist/hooks/use-tambo-voice.js +1 -1
  18. package/dist/hooks/use-tambo-voice.js.map +1 -1
  19. package/dist/hooks/use-tambo-voice.test.d.ts +2 -0
  20. package/dist/hooks/use-tambo-voice.test.d.ts.map +1 -0
  21. package/dist/hooks/use-tambo-voice.test.js +239 -0
  22. package/dist/hooks/use-tambo-voice.test.js.map +1 -0
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/mcp/elicitation.d.ts.map +1 -1
  27. package/dist/mcp/elicitation.js +12 -0
  28. package/dist/mcp/elicitation.js.map +1 -1
  29. package/dist/mcp/elicitation.test.js +8 -1
  30. package/dist/mcp/elicitation.test.js.map +1 -1
  31. package/dist/mcp/mcp-client.d.ts +6 -10
  32. package/dist/mcp/mcp-client.d.ts.map +1 -1
  33. package/dist/mcp/mcp-client.js.map +1 -1
  34. package/dist/mcp/mcp-hooks.d.ts +12 -60
  35. package/dist/mcp/mcp-hooks.d.ts.map +1 -1
  36. package/dist/mcp/mcp-hooks.js +90 -10
  37. package/dist/mcp/mcp-hooks.js.map +1 -1
  38. package/dist/mcp/mcp-hooks.test.js +423 -0
  39. package/dist/mcp/mcp-hooks.test.js.map +1 -1
  40. package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
  41. package/dist/mcp/tambo-mcp-provider.js +3 -0
  42. package/dist/mcp/tambo-mcp-provider.js.map +1 -1
  43. package/dist/mcp/tambo-mcp-provider.test.js +37 -0
  44. package/dist/mcp/tambo-mcp-provider.test.js.map +1 -1
  45. package/dist/model/component-metadata.d.ts +53 -20
  46. package/dist/model/component-metadata.d.ts.map +1 -1
  47. package/dist/model/component-metadata.js.map +1 -1
  48. package/dist/model/tambo-interactable.d.ts +6 -0
  49. package/dist/model/tambo-interactable.d.ts.map +1 -1
  50. package/dist/model/tambo-interactable.js.map +1 -1
  51. package/dist/providers/index.d.ts +1 -1
  52. package/dist/providers/index.d.ts.map +1 -1
  53. package/dist/providers/index.js.map +1 -1
  54. package/dist/providers/tambo-client-provider.d.ts +8 -0
  55. package/dist/providers/tambo-client-provider.d.ts.map +1 -1
  56. package/dist/providers/tambo-client-provider.js +10 -11
  57. package/dist/providers/tambo-client-provider.js.map +1 -1
  58. package/dist/providers/tambo-client-provider.test.d.ts +2 -0
  59. package/dist/providers/tambo-client-provider.test.d.ts.map +1 -0
  60. package/dist/providers/tambo-client-provider.test.js +208 -0
  61. package/dist/providers/tambo-client-provider.test.js.map +1 -0
  62. package/dist/providers/tambo-context-attachment-provider.d.ts +34 -92
  63. package/dist/providers/tambo-context-attachment-provider.d.ts.map +1 -1
  64. package/dist/providers/tambo-context-attachment-provider.js +62 -105
  65. package/dist/providers/tambo-context-attachment-provider.js.map +1 -1
  66. package/dist/providers/tambo-context-attachment-provider.test.js +229 -463
  67. package/dist/providers/tambo-context-attachment-provider.test.js.map +1 -1
  68. package/dist/providers/tambo-interactable-provider.d.ts +2 -0
  69. package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
  70. package/dist/providers/tambo-interactable-provider.js +29 -4
  71. package/dist/providers/tambo-interactable-provider.js.map +1 -1
  72. package/dist/providers/tambo-interactable-provider.test.js +1 -1
  73. package/dist/providers/tambo-interactable-provider.test.js.map +1 -1
  74. package/dist/providers/tambo-interactables-additional-context.test.js +2 -5
  75. package/dist/providers/tambo-interactables-additional-context.test.js.map +1 -1
  76. package/dist/providers/tambo-provider.d.ts +2 -3
  77. package/dist/providers/tambo-provider.d.ts.map +1 -1
  78. package/dist/providers/tambo-provider.js +5 -6
  79. package/dist/providers/tambo-provider.js.map +1 -1
  80. package/dist/providers/tambo-registry-provider.test.js +16 -0
  81. package/dist/providers/tambo-registry-provider.test.js.map +1 -1
  82. package/dist/providers/tambo-registry-schema-compat.test.js +31 -0
  83. package/dist/providers/tambo-registry-schema-compat.test.js.map +1 -1
  84. package/dist/providers/tambo-thread-input-provider.d.ts +1 -1
  85. package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
  86. package/dist/providers/tambo-thread-input-provider.js +5 -1
  87. package/dist/providers/tambo-thread-input-provider.js.map +1 -1
  88. package/dist/providers/tambo-thread-provider-initial-messages.test.js +84 -2
  89. package/dist/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  90. package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
  91. package/dist/providers/tambo-thread-provider.js +53 -42
  92. package/dist/providers/tambo-thread-provider.js.map +1 -1
  93. package/dist/providers/tambo-thread-provider.test.js +368 -262
  94. package/dist/providers/tambo-thread-provider.test.js.map +1 -1
  95. package/dist/schema/json-schema.js +29 -29
  96. package/dist/schema/json-schema.js.map +1 -1
  97. package/dist/schema/schema.test.js +237 -0
  98. package/dist/schema/schema.test.js.map +1 -1
  99. package/dist/schema/standard-schema.d.ts +1 -0
  100. package/dist/schema/standard-schema.d.ts.map +1 -1
  101. package/dist/schema/standard-schema.js +18 -13
  102. package/dist/schema/standard-schema.js.map +1 -1
  103. package/dist/schema/standard-schema.test.d.ts +2 -0
  104. package/dist/schema/standard-schema.test.d.ts.map +1 -0
  105. package/dist/schema/standard-schema.test.js +165 -0
  106. package/dist/schema/standard-schema.test.js.map +1 -0
  107. package/dist/schema/validate.test.js +149 -0
  108. package/dist/schema/validate.test.js.map +1 -1
  109. package/dist/schema/zod.d.ts +7 -4
  110. package/dist/schema/zod.d.ts.map +1 -1
  111. package/dist/schema/zod.js +65 -22
  112. package/dist/schema/zod.js.map +1 -1
  113. package/dist/schema/zod.test.js +112 -0
  114. package/dist/schema/zod.test.js.map +1 -1
  115. package/dist/testing/tools.d.ts +4 -1
  116. package/dist/testing/tools.d.ts.map +1 -1
  117. package/dist/testing/tools.js +6 -1
  118. package/dist/testing/tools.js.map +1 -1
  119. package/dist/util/generate-component.d.ts.map +1 -1
  120. package/dist/util/generate-component.js +18 -3
  121. package/dist/util/generate-component.js.map +1 -1
  122. package/dist/util/generate-component.test.d.ts +2 -0
  123. package/dist/util/generate-component.test.d.ts.map +1 -0
  124. package/dist/util/generate-component.test.js +340 -0
  125. package/dist/util/generate-component.test.js.map +1 -0
  126. package/dist/util/is-promise.d.ts +9 -0
  127. package/dist/util/is-promise.d.ts.map +1 -0
  128. package/dist/util/is-promise.js +20 -0
  129. package/dist/util/is-promise.js.map +1 -0
  130. package/dist/util/is-promise.test.d.ts +2 -0
  131. package/dist/util/is-promise.test.d.ts.map +1 -0
  132. package/dist/util/is-promise.test.js +48 -0
  133. package/dist/util/is-promise.test.js.map +1 -0
  134. package/dist/util/query-utils.test.d.ts +2 -0
  135. package/dist/util/query-utils.test.d.ts.map +1 -0
  136. package/dist/util/query-utils.test.js +382 -0
  137. package/dist/util/query-utils.test.js.map +1 -0
  138. package/dist/util/registry-validators.d.ts.map +1 -1
  139. package/dist/util/registry-validators.js +7 -0
  140. package/dist/util/registry-validators.js.map +1 -1
  141. package/dist/util/registry-validators.test.js +57 -0
  142. package/dist/util/registry-validators.test.js.map +1 -1
  143. package/dist/util/registry.d.ts.map +1 -1
  144. package/dist/util/registry.js +9 -0
  145. package/dist/util/registry.js.map +1 -1
  146. package/dist/util/registry.test.js +323 -1
  147. package/dist/util/registry.test.js.map +1 -1
  148. package/dist/util/resource-validators.test.d.ts +2 -0
  149. package/dist/util/resource-validators.test.d.ts.map +1 -0
  150. package/dist/util/resource-validators.test.js +90 -0
  151. package/dist/util/resource-validators.test.js.map +1 -0
  152. package/dist/util/tool-caller.d.ts +2 -2
  153. package/dist/util/tool-caller.d.ts.map +1 -1
  154. package/dist/util/tool-caller.js +8 -8
  155. package/dist/util/tool-caller.js.map +1 -1
  156. package/dist/util/validate-component-name.test.d.ts +2 -0
  157. package/dist/util/validate-component-name.test.d.ts.map +1 -0
  158. package/dist/util/validate-component-name.test.js +35 -0
  159. package/dist/util/validate-component-name.test.js.map +1 -0
  160. package/esm/context-helpers/context-helpers.test.js +16 -4
  161. package/esm/context-helpers/context-helpers.test.js.map +1 -1
  162. package/esm/context-helpers/current-interactables-context-helper.d.ts +2 -2
  163. package/esm/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
  164. package/esm/context-helpers/current-interactables-context-helper.js +31 -15
  165. package/esm/context-helpers/current-interactables-context-helper.js.map +1 -1
  166. package/esm/context-helpers/registry.d.ts +2 -2
  167. package/esm/context-helpers/registry.d.ts.map +1 -1
  168. package/esm/context-helpers/registry.js.map +1 -1
  169. package/esm/context-helpers/types.d.ts +2 -2
  170. package/esm/context-helpers/types.d.ts.map +1 -1
  171. package/esm/context-helpers/types.js.map +1 -1
  172. package/esm/hooks/use-message-images.test.js +174 -37
  173. package/esm/hooks/use-message-images.test.js.map +1 -1
  174. package/esm/hooks/use-tambo-voice.d.ts +1 -1
  175. package/esm/hooks/use-tambo-voice.js +1 -1
  176. package/esm/hooks/use-tambo-voice.js.map +1 -1
  177. package/esm/hooks/use-tambo-voice.test.d.ts +2 -0
  178. package/esm/hooks/use-tambo-voice.test.d.ts.map +1 -0
  179. package/esm/hooks/use-tambo-voice.test.js +234 -0
  180. package/esm/hooks/use-tambo-voice.test.js.map +1 -0
  181. package/esm/index.d.ts +2 -2
  182. package/esm/index.d.ts.map +1 -1
  183. package/esm/index.js.map +1 -1
  184. package/esm/mcp/elicitation.d.ts.map +1 -1
  185. package/esm/mcp/elicitation.js +12 -0
  186. package/esm/mcp/elicitation.js.map +1 -1
  187. package/esm/mcp/elicitation.test.js +8 -1
  188. package/esm/mcp/elicitation.test.js.map +1 -1
  189. package/esm/mcp/mcp-client.d.ts +6 -10
  190. package/esm/mcp/mcp-client.d.ts.map +1 -1
  191. package/esm/mcp/mcp-client.js.map +1 -1
  192. package/esm/mcp/mcp-hooks.d.ts +12 -60
  193. package/esm/mcp/mcp-hooks.d.ts.map +1 -1
  194. package/esm/mcp/mcp-hooks.js +57 -10
  195. package/esm/mcp/mcp-hooks.js.map +1 -1
  196. package/esm/mcp/mcp-hooks.test.js +423 -0
  197. package/esm/mcp/mcp-hooks.test.js.map +1 -1
  198. package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
  199. package/esm/mcp/tambo-mcp-provider.js +3 -0
  200. package/esm/mcp/tambo-mcp-provider.js.map +1 -1
  201. package/esm/mcp/tambo-mcp-provider.test.js +37 -0
  202. package/esm/mcp/tambo-mcp-provider.test.js.map +1 -1
  203. package/esm/model/component-metadata.d.ts +53 -20
  204. package/esm/model/component-metadata.d.ts.map +1 -1
  205. package/esm/model/component-metadata.js.map +1 -1
  206. package/esm/model/tambo-interactable.d.ts +6 -0
  207. package/esm/model/tambo-interactable.d.ts.map +1 -1
  208. package/esm/model/tambo-interactable.js.map +1 -1
  209. package/esm/providers/index.d.ts +1 -1
  210. package/esm/providers/index.d.ts.map +1 -1
  211. package/esm/providers/index.js.map +1 -1
  212. package/esm/providers/tambo-client-provider.d.ts +8 -0
  213. package/esm/providers/tambo-client-provider.d.ts.map +1 -1
  214. package/esm/providers/tambo-client-provider.js +11 -12
  215. package/esm/providers/tambo-client-provider.js.map +1 -1
  216. package/esm/providers/tambo-client-provider.test.d.ts +2 -0
  217. package/esm/providers/tambo-client-provider.test.d.ts.map +1 -0
  218. package/esm/providers/tambo-client-provider.test.js +203 -0
  219. package/esm/providers/tambo-client-provider.test.js.map +1 -0
  220. package/esm/providers/tambo-context-attachment-provider.d.ts +34 -92
  221. package/esm/providers/tambo-context-attachment-provider.d.ts.map +1 -1
  222. package/esm/providers/tambo-context-attachment-provider.js +63 -106
  223. package/esm/providers/tambo-context-attachment-provider.js.map +1 -1
  224. package/esm/providers/tambo-context-attachment-provider.test.js +230 -464
  225. package/esm/providers/tambo-context-attachment-provider.test.js.map +1 -1
  226. package/esm/providers/tambo-interactable-provider.d.ts +2 -0
  227. package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
  228. package/esm/providers/tambo-interactable-provider.js +29 -4
  229. package/esm/providers/tambo-interactable-provider.js.map +1 -1
  230. package/esm/providers/tambo-interactable-provider.test.js +1 -1
  231. package/esm/providers/tambo-interactable-provider.test.js.map +1 -1
  232. package/esm/providers/tambo-interactables-additional-context.test.js +2 -5
  233. package/esm/providers/tambo-interactables-additional-context.test.js.map +1 -1
  234. package/esm/providers/tambo-provider.d.ts +2 -3
  235. package/esm/providers/tambo-provider.d.ts.map +1 -1
  236. package/esm/providers/tambo-provider.js +5 -6
  237. package/esm/providers/tambo-provider.js.map +1 -1
  238. package/esm/providers/tambo-registry-provider.test.js +16 -0
  239. package/esm/providers/tambo-registry-provider.test.js.map +1 -1
  240. package/esm/providers/tambo-registry-schema-compat.test.js +31 -0
  241. package/esm/providers/tambo-registry-schema-compat.test.js.map +1 -1
  242. package/esm/providers/tambo-thread-input-provider.d.ts +1 -1
  243. package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
  244. package/esm/providers/tambo-thread-input-provider.js +5 -1
  245. package/esm/providers/tambo-thread-input-provider.js.map +1 -1
  246. package/esm/providers/tambo-thread-provider-initial-messages.test.js +84 -2
  247. package/esm/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
  248. package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
  249. package/esm/providers/tambo-thread-provider.js +53 -42
  250. package/esm/providers/tambo-thread-provider.js.map +1 -1
  251. package/esm/providers/tambo-thread-provider.test.js +368 -262
  252. package/esm/providers/tambo-thread-provider.test.js.map +1 -1
  253. package/esm/schema/json-schema.js +1 -1
  254. package/esm/schema/json-schema.js.map +1 -1
  255. package/esm/schema/schema.test.js +238 -1
  256. package/esm/schema/schema.test.js.map +1 -1
  257. package/esm/schema/standard-schema.d.ts +1 -0
  258. package/esm/schema/standard-schema.d.ts.map +1 -1
  259. package/esm/schema/standard-schema.js +18 -13
  260. package/esm/schema/standard-schema.js.map +1 -1
  261. package/esm/schema/standard-schema.test.d.ts +2 -0
  262. package/esm/schema/standard-schema.test.d.ts.map +1 -0
  263. package/esm/schema/standard-schema.test.js +130 -0
  264. package/esm/schema/standard-schema.test.js.map +1 -0
  265. package/esm/schema/validate.test.js +149 -0
  266. package/esm/schema/validate.test.js.map +1 -1
  267. package/esm/schema/zod.d.ts +7 -4
  268. package/esm/schema/zod.d.ts.map +1 -1
  269. package/esm/schema/zod.js +65 -22
  270. package/esm/schema/zod.js.map +1 -1
  271. package/esm/schema/zod.test.js +113 -1
  272. package/esm/schema/zod.test.js.map +1 -1
  273. package/esm/testing/tools.d.ts +4 -1
  274. package/esm/testing/tools.d.ts.map +1 -1
  275. package/esm/testing/tools.js +6 -1
  276. package/esm/testing/tools.js.map +1 -1
  277. package/esm/util/generate-component.d.ts.map +1 -1
  278. package/esm/util/generate-component.js +18 -3
  279. package/esm/util/generate-component.js.map +1 -1
  280. package/esm/util/generate-component.test.d.ts +2 -0
  281. package/esm/util/generate-component.test.d.ts.map +1 -0
  282. package/esm/util/generate-component.test.js +302 -0
  283. package/esm/util/generate-component.test.js.map +1 -0
  284. package/esm/util/is-promise.d.ts +9 -0
  285. package/esm/util/is-promise.d.ts.map +1 -0
  286. package/esm/util/is-promise.js +17 -0
  287. package/esm/util/is-promise.js.map +1 -0
  288. package/esm/util/is-promise.test.d.ts +2 -0
  289. package/esm/util/is-promise.test.d.ts.map +1 -0
  290. package/esm/util/is-promise.test.js +46 -0
  291. package/esm/util/is-promise.test.js.map +1 -0
  292. package/esm/util/query-utils.test.d.ts +2 -0
  293. package/esm/util/query-utils.test.d.ts.map +1 -0
  294. package/esm/util/query-utils.test.js +380 -0
  295. package/esm/util/query-utils.test.js.map +1 -0
  296. package/esm/util/registry-validators.d.ts.map +1 -1
  297. package/esm/util/registry-validators.js +7 -0
  298. package/esm/util/registry-validators.js.map +1 -1
  299. package/esm/util/registry-validators.test.js +57 -0
  300. package/esm/util/registry-validators.test.js.map +1 -1
  301. package/esm/util/registry.d.ts.map +1 -1
  302. package/esm/util/registry.js +9 -0
  303. package/esm/util/registry.js.map +1 -1
  304. package/esm/util/registry.test.js +324 -2
  305. package/esm/util/registry.test.js.map +1 -1
  306. package/esm/util/resource-validators.test.d.ts +2 -0
  307. package/esm/util/resource-validators.test.d.ts.map +1 -0
  308. package/esm/util/resource-validators.test.js +88 -0
  309. package/esm/util/resource-validators.test.js.map +1 -0
  310. package/esm/util/tool-caller.d.ts +2 -2
  311. package/esm/util/tool-caller.d.ts.map +1 -1
  312. package/esm/util/tool-caller.js +8 -8
  313. package/esm/util/tool-caller.js.map +1 -1
  314. package/esm/util/validate-component-name.test.d.ts +2 -0
  315. package/esm/util/validate-component-name.test.d.ts.map +1 -0
  316. package/esm/util/validate-component-name.test.js +33 -0
  317. package/esm/util/validate-component-name.test.js.map +1 -0
  318. package/package.json +15 -23
  319. package/dist/schema/alias.d.ts +0 -3
  320. package/dist/schema/alias.d.ts.map +0 -1
  321. package/dist/schema/alias.js +0 -6
  322. package/dist/schema/alias.js.map +0 -1
  323. package/esm/schema/alias.d.ts +0 -3
  324. package/esm/schema/alias.d.ts.map +0 -1
  325. package/esm/schema/alias.js +0 -13
  326. package/esm/schema/alias.js.map +0 -1
@@ -7,28 +7,32 @@ const react_1 = require("@testing-library/react");
7
7
  const react_2 = __importDefault(require("react"));
8
8
  const tambo_context_attachment_provider_1 = require("./tambo-context-attachment-provider");
9
9
  const tambo_context_helpers_provider_1 = require("./tambo-context-helpers-provider");
10
+ // Mock the context helpers provider
11
+ jest.mock("./tambo-context-helpers-provider");
12
+ const mockAddContextHelper = jest.fn();
13
+ const mockRemoveContextHelper = jest.fn();
14
+ const CONTEXT_ATTACHMENTS_HELPER_KEY = "contextAttachments";
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ tambo_context_helpers_provider_1.useTamboContextHelpers.mockReturnValue({
18
+ addContextHelper: mockAddContextHelper,
19
+ removeContextHelper: mockRemoveContextHelper,
20
+ });
21
+ });
10
22
  /**
11
23
  * Test suite for TamboContextAttachmentProvider
12
24
  *
13
25
  * 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
26
+ * - Adding context attachments that will be sent with the next message
27
+ * - Automatic registration/deregistration of context helpers
18
28
  */
19
29
  describe("TamboContextAttachmentProvider", () => {
20
- beforeEach(() => {
21
- jest.clearAllMocks();
22
- });
23
30
  /**
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
31
+ * Base wrapper with TamboContextAttachmentProvider
32
+ * @returns A React component that wraps children with the provider
28
33
  */
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)));
34
+ const createWrapper = () => {
35
+ const Wrapper = ({ children }) => (react_2.default.createElement(tambo_context_attachment_provider_1.TamboContextAttachmentProvider, null, children));
32
36
  Wrapper.displayName = "TestWrapper";
33
37
  return Wrapper;
34
38
  };
@@ -44,8 +48,6 @@ describe("TamboContextAttachmentProvider", () => {
44
48
  expect(result.current).toHaveProperty("addContextAttachment");
45
49
  expect(result.current).toHaveProperty("removeContextAttachment");
46
50
  expect(result.current).toHaveProperty("clearContextAttachments");
47
- expect(result.current).toHaveProperty("customSuggestions");
48
- expect(result.current).toHaveProperty("setCustomSuggestions");
49
51
  });
50
52
  /**
51
53
  * Hook should throw error when used outside of provider
@@ -63,154 +65,318 @@ describe("TamboContextAttachmentProvider", () => {
63
65
  });
64
66
  describe("Adding Context Attachments", () => {
65
67
  /**
66
- * Should add a context attachment with auto-generated ID
68
+ * Should add a context attachment and register/update the merged context helper
67
69
  */
68
- it("should add a context attachment", () => {
70
+ it("should add a context attachment", async () => {
69
71
  const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
70
72
  wrapper: createWrapper(),
71
73
  });
74
+ let attachment;
72
75
  (0, react_1.act)(() => {
73
- result.current.addContextAttachment({
74
- name: "Button.tsx",
75
- metadata: { filePath: "/src/Button.tsx" },
76
+ attachment = result.current.addContextAttachment({
77
+ context: "selectedFile",
78
+ displayName: "Button.tsx",
79
+ type: "file",
76
80
  });
77
81
  });
78
82
  expect(result.current.attachments).toHaveLength(1);
79
83
  expect(result.current.attachments[0]).toMatchObject({
80
- name: "Button.tsx",
81
- metadata: { filePath: "/src/Button.tsx" },
84
+ displayName: "Button.tsx",
85
+ context: "selectedFile",
86
+ type: "file",
82
87
  });
83
88
  expect(result.current.attachments[0].id).toBeDefined();
89
+ expect(attachment.id).toBe(result.current.attachments[0].id);
90
+ // Wait for useEffect to run and register the merged helper with the attachment
91
+ await (0, react_1.waitFor)(() => {
92
+ const lastCall = mockAddContextHelper.mock.calls
93
+ .filter((call) => call[0] === CONTEXT_ATTACHMENTS_HELPER_KEY)
94
+ .pop();
95
+ if (lastCall) {
96
+ const helperFn = lastCall[1];
97
+ const helperResult = helperFn();
98
+ expect(helperResult).not.toBeNull();
99
+ expect(helperResult).toHaveLength(1);
100
+ }
101
+ else {
102
+ throw new Error("Helper not registered");
103
+ }
104
+ });
105
+ // Verify the helper function returns the merged attachments array
106
+ const helperFn = mockAddContextHelper.mock.calls
107
+ .filter((call) => call[0] === CONTEXT_ATTACHMENTS_HELPER_KEY)
108
+ .pop()?.[1];
109
+ expect(helperFn).toBeDefined();
110
+ const helperResult = helperFn();
111
+ expect(helperResult).toEqual([
112
+ {
113
+ id: attachment.id,
114
+ displayName: "Button.tsx",
115
+ context: "selectedFile",
116
+ type: "file",
117
+ },
118
+ ]);
84
119
  });
85
120
  /**
86
- * Should add multiple different attachments
121
+ * Should add a context attachment without displayName
87
122
  */
88
- it("should add multiple context attachments", () => {
123
+ it("should add a context attachment without displayName", () => {
124
+ const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
125
+ wrapper: createWrapper(),
126
+ });
127
+ let attachment;
128
+ (0, react_1.act)(() => {
129
+ attachment = result.current.addContextAttachment({
130
+ context: "selectedFile",
131
+ });
132
+ });
133
+ expect(result.current.attachments).toHaveLength(1);
134
+ expect(result.current.attachments[0]).toMatchObject({
135
+ context: "selectedFile",
136
+ });
137
+ expect(result.current.attachments[0].displayName).toBeUndefined();
138
+ expect(attachment.id).toBe(result.current.attachments[0].id);
139
+ });
140
+ /**
141
+ * Should add multiple different context attachments and update the merged helper
142
+ */
143
+ it("should add multiple context attachments", async () => {
89
144
  const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
90
145
  wrapper: createWrapper(),
91
146
  });
92
147
  (0, react_1.act)(() => {
93
148
  result.current.addContextAttachment({
94
- name: "Button.tsx",
149
+ context: "file1",
150
+ displayName: "Button.tsx",
151
+ type: "file",
95
152
  });
96
153
  result.current.addContextAttachment({
97
- name: "Card.tsx",
154
+ context: "file2",
155
+ displayName: "Card.tsx",
156
+ type: "file",
98
157
  });
99
158
  });
100
159
  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");
160
+ expect(result.current.attachments[0].displayName).toBe("Button.tsx");
161
+ expect(result.current.attachments[1].displayName).toBe("Card.tsx");
162
+ // Wait for useEffect to run and update the merged helper
163
+ await (0, react_1.waitFor)(() => {
164
+ // The helper should be called/updated for each attachment change
165
+ expect(mockAddContextHelper).toHaveBeenCalledWith(CONTEXT_ATTACHMENTS_HELPER_KEY, expect.any(Function));
166
+ });
167
+ // Verify the helper function returns all attachments
168
+ const helperFn = mockAddContextHelper.mock.calls
169
+ .filter((call) => call[0] === CONTEXT_ATTACHMENTS_HELPER_KEY)
170
+ .pop()?.[1];
171
+ expect(helperFn).toBeDefined();
172
+ const helperResult = helperFn();
173
+ expect(helperResult).toHaveLength(2);
174
+ expect(helperResult[0].displayName).toBe("Button.tsx");
175
+ expect(helperResult[1].displayName).toBe("Card.tsx");
103
176
  });
104
177
  /**
105
- * Should prevent duplicates with the same name
178
+ * Should allow multiple attachments with the same context value
106
179
  */
107
- it("should prevent duplicate attachments with same name", () => {
180
+ it("should allow multiple attachments with same context value", async () => {
108
181
  const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
109
182
  wrapper: createWrapper(),
110
183
  });
111
184
  (0, react_1.act)(() => {
112
185
  result.current.addContextAttachment({
113
- name: "Button.tsx",
186
+ context: "selectedFile",
187
+ displayName: "Button.tsx",
188
+ type: "file",
114
189
  });
115
190
  result.current.addContextAttachment({
116
- name: "Button.tsx",
191
+ context: "selectedFile",
192
+ displayName: "Card.tsx",
193
+ type: "file",
117
194
  });
118
195
  });
119
- expect(result.current.attachments).toHaveLength(1);
196
+ expect(result.current.attachments).toHaveLength(2);
197
+ // Wait for useEffect to update the merged helper
198
+ await (0, react_1.waitFor)(() => {
199
+ expect(mockAddContextHelper).toHaveBeenCalledWith(CONTEXT_ATTACHMENTS_HELPER_KEY, expect.any(Function));
200
+ });
201
+ // Verify both attachments are included in the merged result
202
+ const helperFn = mockAddContextHelper.mock.calls
203
+ .filter((call) => call[0] === CONTEXT_ATTACHMENTS_HELPER_KEY)
204
+ .pop()?.[1];
205
+ expect(helperFn).toBeDefined();
206
+ const helperResult = helperFn();
207
+ expect(helperResult).toHaveLength(2);
120
208
  });
121
209
  /**
122
- * Should support optional icon property
210
+ * Should support optional type property
123
211
  */
124
- it("should support icon property", () => {
212
+ it("should support optional type property", () => {
125
213
  const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
126
214
  wrapper: createWrapper(),
127
215
  });
128
- const icon = react_2.default.createElement("span", null, "\uD83D\uDCC4");
129
216
  (0, react_1.act)(() => {
130
217
  result.current.addContextAttachment({
131
- name: "File.txt",
132
- icon,
218
+ context: "file1",
219
+ displayName: "Button.tsx",
220
+ type: "file",
221
+ });
222
+ result.current.addContextAttachment({
223
+ context: "page1",
224
+ displayName: "Dashboard",
225
+ type: "page",
133
226
  });
134
227
  });
135
- expect(result.current.attachments[0].icon).toBe(icon);
228
+ expect(result.current.attachments[0].type).toBe("file");
229
+ expect(result.current.attachments[1].type).toBe("page");
136
230
  });
137
231
  });
138
232
  describe("Removing Context Attachments", () => {
139
233
  /**
140
- * Should remove a specific attachment by ID
234
+ * Should remove a specific context attachment by ID and update the merged helper
141
235
  */
142
- it("should remove context attachment by ID", () => {
236
+ it("should remove context attachment by ID", async () => {
143
237
  const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
144
238
  wrapper: createWrapper(),
145
239
  });
240
+ let attachmentId = "";
146
241
  (0, react_1.act)(() => {
147
- result.current.addContextAttachment({
148
- name: "Button.tsx",
242
+ const attachment = result.current.addContextAttachment({
243
+ context: "selectedFile",
244
+ displayName: "Button.tsx",
149
245
  });
246
+ attachmentId = attachment.id;
150
247
  });
151
248
  expect(result.current.attachments).toHaveLength(1);
152
- const attachmentId = result.current.attachments[0].id;
249
+ // Wait for initial helper registration
250
+ await (0, react_1.waitFor)(() => {
251
+ expect(mockAddContextHelper).toHaveBeenCalledWith(CONTEXT_ATTACHMENTS_HELPER_KEY, expect.any(Function));
252
+ });
153
253
  (0, react_1.act)(() => {
154
254
  result.current.removeContextAttachment(attachmentId);
155
255
  });
156
256
  expect(result.current.attachments).toHaveLength(0);
257
+ // Wait for useEffect to update the helper (should return null when empty)
258
+ await (0, react_1.waitFor)(() => {
259
+ const lastCall = mockAddContextHelper.mock.calls
260
+ .filter((call) => call[0] === CONTEXT_ATTACHMENTS_HELPER_KEY)
261
+ .pop();
262
+ if (lastCall) {
263
+ const helperFn = lastCall[1];
264
+ expect(helperFn()).toBeNull();
265
+ }
266
+ });
157
267
  });
158
268
  /**
159
269
  * Should only remove the specified attachment when multiple exist
160
270
  */
161
- it("should only remove specified attachment", () => {
271
+ it("should only remove specified attachment", async () => {
162
272
  const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
163
273
  wrapper: createWrapper(),
164
274
  });
275
+ let firstId = "";
165
276
  (0, react_1.act)(() => {
166
- result.current.addContextAttachment({ name: "First.tsx" });
167
- result.current.addContextAttachment({ name: "Second.tsx" });
277
+ const first = result.current.addContextAttachment({
278
+ context: "file1",
279
+ displayName: "First.tsx",
280
+ });
281
+ result.current.addContextAttachment({
282
+ context: "file2",
283
+ displayName: "Second.tsx",
284
+ });
285
+ firstId = first.id;
168
286
  });
169
287
  expect(result.current.attachments).toHaveLength(2);
170
- const firstId = result.current.attachments[0].id;
288
+ // Wait for initial helper registration
289
+ await (0, react_1.waitFor)(() => {
290
+ expect(mockAddContextHelper).toHaveBeenCalledWith(CONTEXT_ATTACHMENTS_HELPER_KEY, expect.any(Function));
291
+ });
171
292
  (0, react_1.act)(() => {
172
293
  result.current.removeContextAttachment(firstId);
173
294
  });
174
295
  expect(result.current.attachments).toHaveLength(1);
175
- expect(result.current.attachments[0].name).toBe("Second.tsx");
296
+ expect(result.current.attachments[0].displayName).toBe("Second.tsx");
297
+ // Wait for useEffect to update the helper with remaining attachment
298
+ await (0, react_1.waitFor)(() => {
299
+ const lastCall = mockAddContextHelper.mock.calls
300
+ .filter((call) => call[0] === CONTEXT_ATTACHMENTS_HELPER_KEY)
301
+ .pop();
302
+ if (lastCall) {
303
+ const helperFn = lastCall[1];
304
+ const helperResult = helperFn();
305
+ expect(helperResult).toHaveLength(1);
306
+ expect(helperResult[0].displayName).toBe("Second.tsx");
307
+ }
308
+ });
176
309
  });
177
310
  /**
178
311
  * Should handle removing non-existent attachment gracefully
179
312
  */
180
- it("should handle removing non-existent attachment gracefully", () => {
313
+ it("should handle removing non-existent attachment gracefully", async () => {
181
314
  const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
182
315
  wrapper: createWrapper(),
183
316
  });
317
+ // Wait for initial helper registration (useEffect runs on mount)
318
+ await (0, react_1.waitFor)(() => {
319
+ expect(mockAddContextHelper).toHaveBeenCalledWith(CONTEXT_ATTACHMENTS_HELPER_KEY, expect.any(Function));
320
+ });
184
321
  expect(() => {
185
322
  (0, react_1.act)(() => {
186
323
  result.current.removeContextAttachment("non-existent-id");
187
324
  });
188
325
  }).not.toThrow();
326
+ // The helper is registered on mount, so removeContextHelper will be called
327
+ // when the component unmounts or when attachments change, but not for
328
+ // removing a non-existent attachment since attachments didn't change
329
+ // However, the cleanup function from the initial useEffect registration
330
+ // may be called. Let's just verify the attachment list is still empty.
331
+ expect(result.current.attachments).toHaveLength(0);
189
332
  });
190
333
  });
191
- describe("Clearing All Attachments", () => {
334
+ describe("Clearing All Context Attachments", () => {
192
335
  /**
193
- * Should clear all attachments at once
336
+ * Should clear all context attachments and update the merged helper to return null
194
337
  */
195
- it("should clear all context attachments", () => {
338
+ it("should clear all context attachments", async () => {
196
339
  const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
197
340
  wrapper: createWrapper(),
198
341
  });
199
342
  (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" });
343
+ result.current.addContextAttachment({
344
+ context: "file1",
345
+ displayName: "First.tsx",
346
+ });
347
+ result.current.addContextAttachment({
348
+ context: "file2",
349
+ displayName: "Second.tsx",
350
+ });
351
+ result.current.addContextAttachment({
352
+ context: "file3",
353
+ displayName: "Third.tsx",
354
+ });
203
355
  });
204
356
  expect(result.current.attachments).toHaveLength(3);
357
+ // Wait for initial helper registration
358
+ await (0, react_1.waitFor)(() => {
359
+ expect(mockAddContextHelper).toHaveBeenCalledWith(CONTEXT_ATTACHMENTS_HELPER_KEY, expect.any(Function));
360
+ });
205
361
  (0, react_1.act)(() => {
206
362
  result.current.clearContextAttachments();
207
363
  });
208
364
  expect(result.current.attachments).toHaveLength(0);
365
+ // Wait for useEffect to update the helper (should return null when empty)
366
+ await (0, react_1.waitFor)(() => {
367
+ const lastCall = mockAddContextHelper.mock.calls
368
+ .filter((call) => call[0] === CONTEXT_ATTACHMENTS_HELPER_KEY)
369
+ .pop();
370
+ if (lastCall) {
371
+ const helperFn = lastCall[1];
372
+ expect(helperFn()).toBeNull();
373
+ }
374
+ });
209
375
  });
210
376
  /**
211
377
  * Should handle clearing when no attachments exist
212
378
  */
213
- it("should handle clearing when no attachments exist", () => {
379
+ it("should handle clearing when no attachments exist", async () => {
214
380
  const { result } = (0, react_1.renderHook)(() => (0, tambo_context_attachment_provider_1.useTamboContextAttachment)(), {
215
381
  wrapper: createWrapper(),
216
382
  });
@@ -220,413 +386,13 @@ describe("TamboContextAttachmentProvider", () => {
220
386
  });
221
387
  }).not.toThrow();
222
388
  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
389
+ // Wait for useEffect to run - helper should be registered and return null
430
390
  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
- });
391
+ expect(mockAddContextHelper).toHaveBeenCalledWith(CONTEXT_ATTACHMENTS_HELPER_KEY, expect.any(Function));
612
392
  });
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);
393
+ const helperFn = mockAddContextHelper.mock.calls.find((call) => call[0] === CONTEXT_ATTACHMENTS_HELPER_KEY)?.[1];
394
+ expect(helperFn).toBeDefined();
395
+ expect(helperFn()).toBeNull();
630
396
  });
631
397
  });
632
398
  });