@copilotkit/react-core 1.57.3 → 1.58.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 (282) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{copilotkit-CC8DjOiC.mjs → copilotkit-BIn7HE8f.mjs} +2 -2
  3. package/dist/{copilotkit-CC8DjOiC.mjs.map → copilotkit-BIn7HE8f.mjs.map} +1 -1
  4. package/dist/{copilotkit-CtXcs1ea.cjs → copilotkit-Drw-g6zA.cjs} +2 -2
  5. package/dist/{copilotkit-CtXcs1ea.cjs.map → copilotkit-Drw-g6zA.cjs.map} +1 -1
  6. package/dist/index.cjs +3 -77
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.mjs +3 -77
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/index.umd.js +3 -77
  13. package/dist/index.umd.js.map +1 -1
  14. package/dist/v2/index.cjs +1 -1
  15. package/dist/v2/index.mjs +1 -1
  16. package/dist/v2/index.umd.js +1 -1
  17. package/dist/v2/index.umd.js.map +1 -1
  18. package/package.json +12 -13
  19. package/skills/react-core/SKILL.md +108 -0
  20. package/skills/react-core/references/agent-access.md +288 -0
  21. package/skills/react-core/references/attachments.md +291 -0
  22. package/skills/react-core/references/capabilities.md +138 -0
  23. package/skills/react-core/references/chat-components.md +221 -0
  24. package/skills/react-core/references/client-side-tools.md +358 -0
  25. package/skills/react-core/references/custom-message-renderers.md +226 -0
  26. package/skills/react-core/references/debug-mode.md +153 -0
  27. package/skills/react-core/references/human-in-the-loop.md +312 -0
  28. package/skills/react-core/references/provider-setup.md +326 -0
  29. package/skills/react-core/references/rendering-activity-messages.md +207 -0
  30. package/skills/react-core/references/rendering-tool-calls.md +319 -0
  31. package/skills/react-core/references/suggestions.md +211 -0
  32. package/skills/react-core/references/switching-agents-recipes.md +160 -0
  33. package/skills/react-core/references/switching-agents.md +231 -0
  34. package/skills/react-core/references/threads.md +226 -0
  35. package/.attw.json +0 -3
  36. package/CHANGELOG.md +0 -5043
  37. package/scripts/scope-preflight.mjs +0 -100
  38. package/src/components/CopilotListeners.tsx +0 -137
  39. package/src/components/__tests__/CopilotListeners.test.tsx +0 -38
  40. package/src/components/copilot-provider/__tests__/copilot-messages-key.test.tsx +0 -92
  41. package/src/components/copilot-provider/__tests__/copilotkit-error.test.tsx +0 -77
  42. package/src/components/copilot-provider/__tests__/error-visibility-prod.test.tsx +0 -70
  43. package/src/components/copilot-provider/__tests__/v1-explicit-threadid-bridge.test.tsx +0 -107
  44. package/src/components/copilot-provider/copilot-messages.tsx +0 -314
  45. package/src/components/copilot-provider/copilotkit-props.tsx +0 -214
  46. package/src/components/copilot-provider/copilotkit.tsx +0 -853
  47. package/src/components/copilot-provider/index.ts +0 -3
  48. package/src/components/dev-console/console-trigger.tsx +0 -283
  49. package/src/components/dev-console/developer-console-modal.tsx +0 -1016
  50. package/src/components/dev-console/icons.tsx +0 -106
  51. package/src/components/error-boundary/error-boundary.tsx +0 -99
  52. package/src/components/error-boundary/error-utils.tsx +0 -105
  53. package/src/components/index.ts +0 -1
  54. package/src/components/toast/exclamation-mark-icon.tsx +0 -27
  55. package/src/components/toast/toast-provider.tsx +0 -448
  56. package/src/components/usage-banner.tsx +0 -266
  57. package/src/context/__tests__/threads-context.test.tsx +0 -141
  58. package/src/context/coagent-state-renders-context.tsx +0 -89
  59. package/src/context/copilot-context.tsx +0 -365
  60. package/src/context/copilot-messages-context.tsx +0 -35
  61. package/src/context/index.ts +0 -22
  62. package/src/context/threads-context.tsx +0 -69
  63. package/src/hooks/__tests__/use-coagent-config.test.ts +0 -352
  64. package/src/hooks/__tests__/use-coagent-state-render-bridge.helpers.test.ts +0 -107
  65. package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +0 -1209
  66. package/src/hooks/__tests__/use-coagent-state-render.test.tsx +0 -356
  67. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +0 -241
  68. package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +0 -72
  69. package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +0 -102
  70. package/src/hooks/index.ts +0 -33
  71. package/src/hooks/use-agent-nodename.ts +0 -33
  72. package/src/hooks/use-coagent-state-render-bridge.helpers.ts +0 -345
  73. package/src/hooks/use-coagent-state-render-bridge.tsx +0 -222
  74. package/src/hooks/use-coagent-state-render-registry.ts +0 -230
  75. package/src/hooks/use-coagent-state-render.ts +0 -163
  76. package/src/hooks/use-coagent.ts +0 -377
  77. package/src/hooks/use-configure-chat-suggestions.tsx +0 -96
  78. package/src/hooks/use-copilot-action.ts +0 -245
  79. package/src/hooks/use-copilot-additional-instructions.ts +0 -98
  80. package/src/hooks/use-copilot-authenticated-action.ts +0 -73
  81. package/src/hooks/use-copilot-chat-headless_c.ts +0 -264
  82. package/src/hooks/use-copilot-chat-suggestions.tsx +0 -134
  83. package/src/hooks/use-copilot-chat.ts +0 -132
  84. package/src/hooks/use-copilot-chat_internal.ts +0 -875
  85. package/src/hooks/use-copilot-readable.ts +0 -135
  86. package/src/hooks/use-copilot-runtime-client.ts +0 -178
  87. package/src/hooks/use-default-tool.ts +0 -13
  88. package/src/hooks/use-flat-category-store.ts +0 -109
  89. package/src/hooks/use-frontend-tool.ts +0 -113
  90. package/src/hooks/use-human-in-the-loop.ts +0 -138
  91. package/src/hooks/use-langgraph-interrupt.ts +0 -103
  92. package/src/hooks/use-lazy-tool-renderer.tsx +0 -30
  93. package/src/hooks/use-make-copilot-document-readable.ts +0 -30
  94. package/src/hooks/use-render-tool-call.ts +0 -89
  95. package/src/hooks/use-tree.ts +0 -222
  96. package/src/index.tsx +0 -7
  97. package/src/lib/copilot-task.ts +0 -215
  98. package/src/lib/index.ts +0 -1
  99. package/src/lib/status-checker.ts +0 -67
  100. package/src/setupTests.ts +0 -37
  101. package/src/test-helpers/copilot-context.ts +0 -91
  102. package/src/types/chat-suggestion-configuration.ts +0 -23
  103. package/src/types/coagent-action.ts +0 -35
  104. package/src/types/coagent-state.ts +0 -13
  105. package/src/types/crew.ts +0 -89
  106. package/src/types/document-pointer.ts +0 -7
  107. package/src/types/frontend-action.ts +0 -213
  108. package/src/types/index.ts +0 -17
  109. package/src/types/interrupt-action.ts +0 -58
  110. package/src/types/system-message.ts +0 -4
  111. package/src/utils/dev-console.ts +0 -19
  112. package/src/utils/index.ts +0 -2
  113. package/src/utils/suggestions-constants.ts +0 -8
  114. package/src/utils/utils.test.ts +0 -7
  115. package/src/utils/utils.ts +0 -6
  116. package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +0 -240
  117. package/src/v2/__tests__/globalSetup.ts +0 -14
  118. package/src/v2/__tests__/setup.ts +0 -93
  119. package/src/v2/__tests__/utils/test-helpers.tsx +0 -570
  120. package/src/v2/a2ui/A2UICatalogContext.tsx +0 -79
  121. package/src/v2/a2ui/A2UIMessageRenderer.tsx +0 -294
  122. package/src/v2/a2ui/A2UIToolCallRenderer.tsx +0 -290
  123. package/src/v2/components/CopilotKitInspector.tsx +0 -52
  124. package/src/v2/components/MCPAppsActivityRenderer.tsx +0 -815
  125. package/src/v2/components/OpenGenerativeUIRenderer.tsx +0 -598
  126. package/src/v2/components/WildcardToolCallRender.tsx +0 -86
  127. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.tsx +0 -665
  128. package/src/v2/components/chat/CopilotChat.tsx +0 -664
  129. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +0 -393
  130. package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +0 -374
  131. package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +0 -159
  132. package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +0 -350
  133. package/src/v2/components/chat/CopilotChatInput.tsx +0 -1412
  134. package/src/v2/components/chat/CopilotChatMessageView.tsx +0 -716
  135. package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +0 -265
  136. package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +0 -59
  137. package/src/v2/components/chat/CopilotChatSuggestionView.tsx +0 -134
  138. package/src/v2/components/chat/CopilotChatToggleButton.tsx +0 -171
  139. package/src/v2/components/chat/CopilotChatToolCallsView.tsx +0 -40
  140. package/src/v2/components/chat/CopilotChatUserMessage.tsx +0 -445
  141. package/src/v2/components/chat/CopilotChatView.tsx +0 -890
  142. package/src/v2/components/chat/CopilotModalHeader.tsx +0 -129
  143. package/src/v2/components/chat/CopilotPopup.tsx +0 -81
  144. package/src/v2/components/chat/CopilotPopupView.tsx +0 -317
  145. package/src/v2/components/chat/CopilotSidebar.tsx +0 -80
  146. package/src/v2/components/chat/CopilotSidebarView.tsx +0 -269
  147. package/src/v2/components/chat/Lightbox.tsx +0 -103
  148. package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +0 -66
  149. package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.tsx +0 -168
  150. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +0 -1239
  151. package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +0 -73
  152. package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +0 -432
  153. package/src/v2/components/chat/__tests__/CopilotChat.suggestionsAlways.test.tsx +0 -183
  154. package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +0 -184
  155. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +0 -649
  156. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +0 -624
  157. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +0 -702
  158. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.thumbs.test.tsx +0 -72
  159. package/src/v2/components/chat/__tests__/CopilotChatCopyButton.clipboard.test.tsx +0 -241
  160. package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +0 -107
  161. package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +0 -929
  162. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +0 -1567
  163. package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +0 -1004
  164. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +0 -279
  165. package/src/v2/components/chat/__tests__/CopilotChatPerf.e2e.test.tsx +0 -336
  166. package/src/v2/components/chat/__tests__/CopilotChatPropsRerender.e2e.test.tsx +0 -249
  167. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +0 -530
  168. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +0 -785
  169. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +0 -2416
  170. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +0 -621
  171. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +0 -56
  172. package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.tsx +0 -264
  173. package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +0 -853
  174. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +0 -94
  175. package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +0 -1050
  176. package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +0 -484
  177. package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +0 -612
  178. package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.tsx +0 -159
  179. package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +0 -502
  180. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +0 -1068
  181. package/src/v2/components/chat/__tests__/MCPAppsProxy.e2e.test.tsx +0 -589
  182. package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.tsx +0 -403
  183. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -137
  184. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +0 -37
  185. package/src/v2/components/chat/__tests__/setup.ts +0 -1
  186. package/src/v2/components/chat/index.ts +0 -90
  187. package/src/v2/components/chat/last-user-message-context.ts +0 -21
  188. package/src/v2/components/chat/normalize-auto-scroll.ts +0 -17
  189. package/src/v2/components/chat/scroll-element-context.ts +0 -13
  190. package/src/v2/components/index.ts +0 -8
  191. package/src/v2/components/intelligence-indicator/IntelligenceIndicator.tsx +0 -286
  192. package/src/v2/components/intelligence-indicator/__tests__/IntelligenceIndicator.e2e.test.tsx +0 -464
  193. package/src/v2/components/intelligence-indicator/index.ts +0 -2
  194. package/src/v2/components/license-warning-banner.tsx +0 -217
  195. package/src/v2/components/ui/button.tsx +0 -124
  196. package/src/v2/components/ui/dropdown-menu.tsx +0 -258
  197. package/src/v2/components/ui/tooltip.tsx +0 -60
  198. package/src/v2/context.ts +0 -62
  199. package/src/v2/headless.ts +0 -64
  200. package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +0 -152
  201. package/src/v2/hooks/__tests__/standard-schema.test.tsx +0 -282
  202. package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +0 -140
  203. package/src/v2/hooks/__tests__/use-agent-context.test.tsx +0 -401
  204. package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +0 -44
  205. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +0 -211
  206. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +0 -1029
  207. package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +0 -159
  208. package/src/v2/hooks/__tests__/use-attachments.test.tsx +0 -169
  209. package/src/v2/hooks/__tests__/use-capabilities.test.tsx +0 -76
  210. package/src/v2/hooks/__tests__/use-component.test.tsx +0 -126
  211. package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +0 -696
  212. package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +0 -153
  213. package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +0 -167
  214. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +0 -2148
  215. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +0 -1261
  216. package/src/v2/hooks/__tests__/use-interrupt.test.tsx +0 -397
  217. package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +0 -56
  218. package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +0 -192
  219. package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +0 -219
  220. package/src/v2/hooks/__tests__/use-render-custom-messages.test.tsx +0 -55
  221. package/src/v2/hooks/__tests__/use-render-tool.test.tsx +0 -259
  222. package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +0 -524
  223. package/src/v2/hooks/__tests__/use-threads.test.tsx +0 -757
  224. package/src/v2/hooks/__tests__/zod-regression.test.tsx +0 -311
  225. package/src/v2/hooks/index.ts +0 -24
  226. package/src/v2/hooks/use-agent-context.tsx +0 -45
  227. package/src/v2/hooks/use-agent.tsx +0 -227
  228. package/src/v2/hooks/use-attachments.tsx +0 -269
  229. package/src/v2/hooks/use-capabilities.tsx +0 -25
  230. package/src/v2/hooks/use-component.tsx +0 -91
  231. package/src/v2/hooks/use-configure-suggestions.tsx +0 -236
  232. package/src/v2/hooks/use-default-render-tool.tsx +0 -271
  233. package/src/v2/hooks/use-frontend-tool.tsx +0 -46
  234. package/src/v2/hooks/use-human-in-the-loop.tsx +0 -81
  235. package/src/v2/hooks/use-interrupt.tsx +0 -305
  236. package/src/v2/hooks/use-keyboard-height.tsx +0 -67
  237. package/src/v2/hooks/use-pin-to-send.ts +0 -94
  238. package/src/v2/hooks/use-render-activity-message.tsx +0 -72
  239. package/src/v2/hooks/use-render-custom-messages.tsx +0 -93
  240. package/src/v2/hooks/use-render-tool-call.tsx +0 -208
  241. package/src/v2/hooks/use-render-tool.tsx +0 -184
  242. package/src/v2/hooks/use-suggestions.tsx +0 -91
  243. package/src/v2/hooks/use-threads.tsx +0 -325
  244. package/src/v2/hooks/useKatexStyles.ts +0 -27
  245. package/src/v2/index.css +0 -1
  246. package/src/v2/index.ts +0 -27
  247. package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +0 -495
  248. package/src/v2/lib/__tests__/processPartialHtml.test.ts +0 -112
  249. package/src/v2/lib/__tests__/renderSlot.test.tsx +0 -588
  250. package/src/v2/lib/__tests__/slots.test.ts +0 -56
  251. package/src/v2/lib/processPartialHtml.ts +0 -45
  252. package/src/v2/lib/react-core.ts +0 -156
  253. package/src/v2/lib/slots.tsx +0 -184
  254. package/src/v2/lib/transcription-client.ts +0 -184
  255. package/src/v2/lib/utils.ts +0 -8
  256. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +0 -196
  257. package/src/v2/providers/CopilotKitProvider.tsx +0 -800
  258. package/src/v2/providers/SandboxFunctionsContext.ts +0 -10
  259. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +0 -652
  260. package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +0 -101
  261. package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +0 -69
  262. package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +0 -881
  263. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.tsx +0 -198
  264. package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +0 -740
  265. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +0 -713
  266. package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +0 -294
  267. package/src/v2/providers/index.ts +0 -21
  268. package/src/v2/styles/globals.css +0 -349
  269. package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +0 -525
  270. package/src/v2/types/defineToolCallRenderer.ts +0 -68
  271. package/src/v2/types/frontend-tool.ts +0 -8
  272. package/src/v2/types/human-in-the-loop.ts +0 -33
  273. package/src/v2/types/index.ts +0 -8
  274. package/src/v2/types/interrupt.ts +0 -15
  275. package/src/v2/types/react-activity-message-renderer.ts +0 -27
  276. package/src/v2/types/react-custom-message-renderer.ts +0 -17
  277. package/src/v2/types/react-tool-call-renderer.ts +0 -35
  278. package/src/v2/types/sandbox-function.ts +0 -11
  279. package/tsconfig.json +0 -8
  280. package/tsdown.config.ts +0 -193
  281. package/typedoc.json +0 -4
  282. package/vitest.config.mjs +0 -31
