@copilotkit/vue 1.57.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +50 -0
- package/CHANGELOG.md +13 -0
- package/PARITY.md +434 -0
- package/README.md +396 -0
- package/dist/components/copilot-provider/CopilotKit.vue.d.ts +20 -0
- package/dist/components/copilot-provider/CopilotKit.vue.d.ts.map +1 -0
- package/dist/components/copilot-provider/index.d.ts +3 -0
- package/dist/components/copilot-provider/index.d.ts.map +1 -0
- package/dist/components/copilot-provider/types.d.ts +22 -0
- package/dist/components/copilot-provider/types.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/use-copilot-action.d.ts +27 -0
- package/dist/hooks/use-copilot-action.d.ts.map +1 -0
- package/dist/hooks/use-copilot-readable.d.ts +20 -0
- package/dist/hooks/use-copilot-readable.d.ts.map +1 -0
- package/dist/hooks/use-frontend-tool.d.ts +21 -0
- package/dist/hooks/use-frontend-tool.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +252 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/use-render-activity-message-BRL1Rpl-.cjs +85 -0
- package/dist/use-render-activity-message-BRL1Rpl-.cjs.map +1 -0
- package/dist/use-render-activity-message-CqtxiFSs.js +8927 -0
- package/dist/use-render-activity-message-CqtxiFSs.js.map +1 -0
- package/dist/v2/components/A2UIMessageRenderer.d.ts +9 -0
- package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -0
- package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts +16 -0
- package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -0
- package/dist/v2/components/CopilotKitInspector.vue.d.ts +7 -0
- package/dist/v2/components/CopilotKitInspector.vue.d.ts.map +1 -0
- package/dist/v2/components/InlineFeatureWarning.vue.d.ts +6 -0
- package/dist/v2/components/InlineFeatureWarning.vue.d.ts.map +1 -0
- package/dist/v2/components/LicenseWarningBanner.vue.d.ts +18 -0
- package/dist/v2/components/LicenseWarningBanner.vue.d.ts.map +1 -0
- package/dist/v2/components/MCPAppsActivityRenderer.d.ts +88 -0
- package/dist/v2/components/MCPAppsActivityRenderer.d.ts.map +1 -0
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +154 -0
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -0
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +19 -0
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -0
- package/dist/v2/components/a2ui/A2UICatalogContext.d.ts +16 -0
- package/dist/v2/components/a2ui/A2UICatalogContext.d.ts.map +1 -0
- package/dist/v2/components/a2ui/VueSurface.d.ts +62 -0
- package/dist/v2/components/a2ui/VueSurface.d.ts.map +1 -0
- package/dist/v2/components/a2ui/adapter.d.ts +38 -0
- package/dist/v2/components/a2ui/adapter.d.ts.map +1 -0
- package/dist/v2/components/a2ui/catalog.d.ts +29 -0
- package/dist/v2/components/a2ui/catalog.d.ts.map +1 -0
- package/dist/v2/components/a2ui/index.d.ts +5 -0
- package/dist/v2/components/a2ui/index.d.ts.map +1 -0
- package/dist/v2/components/a2ui/utils.d.ts +18 -0
- package/dist/v2/components/a2ui/utils.d.ts.map +1 -0
- package/dist/v2/components/a2ui.d.ts +12 -0
- package/dist/v2/components/a2ui.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChat.vue.d.ts +50 -0
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts +164 -0
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts +12 -0
- package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts +7 -0
- package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts +12 -0
- package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +290 -0
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts +72 -0
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts +65 -0
- package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatSuggestionPill.vue.d.ts +27 -0
- package/dist/v2/components/chat/CopilotChatSuggestionPill.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatSuggestionView.vue.d.ts +26 -0
- package/dist/v2/components/chat/CopilotChatSuggestionView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToggleButton.vue.d.ts +17 -0
- package/dist/v2/components/chat/CopilotChatToggleButton.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToggleButtonCloseIcon.d.ts +5 -0
- package/dist/v2/components/chat/CopilotChatToggleButtonCloseIcon.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToggleButtonOpenIcon.d.ts +5 -0
- package/dist/v2/components/chat/CopilotChatToggleButtonOpenIcon.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts +21 -0
- package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts +34 -0
- package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts +106 -0
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts +15 -0
- package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotModalHeaderCloseButton.d.ts +5 -0
- package/dist/v2/components/chat/CopilotModalHeaderCloseButton.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotModalHeaderTitle.d.ts +5 -0
- package/dist/v2/components/chat/CopilotModalHeaderTitle.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotPopup.vue.d.ts +50 -0
- package/dist/v2/components/chat/CopilotPopup.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotPopupView.vue.d.ts +55 -0
- package/dist/v2/components/chat/CopilotPopupView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts +55 -0
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts +28 -0
- package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotSidebar.vue.d.ts +48 -0
- package/dist/v2/components/chat/CopilotSidebar.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +62 -0
- package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +53 -0
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts +28 -0
- package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/audioRecorder.d.ts +11 -0
- package/dist/v2/components/chat/audioRecorder.d.ts.map +1 -0
- package/dist/v2/components/chat/index.d.ts +682 -0
- package/dist/v2/components/chat/index.d.ts.map +1 -0
- package/dist/v2/components/chat/last-user-message-context.d.ts +29 -0
- package/dist/v2/components/chat/last-user-message-context.d.ts.map +1 -0
- package/dist/v2/components/chat/normalize-auto-scroll.d.ts +3 -0
- package/dist/v2/components/chat/normalize-auto-scroll.d.ts.map +1 -0
- package/dist/v2/components/chat/types.d.ts +380 -0
- package/dist/v2/components/chat/types.d.ts.map +1 -0
- package/dist/v2/components/icons/index.d.ts +2 -0
- package/dist/v2/components/icons/index.d.ts.map +1 -0
- package/dist/v2/components/index.d.ts +8 -0
- package/dist/v2/components/index.d.ts.map +1 -0
- package/dist/v2/hooks/index.d.ts +24 -0
- package/dist/v2/hooks/index.d.ts.map +1 -0
- package/dist/v2/hooks/use-agent-context.d.ts +24 -0
- package/dist/v2/hooks/use-agent-context.d.ts.map +1 -0
- package/dist/v2/hooks/use-agent.d.ts +53 -0
- package/dist/v2/hooks/use-agent.d.ts.map +1 -0
- package/dist/v2/hooks/use-attachments.d.ts +21 -0
- package/dist/v2/hooks/use-attachments.d.ts.map +1 -0
- package/dist/v2/hooks/use-capabilities.d.ts +16 -0
- package/dist/v2/hooks/use-capabilities.d.ts.map +1 -0
- package/dist/v2/hooks/use-component.d.ts +13 -0
- package/dist/v2/hooks/use-component.d.ts.map +1 -0
- package/dist/v2/hooks/use-configure-suggestions.d.ts +24 -0
- package/dist/v2/hooks/use-configure-suggestions.d.ts.map +1 -0
- package/dist/v2/hooks/use-default-render-tool.d.ts +14 -0
- package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -0
- package/dist/v2/hooks/use-frontend-tool.d.ts +19 -0
- package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -0
- package/dist/v2/hooks/use-human-in-the-loop.d.ts +19 -0
- package/dist/v2/hooks/use-human-in-the-loop.d.ts.map +1 -0
- package/dist/v2/hooks/use-interrupt.d.ts +36 -0
- package/dist/v2/hooks/use-interrupt.d.ts.map +1 -0
- package/dist/v2/hooks/use-katex-styles.d.ts +22 -0
- package/dist/v2/hooks/use-katex-styles.d.ts.map +1 -0
- package/dist/v2/hooks/use-keyboard-height.d.ts +33 -0
- package/dist/v2/hooks/use-keyboard-height.d.ts.map +1 -0
- package/dist/v2/hooks/use-pin-to-send.d.ts +28 -0
- package/dist/v2/hooks/use-pin-to-send.d.ts.map +1 -0
- package/dist/v2/hooks/use-render-activity-message.d.ts +21 -0
- package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -0
- package/dist/v2/hooks/use-render-custom-messages.d.ts +27 -0
- package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -0
- package/dist/v2/hooks/use-render-tool.d.ts +36 -0
- package/dist/v2/hooks/use-render-tool.d.ts.map +1 -0
- package/dist/v2/hooks/use-suggestions.d.ts +26 -0
- package/dist/v2/hooks/use-suggestions.d.ts.map +1 -0
- package/dist/v2/hooks/use-threads.d.ts +42 -0
- package/dist/v2/hooks/use-threads.d.ts.map +1 -0
- package/dist/v2/index.cjs +2 -0
- package/dist/v2/index.cjs.map +1 -0
- package/dist/v2/index.d.cts +9 -0
- package/dist/v2/index.d.mts +9 -0
- package/dist/v2/index.d.ts +9 -0
- package/dist/v2/index.d.ts.map +1 -0
- package/dist/v2/index.mjs +75 -0
- package/dist/v2/index.mjs.map +1 -0
- package/dist/v2/lib/processPartialHtml.d.ts +3 -0
- package/dist/v2/lib/processPartialHtml.d.ts.map +1 -0
- package/dist/v2/lib/shallow-stable.d.ts +7 -0
- package/dist/v2/lib/shallow-stable.d.ts.map +1 -0
- package/dist/v2/lib/transcription-client.d.ts +19 -0
- package/dist/v2/lib/transcription-client.d.ts.map +1 -0
- package/dist/v2/lib/vue-core.d.ts +47 -0
- package/dist/v2/lib/vue-core.d.ts.map +1 -0
- package/dist/v2/providers/CopilotChatConfigurationProvider.types.d.ts +15 -0
- package/dist/v2/providers/CopilotChatConfigurationProvider.types.d.ts.map +1 -0
- package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts +17 -0
- package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts.map +1 -0
- package/dist/v2/providers/CopilotKitProvider.types.d.ts +61 -0
- package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -0
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts +37 -0
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -0
- package/dist/v2/providers/SandboxFunctionsContext.d.ts +4 -0
- package/dist/v2/providers/SandboxFunctionsContext.d.ts.map +1 -0
- package/dist/v2/providers/index.d.ts +13 -0
- package/dist/v2/providers/index.d.ts.map +1 -0
- package/dist/v2/providers/keys.d.ts +17 -0
- package/dist/v2/providers/keys.d.ts.map +1 -0
- package/dist/v2/providers/license-context.d.ts +7 -0
- package/dist/v2/providers/license-context.d.ts.map +1 -0
- package/dist/v2/providers/types.d.ts +38 -0
- package/dist/v2/providers/types.d.ts.map +1 -0
- package/dist/v2/providers/useCopilotChatConfiguration.d.ts +4 -0
- package/dist/v2/providers/useCopilotChatConfiguration.d.ts.map +1 -0
- package/dist/v2/providers/useCopilotKit.d.ts +2 -0
- package/dist/v2/providers/useCopilotKit.d.ts.map +1 -0
- package/dist/v2/providers/useLicenseContext.d.ts +14 -0
- package/dist/v2/providers/useLicenseContext.d.ts.map +1 -0
- package/dist/v2/types/a2ui.d.ts +5 -0
- package/dist/v2/types/a2ui.d.ts.map +1 -0
- package/dist/v2/types/defineToolCallRenderer.d.ts +15 -0
- package/dist/v2/types/defineToolCallRenderer.d.ts.map +1 -0
- package/dist/v2/types/frontend-tool.d.ts +6 -0
- package/dist/v2/types/frontend-tool.d.ts.map +1 -0
- package/dist/v2/types/human-in-the-loop.d.ts +29 -0
- package/dist/v2/types/human-in-the-loop.d.ts.map +1 -0
- package/dist/v2/types/index.d.ts +10 -0
- package/dist/v2/types/index.d.ts.map +1 -0
- package/dist/v2/types/interrupt.d.ts +14 -0
- package/dist/v2/types/interrupt.d.ts.map +1 -0
- package/dist/v2/types/sandbox-function.d.ts +8 -0
- package/dist/v2/types/sandbox-function.d.ts.map +1 -0
- package/dist/v2/types/vue-activity-message-renderer.d.ts +18 -0
- package/dist/v2/types/vue-activity-message-renderer.d.ts.map +1 -0
- package/dist/v2/types/vue-custom-message-renderer.d.ts +19 -0
- package/dist/v2/types/vue-custom-message-renderer.d.ts.map +1 -0
- package/dist/v2/types/vue-tool-call-renderer.d.ts +37 -0
- package/dist/v2/types/vue-tool-call-renderer.d.ts.map +1 -0
- package/env.d.ts +7 -0
- package/eslint.config.mjs +42 -0
- package/package.json +130 -0
- package/scripts/scope-preflight.mjs +100 -0
- package/src/components/copilot-provider/CopilotKit.vue +18 -0
- package/src/components/copilot-provider/index.ts +2 -0
- package/src/components/copilot-provider/types.ts +24 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/use-copilot-action.ts +168 -0
- package/src/hooks/use-copilot-readable.ts +75 -0
- package/src/hooks/use-frontend-tool.ts +76 -0
- package/src/index.ts +12 -0
- package/src/styles/globals.css +314 -0
- package/src/v2/__tests__/exports.test.ts +35 -0
- package/src/v2/__tests__/mocks/web-inspector.ts +5 -0
- package/src/v2/__tests__/setup.ts +141 -0
- package/src/v2/__tests__/utils/agents.ts +391 -0
- package/src/v2/__tests__/utils/mount.ts +83 -0
- package/src/v2/__tests__/utils/test-helpers.ts +712 -0
- package/src/v2/components/A2UIMessageRenderer.ts +125 -0
- package/src/v2/components/A2UISurfaceActivityRenderer.vue +186 -0
- package/src/v2/components/CopilotKitInspector.vue +42 -0
- package/src/v2/components/InlineFeatureWarning.vue +35 -0
- package/src/v2/components/LicenseWarningBanner.vue +196 -0
- package/src/v2/components/MCPAppsActivityRenderer.ts +778 -0
- package/src/v2/components/OpenGenerativeUIRenderer.ts +550 -0
- package/src/v2/components/__tests__/A2UIMessageRenderer.test.ts +271 -0
- package/src/v2/components/__tests__/CopilotKitInspector.test.ts +57 -0
- package/src/v2/components/__tests__/MCPAppsActivityRenderer.e2e.test.ts +851 -0
- package/src/v2/components/__tests__/MCPAppsActivityRenderer.test.ts +237 -0
- package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.ts +516 -0
- package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +295 -0
- package/src/v2/components/a2ui/A2UICatalogContext.ts +190 -0
- package/src/v2/components/a2ui/VueSurface.ts +144 -0
- package/src/v2/components/a2ui/adapter.ts +156 -0
- package/src/v2/components/a2ui/catalog.ts +858 -0
- package/src/v2/components/a2ui/index.ts +7 -0
- package/src/v2/components/a2ui/utils.ts +67 -0
- package/src/v2/components/a2ui.ts +30 -0
- package/src/v2/components/chat/CopilotChat.vue +777 -0
- package/src/v2/components/chat/CopilotChatAssistantMessage.vue +891 -0
- package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +411 -0
- package/src/v2/components/chat/CopilotChatAttachmentRenderer.vue +87 -0
- package/src/v2/components/chat/CopilotChatAudioRecorder.vue +269 -0
- package/src/v2/components/chat/CopilotChatInput.vue +1271 -0
- package/src/v2/components/chat/CopilotChatMessageView.vue +476 -0
- package/src/v2/components/chat/CopilotChatReasoningMessage.vue +247 -0
- package/src/v2/components/chat/CopilotChatSuggestionPill.vue +56 -0
- package/src/v2/components/chat/CopilotChatSuggestionView.vue +93 -0
- package/src/v2/components/chat/CopilotChatToggleButton.vue +145 -0
- package/src/v2/components/chat/CopilotChatToggleButtonCloseIcon.ts +17 -0
- package/src/v2/components/chat/CopilotChatToggleButtonOpenIcon.ts +18 -0
- package/src/v2/components/chat/CopilotChatToolCallsView.vue +161 -0
- package/src/v2/components/chat/CopilotChatUserMessage.vue +322 -0
- package/src/v2/components/chat/CopilotChatView.vue +740 -0
- package/src/v2/components/chat/CopilotModalHeader.vue +73 -0
- package/src/v2/components/chat/CopilotModalHeaderCloseButton.ts +38 -0
- package/src/v2/components/chat/CopilotModalHeaderTitle.ts +22 -0
- package/src/v2/components/chat/CopilotPopup.vue +182 -0
- package/src/v2/components/chat/CopilotPopupView.vue +168 -0
- package/src/v2/components/chat/CopilotPopupViewInternal.vue +453 -0
- package/src/v2/components/chat/CopilotPopupWelcomeScreen.vue +140 -0
- package/src/v2/components/chat/CopilotSidebar.vue +178 -0
- package/src/v2/components/chat/CopilotSidebarView.vue +172 -0
- package/src/v2/components/chat/CopilotSidebarViewInternal.vue +366 -0
- package/src/v2/components/chat/CopilotSidebarWelcomeScreen.vue +142 -0
- package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.ts +237 -0
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.ts +1240 -0
- package/src/v2/components/chat/__tests__/CopilotChat.licenseWarning.test.ts +138 -0
- package/src/v2/components/chat/__tests__/CopilotChat.onError.test.ts +85 -0
- package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.ts +141 -0
- package/src/v2/components/chat/__tests__/CopilotChat.test.ts +652 -0
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.ts +683 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.ts +768 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.ts +1108 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.thumbs.test.ts +87 -0
- package/src/v2/components/chat/__tests__/CopilotChatAttachmentQueue.test.ts +277 -0
- package/src/v2/components/chat/__tests__/CopilotChatAttachmentRenderer.test.ts +124 -0
- package/src/v2/components/chat/__tests__/CopilotChatCopyButton.clipboard.test.ts +230 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.bottomAnchored.test.ts +83 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.ts +1139 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +1051 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.ts +141 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.ts +494 -0
- package/src/v2/components/chat/__tests__/CopilotChatPropsRerender.e2e.test.ts +181 -0
- package/src/v2/components/chat/__tests__/CopilotChatReasoningMessage.test.ts +73 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionPill.test.ts +73 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.ts +674 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.test.ts +91 -0
- package/src/v2/components/chat/__tests__/CopilotChatToggleButton.test.ts +93 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolCallsView.test.ts +382 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.ts +1019 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.ts +516 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.ts +701 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.test.ts +337 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.ts +135 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.ts +278 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.ts +1082 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.ts +166 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.ts +1145 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.test.ts +374 -0
- package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.ts +636 -0
- package/src/v2/components/chat/__tests__/CopilotModalHeader.test.ts +112 -0
- package/src/v2/components/chat/__tests__/CopilotPopup.test.ts +58 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.ts +725 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.test.ts +112 -0
- package/src/v2/components/chat/__tests__/CopilotSidebar.test.ts +58 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.ts +603 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.test.ts +214 -0
- package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.ts +394 -0
- package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.ts +82 -0
- package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +39 -0
- package/src/v2/components/chat/audioRecorder.ts +15 -0
- package/src/v2/components/chat/index.ts +52 -0
- package/src/v2/components/chat/last-user-message-context.ts +39 -0
- package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
- package/src/v2/components/chat/types.ts +481 -0
- package/src/v2/components/icons/__tests__/icons.test.ts +86 -0
- package/src/v2/components/icons/index.ts +22 -0
- package/src/v2/components/index.ts +7 -0
- package/src/v2/hooks/__tests__/standard-schema-types.test.ts +149 -0
- package/src/v2/hooks/__tests__/standard-schema.test.ts +315 -0
- package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.ts +144 -0
- package/src/v2/hooks/__tests__/use-agent-context.test.ts +271 -0
- package/src/v2/hooks/__tests__/use-agent-error-state.test.ts +64 -0
- package/src/v2/hooks/__tests__/use-agent-stability.test.ts +268 -0
- package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.ts +433 -0
- package/src/v2/hooks/__tests__/use-agent-throttle.test.ts +747 -0
- package/src/v2/hooks/__tests__/use-agent.e2e.test.ts +187 -0
- package/src/v2/hooks/__tests__/use-agent.test.ts +126 -0
- package/src/v2/hooks/__tests__/use-attachments.test.ts +181 -0
- package/src/v2/hooks/__tests__/use-component.test.ts +145 -0
- package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.ts +527 -0
- package/src/v2/hooks/__tests__/use-configure-suggestions.test.ts +399 -0
- package/src/v2/hooks/__tests__/use-default-render-tool.test.ts +214 -0
- package/src/v2/hooks/__tests__/use-frontend-tool-available.test.ts +220 -0
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +2320 -0
- package/src/v2/hooks/__tests__/use-frontend-tool.test.ts +648 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +1379 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.test.ts +282 -0
- package/src/v2/hooks/__tests__/use-interrupt.test.ts +345 -0
- package/src/v2/hooks/__tests__/use-katex-styles.test.ts +69 -0
- package/src/v2/hooks/__tests__/use-keyboard-height.test.ts +199 -0
- package/src/v2/hooks/__tests__/use-pin-to-send.test.ts +363 -0
- package/src/v2/hooks/__tests__/use-render-tool.test.ts +329 -0
- package/src/v2/hooks/__tests__/use-suggestions.e2e.test.ts +397 -0
- package/src/v2/hooks/__tests__/use-suggestions.test.ts +198 -0
- package/src/v2/hooks/__tests__/use-threads.test.ts +1041 -0
- package/src/v2/hooks/__tests__/zod-regression.test.ts +339 -0
- package/src/v2/hooks/index.ts +29 -0
- package/src/v2/hooks/use-agent-context.ts +55 -0
- package/src/v2/hooks/use-agent.ts +345 -0
- package/src/v2/hooks/use-attachments.ts +261 -0
- package/src/v2/hooks/use-capabilities.ts +30 -0
- package/src/v2/hooks/use-component.ts +46 -0
- package/src/v2/hooks/use-configure-suggestions.ts +252 -0
- package/src/v2/hooks/use-default-render-tool.ts +130 -0
- package/src/v2/hooks/use-frontend-tool.ts +68 -0
- package/src/v2/hooks/use-human-in-the-loop.ts +90 -0
- package/src/v2/hooks/use-interrupt.ts +257 -0
- package/src/v2/hooks/use-katex-styles.ts +44 -0
- package/src/v2/hooks/use-keyboard-height.ts +87 -0
- package/src/v2/hooks/use-pin-to-send.ts +160 -0
- package/src/v2/hooks/use-render-activity-message.ts +92 -0
- package/src/v2/hooks/use-render-custom-messages.ts +129 -0
- package/src/v2/hooks/use-render-tool.ts +128 -0
- package/src/v2/hooks/use-suggestions.ts +98 -0
- package/src/v2/hooks/use-threads.ts +208 -0
- package/src/v2/index.ts +11 -0
- package/src/v2/lib/__tests__/processPartialHtml.test.ts +84 -0
- package/src/v2/lib/__tests__/transcription-client.test.ts +65 -0
- package/src/v2/lib/processPartialHtml.ts +21 -0
- package/src/v2/lib/shallow-stable.ts +54 -0
- package/src/v2/lib/transcription-client.ts +151 -0
- package/src/v2/lib/vue-core.ts +161 -0
- package/src/v2/providers/CopilotChatConfigurationProvider.types.ts +15 -0
- package/src/v2/providers/CopilotChatConfigurationProvider.vue +95 -0
- package/src/v2/providers/CopilotKitProvider.types.ts +66 -0
- package/src/v2/providers/CopilotKitProvider.vue +653 -0
- package/src/v2/providers/SandboxFunctionsContext.ts +11 -0
- package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.ts +309 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.debug.test.ts +295 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.license.test.ts +110 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.ts +67 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.ts +901 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.ts +141 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.ts +871 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +603 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.ts +104 -0
- package/src/v2/providers/index.ts +21 -0
- package/src/v2/providers/keys.ts +25 -0
- package/src/v2/providers/license-context.ts +16 -0
- package/src/v2/providers/types.ts +40 -0
- package/src/v2/providers/useCopilotChatConfiguration.ts +11 -0
- package/src/v2/providers/useCopilotKit.ts +11 -0
- package/src/v2/providers/useLicenseContext.ts +21 -0
- package/src/v2/types/__tests__/defineToolCallRenderer.test.ts +157 -0
- package/src/v2/types/a2ui.ts +5 -0
- package/src/v2/types/defineToolCallRenderer.ts +32 -0
- package/src/v2/types/frontend-tool.ts +8 -0
- package/src/v2/types/human-in-the-loop.ts +38 -0
- package/src/v2/types/index.ts +9 -0
- package/src/v2/types/interrupt.ts +15 -0
- package/src/v2/types/sandbox-function.ts +8 -0
- package/src/v2/types/vue-activity-message-renderer.ts +22 -0
- package/src/v2/types/vue-custom-message-renderer.ts +24 -0
- package/src/v2/types/vue-tool-call-renderer.ts +44 -0
- package/tsconfig.json +27 -0
- package/vite.config.ts +49 -0
- package/vitest.config.ts +23 -0
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
import { defineComponent, h, ref } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
render,
|
|
4
|
+
screen,
|
|
5
|
+
fireEvent,
|
|
6
|
+
waitFor,
|
|
7
|
+
within,
|
|
8
|
+
} from "@testing-library/vue";
|
|
9
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
10
|
+
import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue";
|
|
11
|
+
import CopilotChatInput from "../CopilotChatInput.vue";
|
|
12
|
+
|
|
13
|
+
const TEST_THREAD_ID = "test-thread";
|
|
14
|
+
const mockOnSubmitMessage = vi.fn();
|
|
15
|
+
|
|
16
|
+
const getSendButton = () =>
|
|
17
|
+
screen.getByTestId("copilot-chat-input-send") as HTMLButtonElement;
|
|
18
|
+
|
|
19
|
+
const getAddMenuButton = () =>
|
|
20
|
+
screen.getByTestId("copilot-chat-input-add") as HTMLButtonElement;
|
|
21
|
+
|
|
22
|
+
const rectFactory = (width: number) => () => ({
|
|
23
|
+
width,
|
|
24
|
+
height: 40,
|
|
25
|
+
top: 0,
|
|
26
|
+
left: 0,
|
|
27
|
+
right: width,
|
|
28
|
+
bottom: 40,
|
|
29
|
+
x: 0,
|
|
30
|
+
y: 0,
|
|
31
|
+
toJSON: () => ({}),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const mockLayoutMetrics = (
|
|
35
|
+
container: HTMLElement,
|
|
36
|
+
options?: { gridWidth?: number; addWidth?: number; actionsWidth?: number },
|
|
37
|
+
) => {
|
|
38
|
+
const grid = container.querySelector("div.cpk\\:grid") as HTMLElement | null;
|
|
39
|
+
if (!grid) {
|
|
40
|
+
throw new Error("Grid container not found in CopilotChatInput layout");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { gridWidth = 640, addWidth = 48, actionsWidth = 96 } = options ?? {};
|
|
44
|
+
|
|
45
|
+
Object.defineProperty(grid, "clientWidth", {
|
|
46
|
+
value: gridWidth,
|
|
47
|
+
configurable: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const addContainer = grid.children[0] as HTMLElement;
|
|
51
|
+
const actionsContainer = grid.children[2] as HTMLElement;
|
|
52
|
+
|
|
53
|
+
Object.defineProperty(addContainer, "getBoundingClientRect", {
|
|
54
|
+
value: rectFactory(addWidth),
|
|
55
|
+
configurable: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
Object.defineProperty(actionsContainer, "getBoundingClientRect", {
|
|
59
|
+
value: rectFactory(actionsWidth),
|
|
60
|
+
configurable: true,
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
Object.defineProperty(HTMLTextAreaElement.prototype, "scrollHeight", {
|
|
65
|
+
configurable: true,
|
|
66
|
+
get: function (this: HTMLTextAreaElement) {
|
|
67
|
+
const text = this.value || "";
|
|
68
|
+
const explicitLines = text.split("\n").length;
|
|
69
|
+
let wrappedLines = 0;
|
|
70
|
+
text.split("\n").forEach((line) => {
|
|
71
|
+
const lineWraps = Math.ceil(line.length / 50);
|
|
72
|
+
wrappedLines += Math.max(1, lineWraps);
|
|
73
|
+
});
|
|
74
|
+
const totalLines = Math.max(explicitLines, wrappedLines);
|
|
75
|
+
return totalLines * 24;
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function renderWithProvider(args?: {
|
|
80
|
+
props?: Record<string, unknown>;
|
|
81
|
+
listeners?: Record<string, (...value: unknown[]) => unknown>;
|
|
82
|
+
template?: string;
|
|
83
|
+
}) {
|
|
84
|
+
if (args?.template) {
|
|
85
|
+
const Host = defineComponent({
|
|
86
|
+
components: {
|
|
87
|
+
CopilotChatConfigurationProvider,
|
|
88
|
+
CopilotChatInput,
|
|
89
|
+
},
|
|
90
|
+
setup() {
|
|
91
|
+
return {
|
|
92
|
+
TEST_THREAD_ID,
|
|
93
|
+
inputProps: args.props ?? {},
|
|
94
|
+
listeners: args.listeners ?? {},
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
template: args.template,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return render(Host);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const Host = defineComponent({
|
|
104
|
+
setup() {
|
|
105
|
+
return {
|
|
106
|
+
inputProps: args?.props ?? {},
|
|
107
|
+
listeners: args?.listeners ?? {},
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
render() {
|
|
111
|
+
return h(
|
|
112
|
+
CopilotChatConfigurationProvider,
|
|
113
|
+
{ threadId: TEST_THREAD_ID },
|
|
114
|
+
{
|
|
115
|
+
default: () =>
|
|
116
|
+
h(CopilotChatInput, { ...this.inputProps, ...this.listeners }),
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return render(Host);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
mockOnSubmitMessage.mockClear();
|
|
127
|
+
vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockImplementation(
|
|
128
|
+
() =>
|
|
129
|
+
({
|
|
130
|
+
measureText: (text: string) => ({ width: text.length * 8 }),
|
|
131
|
+
font: "",
|
|
132
|
+
}) as unknown as CanvasRenderingContext2D,
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("CopilotChatInput", () => {
|
|
137
|
+
it("renders with default components and styling", () => {
|
|
138
|
+
const onUpdateModelValue = vi.fn();
|
|
139
|
+
renderWithProvider({
|
|
140
|
+
props: { modelValue: "" },
|
|
141
|
+
listeners: {
|
|
142
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
143
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const input = screen.getByPlaceholderText("Type a message...");
|
|
148
|
+
const sendButton = getSendButton();
|
|
149
|
+
|
|
150
|
+
expect(input).toBeDefined();
|
|
151
|
+
expect(sendButton).not.toBeNull();
|
|
152
|
+
expect(sendButton.disabled).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("calls onSubmitMessage with trimmed text when Enter is pressed", async () => {
|
|
156
|
+
const onUpdateModelValue = vi.fn();
|
|
157
|
+
renderWithProvider({
|
|
158
|
+
props: { modelValue: " hello world " },
|
|
159
|
+
listeners: {
|
|
160
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
161
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const input = screen.getByPlaceholderText("Type a message...");
|
|
166
|
+
await fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
|
167
|
+
|
|
168
|
+
expect(mockOnSubmitMessage).toHaveBeenCalledWith("hello world");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("calls onSubmitMessage when button is clicked", async () => {
|
|
172
|
+
const onUpdateModelValue = vi.fn();
|
|
173
|
+
renderWithProvider({
|
|
174
|
+
props: { modelValue: "test message" },
|
|
175
|
+
listeners: {
|
|
176
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
177
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const sendButton = getSendButton();
|
|
182
|
+
expect(sendButton).not.toBeNull();
|
|
183
|
+
await fireEvent.click(sendButton);
|
|
184
|
+
|
|
185
|
+
expect(mockOnSubmitMessage).toHaveBeenCalledWith("test message");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("manages text state internally when uncontrolled", async () => {
|
|
189
|
+
renderWithProvider({
|
|
190
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const input = screen.getByPlaceholderText("Type a message...");
|
|
194
|
+
const sendButton = getSendButton();
|
|
195
|
+
|
|
196
|
+
await fireEvent.input(input, { target: { value: "hello" } });
|
|
197
|
+
await fireEvent.click(sendButton);
|
|
198
|
+
|
|
199
|
+
expect(mockOnSubmitMessage).toHaveBeenCalledWith("hello");
|
|
200
|
+
expect((input as HTMLTextAreaElement).value).toBe("");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("does not send when Enter is pressed with Shift key", async () => {
|
|
204
|
+
const onUpdateModelValue = vi.fn();
|
|
205
|
+
renderWithProvider({
|
|
206
|
+
props: { modelValue: "test" },
|
|
207
|
+
listeners: {
|
|
208
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
209
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const input = screen.getByPlaceholderText("Type a message...");
|
|
214
|
+
await fireEvent.keyDown(input, { key: "Enter", shiftKey: true });
|
|
215
|
+
|
|
216
|
+
expect(mockOnSubmitMessage).not.toHaveBeenCalled();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("does not send empty or whitespace-only messages", async () => {
|
|
220
|
+
const onUpdateModelValue = vi.fn();
|
|
221
|
+
const first = renderWithProvider({
|
|
222
|
+
props: { modelValue: "" },
|
|
223
|
+
listeners: {
|
|
224
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
225
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
await fireEvent.click(getSendButton());
|
|
230
|
+
expect(mockOnSubmitMessage).not.toHaveBeenCalled();
|
|
231
|
+
first.unmount();
|
|
232
|
+
|
|
233
|
+
renderWithProvider({
|
|
234
|
+
props: { modelValue: " " },
|
|
235
|
+
listeners: {
|
|
236
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
237
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
await fireEvent.click(getSendButton());
|
|
241
|
+
expect(mockOnSubmitMessage).not.toHaveBeenCalled();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("keeps input value when no submit handler is provided", async () => {
|
|
245
|
+
renderWithProvider();
|
|
246
|
+
|
|
247
|
+
const input = screen.getByPlaceholderText("Type a message...");
|
|
248
|
+
await fireEvent.input(input, { target: { value: "draft" } });
|
|
249
|
+
await fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
|
250
|
+
|
|
251
|
+
expect((input as HTMLTextAreaElement).value).toBe("draft");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("enables button based on value prop", async () => {
|
|
255
|
+
const onUpdateModelValue = vi.fn();
|
|
256
|
+
const first = renderWithProvider({
|
|
257
|
+
props: { modelValue: "" },
|
|
258
|
+
listeners: {
|
|
259
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
260
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
expect(getSendButton().disabled).toBe(true);
|
|
264
|
+
first.unmount();
|
|
265
|
+
|
|
266
|
+
const second = renderWithProvider({
|
|
267
|
+
props: { modelValue: "hello" },
|
|
268
|
+
listeners: {
|
|
269
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
270
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
expect(getSendButton().disabled).toBe(false);
|
|
274
|
+
second.unmount();
|
|
275
|
+
|
|
276
|
+
renderWithProvider({
|
|
277
|
+
props: { modelValue: "" },
|
|
278
|
+
listeners: {
|
|
279
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
280
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
expect(getSendButton().disabled).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("accepts custom slot classes", () => {
|
|
287
|
+
const Host = defineComponent({
|
|
288
|
+
components: { CopilotChatConfigurationProvider, CopilotChatInput },
|
|
289
|
+
setup() {
|
|
290
|
+
return { TEST_THREAD_ID };
|
|
291
|
+
},
|
|
292
|
+
template: `
|
|
293
|
+
<CopilotChatConfigurationProvider :thread-id="TEST_THREAD_ID">
|
|
294
|
+
<CopilotChatInput
|
|
295
|
+
class="custom-container"
|
|
296
|
+
@submit-message="() => {}"
|
|
297
|
+
>
|
|
298
|
+
<template #text-area="{ value, onInput, onKeydown }">
|
|
299
|
+
<textarea
|
|
300
|
+
data-testid="custom-text-area"
|
|
301
|
+
class="custom-textarea"
|
|
302
|
+
:value="value"
|
|
303
|
+
@input="onInput"
|
|
304
|
+
@keydown="onKeydown"
|
|
305
|
+
/>
|
|
306
|
+
</template>
|
|
307
|
+
<template #send-button="{ onClick, disabled }">
|
|
308
|
+
<button
|
|
309
|
+
data-testid="custom-send-button"
|
|
310
|
+
class="custom-button"
|
|
311
|
+
:disabled="disabled"
|
|
312
|
+
@click="onClick"
|
|
313
|
+
>
|
|
314
|
+
Send
|
|
315
|
+
</button>
|
|
316
|
+
</template>
|
|
317
|
+
</CopilotChatInput>
|
|
318
|
+
</CopilotChatConfigurationProvider>
|
|
319
|
+
`,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const { container } = render(Host);
|
|
323
|
+
expect(container.querySelector(".custom-container")).toBeDefined();
|
|
324
|
+
expect(container.querySelector(".custom-textarea")).toBeDefined();
|
|
325
|
+
expect(container.querySelector(".custom-button")).toBeDefined();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("accepts custom components via slots", () => {
|
|
329
|
+
const Host = defineComponent({
|
|
330
|
+
components: { CopilotChatConfigurationProvider, CopilotChatInput },
|
|
331
|
+
setup() {
|
|
332
|
+
return { TEST_THREAD_ID };
|
|
333
|
+
},
|
|
334
|
+
template: `
|
|
335
|
+
<CopilotChatConfigurationProvider :thread-id="TEST_THREAD_ID">
|
|
336
|
+
<CopilotChatInput @submit-message="() => {}">
|
|
337
|
+
<template #send-button="{ onClick, disabled }">
|
|
338
|
+
<button data-testid="custom-button" :disabled="disabled" @click="onClick">
|
|
339
|
+
Send Now
|
|
340
|
+
</button>
|
|
341
|
+
</template>
|
|
342
|
+
</CopilotChatInput>
|
|
343
|
+
</CopilotChatConfigurationProvider>
|
|
344
|
+
`,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
render(Host);
|
|
348
|
+
const customButton = screen.getByTestId("custom-button");
|
|
349
|
+
expect(customButton).toBeDefined();
|
|
350
|
+
expect(customButton.textContent?.includes("Send Now")).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("supports custom layout via children render prop", () => {
|
|
354
|
+
const Host = defineComponent({
|
|
355
|
+
components: { CopilotChatConfigurationProvider, CopilotChatInput },
|
|
356
|
+
setup() {
|
|
357
|
+
return { TEST_THREAD_ID };
|
|
358
|
+
},
|
|
359
|
+
template: `
|
|
360
|
+
<CopilotChatConfigurationProvider :thread-id="TEST_THREAD_ID">
|
|
361
|
+
<CopilotChatInput @submit-message="() => {}">
|
|
362
|
+
<template #layout="{ onSendClick }">
|
|
363
|
+
<div data-testid="custom-layout">
|
|
364
|
+
Custom Layout:
|
|
365
|
+
<button data-testid="layout-send" @click="onSendClick">Send</button>
|
|
366
|
+
<textarea data-testid="layout-textarea" />
|
|
367
|
+
</div>
|
|
368
|
+
</template>
|
|
369
|
+
</CopilotChatInput>
|
|
370
|
+
</CopilotChatConfigurationProvider>
|
|
371
|
+
`,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
render(Host);
|
|
375
|
+
const customLayout = screen.getByTestId("custom-layout");
|
|
376
|
+
expect(customLayout).toBeDefined();
|
|
377
|
+
expect(customLayout.textContent?.includes("Custom Layout:")).toBe(true);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("updates its internal layout data attribute when content expands", async () => {
|
|
381
|
+
renderWithProvider({
|
|
382
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const textarea = screen.getByRole("textbox");
|
|
386
|
+
const grid = textarea.closest("[data-layout]") as HTMLElement | null;
|
|
387
|
+
expect(grid?.getAttribute("data-layout")).toBe("compact");
|
|
388
|
+
|
|
389
|
+
await fireEvent.input(textarea, {
|
|
390
|
+
target: { value: "line one\nline two" },
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
await waitFor(() => {
|
|
394
|
+
expect(grid?.getAttribute("data-layout")).toBe("expanded");
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("executes slash commands via keyboard selection", async () => {
|
|
399
|
+
const handleFirst = vi.fn();
|
|
400
|
+
const handleSecond = vi.fn();
|
|
401
|
+
|
|
402
|
+
renderWithProvider({
|
|
403
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
404
|
+
props: {
|
|
405
|
+
toolsMenu: [
|
|
406
|
+
{ label: "Say hi", action: handleFirst },
|
|
407
|
+
{ label: "Open docs", action: handleSecond },
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const textarea = screen.getByRole("textbox");
|
|
413
|
+
await fireEvent.input(textarea, { target: { value: "/" } });
|
|
414
|
+
|
|
415
|
+
const menu = await screen.findByTestId("copilot-slash-menu");
|
|
416
|
+
expect(menu).not.toBeNull();
|
|
417
|
+
expect(screen.queryByText("Say hi")).not.toBeNull();
|
|
418
|
+
expect(screen.queryByText("Open docs")).not.toBeNull();
|
|
419
|
+
|
|
420
|
+
await fireEvent.keyDown(textarea, {
|
|
421
|
+
key: "ArrowDown",
|
|
422
|
+
code: "ArrowDown",
|
|
423
|
+
keyCode: 40,
|
|
424
|
+
});
|
|
425
|
+
await fireEvent.keyDown(textarea, {
|
|
426
|
+
key: "Enter",
|
|
427
|
+
code: "Enter",
|
|
428
|
+
keyCode: 13,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
expect(handleSecond).toHaveBeenCalledTimes(1);
|
|
432
|
+
expect(handleFirst).not.toHaveBeenCalled();
|
|
433
|
+
|
|
434
|
+
await waitFor(() => {
|
|
435
|
+
expect(screen.queryByTestId("copilot-slash-menu")).toBeNull();
|
|
436
|
+
});
|
|
437
|
+
expect((textarea as HTMLTextAreaElement).value).toBe("");
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("prioritizes prefix matches when filtering slash commands", async () => {
|
|
441
|
+
renderWithProvider({
|
|
442
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
443
|
+
props: {
|
|
444
|
+
toolsMenu: [
|
|
445
|
+
{ label: "Reopen previous chat", action: vi.fn() },
|
|
446
|
+
{ label: "Open CopilotKit", action: vi.fn() },
|
|
447
|
+
{ label: "Help me operate", action: vi.fn() },
|
|
448
|
+
],
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const textarea = screen.getByRole("textbox");
|
|
453
|
+
await fireEvent.input(textarea, { target: { value: "/op" } });
|
|
454
|
+
|
|
455
|
+
const menu = await screen.findByTestId("copilot-slash-menu");
|
|
456
|
+
const options = within(menu).getAllByRole("option");
|
|
457
|
+
expect(options[0]?.textContent?.includes("Open CopilotKit")).toBe(true);
|
|
458
|
+
expect(options[0]?.getAttribute("aria-selected")).toBe("true");
|
|
459
|
+
|
|
460
|
+
await fireEvent.keyDown(textarea, {
|
|
461
|
+
key: "ArrowDown",
|
|
462
|
+
code: "ArrowDown",
|
|
463
|
+
keyCode: 40,
|
|
464
|
+
});
|
|
465
|
+
await waitFor(() => {
|
|
466
|
+
const updated = within(menu).getAllByRole("option");
|
|
467
|
+
expect(updated[1]?.getAttribute("aria-selected")).toBe("true");
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
await fireEvent.input(textarea, { target: { value: "/ope" } });
|
|
471
|
+
await waitFor(() => {
|
|
472
|
+
const updated = within(menu).getAllByRole("option");
|
|
473
|
+
expect(updated[0]?.getAttribute("aria-selected")).toBe("true");
|
|
474
|
+
expect(updated[0]?.textContent?.startsWith("Open CopilotKit")).toBe(true);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it("limits slash menu height when commands exceed five items", async () => {
|
|
479
|
+
const tools = Array.from({ length: 6 }, (_, index) => ({
|
|
480
|
+
label: `Command ${index + 1}`,
|
|
481
|
+
action: vi.fn(),
|
|
482
|
+
}));
|
|
483
|
+
|
|
484
|
+
renderWithProvider({
|
|
485
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
486
|
+
props: { toolsMenu: tools },
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const textarea = screen.getByRole("textbox");
|
|
490
|
+
await fireEvent.input(textarea, { target: { value: "/" } });
|
|
491
|
+
|
|
492
|
+
const menu = await screen.findByTestId("copilot-slash-menu");
|
|
493
|
+
await waitFor(() => {
|
|
494
|
+
expect((menu as HTMLElement).style.maxHeight).toBe("200px");
|
|
495
|
+
});
|
|
496
|
+
expect(within(menu).getAllByRole("option").length).toBe(6);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it("allows slash command actions to populate the input", async () => {
|
|
500
|
+
const greeting = "Hello Copilot! Could you help me with something?";
|
|
501
|
+
const label = "Say hi to CopilotKit";
|
|
502
|
+
|
|
503
|
+
renderWithProvider({
|
|
504
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
505
|
+
props: {
|
|
506
|
+
toolsMenu: [
|
|
507
|
+
{
|
|
508
|
+
label,
|
|
509
|
+
action: () => {
|
|
510
|
+
const textareaElement =
|
|
511
|
+
document.querySelector<HTMLTextAreaElement>("textarea");
|
|
512
|
+
if (!textareaElement) return;
|
|
513
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(
|
|
514
|
+
window.HTMLTextAreaElement.prototype,
|
|
515
|
+
"value",
|
|
516
|
+
)?.set;
|
|
517
|
+
nativeSetter?.call(textareaElement, greeting);
|
|
518
|
+
textareaElement.dispatchEvent(
|
|
519
|
+
new Event("input", { bubbles: true }),
|
|
520
|
+
);
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const textarea = screen.getByRole("textbox");
|
|
528
|
+
await fireEvent.input(textarea, { target: { value: "/" } });
|
|
529
|
+
|
|
530
|
+
const option = await screen.findByRole("option", { name: label });
|
|
531
|
+
await fireEvent.mouseDown(option);
|
|
532
|
+
|
|
533
|
+
await waitFor(() => {
|
|
534
|
+
expect((textarea as HTMLTextAreaElement).value).toBe(greeting);
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it("shows cancel and finish buttons in transcribe mode", () => {
|
|
539
|
+
renderWithProvider({
|
|
540
|
+
props: {
|
|
541
|
+
mode: "transcribe",
|
|
542
|
+
toolsMenu: [{ label: "Test Tool", action: () => {} }],
|
|
543
|
+
},
|
|
544
|
+
listeners: {
|
|
545
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
546
|
+
onStartTranscribe: () => {},
|
|
547
|
+
onCancelTranscribe: () => {},
|
|
548
|
+
onFinishTranscribe: () => {},
|
|
549
|
+
onAddFile: () => {},
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
expect(
|
|
554
|
+
screen.getByTestId("copilot-chat-input-cancel-transcribe"),
|
|
555
|
+
).toBeDefined();
|
|
556
|
+
expect(
|
|
557
|
+
screen.getByTestId("copilot-chat-input-finish-transcribe"),
|
|
558
|
+
).toBeDefined();
|
|
559
|
+
expect(
|
|
560
|
+
screen.queryByTestId("copilot-chat-input-start-transcribe"),
|
|
561
|
+
).toBeNull();
|
|
562
|
+
expect(screen.queryByTestId("copilot-chat-input-send")).toBeNull();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("disables add menu button in transcribe mode", () => {
|
|
566
|
+
renderWithProvider({
|
|
567
|
+
props: {
|
|
568
|
+
mode: "transcribe",
|
|
569
|
+
toolsMenu: [{ label: "Test Tool", action: () => {} }],
|
|
570
|
+
},
|
|
571
|
+
listeners: {
|
|
572
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
573
|
+
onStartTranscribe: () => {},
|
|
574
|
+
onCancelTranscribe: () => {},
|
|
575
|
+
onFinishTranscribe: () => {},
|
|
576
|
+
onAddFile: () => {},
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
expect(getAddMenuButton().disabled).toBe(true);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("shows recording indicator instead of textarea in transcribe mode", () => {
|
|
584
|
+
const { container } = renderWithProvider({
|
|
585
|
+
props: { mode: "transcribe" },
|
|
586
|
+
listeners: {
|
|
587
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
588
|
+
onStartTranscribe: () => {},
|
|
589
|
+
onCancelTranscribe: () => {},
|
|
590
|
+
onFinishTranscribe: () => {},
|
|
591
|
+
onAddFile: () => {},
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
expect(container.querySelector("canvas")).toBeDefined();
|
|
596
|
+
expect(screen.queryByRole("textbox")).toBeNull();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it("shows textarea in input mode", () => {
|
|
600
|
+
const { container } = renderWithProvider({
|
|
601
|
+
props: { mode: "input" },
|
|
602
|
+
listeners: {
|
|
603
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
604
|
+
onStartTranscribe: () => {},
|
|
605
|
+
onCancelTranscribe: () => {},
|
|
606
|
+
onFinishTranscribe: () => {},
|
|
607
|
+
onAddFile: () => {},
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
expect(screen.getByRole("textbox")).toBeDefined();
|
|
612
|
+
expect(container.querySelector("canvas")).toBeNull();
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it("positions the textarea next to the add menu button when single line", () => {
|
|
616
|
+
renderWithProvider({
|
|
617
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const textarea = screen.getByRole("textbox");
|
|
621
|
+
const layoutCell = textarea.parentElement as HTMLElement;
|
|
622
|
+
const gridContainer = layoutCell.parentElement as HTMLElement;
|
|
623
|
+
|
|
624
|
+
expect(layoutCell.className).toContain("col-start-2");
|
|
625
|
+
expect(layoutCell.className).not.toContain("col-span-3");
|
|
626
|
+
expect(gridContainer.className).toContain("items-center");
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it("toggles textarea padding based on multiline state", async () => {
|
|
630
|
+
const { container } = renderWithProvider({
|
|
631
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
632
|
+
});
|
|
633
|
+
mockLayoutMetrics(container);
|
|
634
|
+
|
|
635
|
+
const textarea = screen.getByRole("textbox");
|
|
636
|
+
expect(textarea.className).toContain("pr-5");
|
|
637
|
+
expect(textarea.className).not.toContain("px-5");
|
|
638
|
+
|
|
639
|
+
await fireEvent.input(textarea, {
|
|
640
|
+
target: {
|
|
641
|
+
value:
|
|
642
|
+
"a very long line that should wrap once it exceeds the width of the input",
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
await waitFor(() => {
|
|
647
|
+
expect(textarea.className).toContain("px-5");
|
|
648
|
+
expect(textarea.className).not.toContain("pr-5");
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it("returns to the compact layout when text no longer needs extra space", async () => {
|
|
653
|
+
const { container } = renderWithProvider({
|
|
654
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
655
|
+
});
|
|
656
|
+
mockLayoutMetrics(container);
|
|
657
|
+
|
|
658
|
+
const textarea = screen.getByRole("textbox");
|
|
659
|
+
const layoutCell = textarea.parentElement as HTMLElement;
|
|
660
|
+
|
|
661
|
+
await fireEvent.input(textarea, {
|
|
662
|
+
target: {
|
|
663
|
+
value:
|
|
664
|
+
"this is a very long line that should expand the layout before it wraps so we can see the stacked arrangement",
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
await waitFor(() => {
|
|
668
|
+
expect(layoutCell.className).toContain("col-span-3");
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
await fireEvent.input(textarea, { target: { value: "short" } });
|
|
672
|
+
await waitFor(() => {
|
|
673
|
+
expect(layoutCell.className).toContain("col-start-2");
|
|
674
|
+
expect(layoutCell.className).not.toContain("col-span-3");
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it("moves the textarea above the add menu button when multiple lines", async () => {
|
|
679
|
+
renderWithProvider({
|
|
680
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
const textarea = screen.getByRole("textbox");
|
|
684
|
+
await fireEvent.input(textarea, {
|
|
685
|
+
target: { value: "first line\nsecond line" },
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
await waitFor(() => {
|
|
689
|
+
const layoutCell = textarea.parentElement as HTMLElement;
|
|
690
|
+
expect(layoutCell.className).toContain("col-span-3");
|
|
691
|
+
expect(layoutCell.className).not.toContain("col-start-2");
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it("disables the add menu button when no menu items are provided", () => {
|
|
696
|
+
renderWithProvider({
|
|
697
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
const addButton = getAddMenuButton();
|
|
701
|
+
expect(addButton).not.toBeNull();
|
|
702
|
+
expect(addButton.disabled).toBe(true);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it("opens the add menu and runs onAddFile when the default item is clicked", async () => {
|
|
706
|
+
const handleAddFile = vi.fn();
|
|
707
|
+
const { container } = renderWithProvider({
|
|
708
|
+
listeners: {
|
|
709
|
+
onSubmitMessage: mockOnSubmitMessage,
|
|
710
|
+
onAddFile: handleAddFile,
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
mockLayoutMetrics(container);
|
|
714
|
+
|
|
715
|
+
const addButton = getAddMenuButton();
|
|
716
|
+
expect(addButton.disabled).toBe(false);
|
|
717
|
+
|
|
718
|
+
await fireEvent.click(addButton);
|
|
719
|
+
const menuItem = await screen.findByRole("menuitem", {
|
|
720
|
+
name: "Add photos or files",
|
|
721
|
+
});
|
|
722
|
+
await fireEvent.click(menuItem);
|
|
723
|
+
expect(handleAddFile).toHaveBeenCalledTimes(1);
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
it("renders additional custom menu items from the tools menu", async () => {
|
|
727
|
+
const handleCustom = vi.fn();
|
|
728
|
+
const { container } = renderWithProvider({
|
|
729
|
+
props: { toolsMenu: [{ label: "Custom action", action: handleCustom }] },
|
|
730
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
731
|
+
});
|
|
732
|
+
mockLayoutMetrics(container);
|
|
733
|
+
|
|
734
|
+
const addButton = getAddMenuButton();
|
|
735
|
+
await fireEvent.click(addButton);
|
|
736
|
+
|
|
737
|
+
const menuItem = await screen.findByRole("menuitem", {
|
|
738
|
+
name: "Custom action",
|
|
739
|
+
});
|
|
740
|
+
await fireEvent.click(menuItem);
|
|
741
|
+
expect(handleCustom).toHaveBeenCalledTimes(1);
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
describe("Controlled component behavior", () => {
|
|
745
|
+
it("displays the provided value prop", () => {
|
|
746
|
+
const onUpdateModelValue = vi.fn();
|
|
747
|
+
const onSubmit = vi.fn();
|
|
748
|
+
renderWithProvider({
|
|
749
|
+
props: { modelValue: "test value" },
|
|
750
|
+
listeners: {
|
|
751
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
752
|
+
onSubmitMessage: onSubmit,
|
|
753
|
+
},
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const input = screen.getByRole("textbox");
|
|
757
|
+
expect((input as HTMLTextAreaElement).value).toBe("test value");
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it("calls onChange when user types", async () => {
|
|
761
|
+
const onUpdateModelValue = vi.fn();
|
|
762
|
+
const onSubmit = vi.fn();
|
|
763
|
+
renderWithProvider({
|
|
764
|
+
props: { modelValue: "" },
|
|
765
|
+
listeners: {
|
|
766
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
767
|
+
onSubmitMessage: onSubmit,
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
const input = screen.getByRole("textbox");
|
|
772
|
+
await fireEvent.input(input, { target: { value: "new text" } });
|
|
773
|
+
expect(onUpdateModelValue).toHaveBeenCalledWith("new text");
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it("calls onSubmitMessage when form is submitted", async () => {
|
|
777
|
+
const onUpdateModelValue = vi.fn();
|
|
778
|
+
const onSubmit = vi.fn();
|
|
779
|
+
renderWithProvider({
|
|
780
|
+
props: { modelValue: "hello world" },
|
|
781
|
+
listeners: {
|
|
782
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
783
|
+
onSubmitMessage: onSubmit,
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
const input = screen.getByRole("textbox");
|
|
788
|
+
await fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
|
789
|
+
expect(onSubmit).toHaveBeenCalledWith("hello world");
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it("calls onSubmitMessage when send button is clicked", async () => {
|
|
793
|
+
const onUpdateModelValue = vi.fn();
|
|
794
|
+
const onSubmit = vi.fn();
|
|
795
|
+
renderWithProvider({
|
|
796
|
+
props: { modelValue: "test message" },
|
|
797
|
+
listeners: {
|
|
798
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
799
|
+
onSubmitMessage: onSubmit,
|
|
800
|
+
},
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
await fireEvent.click(getSendButton());
|
|
804
|
+
expect(onSubmit).toHaveBeenCalledWith("test message");
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it("trims whitespace when submitting", async () => {
|
|
808
|
+
const onUpdateModelValue = vi.fn();
|
|
809
|
+
const onSubmit = vi.fn();
|
|
810
|
+
renderWithProvider({
|
|
811
|
+
props: { modelValue: " hello world " },
|
|
812
|
+
listeners: {
|
|
813
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
814
|
+
onSubmitMessage: onSubmit,
|
|
815
|
+
},
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
const input = screen.getByRole("textbox");
|
|
819
|
+
await fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
|
820
|
+
expect(onSubmit).toHaveBeenCalledWith("hello world");
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
it("does not submit empty or whitespace-only messages", async () => {
|
|
824
|
+
const onUpdateModelValue = vi.fn();
|
|
825
|
+
const onSubmit = vi.fn();
|
|
826
|
+
renderWithProvider({
|
|
827
|
+
props: { modelValue: " " },
|
|
828
|
+
listeners: {
|
|
829
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
830
|
+
onSubmitMessage: onSubmit,
|
|
831
|
+
},
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
await fireEvent.click(getSendButton());
|
|
835
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
it("disables send button when onSubmitMessage is not provided", () => {
|
|
839
|
+
renderWithProvider({
|
|
840
|
+
props: { modelValue: "some text" },
|
|
841
|
+
listeners: { "onUpdate:modelValue": vi.fn() },
|
|
842
|
+
});
|
|
843
|
+
expect(getSendButton().disabled).toBe(true);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
it("disables send button when value is empty", () => {
|
|
847
|
+
renderWithProvider({
|
|
848
|
+
props: { modelValue: "" },
|
|
849
|
+
listeners: {
|
|
850
|
+
"onUpdate:modelValue": vi.fn(),
|
|
851
|
+
onSubmitMessage: vi.fn(),
|
|
852
|
+
},
|
|
853
|
+
});
|
|
854
|
+
expect(getSendButton().disabled).toBe(true);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("enables send button when value has content and onSubmitMessage is provided", () => {
|
|
858
|
+
renderWithProvider({
|
|
859
|
+
props: { modelValue: "hello" },
|
|
860
|
+
listeners: {
|
|
861
|
+
"onUpdate:modelValue": vi.fn(),
|
|
862
|
+
onSubmitMessage: vi.fn(),
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
expect(getSendButton().disabled).toBe(false);
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it("works as a fully controlled component", async () => {
|
|
869
|
+
const Host = defineComponent({
|
|
870
|
+
components: { CopilotChatConfigurationProvider, CopilotChatInput },
|
|
871
|
+
setup() {
|
|
872
|
+
const value = ref("initial");
|
|
873
|
+
return { TEST_THREAD_ID, value };
|
|
874
|
+
},
|
|
875
|
+
template: `
|
|
876
|
+
<CopilotChatConfigurationProvider :thread-id="TEST_THREAD_ID">
|
|
877
|
+
<CopilotChatInput
|
|
878
|
+
:model-value="value"
|
|
879
|
+
@update:model-value="(next) => value = next"
|
|
880
|
+
@submit-message="() => {}"
|
|
881
|
+
/>
|
|
882
|
+
<button data-testid="set-updated" @click="value = 'updated'">set</button>
|
|
883
|
+
</CopilotChatConfigurationProvider>
|
|
884
|
+
`,
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
render(Host);
|
|
888
|
+
const input = screen.getByRole("textbox");
|
|
889
|
+
expect((input as HTMLTextAreaElement).value).toBe("initial");
|
|
890
|
+
await fireEvent.click(screen.getByTestId("set-updated"));
|
|
891
|
+
expect((input as HTMLTextAreaElement).value).toBe("updated");
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
it("does not clear input after submission when controlled", async () => {
|
|
895
|
+
const onUpdateModelValue = vi.fn();
|
|
896
|
+
const onSubmit = vi.fn();
|
|
897
|
+
renderWithProvider({
|
|
898
|
+
props: { modelValue: "test message" },
|
|
899
|
+
listeners: {
|
|
900
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
901
|
+
onSubmitMessage: onSubmit,
|
|
902
|
+
},
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
const input = screen.getByRole("textbox");
|
|
906
|
+
await fireEvent.click(getSendButton());
|
|
907
|
+
|
|
908
|
+
expect((input as HTMLTextAreaElement).value).toBe("test message");
|
|
909
|
+
expect(onSubmit).toHaveBeenCalledWith("test message");
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
it("emits update:modelValue('') after button-click submit in controlled mode", async () => {
|
|
913
|
+
const onUpdateModelValue = vi.fn();
|
|
914
|
+
const onSubmit = vi.fn();
|
|
915
|
+
renderWithProvider({
|
|
916
|
+
props: { modelValue: "test message" },
|
|
917
|
+
listeners: {
|
|
918
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
919
|
+
onSubmitMessage: onSubmit,
|
|
920
|
+
},
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
await fireEvent.click(getSendButton());
|
|
924
|
+
|
|
925
|
+
expect(onSubmit).toHaveBeenCalledWith("test message");
|
|
926
|
+
expect(onUpdateModelValue).toHaveBeenCalledWith("");
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
it("emits update:modelValue('') after Enter submit in controlled mode", async () => {
|
|
930
|
+
const onUpdateModelValue = vi.fn();
|
|
931
|
+
const onSubmit = vi.fn();
|
|
932
|
+
renderWithProvider({
|
|
933
|
+
props: { modelValue: "hello world" },
|
|
934
|
+
listeners: {
|
|
935
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
936
|
+
onSubmitMessage: onSubmit,
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
const input = screen.getByRole("textbox");
|
|
941
|
+
await fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
|
942
|
+
|
|
943
|
+
expect(onSubmit).toHaveBeenCalledWith("hello world");
|
|
944
|
+
expect(onUpdateModelValue).toHaveBeenCalledWith("");
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
describe("IME composition parity", () => {
|
|
949
|
+
it("does not submit on Enter while composition is active", async () => {
|
|
950
|
+
const onUpdateModelValue = vi.fn();
|
|
951
|
+
const onSubmit = vi.fn();
|
|
952
|
+
renderWithProvider({
|
|
953
|
+
props: { modelValue: "こんに" },
|
|
954
|
+
listeners: {
|
|
955
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
956
|
+
onSubmitMessage: onSubmit,
|
|
957
|
+
},
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
const input = screen.getByRole("textbox");
|
|
961
|
+
await fireEvent.compositionStart(input);
|
|
962
|
+
await fireEvent.keyDown(input, {
|
|
963
|
+
key: "Enter",
|
|
964
|
+
shiftKey: false,
|
|
965
|
+
isComposing: true,
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
it("submits on Enter after compositionend", async () => {
|
|
972
|
+
const onUpdateModelValue = vi.fn();
|
|
973
|
+
const onSubmit = vi.fn();
|
|
974
|
+
renderWithProvider({
|
|
975
|
+
props: { modelValue: "こんにちは" },
|
|
976
|
+
listeners: {
|
|
977
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
978
|
+
onSubmitMessage: onSubmit,
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
const input = screen.getByRole("textbox");
|
|
983
|
+
await fireEvent.compositionStart(input);
|
|
984
|
+
await fireEvent.compositionEnd(input);
|
|
985
|
+
await fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
|
986
|
+
|
|
987
|
+
expect(onSubmit).toHaveBeenCalledWith("こんにちは");
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
it("does not submit when keydown reports isComposing: true", async () => {
|
|
991
|
+
const onUpdateModelValue = vi.fn();
|
|
992
|
+
const onSubmit = vi.fn();
|
|
993
|
+
renderWithProvider({
|
|
994
|
+
props: { modelValue: "abc" },
|
|
995
|
+
listeners: {
|
|
996
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
997
|
+
onSubmitMessage: onSubmit,
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
const input = screen.getByRole("textbox");
|
|
1002
|
+
await fireEvent.keyDown(input, {
|
|
1003
|
+
key: "Enter",
|
|
1004
|
+
shiftKey: false,
|
|
1005
|
+
isComposing: true,
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
it("does not submit when keydown reports keyCode 229", async () => {
|
|
1012
|
+
const onUpdateModelValue = vi.fn();
|
|
1013
|
+
const onSubmit = vi.fn();
|
|
1014
|
+
renderWithProvider({
|
|
1015
|
+
props: { modelValue: "abc" },
|
|
1016
|
+
listeners: {
|
|
1017
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
1018
|
+
onSubmitMessage: onSubmit,
|
|
1019
|
+
},
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
const input = screen.getByRole("textbox");
|
|
1023
|
+
await fireEvent.keyDown(input, {
|
|
1024
|
+
key: "Enter",
|
|
1025
|
+
shiftKey: false,
|
|
1026
|
+
keyCode: 229,
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
it("does not reset textarea value during active composition", async () => {
|
|
1033
|
+
const onUpdateModelValue = vi.fn();
|
|
1034
|
+
const onSubmit = vi.fn();
|
|
1035
|
+
renderWithProvider({
|
|
1036
|
+
props: { modelValue: "" },
|
|
1037
|
+
listeners: {
|
|
1038
|
+
"onUpdate:modelValue": onUpdateModelValue,
|
|
1039
|
+
onSubmitMessage: onSubmit,
|
|
1040
|
+
},
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
const input = screen.getByRole("textbox") as HTMLTextAreaElement;
|
|
1044
|
+
await fireEvent.compositionStart(input);
|
|
1045
|
+
input.value = "部分";
|
|
1046
|
+
await fireEvent.input(input, { target: { value: "部分" } });
|
|
1047
|
+
|
|
1048
|
+
expect(input.value).toBe("部分");
|
|
1049
|
+
});
|
|
1050
|
+
});
|
|
1051
|
+
});
|