@butlerw/vellum 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +411 -0
- package/__fixtures__/responses/code-generation.json +42 -0
- package/__fixtures__/responses/error-response.json +20 -0
- package/__fixtures__/responses/hello-world.json +32 -0
- package/dist/auth-6MCXESOH.js +26 -0
- package/dist/chunk-SECXJGWA.js +597 -0
- package/dist/index.js +34023 -0
- package/package.json +67 -0
- package/src/__tests__/commands.e2e.test.ts +728 -0
- package/src/__tests__/credentials.test.ts +713 -0
- package/src/__tests__/mode-e2e.test.ts +391 -0
- package/src/__tests__/tui-integration.test.tsx +1271 -0
- package/src/agents/__tests__/task-persistence.test.ts +235 -0
- package/src/agents/commands/delegate.ts +240 -0
- package/src/agents/commands/index.ts +10 -0
- package/src/agents/commands/resume.ts +335 -0
- package/src/agents/index.ts +29 -0
- package/src/agents/task-persistence.ts +272 -0
- package/src/agents/task-resumption.ts +242 -0
- package/src/app.tsx +4737 -0
- package/src/commands/__tests__/.gitkeep +1 -0
- package/src/commands/__tests__/agents.test.ts +606 -0
- package/src/commands/__tests__/auth.test.ts +626 -0
- package/src/commands/__tests__/autocomplete.test.ts +683 -0
- package/src/commands/__tests__/batch.test.ts +287 -0
- package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
- package/src/commands/__tests__/completion.test.ts +238 -0
- package/src/commands/__tests__/core.test.ts +363 -0
- package/src/commands/__tests__/executor.test.ts +496 -0
- package/src/commands/__tests__/exit-codes.test.ts +220 -0
- package/src/commands/__tests__/init.test.ts +243 -0
- package/src/commands/__tests__/language.test.ts +353 -0
- package/src/commands/__tests__/mode-cli.test.ts +667 -0
- package/src/commands/__tests__/model.test.ts +277 -0
- package/src/commands/__tests__/parser.test.ts +493 -0
- package/src/commands/__tests__/performance.bench.ts +380 -0
- package/src/commands/__tests__/registry.test.ts +534 -0
- package/src/commands/__tests__/resume.test.ts +449 -0
- package/src/commands/__tests__/security.test.ts +845 -0
- package/src/commands/__tests__/stream-json.test.ts +372 -0
- package/src/commands/__tests__/user-commands.test.ts +597 -0
- package/src/commands/adapters.ts +267 -0
- package/src/commands/agent.ts +395 -0
- package/src/commands/agents/generate.ts +506 -0
- package/src/commands/agents/index.ts +272 -0
- package/src/commands/agents/show.ts +271 -0
- package/src/commands/agents/validate.ts +387 -0
- package/src/commands/auth.ts +883 -0
- package/src/commands/autocomplete.ts +480 -0
- package/src/commands/batch/command.ts +388 -0
- package/src/commands/batch/executor.ts +361 -0
- package/src/commands/batch/index.ts +12 -0
- package/src/commands/commit.ts +235 -0
- package/src/commands/completion/index.ts +371 -0
- package/src/commands/condense.ts +191 -0
- package/src/commands/config.ts +344 -0
- package/src/commands/context-provider.ts +173 -0
- package/src/commands/copy.ts +329 -0
- package/src/commands/core/clear.ts +38 -0
- package/src/commands/core/exit.ts +43 -0
- package/src/commands/core/help.ts +354 -0
- package/src/commands/core/index.ts +15 -0
- package/src/commands/cost.ts +179 -0
- package/src/commands/credentials.tsx +618 -0
- package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
- package/src/commands/custom-agents/create.ts +377 -0
- package/src/commands/custom-agents/export.ts +135 -0
- package/src/commands/custom-agents/import.ts +199 -0
- package/src/commands/custom-agents/index.ts +372 -0
- package/src/commands/custom-agents/info.ts +318 -0
- package/src/commands/custom-agents/list.ts +267 -0
- package/src/commands/custom-agents/validate.ts +388 -0
- package/src/commands/diff-mode.ts +241 -0
- package/src/commands/env.ts +53 -0
- package/src/commands/executor.ts +579 -0
- package/src/commands/exit-codes.ts +202 -0
- package/src/commands/index.ts +701 -0
- package/src/commands/init/index.ts +15 -0
- package/src/commands/init/prompts.ts +366 -0
- package/src/commands/init/templates/commands-readme.md +80 -0
- package/src/commands/init/templates/example-command.md +79 -0
- package/src/commands/init/templates/example-skill.md +168 -0
- package/src/commands/init/templates/example-workflow.md +101 -0
- package/src/commands/init/templates/prompts-readme.md +52 -0
- package/src/commands/init/templates/rules-readme.md +63 -0
- package/src/commands/init/templates/skills-readme.md +83 -0
- package/src/commands/init/templates/workflows-readme.md +94 -0
- package/src/commands/init.ts +391 -0
- package/src/commands/install.ts +90 -0
- package/src/commands/language.ts +191 -0
- package/src/commands/loaders/.gitkeep +1 -0
- package/src/commands/lsp.ts +199 -0
- package/src/commands/markdown-commands.ts +253 -0
- package/src/commands/mcp.ts +588 -0
- package/src/commands/memory/export.ts +341 -0
- package/src/commands/memory/index.ts +148 -0
- package/src/commands/memory/list.ts +261 -0
- package/src/commands/memory/search.ts +346 -0
- package/src/commands/memory/utils.ts +15 -0
- package/src/commands/metrics.ts +75 -0
- package/src/commands/migrate/index.ts +16 -0
- package/src/commands/migrate/prompts.ts +477 -0
- package/src/commands/mode.ts +331 -0
- package/src/commands/model.ts +298 -0
- package/src/commands/onboard.ts +205 -0
- package/src/commands/open.ts +169 -0
- package/src/commands/output/stream-json.ts +373 -0
- package/src/commands/parser/chain-parser.ts +370 -0
- package/src/commands/parser/index.ts +29 -0
- package/src/commands/parser/pipe-parser.ts +480 -0
- package/src/commands/parser.ts +588 -0
- package/src/commands/persistence.ts +355 -0
- package/src/commands/progress.ts +18 -0
- package/src/commands/prompt/index.ts +17 -0
- package/src/commands/prompt/validate.ts +621 -0
- package/src/commands/prompt-priority.ts +401 -0
- package/src/commands/registry.ts +374 -0
- package/src/commands/sandbox/index.ts +131 -0
- package/src/commands/security/index.ts +21 -0
- package/src/commands/security/input-sanitizer.ts +168 -0
- package/src/commands/security/permission-checker.ts +456 -0
- package/src/commands/security/sensitive-data.ts +350 -0
- package/src/commands/session/delete.ts +38 -0
- package/src/commands/session/export.ts +39 -0
- package/src/commands/session/index.ts +26 -0
- package/src/commands/session/list.ts +26 -0
- package/src/commands/session/resume.ts +562 -0
- package/src/commands/session/search.ts +434 -0
- package/src/commands/session/show.ts +26 -0
- package/src/commands/settings.ts +368 -0
- package/src/commands/setup.ts +23 -0
- package/src/commands/shell/index.ts +16 -0
- package/src/commands/shell/setup.ts +422 -0
- package/src/commands/shell-init.ts +50 -0
- package/src/commands/shell-integration/index.ts +194 -0
- package/src/commands/skill.ts +1220 -0
- package/src/commands/spec.ts +558 -0
- package/src/commands/status.ts +246 -0
- package/src/commands/theme.ts +211 -0
- package/src/commands/think.ts +551 -0
- package/src/commands/trust.ts +211 -0
- package/src/commands/tutorial.ts +522 -0
- package/src/commands/types.ts +512 -0
- package/src/commands/update.ts +274 -0
- package/src/commands/usage.ts +213 -0
- package/src/commands/user-commands.ts +630 -0
- package/src/commands/utils.ts +142 -0
- package/src/commands/vim.ts +152 -0
- package/src/commands/workflow.ts +257 -0
- package/src/components/header.tsx +25 -0
- package/src/components/input.tsx +25 -0
- package/src/components/message-list.tsx +32 -0
- package/src/components/status-bar.tsx +23 -0
- package/src/index.tsx +614 -0
- package/src/onboarding/__tests__/tutorial.test.ts +740 -0
- package/src/onboarding/index.ts +69 -0
- package/src/onboarding/tips/index.ts +9 -0
- package/src/onboarding/tips/tip-engine.ts +459 -0
- package/src/onboarding/tutorial/index.ts +88 -0
- package/src/onboarding/tutorial/lessons/basics.ts +151 -0
- package/src/onboarding/tutorial/lessons/index.ts +151 -0
- package/src/onboarding/tutorial/lessons/modes.ts +230 -0
- package/src/onboarding/tutorial/lessons/tools.ts +172 -0
- package/src/onboarding/tutorial/progress-tracker.ts +350 -0
- package/src/onboarding/tutorial/storage.ts +249 -0
- package/src/onboarding/tutorial/tutorial-system.ts +462 -0
- package/src/onboarding/tutorial/types.ts +310 -0
- package/src/orchestrator-singleton.ts +129 -0
- package/src/shutdown.ts +33 -0
- package/src/test/e2e/assertions.ts +267 -0
- package/src/test/e2e/fixtures.ts +204 -0
- package/src/test/e2e/harness.ts +575 -0
- package/src/test/e2e/index.ts +57 -0
- package/src/test/e2e/types.ts +228 -0
- package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
- package/src/test/fixtures/fake-response-loader.ts +314 -0
- package/src/test/fixtures/index.ts +20 -0
- package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
- package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
- package/src/tui/__tests__/mode-components.test.tsx +395 -0
- package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
- package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
- package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
- package/src/tui/adapters/agent-adapter.ts +1008 -0
- package/src/tui/adapters/index.ts +48 -0
- package/src/tui/adapters/message-adapter.ts +315 -0
- package/src/tui/adapters/persistence-bridge.ts +331 -0
- package/src/tui/adapters/session-adapter.ts +419 -0
- package/src/tui/buffered-stdout.ts +223 -0
- package/src/tui/components/AgentProgress.tsx +424 -0
- package/src/tui/components/Banner/AsciiArt.ts +160 -0
- package/src/tui/components/Banner/Banner.tsx +355 -0
- package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
- package/src/tui/components/Banner/ShimmerText.tsx +193 -0
- package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
- package/src/tui/components/Banner/index.ts +61 -0
- package/src/tui/components/Banner/useShimmer.ts +241 -0
- package/src/tui/components/ChatView.tsx +11 -0
- package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
- package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
- package/src/tui/components/Checkpoint/index.ts +19 -0
- package/src/tui/components/CostDisplay.tsx +226 -0
- package/src/tui/components/InitErrorBanner.tsx +122 -0
- package/src/tui/components/Input/Autocomplete.tsx +603 -0
- package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
- package/src/tui/components/Input/HighlightedText.tsx +236 -0
- package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
- package/src/tui/components/Input/TextInput.tsx +1002 -0
- package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
- package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
- package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
- package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
- package/src/tui/components/Input/highlight.ts +362 -0
- package/src/tui/components/Input/index.ts +36 -0
- package/src/tui/components/Input/slash-command-utils.ts +135 -0
- package/src/tui/components/Layout.tsx +432 -0
- package/src/tui/components/McpPanel.tsx +137 -0
- package/src/tui/components/MemoryPanel.tsx +448 -0
- package/src/tui/components/Messages/CodeBlock.tsx +527 -0
- package/src/tui/components/Messages/DiffView.tsx +679 -0
- package/src/tui/components/Messages/ImageReference.tsx +89 -0
- package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
- package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
- package/src/tui/components/Messages/MessageBubble.tsx +270 -0
- package/src/tui/components/Messages/MessageList.tsx +1719 -0
- package/src/tui/components/Messages/StreamingText.tsx +216 -0
- package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
- package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
- package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
- package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
- package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
- package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
- package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
- package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
- package/src/tui/components/Messages/index.ts +25 -0
- package/src/tui/components/ModeIndicator.tsx +177 -0
- package/src/tui/components/ModeSelector.tsx +216 -0
- package/src/tui/components/ModelSelector.tsx +339 -0
- package/src/tui/components/OnboardingWizard.tsx +670 -0
- package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
- package/src/tui/components/RateLimitIndicator.tsx +82 -0
- package/src/tui/components/ScreenReaderLayout.tsx +295 -0
- package/src/tui/components/SettingsPanel.tsx +643 -0
- package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
- package/src/tui/components/Sidebar/index.ts +9 -0
- package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
- package/src/tui/components/Status/index.ts +12 -0
- package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
- package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
- package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
- package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
- package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
- package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
- package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
- package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
- package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
- package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
- package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
- package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
- package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
- package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
- package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
- package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
- package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
- package/src/tui/components/StatusBar/index.ts +60 -0
- package/src/tui/components/TipBanner.tsx +115 -0
- package/src/tui/components/TodoItem.tsx +208 -0
- package/src/tui/components/TodoPanel.tsx +455 -0
- package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
- package/src/tui/components/Tools/OptionSelector.tsx +160 -0
- package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
- package/src/tui/components/Tools/ToolParams.tsx +483 -0
- package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
- package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
- package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
- package/src/tui/components/Tools/index.ts +21 -0
- package/src/tui/components/TrustPrompt.tsx +279 -0
- package/src/tui/components/UpdateBanner.tsx +166 -0
- package/src/tui/components/VimModeIndicator.tsx +112 -0
- package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
- package/src/tui/components/backtrack/index.ts +13 -0
- package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
- package/src/tui/components/common/CostWarning.tsx +294 -0
- package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
- package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
- package/src/tui/components/common/ErrorBoundary.tsx +140 -0
- package/src/tui/components/common/GradientText.tsx +224 -0
- package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
- package/src/tui/components/common/HotkeyHints.tsx +70 -0
- package/src/tui/components/common/MaxSizedBox.tsx +354 -0
- package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
- package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
- package/src/tui/components/common/ScrollIndicator.tsx +160 -0
- package/src/tui/components/common/Spinner.tsx +342 -0
- package/src/tui/components/common/StreamingIndicator.tsx +316 -0
- package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
- package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
- package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
- package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
- package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
- package/src/tui/components/common/VirtualizedList/index.ts +30 -0
- package/src/tui/components/common/VirtualizedList/types.ts +107 -0
- package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
- package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
- package/src/tui/components/common/index.ts +110 -0
- package/src/tui/components/index.ts +79 -0
- package/src/tui/components/session/CheckpointPanel.tsx +323 -0
- package/src/tui/components/session/RollbackDialog.tsx +169 -0
- package/src/tui/components/session/SessionItem.tsx +136 -0
- package/src/tui/components/session/SessionListPanel.tsx +252 -0
- package/src/tui/components/session/SessionPicker.tsx +449 -0
- package/src/tui/components/session/SessionPreview.tsx +240 -0
- package/src/tui/components/session/__tests__/session.test.tsx +408 -0
- package/src/tui/components/session/index.ts +28 -0
- package/src/tui/components/session/types.ts +116 -0
- package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
- package/src/tui/components/theme/index.ts +227 -0
- package/src/tui/components/theme/tokens.ts +484 -0
- package/src/tui/config/defaults.ts +134 -0
- package/src/tui/config/index.ts +17 -0
- package/src/tui/context/AnimationContext.tsx +284 -0
- package/src/tui/context/AppContext.tsx +349 -0
- package/src/tui/context/BracketedPasteContext.tsx +372 -0
- package/src/tui/context/LspContext.tsx +192 -0
- package/src/tui/context/McpContext.tsx +325 -0
- package/src/tui/context/MessagesContext.tsx +870 -0
- package/src/tui/context/OverflowContext.tsx +213 -0
- package/src/tui/context/RateLimitContext.tsx +108 -0
- package/src/tui/context/ResilienceContext.tsx +275 -0
- package/src/tui/context/RootProvider.tsx +136 -0
- package/src/tui/context/ScrollContext.tsx +331 -0
- package/src/tui/context/ToolsContext.tsx +702 -0
- package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
- package/src/tui/context/index.ts +140 -0
- package/src/tui/enterprise-integration.ts +282 -0
- package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
- package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
- package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
- package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
- package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
- package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
- package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
- package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
- package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
- package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
- package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
- package/src/tui/hooks/index.ts +252 -0
- package/src/tui/hooks/useAgentLoop.ts +712 -0
- package/src/tui/hooks/useAlternateBuffer.ts +398 -0
- package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
- package/src/tui/hooks/useBacktrack.ts +443 -0
- package/src/tui/hooks/useBracketedPaste.ts +104 -0
- package/src/tui/hooks/useCollapsible.ts +240 -0
- package/src/tui/hooks/useCopyMode.ts +382 -0
- package/src/tui/hooks/useCostSummary.ts +75 -0
- package/src/tui/hooks/useDesktopNotification.ts +414 -0
- package/src/tui/hooks/useDiffMode.ts +44 -0
- package/src/tui/hooks/useFileChangeStats.ts +110 -0
- package/src/tui/hooks/useFileSuggestions.ts +284 -0
- package/src/tui/hooks/useFlickerDetector.ts +250 -0
- package/src/tui/hooks/useGitStatus.ts +200 -0
- package/src/tui/hooks/useHotkeys.ts +579 -0
- package/src/tui/hooks/useImagePaste.ts +114 -0
- package/src/tui/hooks/useInputHighlight.ts +145 -0
- package/src/tui/hooks/useInputHistory.ts +246 -0
- package/src/tui/hooks/useKeyboardScroll.ts +209 -0
- package/src/tui/hooks/useLineBuffer.ts +356 -0
- package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
- package/src/tui/hooks/useModeController.ts +167 -0
- package/src/tui/hooks/useModeShortcuts.ts +196 -0
- package/src/tui/hooks/usePermissionHandler.ts +146 -0
- package/src/tui/hooks/usePersistence.ts +480 -0
- package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
- package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
- package/src/tui/hooks/useProviderStatus.ts +270 -0
- package/src/tui/hooks/useRateLimitStatus.ts +90 -0
- package/src/tui/hooks/useScreenReader.ts +315 -0
- package/src/tui/hooks/useScrollController.ts +450 -0
- package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
- package/src/tui/hooks/useSidebarPanelData.ts +115 -0
- package/src/tui/hooks/useSmoothScroll.ts +202 -0
- package/src/tui/hooks/useSnapshots.ts +300 -0
- package/src/tui/hooks/useStateAndRef.ts +50 -0
- package/src/tui/hooks/useTerminalSize.ts +206 -0
- package/src/tui/hooks/useToolApprovalController.ts +91 -0
- package/src/tui/hooks/useVim.ts +334 -0
- package/src/tui/hooks/useWorkspace.ts +56 -0
- package/src/tui/i18n/__tests__/init.test.ts +278 -0
- package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
- package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
- package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
- package/src/tui/i18n/index.ts +72 -0
- package/src/tui/i18n/init.ts +131 -0
- package/src/tui/i18n/language-config.ts +106 -0
- package/src/tui/i18n/locale-detection.ts +173 -0
- package/src/tui/i18n/settings-integration.ts +557 -0
- package/src/tui/i18n/tui-namespace.ts +538 -0
- package/src/tui/i18n/types.ts +312 -0
- package/src/tui/index.ts +43 -0
- package/src/tui/lsp-integration.ts +409 -0
- package/src/tui/metrics-integration.ts +366 -0
- package/src/tui/plugins.ts +383 -0
- package/src/tui/resilience.ts +342 -0
- package/src/tui/sandbox-integration.ts +317 -0
- package/src/tui/services/clipboard.ts +348 -0
- package/src/tui/services/fuzzy-search.ts +441 -0
- package/src/tui/services/index.ts +72 -0
- package/src/tui/services/markdown-renderer.ts +565 -0
- package/src/tui/services/open-external.ts +247 -0
- package/src/tui/services/syntax-highlighter.ts +483 -0
- package/src/tui/slash-commands.ts +12 -0
- package/src/tui/theme/index.ts +15 -0
- package/src/tui/theme/provider.tsx +206 -0
- package/src/tui/tip-integration.ts +300 -0
- package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
- package/src/tui/types/ink-extended.ts +87 -0
- package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
- package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
- package/src/tui/utils/__tests__/text-width.test.ts +158 -0
- package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
- package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
- package/src/tui/utils/bracketedPaste.ts +107 -0
- package/src/tui/utils/cursor-manager.ts +131 -0
- package/src/tui/utils/detectTerminal.ts +596 -0
- package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
- package/src/tui/utils/heightEstimator.ts +198 -0
- package/src/tui/utils/index.ts +91 -0
- package/src/tui/utils/isNarrowWidth.ts +52 -0
- package/src/tui/utils/stdoutGuard.ts +90 -0
- package/src/tui/utils/synchronized-update.ts +70 -0
- package/src/tui/utils/text-width.ts +225 -0
- package/src/tui/utils/textSanitizer.ts +225 -0
- package/src/tui/utils/textUtils.ts +114 -0
- package/src/tui/utils/ui-sizing.ts +192 -0
- package/src/tui-blessed/app.ts +160 -0
- package/src/tui-blessed/index.ts +2 -0
- package/src/tui-blessed/neo-blessed.d.ts +6 -0
- package/src/tui-blessed/test.ts +21 -0
- package/src/tui-blessed/types.ts +14 -0
- package/src/utils/icons.ts +130 -0
- package/src/utils/index.ts +33 -0
- package/src/utils/resume-hint.ts +86 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autocomplete Component Tests (T011)
|
|
3
|
+
*
|
|
4
|
+
* Tests for the Autocomplete dropdown component.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { render } from "ink-testing-library";
|
|
8
|
+
import type React from "react";
|
|
9
|
+
import { act } from "react";
|
|
10
|
+
import { describe, expect, it, vi } from "vitest";
|
|
11
|
+
import { ThemeProvider } from "../../../theme/index.js";
|
|
12
|
+
import { Autocomplete } from "../Autocomplete.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Wrapper to provide theme context for tests
|
|
16
|
+
*/
|
|
17
|
+
function renderWithTheme(element: React.ReactElement) {
|
|
18
|
+
return render(<ThemeProvider>{element}</ThemeProvider>);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("Autocomplete", () => {
|
|
22
|
+
const defaultOptions = ["/help", "/history", "/hello", "/clear", "/quit"];
|
|
23
|
+
|
|
24
|
+
describe("Rendering", () => {
|
|
25
|
+
it("should render without crashing when visible", () => {
|
|
26
|
+
const onSelect = vi.fn();
|
|
27
|
+
const onCancel = vi.fn();
|
|
28
|
+
|
|
29
|
+
const { lastFrame } = renderWithTheme(
|
|
30
|
+
<Autocomplete
|
|
31
|
+
input="/he"
|
|
32
|
+
options={defaultOptions}
|
|
33
|
+
onSelect={onSelect}
|
|
34
|
+
onCancel={onCancel}
|
|
35
|
+
visible={true}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(lastFrame()).toBeDefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should render nothing when not visible", () => {
|
|
43
|
+
const onSelect = vi.fn();
|
|
44
|
+
const onCancel = vi.fn();
|
|
45
|
+
|
|
46
|
+
const { lastFrame } = renderWithTheme(
|
|
47
|
+
<Autocomplete
|
|
48
|
+
input="/he"
|
|
49
|
+
options={defaultOptions}
|
|
50
|
+
onSelect={onSelect}
|
|
51
|
+
onCancel={onCancel}
|
|
52
|
+
visible={false}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Should render empty or minimal when not visible
|
|
57
|
+
const frame = lastFrame() ?? "";
|
|
58
|
+
expect(frame.trim()).toBe("");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should render nothing when no options match", () => {
|
|
62
|
+
const onSelect = vi.fn();
|
|
63
|
+
const onCancel = vi.fn();
|
|
64
|
+
|
|
65
|
+
const { lastFrame } = renderWithTheme(
|
|
66
|
+
<Autocomplete
|
|
67
|
+
input="/xyz"
|
|
68
|
+
options={defaultOptions}
|
|
69
|
+
onSelect={onSelect}
|
|
70
|
+
onCancel={onCancel}
|
|
71
|
+
visible={true}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const frame = lastFrame() ?? "";
|
|
76
|
+
expect(frame.trim()).toBe("");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should render nothing when input is empty", () => {
|
|
80
|
+
const onSelect = vi.fn();
|
|
81
|
+
const onCancel = vi.fn();
|
|
82
|
+
|
|
83
|
+
const { lastFrame } = renderWithTheme(
|
|
84
|
+
<Autocomplete
|
|
85
|
+
input=""
|
|
86
|
+
options={defaultOptions}
|
|
87
|
+
onSelect={onSelect}
|
|
88
|
+
onCancel={onCancel}
|
|
89
|
+
visible={true}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const frame = lastFrame() ?? "";
|
|
94
|
+
// Empty input shows all options (useful for '/' picker)
|
|
95
|
+
expect(frame).toContain("help");
|
|
96
|
+
expect(frame).toContain("history");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("Filtering", () => {
|
|
101
|
+
it("should filter options by prefix (case-insensitive)", () => {
|
|
102
|
+
const onSelect = vi.fn();
|
|
103
|
+
const onCancel = vi.fn();
|
|
104
|
+
|
|
105
|
+
const { lastFrame } = renderWithTheme(
|
|
106
|
+
<Autocomplete
|
|
107
|
+
input="/he"
|
|
108
|
+
options={defaultOptions}
|
|
109
|
+
onSelect={onSelect}
|
|
110
|
+
onCancel={onCancel}
|
|
111
|
+
visible={true}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const frame = lastFrame() ?? "";
|
|
116
|
+
// Should show /help and /hello (prefix match)
|
|
117
|
+
expect(frame).toContain("help");
|
|
118
|
+
expect(frame).toContain("hello");
|
|
119
|
+
// Should NOT show /history (different prefix)
|
|
120
|
+
expect(frame).not.toContain("history");
|
|
121
|
+
// Should NOT show /clear or /quit
|
|
122
|
+
expect(frame).not.toContain("clear");
|
|
123
|
+
expect(frame).not.toContain("quit");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should be case-insensitive when filtering", () => {
|
|
127
|
+
const onSelect = vi.fn();
|
|
128
|
+
const onCancel = vi.fn();
|
|
129
|
+
const options = ["/Help", "/HELLO", "/History"];
|
|
130
|
+
|
|
131
|
+
const { lastFrame } = renderWithTheme(
|
|
132
|
+
<Autocomplete
|
|
133
|
+
input="/he"
|
|
134
|
+
options={options}
|
|
135
|
+
onSelect={onSelect}
|
|
136
|
+
onCancel={onCancel}
|
|
137
|
+
visible={true}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const frame = lastFrame() ?? "";
|
|
142
|
+
// Should match regardless of case
|
|
143
|
+
expect(frame).toContain("Help");
|
|
144
|
+
expect(frame).toContain("HELLO");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should show all matching options when input is a common prefix", () => {
|
|
148
|
+
const onSelect = vi.fn();
|
|
149
|
+
const onCancel = vi.fn();
|
|
150
|
+
|
|
151
|
+
const { lastFrame } = renderWithTheme(
|
|
152
|
+
<Autocomplete
|
|
153
|
+
input="/h"
|
|
154
|
+
options={defaultOptions}
|
|
155
|
+
onSelect={onSelect}
|
|
156
|
+
onCancel={onCancel}
|
|
157
|
+
visible={true}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const frame = lastFrame() ?? "";
|
|
162
|
+
// Should show /help, /history, and /hello
|
|
163
|
+
expect(frame).toContain("help");
|
|
164
|
+
expect(frame).toContain("history");
|
|
165
|
+
expect(frame).toContain("hello");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("maxVisible", () => {
|
|
170
|
+
it("should limit displayed options to maxVisible", () => {
|
|
171
|
+
const onSelect = vi.fn();
|
|
172
|
+
const onCancel = vi.fn();
|
|
173
|
+
// Use equal-length options so fuzzy scoring doesn't reorder them
|
|
174
|
+
const manyOptions = ["/opt1", "/opt2", "/opt3", "/opt4", "/opt5", "/opt6", "/opt7"];
|
|
175
|
+
|
|
176
|
+
const { lastFrame } = renderWithTheme(
|
|
177
|
+
<Autocomplete
|
|
178
|
+
input="/opt"
|
|
179
|
+
options={manyOptions}
|
|
180
|
+
onSelect={onSelect}
|
|
181
|
+
onCancel={onCancel}
|
|
182
|
+
visible={true}
|
|
183
|
+
maxVisible={3}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const frame = lastFrame() ?? "";
|
|
188
|
+
// Should show exactly 3 options and overflow indicator
|
|
189
|
+
const optionMatches = frame.match(/opt\d/g) ?? [];
|
|
190
|
+
expect(optionMatches.length).toBe(3);
|
|
191
|
+
// Should show "4 more" indicator
|
|
192
|
+
expect(frame).toContain("4 more");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should not show overflow indicator when all options fit", () => {
|
|
196
|
+
const onSelect = vi.fn();
|
|
197
|
+
const onCancel = vi.fn();
|
|
198
|
+
|
|
199
|
+
const { lastFrame } = renderWithTheme(
|
|
200
|
+
<Autocomplete
|
|
201
|
+
input="/he"
|
|
202
|
+
options={defaultOptions}
|
|
203
|
+
onSelect={onSelect}
|
|
204
|
+
onCancel={onCancel}
|
|
205
|
+
visible={true}
|
|
206
|
+
maxVisible={5}
|
|
207
|
+
/>
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const frame = lastFrame() ?? "";
|
|
211
|
+
// Only 2 options match (/help, /hello), so no overflow
|
|
212
|
+
expect(frame).not.toContain("more");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should use default maxVisible of 10", () => {
|
|
216
|
+
const onSelect = vi.fn();
|
|
217
|
+
const onCancel = vi.fn();
|
|
218
|
+
// Need more than 10 options to see overflow
|
|
219
|
+
const manyOptions = [
|
|
220
|
+
"/a1",
|
|
221
|
+
"/a2",
|
|
222
|
+
"/a3",
|
|
223
|
+
"/a4",
|
|
224
|
+
"/a5",
|
|
225
|
+
"/a6",
|
|
226
|
+
"/a7",
|
|
227
|
+
"/a8",
|
|
228
|
+
"/a9",
|
|
229
|
+
"/a10",
|
|
230
|
+
"/a11",
|
|
231
|
+
"/a12",
|
|
232
|
+
"/a13",
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
const { lastFrame } = renderWithTheme(
|
|
236
|
+
<Autocomplete
|
|
237
|
+
input="/a"
|
|
238
|
+
options={manyOptions}
|
|
239
|
+
onSelect={onSelect}
|
|
240
|
+
onCancel={onCancel}
|
|
241
|
+
visible={true}
|
|
242
|
+
/>
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const frame = lastFrame() ?? "";
|
|
246
|
+
// Default is 10, so should show "3 more" for 13 options
|
|
247
|
+
expect(frame).toContain("3 more");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("Selection indicator", () => {
|
|
252
|
+
it("should show selection indicator for first item by default", () => {
|
|
253
|
+
const onSelect = vi.fn();
|
|
254
|
+
const onCancel = vi.fn();
|
|
255
|
+
|
|
256
|
+
const { lastFrame } = renderWithTheme(
|
|
257
|
+
<Autocomplete
|
|
258
|
+
input="/he"
|
|
259
|
+
options={defaultOptions}
|
|
260
|
+
onSelect={onSelect}
|
|
261
|
+
onCancel={onCancel}
|
|
262
|
+
visible={true}
|
|
263
|
+
/>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const frame = lastFrame() ?? "";
|
|
267
|
+
// Should have the selection indicator (›)
|
|
268
|
+
expect(frame).toContain("›");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("keeps the selected option visible when navigating beyond maxVisible", async () => {
|
|
272
|
+
const onSelect = vi.fn();
|
|
273
|
+
const onCancel = vi.fn();
|
|
274
|
+
const manyOptions = ["/a1", "/a2", "/a3", "/a4", "/a5"];
|
|
275
|
+
|
|
276
|
+
const { lastFrame, stdin } = renderWithTheme(
|
|
277
|
+
<Autocomplete
|
|
278
|
+
input=""
|
|
279
|
+
options={manyOptions}
|
|
280
|
+
onSelect={onSelect}
|
|
281
|
+
onCancel={onCancel}
|
|
282
|
+
visible={true}
|
|
283
|
+
maxVisible={2}
|
|
284
|
+
/>
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Initially shows first window
|
|
288
|
+
expect(lastFrame()).toContain("a1");
|
|
289
|
+
expect(lastFrame()).toContain("a2");
|
|
290
|
+
expect(lastFrame()).toContain("›");
|
|
291
|
+
|
|
292
|
+
// Move selection down 3 times: should scroll window and keep highlight visible
|
|
293
|
+
await act(async () => {
|
|
294
|
+
stdin.write("\u001b[B");
|
|
295
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
296
|
+
});
|
|
297
|
+
await act(async () => {
|
|
298
|
+
stdin.write("\u001b[B");
|
|
299
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
300
|
+
});
|
|
301
|
+
await act(async () => {
|
|
302
|
+
stdin.write("\u001b[B");
|
|
303
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const frame = lastFrame() ?? "";
|
|
307
|
+
|
|
308
|
+
// With maxVisible=2 and selection index=3, the window should include a3 and a4
|
|
309
|
+
expect(frame).toContain("a3");
|
|
310
|
+
expect(frame).toContain("a4");
|
|
311
|
+
// Ensure highlight indicator never disappears
|
|
312
|
+
expect(frame).toContain("›");
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("Props interface", () => {
|
|
317
|
+
it("should accept all required props", () => {
|
|
318
|
+
const onSelect = vi.fn();
|
|
319
|
+
const onCancel = vi.fn();
|
|
320
|
+
|
|
321
|
+
const { lastFrame } = renderWithTheme(
|
|
322
|
+
<Autocomplete
|
|
323
|
+
input="/test"
|
|
324
|
+
options={["/test1", "/test2"]}
|
|
325
|
+
onSelect={onSelect}
|
|
326
|
+
onCancel={onCancel}
|
|
327
|
+
/>
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
expect(lastFrame()).toBeTruthy();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("should accept all optional props", () => {
|
|
334
|
+
const onSelect = vi.fn();
|
|
335
|
+
const onCancel = vi.fn();
|
|
336
|
+
|
|
337
|
+
const { lastFrame } = renderWithTheme(
|
|
338
|
+
<Autocomplete
|
|
339
|
+
input="/test"
|
|
340
|
+
options={["/test1", "/test2"]}
|
|
341
|
+
onSelect={onSelect}
|
|
342
|
+
onCancel={onCancel}
|
|
343
|
+
visible={true}
|
|
344
|
+
maxVisible={10}
|
|
345
|
+
/>
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
expect(lastFrame()).toBeTruthy();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe("Border styling", () => {
|
|
353
|
+
it("should render with a bordered box", () => {
|
|
354
|
+
const onSelect = vi.fn();
|
|
355
|
+
const onCancel = vi.fn();
|
|
356
|
+
|
|
357
|
+
const { lastFrame } = renderWithTheme(
|
|
358
|
+
<Autocomplete
|
|
359
|
+
input="/he"
|
|
360
|
+
options={defaultOptions}
|
|
361
|
+
onSelect={onSelect}
|
|
362
|
+
onCancel={onCancel}
|
|
363
|
+
visible={true}
|
|
364
|
+
/>
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const frame = lastFrame() ?? "";
|
|
368
|
+
// Ink's Box with borderStyle="single" uses unicode box characters
|
|
369
|
+
// Check that content is displayed (border characters may vary)
|
|
370
|
+
expect(frame.length).toBeGreaterThan(0);
|
|
371
|
+
expect(frame).toContain("help");
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TextInput Component Tests (T009, T013)
|
|
3
|
+
*
|
|
4
|
+
* Tests for the TextInput component with multiline support.
|
|
5
|
+
*
|
|
6
|
+
* Note: ink-testing-library's stdin.write() does not synchronously trigger
|
|
7
|
+
* useInput hooks. Tests focus on:
|
|
8
|
+
* - Rendering behavior (verifiable via lastFrame())
|
|
9
|
+
* - Props contract verification
|
|
10
|
+
* - Visual state assertions
|
|
11
|
+
*
|
|
12
|
+
* Behavioral tests (onChange, onSubmit) are validated via:
|
|
13
|
+
* - Integration tests at the CLI level
|
|
14
|
+
* - Manual testing documentation
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { render } from "ink-testing-library";
|
|
18
|
+
import type React from "react";
|
|
19
|
+
import { describe, expect, it, vi } from "vitest";
|
|
20
|
+
import { ThemeProvider } from "../../../theme/index.js";
|
|
21
|
+
import { TextInput } from "../TextInput.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wrapper to provide theme context for tests
|
|
25
|
+
*/
|
|
26
|
+
function renderWithTheme(element: React.ReactElement) {
|
|
27
|
+
return render(<ThemeProvider>{element}</ThemeProvider>);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("TextInput", () => {
|
|
31
|
+
describe("Rendering", () => {
|
|
32
|
+
it("should render without crashing", () => {
|
|
33
|
+
const onChange = vi.fn();
|
|
34
|
+
const { lastFrame } = renderWithTheme(<TextInput value="" onChange={onChange} />);
|
|
35
|
+
// Empty value with focus shows cursor, which renders as a space with inverse
|
|
36
|
+
expect(lastFrame()).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should render placeholder when value is empty", () => {
|
|
40
|
+
const onChange = vi.fn();
|
|
41
|
+
const { lastFrame } = renderWithTheme(
|
|
42
|
+
<TextInput value="" onChange={onChange} placeholder="Type here..." focused={false} />
|
|
43
|
+
);
|
|
44
|
+
expect(lastFrame()).toContain("Type here...");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should render value when provided", () => {
|
|
48
|
+
const onChange = vi.fn();
|
|
49
|
+
const { lastFrame } = renderWithTheme(
|
|
50
|
+
<TextInput value="Hello World" onChange={onChange} focused={false} />
|
|
51
|
+
);
|
|
52
|
+
expect(lastFrame()).toContain("Hello World");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should show cursor indicator when focused", () => {
|
|
56
|
+
const onChange = vi.fn();
|
|
57
|
+
const { lastFrame } = renderWithTheme(
|
|
58
|
+
<TextInput value="Test" onChange={onChange} focused={true} />
|
|
59
|
+
);
|
|
60
|
+
// The cursor should be at the end, shown as inverse text
|
|
61
|
+
const frame = lastFrame() ?? "";
|
|
62
|
+
expect(frame).toContain("Test");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("Multiline mode", () => {
|
|
67
|
+
it("should render multiple lines", () => {
|
|
68
|
+
const onChange = vi.fn();
|
|
69
|
+
const { lastFrame } = renderWithTheme(
|
|
70
|
+
<TextInput value={"Line 1\nLine 2\nLine 3"} onChange={onChange} multiline focused={false} />
|
|
71
|
+
);
|
|
72
|
+
const frame = lastFrame() ?? "";
|
|
73
|
+
expect(frame).toContain("Line 1");
|
|
74
|
+
expect(frame).toContain("Line 2");
|
|
75
|
+
expect(frame).toContain("Line 3");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should render cursor on correct line in multiline mode", () => {
|
|
79
|
+
const onChange = vi.fn();
|
|
80
|
+
const { lastFrame } = renderWithTheme(
|
|
81
|
+
<TextInput value={"First\nSecond"} onChange={onChange} multiline focused={true} />
|
|
82
|
+
);
|
|
83
|
+
const frame = lastFrame() ?? "";
|
|
84
|
+
expect(frame).toContain("First");
|
|
85
|
+
expect(frame).toContain("Second");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("Props contract", () => {
|
|
90
|
+
it("should accept all required props", () => {
|
|
91
|
+
const onChange = vi.fn();
|
|
92
|
+
const onSubmit = vi.fn();
|
|
93
|
+
|
|
94
|
+
const { lastFrame } = renderWithTheme(
|
|
95
|
+
<TextInput
|
|
96
|
+
value="test"
|
|
97
|
+
onChange={onChange}
|
|
98
|
+
onSubmit={onSubmit}
|
|
99
|
+
placeholder="placeholder"
|
|
100
|
+
multiline={true}
|
|
101
|
+
disabled={false}
|
|
102
|
+
maxLength={100}
|
|
103
|
+
focused={true}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(lastFrame()).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should render with disabled state", () => {
|
|
111
|
+
const onChange = vi.fn();
|
|
112
|
+
const { lastFrame } = renderWithTheme(
|
|
113
|
+
<TextInput value="disabled text" onChange={onChange} disabled={true} />
|
|
114
|
+
);
|
|
115
|
+
const frame = lastFrame() ?? "";
|
|
116
|
+
expect(frame).toContain("disabled text");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should respect maxLength visually (value can be at max)", () => {
|
|
120
|
+
const onChange = vi.fn();
|
|
121
|
+
const { lastFrame } = renderWithTheme(
|
|
122
|
+
<TextInput value="12345" onChange={onChange} maxLength={5} focused={false} />
|
|
123
|
+
);
|
|
124
|
+
expect(lastFrame()).toContain("12345");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("onChange callback contract", () => {
|
|
129
|
+
it("should accept onChange prop as function", () => {
|
|
130
|
+
const onChange = vi.fn();
|
|
131
|
+
// This verifies the prop type contract
|
|
132
|
+
expect(() => {
|
|
133
|
+
renderWithTheme(<TextInput value="" onChange={onChange} />);
|
|
134
|
+
}).not.toThrow();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should not throw when onChange is called with new value", () => {
|
|
138
|
+
// Simulates controlled component pattern
|
|
139
|
+
let currentValue = "";
|
|
140
|
+
const onChange = vi.fn((newValue: string) => {
|
|
141
|
+
currentValue = newValue;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const { rerender } = renderWithTheme(<TextInput value={currentValue} onChange={onChange} />);
|
|
145
|
+
|
|
146
|
+
// Simulate external value change (as would happen from onChange callback)
|
|
147
|
+
currentValue = "typed text";
|
|
148
|
+
expect(() => {
|
|
149
|
+
rerender(
|
|
150
|
+
<ThemeProvider>
|
|
151
|
+
<TextInput value={currentValue} onChange={onChange} />
|
|
152
|
+
</ThemeProvider>
|
|
153
|
+
);
|
|
154
|
+
}).not.toThrow();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("onSubmit callback contract", () => {
|
|
159
|
+
it("should accept onSubmit prop as function", () => {
|
|
160
|
+
const onChange = vi.fn();
|
|
161
|
+
const onSubmit = vi.fn();
|
|
162
|
+
expect(() => {
|
|
163
|
+
renderWithTheme(<TextInput value="" onChange={onChange} onSubmit={onSubmit} />);
|
|
164
|
+
}).not.toThrow();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should work without onSubmit (optional prop)", () => {
|
|
168
|
+
const onChange = vi.fn();
|
|
169
|
+
expect(() => {
|
|
170
|
+
renderWithTheme(<TextInput value="" onChange={onChange} />);
|
|
171
|
+
}).not.toThrow();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("Disabled state contract", () => {
|
|
176
|
+
it("should accept disabled prop", () => {
|
|
177
|
+
const onChange = vi.fn();
|
|
178
|
+
expect(() => {
|
|
179
|
+
renderWithTheme(<TextInput value="test" onChange={onChange} disabled={true} />);
|
|
180
|
+
}).not.toThrow();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should render differently when disabled vs enabled", () => {
|
|
184
|
+
const onChange = vi.fn();
|
|
185
|
+
|
|
186
|
+
const { lastFrame: enabledFrame } = renderWithTheme(
|
|
187
|
+
<TextInput value="test" onChange={onChange} disabled={false} focused={true} />
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const { lastFrame: disabledFrame } = renderWithTheme(
|
|
191
|
+
<TextInput value="test" onChange={onChange} disabled={true} focused={true} />
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Both should render, component handles disabled state
|
|
195
|
+
expect(enabledFrame()).toBeTruthy();
|
|
196
|
+
expect(disabledFrame()).toBeTruthy();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("Multiline mode contract", () => {
|
|
201
|
+
it("should accept multiline prop", () => {
|
|
202
|
+
const onChange = vi.fn();
|
|
203
|
+
expect(() => {
|
|
204
|
+
renderWithTheme(<TextInput value="test" onChange={onChange} multiline={true} />);
|
|
205
|
+
}).not.toThrow();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should handle newlines in value when multiline", () => {
|
|
209
|
+
const onChange = vi.fn();
|
|
210
|
+
const { lastFrame } = renderWithTheme(
|
|
211
|
+
<TextInput
|
|
212
|
+
value="line1\nline2\nline3"
|
|
213
|
+
onChange={onChange}
|
|
214
|
+
multiline={true}
|
|
215
|
+
focused={false}
|
|
216
|
+
/>
|
|
217
|
+
);
|
|
218
|
+
const frame = lastFrame() ?? "";
|
|
219
|
+
// All lines should be present
|
|
220
|
+
expect(frame).toContain("line1");
|
|
221
|
+
expect(frame).toContain("line2");
|
|
222
|
+
expect(frame).toContain("line3");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("Focus state", () => {
|
|
227
|
+
it("should accept focused prop", () => {
|
|
228
|
+
const onChange = vi.fn();
|
|
229
|
+
expect(() => {
|
|
230
|
+
renderWithTheme(<TextInput value="test" onChange={onChange} focused={true} />);
|
|
231
|
+
}).not.toThrow();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should default focused to true", () => {
|
|
235
|
+
const onChange = vi.fn();
|
|
236
|
+
// Default prop behavior - should render with cursor
|
|
237
|
+
const { lastFrame } = renderWithTheme(<TextInput value="test" onChange={onChange} />);
|
|
238
|
+
expect(lastFrame()).toBeTruthy();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|