@@ -1,588 +0,0 @@
1
- import React, { forwardRef, useImperativeHandle, useRef } from "react";
2
- import { render, fireEvent, screen } from "@testing-library/react";
3
- import { vi } from "vitest";
4
- import "@testing-library/jest-dom";
5
- import { renderSlot, SlotValue } from "../slots";
6
-
7
- // Extend HTMLAttributes to include data attributes
8
- interface ExtendedDivAttributes extends React.HTMLAttributes<HTMLDivElement> {
9
- [key: `data-${string}`]: string | null | undefined;
10
- }
11
-
12
- // Test components for various scenarios
13
- const SimpleDiv: React.FC<ExtendedDivAttributes> = ({
14
- className,
15
- children,
16
- ...props
17
- }) => (
18
- <div className={className} {...props}>
19
- {children}
20
- </div>
21
- );
22
-
23
- const ButtonWithClick: React.FC<
24
- React.ButtonHTMLAttributes<HTMLButtonElement>
25
- > = ({ onClick, className, children, ...props }) => (
26
- <button className={className} onClick={onClick} {...props}>
27
- {children}
28
- </button>
29
- );
30
-
31
- const ComponentWithContent: React.FC<{
32
- content: string;
33
- className?: string;
34
- }> = ({ content, className }) => <div className={className}>{content}</div>;
35
-
36
- const ForwardRefComponent = forwardRef<
37
- HTMLInputElement,
38
- React.InputHTMLAttributes<HTMLInputElement>
39
- >(({ className, ...props }, ref) => (
40
- <input ref={ref} className={className} {...props} />
41
- ));
42
-
43
- ForwardRefComponent.displayName = "ForwardRefComponent";
44
-
45
- interface CustomHandle {
46
- focus: () => void;
47
- getValue: () => string;
48
- }
49
-
50
- const ComponentWithImperativeHandle = forwardRef<
51
- CustomHandle,
52
- { value?: string; className?: string }
53
- >(({ value = "", className }, ref) => {
54
- const inputRef = useRef<HTMLInputElement>(null);
55
-
56
- useImperativeHandle(ref, () => ({
57
- focus: () => inputRef.current?.focus(),
58
- getValue: () => inputRef.current?.value || value,
59
- }));
60
-
61
- return <input ref={inputRef} defaultValue={value} className={className} />;
62
- });
63
-
64
- ComponentWithImperativeHandle.displayName = "ComponentWithImperativeHandle";
65
-
66
- describe("renderSlot", () => {
67
- describe("Basic slot value types", () => {
68
- test("renders default component when slot is undefined", () => {
69
- const element = renderSlot(undefined, SimpleDiv, {
70
- children: "test content",
71
- });
72
- const { container } = render(element);
73
-
74
- expect(container.firstChild).toHaveTextContent("test content");
75
- expect(container.firstChild?.nodeName).toBe("DIV");
76
- });
77
-
78
- test("uses string slot as className", () => {
79
- const element = renderSlot("bg-red-500 text-white", SimpleDiv, {
80
- children: "styled content",
81
- });
82
- const { container } = render(element);
83
-
84
- expect(container.firstChild).toHaveClass("bg-red-500", "text-white");
85
- expect(container.firstChild).toHaveTextContent("styled content");
86
- });
87
-
88
- test("renders custom component when slot is a function", () => {
89
- const CustomComponent: React.FC<{ children: React.ReactNode }> = ({
90
- children,
91
- }) => <span data-testid="custom">{children}</span>;
92
-
93
- const element = renderSlot(CustomComponent, SimpleDiv, {
94
- children: "custom content",
95
- });
96
- render(element);
97
-
98
- expect(screen.getByTestId("custom")).toHaveTextContent("custom content");
99
- });
100
-
101
- test("merges object slot props with base props", () => {
102
- const element = renderSlot(
103
- { className: "slot-class", "data-slot": "true" },
104
- SimpleDiv,
105
- { children: "merged content", "data-base": "true" },
106
- );
107
- const { container } = render(element);
108
-
109
- expect(container.firstChild).toHaveClass("slot-class");
110
- expect(container.firstChild).toHaveAttribute("data-slot", "true");
111
- expect(container.firstChild).toHaveAttribute("data-base", "true");
112
- expect(container.firstChild).toHaveTextContent("merged content");
113
- });
114
- });
115
-
116
- describe("className handling", () => {
117
- test("string slot merges with props className using twMerge", () => {
118
- const element = renderSlot("slot-class", SimpleDiv, {
119
- className: "props-class",
120
- children: "test",
121
- });
122
- const { container } = render(element);
123
-
124
- // twMerge combines both classes
125
- expect(container.firstChild).toHaveClass("slot-class");
126
- expect(container.firstChild).toHaveClass("props-class");
127
- });
128
-
129
- test("object slot className overrides props className", () => {
130
- const element = renderSlot({ className: "slot-class" }, SimpleDiv, {
131
- className: "props-class",
132
- children: "test",
133
- });
134
- const { container } = render(element);
135
-
136
- // Object slots use spread, so slot className overrides props className
137
- expect(container.firstChild).toHaveClass("slot-class");
138
- expect(container.firstChild).not.toHaveClass("props-class");
139
- });
140
-
141
- test("props className is used when slot has no className", () => {
142
- const element = renderSlot({ "data-test": "true" }, SimpleDiv, {
143
- className: "props-class",
144
- children: "test",
145
- });
146
- const { container } = render(element);
147
-
148
- expect(container.firstChild).toHaveClass("props-class");
149
- expect(container.firstChild).toHaveAttribute("data-test", "true");
150
- });
151
-
152
- test("empty string slot preserves props className", () => {
153
- const element = renderSlot("", SimpleDiv, {
154
- className: "props-class",
155
- children: "test",
156
- });
157
- const { container } = render(element);
158
-
159
- // Empty string with twMerge preserves the props className
160
- expect(container.firstChild).toHaveClass("props-class");
161
- });
162
- });
163
-
164
- describe("Event handling and callbacks", () => {
165
- test("passes click handlers correctly", () => {
166
- const mockClick = vi.fn();
167
- const element = renderSlot(undefined, ButtonWithClick, {
168
- onClick: mockClick,
169
- children: "Click me",
170
- });
171
-
172
- render(element);
173
- fireEvent.click(screen.getByRole("button"));
174
-
175
- expect(mockClick).toHaveBeenCalledTimes(1);
176
- });
177
-
178
- test("object slot can override event handlers", () => {
179
- const baseMockClick = vi.fn();
180
- const slotMockClick = vi.fn();
181
-
182
- const element = renderSlot({ onClick: slotMockClick }, ButtonWithClick, {
183
- onClick: baseMockClick,
184
- children: "Click me",
185
- });
186
-
187
- render(element);
188
- fireEvent.click(screen.getByRole("button"));
189
-
190
- expect(slotMockClick).toHaveBeenCalledTimes(1);
191
- expect(baseMockClick).not.toHaveBeenCalled();
192
- });
193
-
194
- test("custom component receives all event handlers", () => {
195
- const mockClick = vi.fn();
196
- const CustomButton: React.FC<
197
- React.ButtonHTMLAttributes<HTMLButtonElement>
198
- > = (props) => <button {...props} data-testid="custom-button" />;
199
-
200
- const element = renderSlot(CustomButton, ButtonWithClick, {
201
- onClick: mockClick,
202
- children: "Custom button",
203
- });
204
-
205
- render(element);
206
- fireEvent.click(screen.getByTestId("custom-button"));
207
-
208
- expect(mockClick).toHaveBeenCalledTimes(1);
209
- });
210
- });
211
-
212
- describe("Ref forwarding", () => {
213
- test("forwards refs to default component", () => {
214
- const ref = React.createRef<HTMLInputElement>();
215
- const element = renderSlot(undefined, ForwardRefComponent, { ref });
216
-
217
- render(element);
218
-
219
- expect(ref.current).toBeInstanceOf(HTMLInputElement);
220
- });
221
-
222
- test("forwards refs to custom component", () => {
223
- const ref = React.createRef<HTMLInputElement>();
224
- const CustomInput = forwardRef<
225
- HTMLInputElement,
226
- React.InputHTMLAttributes<HTMLInputElement>
227
- >((props, forwardedRef) => (
228
- <input {...props} ref={forwardedRef} data-testid="custom-input" />
229
- ));
230
-
231
- const element = renderSlot(CustomInput, ForwardRefComponent, { ref });
232
-
233
- render(element);
234
-
235
- expect(ref.current).toBeInstanceOf(HTMLInputElement);
236
- // Check if the custom component was actually used
237
- const customInput = screen.queryByTestId("custom-input");
238
- if (customInput) {
239
- expect(customInput).toBe(ref.current);
240
- } else {
241
- // If custom component wasn't used, this is a bug in renderSlot
242
- expect(ref.current).toBeInstanceOf(HTMLInputElement);
243
- }
244
- });
245
-
246
- test("works with useImperativeHandle", () => {
247
- const ref = React.createRef<CustomHandle>();
248
- const element = renderSlot(undefined, ComponentWithImperativeHandle, {
249
- ref,
250
- value: "test-value",
251
- });
252
-
253
- render(element);
254
-
255
- expect(ref.current?.getValue()).toBe("test-value");
256
- expect(typeof ref.current?.focus).toBe("function");
257
- });
258
- });
259
-
260
- describe("Complex prop merging", () => {
261
- test("deeply nested object props are merged correctly", () => {
262
- const ComplexComponent: React.FC<{
263
- config?: { theme: string; options: { debug: boolean } };
264
- className?: string;
265
- }> = ({ config, className }) => (
266
- <div className={className} data-config={JSON.stringify(config)}>
267
- Complex component
268
- </div>
269
- );
270
-
271
- const element = renderSlot(
272
- {
273
- config: { theme: "dark", options: { debug: true } },
274
- className: "slot-class",
275
- },
276
- ComplexComponent,
277
- {
278
- config: { theme: "light", options: { debug: false } },
279
- className: "base-class",
280
- },
281
- );
282
-
283
- const { container } = render(element);
284
- const configData = JSON.parse(
285
- (container.firstChild as Element)?.getAttribute("data-config") || "{}",
286
- );
287
-
288
- expect(configData.theme).toBe("dark"); // slot overrides base
289
- expect(configData.options.debug).toBe(true); // slot overrides base
290
- expect(container.firstChild).toHaveClass("slot-class");
291
- });
292
-
293
- test("handles undefined and null prop values", () => {
294
- const element = renderSlot(
295
- { title: undefined, "data-test": null },
296
- SimpleDiv,
297
- { title: "base-title", "data-base": "value", children: "test" },
298
- );
299
- const { container } = render(element);
300
-
301
- expect(container.firstChild).toHaveAttribute("data-base", "value");
302
-
303
- // Note: undefined in slot object overrides base props and removes them
304
- // This is expected JavaScript spread behavior
305
- expect(container.firstChild).not.toHaveAttribute("title");
306
- });
307
- });
308
-
309
- describe("Real-world usage patterns", () => {
310
- test("simulates CopilotChatInput Toolbar usage with twMerge pattern", () => {
311
- // This simulates the complex pattern in CopilotChatInput
312
- const Toolbar: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({
313
- className,
314
- ...props
315
- }) => <div className={`base-toolbar ${className || ""}`} {...props} />;
316
-
317
- const toolbarSlot: SlotValue<typeof Toolbar> = "custom-toolbar-class";
318
-
319
- const element = renderSlot(toolbarSlot, Toolbar, {
320
- children: "toolbar content",
321
- });
322
-
323
- const { container } = render(element);
324
- expect(container.firstChild).toHaveClass("custom-toolbar-class");
325
- expect(container.firstChild).toHaveTextContent("toolbar content");
326
- });
327
-
328
- test("simulates CopilotChatAssistantMessage content passing", () => {
329
- const element = renderSlot(undefined, ComponentWithContent, {
330
- content: "message content",
331
- className: "message-class",
332
- });
333
-
334
- const { container } = render(element);
335
- expect(container.firstChild).toHaveTextContent("message content");
336
- expect(container.firstChild).toHaveClass("message-class");
337
- });
338
-
339
- test("simulates subcomponent property overrides", () => {
340
- // This simulates the pattern from CopilotChatMessageView where subcomponent props are overridden
341
- const SubComponent: React.FC<{
342
- label: string;
343
- disabled?: boolean;
344
- className?: string;
345
- }> = ({ label, disabled, className }) => (
346
- <button disabled={disabled} className={className}>
347
- {label}
348
- </button>
349
- );
350
-
351
- const element = renderSlot(
352
- { disabled: true, className: "override-class" },
353
- SubComponent,
354
- { label: "Click me", disabled: false, className: "base-class" },
355
- );
356
-
357
- render(element);
358
- const button = screen.getByRole("button");
359
-
360
- expect(button).toBeDisabled(); // slot overrides base
361
- expect(button).toHaveClass("override-class");
362
- expect(button).not.toHaveClass("base-class");
363
- expect(button).toHaveTextContent("Click me");
364
- });
365
- });
366
-
367
- describe("Edge cases and error scenarios", () => {
368
- test("handles React elements as slot values", () => {
369
- const reactElement = <div data-testid="react-element">React Element</div>;
370
-
371
- // React elements should be treated as objects, not functions
372
- const element = renderSlot(reactElement as any, SimpleDiv, {
373
- children: "fallback",
374
- });
375
-
376
- render(element);
377
-
378
- // Should render the default component since React elements are treated as objects
379
- expect(screen.queryByTestId("react-element")).not.toBeInTheDocument();
380
- });
381
-
382
- test("handles components with no props", () => {
383
- const NoPropsComponent: React.FC = () => <div>No props component</div>;
384
-
385
- const element = renderSlot(NoPropsComponent, SimpleDiv, {
386
- children: "test",
387
- });
388
-
389
- render(element);
390
- expect(screen.getByText("No props component")).toBeInTheDocument();
391
- });
392
-
393
- test("handles empty object slot", () => {
394
- const element = renderSlot({}, SimpleDiv, { children: "test content" });
395
- const { container } = render(element);
396
-
397
- expect(container.firstChild).toHaveTextContent("test content");
398
- });
399
-
400
- test("handles component with children render prop pattern", () => {
401
- const RenderPropComponent: React.FC<{
402
- children: (data: { count: number }) => React.ReactNode;
403
- className?: string;
404
- }> = ({ children, className }) => (
405
- <div className={className}>{children({ count: 5 })}</div>
406
- );
407
-
408
- const element = renderSlot(undefined, RenderPropComponent, {
409
- children: ({ count }: { count: number }) => <span>Count: {count}</span>,
410
- className: "render-prop-class",
411
- });
412
-
413
- const { container } = render(element);
414
- expect(container.firstChild).toHaveTextContent("Count: 5");
415
- expect(container.firstChild).toHaveClass("render-prop-class");
416
- });
417
-
418
- test("handles boolean and number props", () => {
419
- const ComponentWithBooleans: React.FC<{
420
- isVisible: boolean;
421
- count: number;
422
- className?: string;
423
- }> = ({ isVisible, count, className }) => (
424
- <div className={className}>
425
- {isVisible ? `Visible with count: ${count}` : "Hidden"}
426
- </div>
427
- );
428
-
429
- const element = renderSlot(
430
- { isVisible: false, count: 10 },
431
- ComponentWithBooleans,
432
- { isVisible: true, count: 5, className: "test-class" },
433
- );
434
-
435
- const { container } = render(element);
436
- expect(container.firstChild).toHaveTextContent("Hidden"); // slot overrides
437
- });
438
-
439
- test("handles array props", () => {
440
- const ComponentWithArray: React.FC<{
441
- items: string[];
442
- className?: string;
443
- }> = ({ items, className }) => (
444
- <ul className={className}>
445
- {items.map((item, index) => (
446
- <li key={index}>{item}</li>
447
- ))}
448
- </ul>
449
- );
450
-
451
- const element = renderSlot(
452
- { items: ["slot1", "slot2"] },
453
- ComponentWithArray,
454
- { items: ["base1", "base2"], className: "list-class" },
455
- );
456
-
457
- render(element);
458
- expect(screen.getByText("slot1")).toBeInTheDocument();
459
- expect(screen.getByText("slot2")).toBeInTheDocument();
460
- expect(screen.queryByText("base1")).not.toBeInTheDocument();
461
- });
462
- });
463
-
464
- describe("Performance and optimization", () => {
465
- test("does not recreate elements unnecessarily", () => {
466
- const renderSpy = vi.fn();
467
- const TrackedComponent: React.FC<{ value: string }> = ({ value }) => {
468
- renderSpy(value);
469
- return <div>{value}</div>;
470
- };
471
-
472
- const element1 = renderSlot(undefined, TrackedComponent, {
473
- value: "test",
474
- });
475
- const element2 = renderSlot(undefined, TrackedComponent, {
476
- value: "test",
477
- });
478
-
479
- render(element1);
480
- render(element2);
481
-
482
- expect(renderSpy).toHaveBeenCalledTimes(2);
483
- expect(renderSpy).toHaveBeenCalledWith("test");
484
- });
485
-
486
- test("handles large prop objects efficiently", () => {
487
- const largePropObject: Record<string, string> = {};
488
- for (let i = 0; i < 100; i++) {
489
- largePropObject[`prop${i}`] = `value${i}`;
490
- }
491
-
492
- const element = renderSlot({ className: "slot-class" }, SimpleDiv, {
493
- ...largePropObject,
494
- children: "test",
495
- });
496
-
497
- const { container } = render(element);
498
- expect(container.firstChild).toHaveClass("slot-class");
499
- expect(container.firstChild).toHaveTextContent("test");
500
- });
501
- });
502
-
503
- describe("Type compatibility", () => {
504
- test("preserves component prop types", () => {
505
- // This test ensures type safety is maintained
506
- const TypedComponent: React.FC<{
507
- requiredProp: string;
508
- optionalProp?: number;
509
- className?: string;
510
- }> = ({ requiredProp, optionalProp, className }) => (
511
- <div className={className}>
512
- {requiredProp} - {optionalProp}
513
- </div>
514
- );
515
-
516
- const element = renderSlot({ optionalProp: 42 }, TypedComponent, {
517
- requiredProp: "test",
518
- className: "typed-class",
519
- });
520
-
521
- const { container } = render(element);
522
- expect(container.firstChild).toHaveTextContent("test - 42");
523
- expect(container.firstChild).toHaveClass("typed-class");
524
- });
525
- });
526
-
527
- describe("Additional bug hunting", () => {
528
- test("function component slot should override default component", () => {
529
- const CustomComponent: React.FC<{ children: React.ReactNode }> = ({
530
- children,
531
- }) => <span data-testid="definitely-custom">{children}</span>;
532
-
533
- const element = renderSlot(CustomComponent, SimpleDiv, {
534
- children: "custom content",
535
- });
536
- render(element);
537
-
538
- const customElement = screen.queryByTestId("definitely-custom");
539
- if (customElement) {
540
- expect(customElement).toHaveTextContent("custom content");
541
- } else {
542
- // Fallback assertion to show what actually renders
543
- expect(screen.getByText("custom content")).toBeInTheDocument();
544
- }
545
- });
546
-
547
- test("React.createElement vs JSX differences", () => {
548
- // Test if there are differences between React.createElement and JSX rendering
549
- const TestComponent: React.FC<{ testProp: string }> = ({ testProp }) => (
550
- <div data-test-prop={testProp}>createElement test</div>
551
- );
552
-
553
- const element = renderSlot(undefined, TestComponent, {
554
- testProp: "test-value",
555
- });
556
- const { container } = render(element);
557
-
558
- expect(container.firstChild).toHaveAttribute(
559
- "data-test-prop",
560
- "test-value",
561
- );
562
- expect(container.firstChild).toHaveTextContent("createElement test");
563
- });
564
-
565
- test("nested component slot behavior", () => {
566
- const NestedComponent: React.FC<{ children: React.ReactNode }> = ({
567
- children,
568
- }) => (
569
- <div data-testid="nested-wrapper">
570
- <span data-testid="nested-inner">{children}</span>
571
- </div>
572
- );
573
-
574
- const element = renderSlot(NestedComponent, SimpleDiv, {
575
- children: "nested content",
576
- });
577
- render(element);
578
-
579
- // Check if nested structure is preserved
580
- const wrapper = screen.queryByTestId("nested-wrapper");
581
- const inner = screen.queryByTestId("nested-inner");
582
-
583
- if (wrapper && inner) {
584
- expect(inner).toHaveTextContent("nested content");
585
- }
586
- });
587
- });
588
- });
@@ -1,56 +0,0 @@
1
- import { renderHook } from "@testing-library/react";
2
- import { describe, it, expect } from "vitest";
3
- import { useShallowStableRef } from "../slots";
4
-
5
- describe("useShallowStableRef", () => {
6
- it("returns the same reference when called twice with shallowly equal plain objects", () => {
7
- const initial = { a: 1 };
8
- const { result, rerender } = renderHook(
9
- ({ value }: { value: { a: number } }) => useShallowStableRef(value),
10
- { initialProps: { value: initial } },
11
- );
12
-
13
- const firstRef = result.current;
14
- rerender({ value: { a: 1 } }); // new object, same shape
15
- expect(result.current).toBe(firstRef);
16
- });
17
-
18
- it("updates the reference when the value changes", () => {
19
- const { result, rerender } = renderHook(
20
- ({ value }: { value: { a: number } }) => useShallowStableRef(value),
21
- { initialProps: { value: { a: 1 } } },
22
- );
23
-
24
- const firstRef = result.current;
25
- rerender({ value: { a: 2 } });
26
- expect(result.current).not.toBe(firstRef);
27
- expect(result.current).toEqual({ a: 2 });
28
- });
29
-
30
- it("handles undefined without crashing", () => {
31
- const { result } = renderHook(() =>
32
- useShallowStableRef(undefined as unknown as { a: number }),
33
- );
34
- expect(result.current).toBeUndefined();
35
- });
36
-
37
- it("handles null without crashing", () => {
38
- const { result } = renderHook(() =>
39
- useShallowStableRef(null as unknown as { a: number }),
40
- );
41
- expect(result.current).toBeNull();
42
- });
43
-
44
- it("does not shallow-compare arrays — treats them by reference", () => {
45
- const arr1 = [1, 2, 3];
46
- const { result, rerender } = renderHook(
47
- ({ value }: { value: number[] }) => useShallowStableRef(value),
48
- { initialProps: { value: arr1 } },
49
- );
50
-
51
- const firstRef = result.current;
52
- rerender({ value: [1, 2, 3] }); // new array, same contents
53
- // arrays are not plain objects — reference should update
54
- expect(result.current).not.toBe(firstRef);
55
- });
56
- });
@@ -1,45 +0,0 @@
1
- /**
2
- * Extracts all complete `<style>` blocks from the raw HTML.
3
- * Returns the concatenated style tags, suitable for injection into `<head>`.
4
- */
5
- export function extractCompleteStyles(html: string): string {
6
- const matches = html.match(/<style\b[^>]*>[\s\S]*?<\/style>/gi);
7
- return matches ? matches.join("") : "";
8
- }
9
-
10
- /**
11
- * Processes raw accumulated HTML for safe preview via innerHTML injection.
12
- * Pure function, no DOM dependencies.
13
- *
14
- * Pipeline (order matters):
15
- * 1. Strip incomplete tag at end
16
- * 2. Strip complete <style>, <script>, and <head> blocks
17
- * 3. Strip incomplete <style>/<script>/<head> blocks
18
- * 4. Strip incomplete HTML entities
19
- * 5. Extract body content (or use full string if no <body>)
20
- */
21
- export function processPartialHtml(html: string): string {
22
- let result = html;
23
-
24
- // 1. Strip incomplete tag at end — e.g. `<div class="fo`
25
- result = result.replace(/<[^>]*$/, "");
26
-
27
- // 2. Strip complete <style>, <script>, and <head> blocks
28
- result = result.replace(/<(style|script|head)\b[^>]*>[\s\S]*?<\/\1>/gi, "");
29
-
30
- // 3. Strip incomplete <style>/<script>/<head> blocks (opening tag, no close)
31
- result = result.replace(/<(style|script|head)\b[^>]*>[\s\S]*$/gi, "");
32
-
33
- // 4. Strip incomplete HTML entities — e.g. `&amp` without semicolon
34
- result = result.replace(/&[a-zA-Z0-9#]*$/, "");
35
-
36
- // 5. Extract body content
37
- const bodyMatch = result.match(/<body[^>]*>([\s\S]*)/i);
38
- if (bodyMatch) {
39
- result = bodyMatch[1]!;
40
- // Strip </body> and everything after
41
- result = result.replace(/<\/body>[\s\S]*/i, "");
42
- }
43
-
44
- return result;
45
- }