@vybestack/llxprt-code 0.1.20-nightly.250818.90f427f5 → 0.1.21
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/README.md +31 -0
- package/dist/package.json +3 -3
- package/dist/src/config/auth.test.d.ts +6 -0
- package/dist/src/config/auth.test.js +57 -0
- package/dist/src/config/auth.test.js.map +1 -0
- package/dist/src/config/config.d.ts +1 -1
- package/dist/src/config/config.js +27 -14
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/keyBindings.js +2 -2
- package/dist/src/config/keyBindings.test.d.ts +6 -0
- package/dist/src/config/keyBindings.test.js +51 -0
- package/dist/src/config/keyBindings.test.js.map +1 -0
- package/dist/src/config/logging/loggingConfig.test.d.ts +6 -0
- package/dist/src/config/logging/loggingConfig.test.js +363 -0
- package/dist/src/config/logging/loggingConfig.test.js.map +1 -0
- package/dist/src/config/settingsSchema.d.ts +0 -9
- package/dist/src/config/settingsSchema.js +0 -9
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/config/settingsSchema.test.d.ts +6 -0
- package/dist/src/config/settingsSchema.test.js +195 -0
- package/dist/src/config/settingsSchema.test.js.map +1 -0
- package/dist/src/config/trustedFolders.test.d.ts +6 -0
- package/dist/src/config/trustedFolders.test.js +156 -0
- package/dist/src/config/trustedFolders.test.js.map +1 -0
- package/dist/src/gemini.js +14 -4
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/gemini.test.d.ts +6 -0
- package/dist/src/gemini.test.js +199 -0
- package/dist/src/gemini.test.js.map +1 -0
- package/dist/src/generated/git-commit.d.ts +1 -1
- package/dist/src/generated/git-commit.js +1 -1
- package/dist/src/integration-tests/base-url-behavior.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/base-url-behavior.integration.test.js +492 -0
- package/dist/src/integration-tests/base-url-behavior.integration.test.js.map +1 -0
- package/dist/src/integration-tests/cli-args.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/cli-args.integration.test.js +398 -0
- package/dist/src/integration-tests/cli-args.integration.test.js.map +1 -0
- package/dist/src/integration-tests/compression-settings-apply.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/compression-settings-apply.integration.test.js +436 -0
- package/dist/src/integration-tests/compression-settings-apply.integration.test.js.map +1 -0
- package/dist/src/integration-tests/ephemeral-settings.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/ephemeral-settings.integration.test.js +290 -0
- package/dist/src/integration-tests/ephemeral-settings.integration.test.js.map +1 -0
- package/dist/src/integration-tests/model-params-isolation.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/model-params-isolation.integration.test.js +564 -0
- package/dist/src/integration-tests/model-params-isolation.integration.test.js.map +1 -0
- package/dist/src/integration-tests/modelParams.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/modelParams.integration.test.js +784 -0
- package/dist/src/integration-tests/modelParams.integration.test.js.map +1 -0
- package/dist/src/integration-tests/profile-keyfile.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/profile-keyfile.integration.test.js +429 -0
- package/dist/src/integration-tests/profile-keyfile.integration.test.js.map +1 -0
- package/dist/src/integration-tests/profile-system.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/profile-system.integration.test.js +364 -0
- package/dist/src/integration-tests/profile-system.integration.test.js.map +1 -0
- package/dist/src/integration-tests/provider-switching.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/provider-switching.integration.test.js +207 -0
- package/dist/src/integration-tests/provider-switching.integration.test.js.map +1 -0
- package/dist/src/integration-tests/security.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/security.integration.test.js +319 -0
- package/dist/src/integration-tests/security.integration.test.js.map +1 -0
- package/dist/src/integration-tests/test-utils.test.d.ts +6 -0
- package/dist/src/integration-tests/test-utils.test.js +221 -0
- package/dist/src/integration-tests/test-utils.test.js.map +1 -0
- package/dist/src/integration-tests/todo-continuation.integration.test.d.ts +6 -0
- package/dist/src/integration-tests/todo-continuation.integration.test.js +559 -0
- package/dist/src/integration-tests/todo-continuation.integration.test.js.map +1 -0
- package/dist/src/providers/logging/LoggingProviderWrapper.test.d.ts +6 -0
- package/dist/src/providers/logging/LoggingProviderWrapper.test.js +305 -0
- package/dist/src/providers/logging/LoggingProviderWrapper.test.js.map +1 -0
- package/dist/src/providers/logging/git-stats.integration.test.d.ts +6 -0
- package/dist/src/providers/logging/git-stats.integration.test.js +245 -0
- package/dist/src/providers/logging/git-stats.integration.test.js.map +1 -0
- package/dist/src/providers/logging/git-stats.test.d.ts +6 -0
- package/dist/src/providers/logging/git-stats.test.js +432 -0
- package/dist/src/providers/logging/git-stats.test.js.map +1 -0
- package/dist/src/providers/logging/multi-provider-logging.integration.test.d.ts +6 -0
- package/dist/src/providers/logging/multi-provider-logging.integration.test.js +531 -0
- package/dist/src/providers/logging/multi-provider-logging.integration.test.js.map +1 -0
- package/dist/src/providers/logging/performance.test.d.ts +6 -0
- package/dist/src/providers/logging/performance.test.js +465 -0
- package/dist/src/providers/logging/performance.test.js.map +1 -0
- package/dist/src/providers/provider-gemini-switching.test.d.ts +6 -0
- package/dist/src/providers/provider-gemini-switching.test.js +129 -0
- package/dist/src/providers/provider-gemini-switching.test.js.map +1 -0
- package/dist/src/providers/provider-switching.integration.test.d.ts +6 -0
- package/dist/src/providers/provider-switching.integration.test.js +113 -0
- package/dist/src/providers/provider-switching.integration.test.js.map +1 -0
- package/dist/src/providers/providerManagerInstance.test.d.ts +6 -0
- package/dist/src/providers/providerManagerInstance.test.js +104 -0
- package/dist/src/providers/providerManagerInstance.test.js.map +1 -0
- package/dist/src/services/BuiltinCommandLoader.test.d.ts +6 -0
- package/dist/src/services/BuiltinCommandLoader.test.js +118 -0
- package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -0
- package/dist/src/services/CommandService.test.d.ts +6 -0
- package/dist/src/services/CommandService.test.js +232 -0
- package/dist/src/services/CommandService.test.js.map +1 -0
- package/dist/src/storage/ConversationStorage.test.d.ts +6 -0
- package/dist/src/storage/ConversationStorage.test.js +379 -0
- package/dist/src/storage/ConversationStorage.test.js.map +1 -0
- package/dist/src/test-utils/customMatchers.d.ts +14 -0
- package/dist/src/test-utils/customMatchers.js +46 -0
- package/dist/src/test-utils/customMatchers.js.map +1 -0
- package/dist/src/test-utils/mockCommandContext.d.ts +18 -0
- package/dist/src/test-utils/mockCommandContext.js +86 -0
- package/dist/src/test-utils/mockCommandContext.js.map +1 -0
- package/dist/src/test-utils/mockCommandContext.test.d.ts +6 -0
- package/dist/src/test-utils/mockCommandContext.test.js +51 -0
- package/dist/src/test-utils/mockCommandContext.test.js.map +1 -0
- package/dist/src/test-utils/responsive-testing.d.ts +14 -0
- package/dist/src/test-utils/responsive-testing.js +35 -0
- package/dist/src/test-utils/responsive-testing.js.map +1 -0
- package/dist/src/test-utils/responsive-testing.test.d.ts +6 -0
- package/dist/src/test-utils/responsive-testing.test.js +89 -0
- package/dist/src/test-utils/responsive-testing.test.js.map +1 -0
- package/dist/src/test-utils/testProviderConfig.d.ts +18 -0
- package/dist/src/test-utils/testProviderConfig.js +19 -0
- package/dist/src/test-utils/testProviderConfig.js.map +1 -0
- package/dist/src/ui/App.js +1 -1
- package/dist/src/ui/App.js.map +1 -1
- package/dist/src/ui/IdeIntegrationNudge.js +1 -1
- package/dist/src/ui/IdeIntegrationNudge.js.map +1 -1
- package/dist/src/ui/commands/chatCommand.js +8 -6
- package/dist/src/ui/commands/chatCommand.js.map +1 -1
- package/dist/src/ui/commands/diagnosticsCommand.js +0 -1
- package/dist/src/ui/commands/diagnosticsCommand.js.map +1 -1
- package/dist/src/ui/commands/ideCommand.js +5 -5
- package/dist/src/ui/commands/ideCommand.js.map +1 -1
- package/dist/src/ui/commands/keyCommand.test.d.ts +6 -0
- package/dist/src/ui/commands/keyCommand.test.js +128 -0
- package/dist/src/ui/commands/keyCommand.test.js.map +1 -0
- package/dist/src/ui/commands/profileCommand.test.d.ts +6 -0
- package/dist/src/ui/commands/profileCommand.test.js +343 -0
- package/dist/src/ui/commands/profileCommand.test.js.map +1 -0
- package/dist/src/ui/commands/setCommand.test.d.ts +6 -0
- package/dist/src/ui/commands/setCommand.test.js +431 -0
- package/dist/src/ui/commands/setCommand.test.js.map +1 -0
- package/dist/src/ui/commands/setupGithubCommand.test.d.ts +6 -0
- package/dist/src/ui/commands/setupGithubCommand.test.js +69 -0
- package/dist/src/ui/commands/setupGithubCommand.test.js.map +1 -0
- package/dist/src/ui/commands/toolformatCommand.test.d.ts +1 -0
- package/dist/src/ui/commands/toolformatCommand.test.js +145 -0
- package/dist/src/ui/commands/toolformatCommand.test.js.map +1 -0
- package/dist/src/ui/components/AuthDialog.js +1 -1
- package/dist/src/ui/components/AuthDialog.js.map +1 -1
- package/dist/src/ui/components/AuthDialog.test.d.ts +6 -0
- package/dist/src/ui/components/AuthDialog.test.js +252 -0
- package/dist/src/ui/components/AuthDialog.test.js.map +1 -0
- package/dist/src/ui/components/ContextIndicator.ui.test.d.ts +1 -0
- package/dist/src/ui/components/ContextIndicator.ui.test.js +89 -0
- package/dist/src/ui/components/ContextIndicator.ui.test.js.map +1 -0
- package/dist/src/ui/components/ContextSummaryDisplay.js +1 -1
- package/dist/src/ui/components/ContextUsageDisplay.semantic.test.d.ts +6 -0
- package/dist/src/ui/components/ContextUsageDisplay.semantic.test.js +75 -0
- package/dist/src/ui/components/ContextUsageDisplay.semantic.test.js.map +1 -0
- package/dist/src/ui/components/FolderTrustDialog.test.d.ts +6 -0
- package/dist/src/ui/components/FolderTrustDialog.test.js +26 -0
- package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -0
- package/dist/src/ui/components/Footer.d.ts +1 -0
- package/dist/src/ui/components/Footer.js +2 -2
- package/dist/src/ui/components/Footer.js.map +1 -1
- package/dist/src/ui/components/Footer.responsive.test.d.ts +6 -0
- package/dist/src/ui/components/Footer.responsive.test.js +262 -0
- package/dist/src/ui/components/Footer.responsive.test.js.map +1 -0
- package/dist/src/ui/components/HistoryItemDisplay.test.d.ts +6 -0
- package/dist/src/ui/components/HistoryItemDisplay.test.js +92 -0
- package/dist/src/ui/components/HistoryItemDisplay.test.js.map +1 -0
- package/dist/src/ui/components/InputPrompt.paste.test.d.ts +6 -0
- package/dist/src/ui/components/InputPrompt.paste.test.js +263 -0
- package/dist/src/ui/components/InputPrompt.paste.test.js.map +1 -0
- package/dist/src/ui/components/LoadingIndicator.test.d.ts +6 -0
- package/dist/src/ui/components/LoadingIndicator.test.js +149 -0
- package/dist/src/ui/components/LoadingIndicator.test.js.map +1 -0
- package/dist/src/ui/components/MemoryUsageDisplay.semantic.test.d.ts +6 -0
- package/dist/src/ui/components/MemoryUsageDisplay.semantic.test.js +127 -0
- package/dist/src/ui/components/MemoryUsageDisplay.semantic.test.js.map +1 -0
- package/dist/src/ui/components/ProviderDialog.responsive.test.d.ts +6 -0
- package/dist/src/ui/components/ProviderDialog.responsive.test.js +153 -0
- package/dist/src/ui/components/ProviderDialog.responsive.test.js.map +1 -0
- package/dist/src/ui/components/ProviderModelDialog.responsive.test.d.ts +6 -0
- package/dist/src/ui/components/ProviderModelDialog.responsive.test.js +252 -0
- package/dist/src/ui/components/ProviderModelDialog.responsive.test.js.map +1 -0
- package/dist/src/ui/components/ProviderModelDialog.test.d.ts +6 -0
- package/dist/src/ui/components/ProviderModelDialog.test.js +197 -0
- package/dist/src/ui/components/ProviderModelDialog.test.js.map +1 -0
- package/dist/src/ui/components/SettingsDialog.test.d.ts +6 -0
- package/dist/src/ui/components/SettingsDialog.test.js +555 -0
- package/dist/src/ui/components/SettingsDialog.test.js.map +1 -0
- package/dist/src/ui/components/ShellConfirmationDialog.test.d.ts +6 -0
- package/dist/src/ui/components/ShellConfirmationDialog.test.js +40 -0
- package/dist/src/ui/components/ShellConfirmationDialog.test.js.map +1 -0
- package/dist/src/ui/components/TodoPanel.responsive.test.d.ts +6 -0
- package/dist/src/ui/components/TodoPanel.responsive.test.js +221 -0
- package/dist/src/ui/components/TodoPanel.responsive.test.js.map +1 -0
- package/dist/src/ui/components/TodoPanel.semantic.test.d.ts +6 -0
- package/dist/src/ui/components/TodoPanel.semantic.test.js +137 -0
- package/dist/src/ui/components/TodoPanel.semantic.test.js.map +1 -0
- package/dist/src/ui/components/__tests__/LayoutManager.test.d.ts +6 -0
- package/dist/src/ui/components/__tests__/LayoutManager.test.js +94 -0
- package/dist/src/ui/components/__tests__/LayoutManager.test.js.map +1 -0
- package/dist/src/ui/components/messages/DiffRenderer.js +9 -7
- package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
- package/dist/src/ui/components/messages/DiffRenderer.test.d.ts +6 -0
- package/dist/src/ui/components/messages/DiffRenderer.test.js +239 -0
- package/dist/src/ui/components/messages/DiffRenderer.test.js.map +1 -0
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js +13 -4
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.responsive.test.d.ts +6 -0
- package/dist/src/ui/components/messages/ToolConfirmationMessage.responsive.test.js +172 -0
- package/dist/src/ui/components/messages/ToolConfirmationMessage.responsive.test.js.map +1 -0
- package/dist/src/ui/components/messages/ToolConfirmationMessage.test.d.ts +6 -0
- package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js +37 -0
- package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js.map +1 -0
- package/dist/src/ui/components/messages/ToolMessage.test.d.ts +6 -0
- package/dist/src/ui/components/messages/ToolMessage.test.js +118 -0
- package/dist/src/ui/components/messages/ToolMessage.test.js.map +1 -0
- package/dist/src/ui/components/shared/MaxSizedBox.test.d.ts +6 -0
- package/dist/src/ui/components/shared/MaxSizedBox.test.js +154 -0
- package/dist/src/ui/components/shared/MaxSizedBox.test.js.map +1 -0
- package/dist/src/ui/components/shared/RadioButtonSelect.js +1 -1
- package/dist/src/ui/components/shared/RadioButtonSelect.js.map +1 -1
- package/dist/src/ui/components/shared/RadioButtonSelect.test.d.ts +6 -0
- package/dist/src/ui/components/shared/RadioButtonSelect.test.js +112 -0
- package/dist/src/ui/components/shared/RadioButtonSelect.test.js.map +1 -0
- package/dist/src/ui/containers/SessionController.test.d.ts +6 -0
- package/dist/src/ui/containers/SessionController.test.js +440 -0
- package/dist/src/ui/containers/SessionController.test.js.map +1 -0
- package/dist/src/ui/hooks/atCommandProcessor.test.d.ts +6 -0
- package/dist/src/ui/hooks/atCommandProcessor.test.js +830 -0
- package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -0
- package/dist/src/ui/hooks/shellCommandProcessor.test.d.ts +6 -0
- package/dist/src/ui/hooks/shellCommandProcessor.test.js +328 -0
- package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -0
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.d.ts +6 -0
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +191 -0
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -0
- package/dist/src/ui/hooks/useEditorSettings.test.d.ts +6 -0
- package/dist/src/ui/hooks/useEditorSettings.test.js +221 -0
- package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -0
- package/dist/src/ui/hooks/useGeminiStream.integration.test.d.ts +6 -0
- package/dist/src/ui/hooks/useGeminiStream.integration.test.js +800 -0
- package/dist/src/ui/hooks/useGeminiStream.integration.test.js.map +1 -0
- package/dist/src/ui/hooks/useGeminiStream.js +12 -47
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGitBranchName.test.d.ts +6 -0
- package/dist/src/ui/hooks/useGitBranchName.test.js +170 -0
- package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -0
- package/dist/src/ui/hooks/useHistoryManager.test.d.ts +6 -0
- package/dist/src/ui/hooks/useHistoryManager.test.js +171 -0
- package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -0
- package/dist/src/ui/hooks/useInputHistory.test.d.ts +6 -0
- package/dist/src/ui/hooks/useInputHistory.test.js +207 -0
- package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -0
- package/dist/src/ui/hooks/useKeypress.test.d.ts +6 -0
- package/dist/src/ui/hooks/useKeypress.test.js +171 -0
- package/dist/src/ui/hooks/useKeypress.test.js.map +1 -0
- package/dist/src/ui/hooks/useResponsive.test.d.ts +6 -0
- package/dist/src/ui/hooks/useResponsive.test.js +124 -0
- package/dist/src/ui/hooks/useResponsive.test.js.map +1 -0
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.d.ts +6 -0
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +163 -0
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -0
- package/dist/src/ui/hooks/useShellHistory.test.d.ts +6 -0
- package/dist/src/ui/hooks/useShellHistory.test.js +162 -0
- package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -0
- package/dist/src/ui/hooks/useSlashCompletion.test.d.ts +6 -0
- package/dist/src/ui/hooks/useSlashCompletion.test.js +929 -0
- package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -0
- package/dist/src/ui/hooks/useStableCallback.test.d.ts +6 -0
- package/dist/src/ui/hooks/useStableCallback.test.js +57 -0
- package/dist/src/ui/hooks/useStableCallback.test.js.map +1 -0
- package/dist/src/ui/keyMatchers.test.d.ts +6 -0
- package/dist/src/ui/keyMatchers.test.js +276 -0
- package/dist/src/ui/keyMatchers.test.js.map +1 -0
- package/dist/src/ui/reducers/appReducer.test.d.ts +6 -0
- package/dist/src/ui/reducers/appReducer.test.js +519 -0
- package/dist/src/ui/reducers/appReducer.test.js.map +1 -0
- package/dist/src/ui/themes/color-utils.test.d.ts +6 -0
- package/dist/src/ui/themes/color-utils.test.js +197 -0
- package/dist/src/ui/themes/color-utils.test.js.map +1 -0
- package/dist/src/ui/themes/semantic-resolver.test.d.ts +6 -0
- package/dist/src/ui/themes/semantic-resolver.test.js +210 -0
- package/dist/src/ui/themes/semantic-resolver.test.js.map +1 -0
- package/dist/src/ui/themes/semantic-tokens.test.d.ts +6 -0
- package/dist/src/ui/themes/semantic-tokens.test.js +272 -0
- package/dist/src/ui/themes/semantic-tokens.test.js.map +1 -0
- package/dist/src/ui/themes/theme-manager.test.d.ts +6 -0
- package/dist/src/ui/themes/theme-manager.test.js +151 -0
- package/dist/src/ui/themes/theme-manager.test.js.map +1 -0
- package/dist/src/ui/utils/MarkdownDisplay.test.d.ts +6 -0
- package/dist/src/ui/utils/MarkdownDisplay.test.js +151 -0
- package/dist/src/ui/utils/MarkdownDisplay.test.js.map +1 -0
- package/dist/src/ui/utils/clipboardUtils.test.d.ts +6 -0
- package/dist/src/ui/utils/clipboardUtils.test.js +65 -0
- package/dist/src/ui/utils/clipboardUtils.test.js.map +1 -0
- package/dist/src/ui/utils/commandUtils.test.d.ts +6 -0
- package/dist/src/ui/utils/commandUtils.test.js +294 -0
- package/dist/src/ui/utils/commandUtils.test.js.map +1 -0
- package/dist/src/ui/utils/displayUtils.test.d.ts +6 -0
- package/dist/src/ui/utils/displayUtils.test.js +42 -0
- package/dist/src/ui/utils/displayUtils.test.js.map +1 -0
- package/dist/src/ui/utils/formatters.test.d.ts +6 -0
- package/dist/src/ui/utils/formatters.test.js +56 -0
- package/dist/src/ui/utils/formatters.test.js.map +1 -0
- package/dist/src/ui/utils/markdownUtilities.test.d.ts +6 -0
- package/dist/src/ui/utils/markdownUtilities.test.js +42 -0
- package/dist/src/ui/utils/markdownUtilities.test.js.map +1 -0
- package/dist/src/ui/utils/responsive.test.d.ts +6 -0
- package/dist/src/ui/utils/responsive.test.js +107 -0
- package/dist/src/ui/utils/responsive.test.js.map +1 -0
- package/dist/src/ui/utils/secureInputHandler.test.d.ts +6 -0
- package/dist/src/ui/utils/secureInputHandler.test.js +274 -0
- package/dist/src/ui/utils/secureInputHandler.test.js.map +1 -0
- package/dist/src/ui/utils/updateCheck.test.d.ts +6 -0
- package/dist/src/ui/utils/updateCheck.test.js +202 -0
- package/dist/src/ui/utils/updateCheck.test.js.map +1 -0
- package/dist/src/utils/ConversationContext.test.d.ts +6 -0
- package/dist/src/utils/ConversationContext.test.js +64 -0
- package/dist/src/utils/ConversationContext.test.js.map +1 -0
- package/dist/src/utils/gitUtils.test.d.ts +6 -0
- package/dist/src/utils/gitUtils.test.js +113 -0
- package/dist/src/utils/gitUtils.test.js.map +1 -0
- package/dist/src/utils/installationInfo.test.d.ts +6 -0
- package/dist/src/utils/installationInfo.test.js +242 -0
- package/dist/src/utils/installationInfo.test.js.map +1 -0
- package/dist/src/utils/privacy/ConversationDataRedactor.test.d.ts +6 -0
- package/dist/src/utils/privacy/ConversationDataRedactor.test.js +463 -0
- package/dist/src/utils/privacy/ConversationDataRedactor.test.js.map +1 -0
- package/dist/src/utils/readStdin.js +10 -0
- package/dist/src/utils/readStdin.js.map +1 -1
- package/dist/src/utils/sandbox.js +2 -2
- package/dist/src/utils/sandbox.js.map +1 -1
- package/dist/src/utils/settingsUtils.test.d.ts +6 -0
- package/dist/src/utils/settingsUtils.test.js +514 -0
- package/dist/src/utils/settingsUtils.test.js.map +1 -0
- package/dist/src/utils/userStartupWarnings.js +2 -2
- package/dist/src/utils/userStartupWarnings.test.d.ts +6 -0
- package/dist/src/utils/userStartupWarnings.test.js +67 -0
- package/dist/src/utils/userStartupWarnings.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
@@ -0,0 +1,929 @@
|
|
1
|
+
/**
|
2
|
+
* @license
|
3
|
+
* Copyright 2025 Google LLC
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
5
|
+
*/
|
6
|
+
/** @vitest-environment jsdom */
|
7
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
8
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
9
|
+
import { useSlashCompletion } from './useSlashCompletion.js';
|
10
|
+
import * as fs from 'fs/promises';
|
11
|
+
import * as path from 'path';
|
12
|
+
import * as os from 'os';
|
13
|
+
import { FileDiscoveryService } from '@vybestack/llxprt-code-core';
|
14
|
+
import { useTextBuffer } from '../components/shared/text-buffer.js';
|
15
|
+
describe('useSlashCompletion', () => {
|
16
|
+
let testRootDir;
|
17
|
+
let mockConfig;
|
18
|
+
// A minimal mock is sufficient for these tests.
|
19
|
+
const mockCommandContext = {};
|
20
|
+
let testDirs;
|
21
|
+
async function createEmptyDir(...pathSegments) {
|
22
|
+
const fullPath = path.join(testRootDir, ...pathSegments);
|
23
|
+
await fs.mkdir(fullPath, { recursive: true });
|
24
|
+
return fullPath;
|
25
|
+
}
|
26
|
+
async function createTestFile(content, ...pathSegments) {
|
27
|
+
const fullPath = path.join(testRootDir, ...pathSegments);
|
28
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
29
|
+
await fs.writeFile(fullPath, content);
|
30
|
+
return fullPath;
|
31
|
+
}
|
32
|
+
// Helper to create real TextBuffer objects within renderHook
|
33
|
+
function useTextBufferForTest(text, cursorOffset) {
|
34
|
+
return useTextBuffer({
|
35
|
+
initialText: text,
|
36
|
+
initialCursorOffset: cursorOffset ?? text.length,
|
37
|
+
viewport: { width: 80, height: 20 },
|
38
|
+
isValidPath: () => false,
|
39
|
+
onChange: () => { },
|
40
|
+
});
|
41
|
+
}
|
42
|
+
beforeEach(async () => {
|
43
|
+
testRootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'slash-completion-unit-test-'));
|
44
|
+
testDirs = [testRootDir];
|
45
|
+
mockConfig = {
|
46
|
+
getTargetDir: () => testRootDir,
|
47
|
+
getWorkspaceContext: () => ({
|
48
|
+
getDirectories: () => testDirs,
|
49
|
+
}),
|
50
|
+
getProjectRoot: () => testRootDir,
|
51
|
+
getFileFilteringOptions: vi.fn(() => ({
|
52
|
+
respectGitIgnore: true,
|
53
|
+
respectLlxprtIgnore: true,
|
54
|
+
})),
|
55
|
+
getEnableRecursiveFileSearch: vi.fn(() => true),
|
56
|
+
getFileService: vi.fn(() => new FileDiscoveryService(testRootDir)),
|
57
|
+
};
|
58
|
+
vi.clearAllMocks();
|
59
|
+
});
|
60
|
+
afterEach(async () => {
|
61
|
+
vi.restoreAllMocks();
|
62
|
+
await fs.rm(testRootDir, { recursive: true, force: true });
|
63
|
+
});
|
64
|
+
describe('Core Hook Behavior', () => {
|
65
|
+
describe('State Management', () => {
|
66
|
+
it('should initialize with default state', () => {
|
67
|
+
const slashCommands = [
|
68
|
+
{ name: 'dummy', description: 'dummy' },
|
69
|
+
];
|
70
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest(''), testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig));
|
71
|
+
expect(result.current.suggestions).toEqual([]);
|
72
|
+
expect(result.current.activeSuggestionIndex).toBe(-1);
|
73
|
+
expect(result.current.visibleStartIndex).toBe(0);
|
74
|
+
expect(result.current.showSuggestions).toBe(false);
|
75
|
+
expect(result.current.isLoadingSuggestions).toBe(false);
|
76
|
+
});
|
77
|
+
it('should reset state when isActive becomes false', () => {
|
78
|
+
const slashCommands = [
|
79
|
+
{
|
80
|
+
name: 'help',
|
81
|
+
altNames: ['?'],
|
82
|
+
description: 'Show help',
|
83
|
+
action: vi.fn(),
|
84
|
+
},
|
85
|
+
];
|
86
|
+
const { result, rerender } = renderHook(({ text }) => {
|
87
|
+
const textBuffer = useTextBufferForTest(text);
|
88
|
+
return useSlashCompletion(textBuffer, testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig);
|
89
|
+
}, { initialProps: { text: '/help' } });
|
90
|
+
// Inactive because of the leading space
|
91
|
+
rerender({ text: ' /help' });
|
92
|
+
expect(result.current.suggestions).toEqual([]);
|
93
|
+
expect(result.current.activeSuggestionIndex).toBe(-1);
|
94
|
+
expect(result.current.visibleStartIndex).toBe(0);
|
95
|
+
expect(result.current.showSuggestions).toBe(false);
|
96
|
+
expect(result.current.isLoadingSuggestions).toBe(false);
|
97
|
+
});
|
98
|
+
it('should reset all state to default values', async () => {
|
99
|
+
const slashCommands = [
|
100
|
+
{
|
101
|
+
name: 'help',
|
102
|
+
description: 'Show help',
|
103
|
+
},
|
104
|
+
];
|
105
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/help'), testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig));
|
106
|
+
act(() => {
|
107
|
+
result.current.setActiveSuggestionIndex(5);
|
108
|
+
result.current.setShowSuggestions(true);
|
109
|
+
});
|
110
|
+
act(() => {
|
111
|
+
result.current.resetCompletionState();
|
112
|
+
});
|
113
|
+
// Wait for async suggestions clearing
|
114
|
+
await waitFor(() => {
|
115
|
+
expect(result.current.suggestions).toEqual([]);
|
116
|
+
});
|
117
|
+
expect(result.current.suggestions).toEqual([]);
|
118
|
+
expect(result.current.activeSuggestionIndex).toBe(-1);
|
119
|
+
expect(result.current.visibleStartIndex).toBe(0);
|
120
|
+
expect(result.current.showSuggestions).toBe(false);
|
121
|
+
expect(result.current.isLoadingSuggestions).toBe(false);
|
122
|
+
});
|
123
|
+
});
|
124
|
+
describe('Navigation', () => {
|
125
|
+
it('should handle navigateUp with no suggestions', () => {
|
126
|
+
const slashCommands = [
|
127
|
+
{ name: 'dummy', description: 'dummy' },
|
128
|
+
];
|
129
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest(''), testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig));
|
130
|
+
act(() => {
|
131
|
+
result.current.navigateUp();
|
132
|
+
});
|
133
|
+
expect(result.current.activeSuggestionIndex).toBe(-1);
|
134
|
+
});
|
135
|
+
it('should handle navigateDown with no suggestions', () => {
|
136
|
+
const slashCommands = [
|
137
|
+
{ name: 'dummy', description: 'dummy' },
|
138
|
+
];
|
139
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest(''), testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig));
|
140
|
+
act(() => {
|
141
|
+
result.current.navigateDown();
|
142
|
+
});
|
143
|
+
expect(result.current.activeSuggestionIndex).toBe(-1);
|
144
|
+
});
|
145
|
+
it('should navigate up through suggestions with wrap-around', () => {
|
146
|
+
const slashCommands = [
|
147
|
+
{
|
148
|
+
name: 'help',
|
149
|
+
description: 'Show help',
|
150
|
+
},
|
151
|
+
];
|
152
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/h'), testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig));
|
153
|
+
expect(result.current.suggestions.length).toBe(1);
|
154
|
+
expect(result.current.activeSuggestionIndex).toBe(0);
|
155
|
+
act(() => {
|
156
|
+
result.current.navigateUp();
|
157
|
+
});
|
158
|
+
expect(result.current.activeSuggestionIndex).toBe(0);
|
159
|
+
});
|
160
|
+
it('should navigate down through suggestions with wrap-around', () => {
|
161
|
+
const slashCommands = [
|
162
|
+
{
|
163
|
+
name: 'help',
|
164
|
+
description: 'Show help',
|
165
|
+
},
|
166
|
+
];
|
167
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/h'), testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig));
|
168
|
+
expect(result.current.suggestions.length).toBe(1);
|
169
|
+
expect(result.current.activeSuggestionIndex).toBe(0);
|
170
|
+
act(() => {
|
171
|
+
result.current.navigateDown();
|
172
|
+
});
|
173
|
+
expect(result.current.activeSuggestionIndex).toBe(0);
|
174
|
+
});
|
175
|
+
it('should handle navigation with multiple suggestions', () => {
|
176
|
+
const slashCommands = [
|
177
|
+
{ name: 'help', description: 'Show help' },
|
178
|
+
{ name: 'stats', description: 'Show stats' },
|
179
|
+
{ name: 'clear', description: 'Clear screen' },
|
180
|
+
{ name: 'memory', description: 'Manage memory' },
|
181
|
+
{ name: 'chat', description: 'Manage chat' },
|
182
|
+
];
|
183
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/'), testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig));
|
184
|
+
expect(result.current.suggestions.length).toBe(5);
|
185
|
+
expect(result.current.activeSuggestionIndex).toBe(0);
|
186
|
+
act(() => {
|
187
|
+
result.current.navigateDown();
|
188
|
+
});
|
189
|
+
expect(result.current.activeSuggestionIndex).toBe(1);
|
190
|
+
act(() => {
|
191
|
+
result.current.navigateDown();
|
192
|
+
});
|
193
|
+
expect(result.current.activeSuggestionIndex).toBe(2);
|
194
|
+
act(() => {
|
195
|
+
result.current.navigateUp();
|
196
|
+
});
|
197
|
+
expect(result.current.activeSuggestionIndex).toBe(1);
|
198
|
+
act(() => {
|
199
|
+
result.current.navigateUp();
|
200
|
+
});
|
201
|
+
expect(result.current.activeSuggestionIndex).toBe(0);
|
202
|
+
act(() => {
|
203
|
+
result.current.navigateUp();
|
204
|
+
});
|
205
|
+
expect(result.current.activeSuggestionIndex).toBe(4);
|
206
|
+
});
|
207
|
+
it('should handle navigation with large suggestion lists and scrolling', () => {
|
208
|
+
const largeMockCommands = Array.from({ length: 15 }, (_, i) => ({
|
209
|
+
name: `command${i}`,
|
210
|
+
description: `Command ${i}`,
|
211
|
+
}));
|
212
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/command'), testDirs, testRootDir, largeMockCommands, mockCommandContext, false, mockConfig));
|
213
|
+
expect(result.current.suggestions.length).toBe(15);
|
214
|
+
expect(result.current.activeSuggestionIndex).toBe(0);
|
215
|
+
expect(result.current.visibleStartIndex).toBe(0);
|
216
|
+
act(() => {
|
217
|
+
result.current.navigateUp();
|
218
|
+
});
|
219
|
+
expect(result.current.activeSuggestionIndex).toBe(14);
|
220
|
+
expect(result.current.visibleStartIndex).toBe(Math.max(0, 15 - 8));
|
221
|
+
});
|
222
|
+
});
|
223
|
+
});
|
224
|
+
describe('Slash Command Completion (`/`)', () => {
|
225
|
+
describe('Top-Level Commands', () => {
|
226
|
+
it('should suggest all top-level commands for the root slash', async () => {
|
227
|
+
const slashCommands = [
|
228
|
+
{
|
229
|
+
name: 'help',
|
230
|
+
altNames: ['?'],
|
231
|
+
description: 'Show help',
|
232
|
+
},
|
233
|
+
{
|
234
|
+
name: 'stats',
|
235
|
+
altNames: ['usage'],
|
236
|
+
description: 'check session stats. Usage: /stats [model|tools]',
|
237
|
+
},
|
238
|
+
{
|
239
|
+
name: 'clear',
|
240
|
+
description: 'Clear the screen',
|
241
|
+
},
|
242
|
+
{
|
243
|
+
name: 'memory',
|
244
|
+
description: 'Manage memory',
|
245
|
+
subCommands: [
|
246
|
+
{
|
247
|
+
name: 'show',
|
248
|
+
description: 'Show memory',
|
249
|
+
},
|
250
|
+
],
|
251
|
+
},
|
252
|
+
{
|
253
|
+
name: 'chat',
|
254
|
+
description: 'Manage chat history',
|
255
|
+
},
|
256
|
+
];
|
257
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/'), testDirs, testRootDir, slashCommands, mockCommandContext));
|
258
|
+
expect(result.current.suggestions.length).toBe(slashCommands.length);
|
259
|
+
expect(result.current.suggestions.map((s) => s.label)).toEqual(expect.arrayContaining(['help', 'clear', 'memory', 'chat', 'stats']));
|
260
|
+
});
|
261
|
+
it('should filter commands based on partial input', async () => {
|
262
|
+
const slashCommands = [
|
263
|
+
{
|
264
|
+
name: 'memory',
|
265
|
+
description: 'Manage memory',
|
266
|
+
},
|
267
|
+
];
|
268
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/mem'), testDirs, testRootDir, slashCommands, mockCommandContext));
|
269
|
+
expect(result.current.suggestions).toEqual([
|
270
|
+
{ label: 'memory', value: 'memory', description: 'Manage memory' },
|
271
|
+
]);
|
272
|
+
expect(result.current.showSuggestions).toBe(true);
|
273
|
+
});
|
274
|
+
it('should suggest commands based on partial altNames', async () => {
|
275
|
+
const slashCommands = [
|
276
|
+
{
|
277
|
+
name: 'stats',
|
278
|
+
altNames: ['usage'],
|
279
|
+
description: 'check session stats. Usage: /stats [model|tools]',
|
280
|
+
},
|
281
|
+
];
|
282
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/usag'), // part of the word "usage"
|
283
|
+
testDirs, testRootDir, slashCommands, mockCommandContext));
|
284
|
+
expect(result.current.suggestions).toEqual([
|
285
|
+
{
|
286
|
+
label: 'stats',
|
287
|
+
value: 'stats',
|
288
|
+
description: 'check session stats. Usage: /stats [model|tools]',
|
289
|
+
},
|
290
|
+
]);
|
291
|
+
});
|
292
|
+
it('should NOT provide suggestions for a perfectly typed command that is a leaf node', async () => {
|
293
|
+
const slashCommands = [
|
294
|
+
{
|
295
|
+
name: 'clear',
|
296
|
+
description: 'Clear the screen',
|
297
|
+
action: vi.fn(),
|
298
|
+
},
|
299
|
+
];
|
300
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/clear'), // No trailing space
|
301
|
+
testDirs, testRootDir, slashCommands, mockCommandContext));
|
302
|
+
expect(result.current.suggestions).toHaveLength(0);
|
303
|
+
expect(result.current.showSuggestions).toBe(false);
|
304
|
+
});
|
305
|
+
it.each([['/?'], ['/usage']])('should not suggest commands when altNames is fully typed', async (query) => {
|
306
|
+
const mockSlashCommands = [
|
307
|
+
{
|
308
|
+
name: 'help',
|
309
|
+
altNames: ['?'],
|
310
|
+
description: 'Show help',
|
311
|
+
action: vi.fn(),
|
312
|
+
},
|
313
|
+
{
|
314
|
+
name: 'stats',
|
315
|
+
altNames: ['usage'],
|
316
|
+
description: 'check session stats. Usage: /stats [model|tools]',
|
317
|
+
action: vi.fn(),
|
318
|
+
},
|
319
|
+
];
|
320
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest(query), testDirs, testRootDir, mockSlashCommands, mockCommandContext));
|
321
|
+
expect(result.current.suggestions).toHaveLength(0);
|
322
|
+
});
|
323
|
+
it('should not provide suggestions for a fully typed command that has no sub-commands or argument completion', async () => {
|
324
|
+
const slashCommands = [
|
325
|
+
{
|
326
|
+
name: 'clear',
|
327
|
+
description: 'Clear the screen',
|
328
|
+
},
|
329
|
+
];
|
330
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/clear '), testDirs, testRootDir, slashCommands, mockCommandContext));
|
331
|
+
expect(result.current.suggestions).toHaveLength(0);
|
332
|
+
expect(result.current.showSuggestions).toBe(false);
|
333
|
+
});
|
334
|
+
it('should not provide suggestions for an unknown command', async () => {
|
335
|
+
const slashCommands = [
|
336
|
+
{
|
337
|
+
name: 'help',
|
338
|
+
description: 'Show help',
|
339
|
+
},
|
340
|
+
];
|
341
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/unknown-command'), testDirs, testRootDir, slashCommands, mockCommandContext));
|
342
|
+
expect(result.current.suggestions).toHaveLength(0);
|
343
|
+
expect(result.current.showSuggestions).toBe(false);
|
344
|
+
});
|
345
|
+
});
|
346
|
+
describe('Sub-Commands', () => {
|
347
|
+
it('should suggest sub-commands for a parent command', async () => {
|
348
|
+
const slashCommands = [
|
349
|
+
{
|
350
|
+
name: 'memory',
|
351
|
+
description: 'Manage memory',
|
352
|
+
subCommands: [
|
353
|
+
{
|
354
|
+
name: 'show',
|
355
|
+
description: 'Show memory',
|
356
|
+
},
|
357
|
+
{
|
358
|
+
name: 'add',
|
359
|
+
description: 'Add to memory',
|
360
|
+
},
|
361
|
+
],
|
362
|
+
},
|
363
|
+
];
|
364
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/memory'), // Note: no trailing space
|
365
|
+
testDirs, testRootDir, slashCommands, mockCommandContext));
|
366
|
+
// Assert that suggestions for sub-commands are shown immediately
|
367
|
+
expect(result.current.suggestions).toHaveLength(2);
|
368
|
+
expect(result.current.suggestions).toEqual(expect.arrayContaining([
|
369
|
+
{ label: 'show', value: 'show', description: 'Show memory' },
|
370
|
+
{ label: 'add', value: 'add', description: 'Add to memory' },
|
371
|
+
]));
|
372
|
+
expect(result.current.showSuggestions).toBe(true);
|
373
|
+
});
|
374
|
+
it('should suggest all sub-commands when the query ends with the parent command and a space', async () => {
|
375
|
+
const slashCommands = [
|
376
|
+
{
|
377
|
+
name: 'memory',
|
378
|
+
description: 'Manage memory',
|
379
|
+
subCommands: [
|
380
|
+
{
|
381
|
+
name: 'show',
|
382
|
+
description: 'Show memory',
|
383
|
+
},
|
384
|
+
{
|
385
|
+
name: 'add',
|
386
|
+
description: 'Add to memory',
|
387
|
+
},
|
388
|
+
],
|
389
|
+
},
|
390
|
+
];
|
391
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/memory'), testDirs, testRootDir, slashCommands, mockCommandContext));
|
392
|
+
expect(result.current.suggestions).toHaveLength(2);
|
393
|
+
expect(result.current.suggestions).toEqual(expect.arrayContaining([
|
394
|
+
{ label: 'show', value: 'show', description: 'Show memory' },
|
395
|
+
{ label: 'add', value: 'add', description: 'Add to memory' },
|
396
|
+
]));
|
397
|
+
});
|
398
|
+
it('should filter sub-commands by prefix', async () => {
|
399
|
+
const slashCommands = [
|
400
|
+
{
|
401
|
+
name: 'memory',
|
402
|
+
description: 'Manage memory',
|
403
|
+
subCommands: [
|
404
|
+
{
|
405
|
+
name: 'show',
|
406
|
+
description: 'Show memory',
|
407
|
+
},
|
408
|
+
{
|
409
|
+
name: 'add',
|
410
|
+
description: 'Add to memory',
|
411
|
+
},
|
412
|
+
],
|
413
|
+
},
|
414
|
+
];
|
415
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/memory a'), testDirs, testRootDir, slashCommands, mockCommandContext));
|
416
|
+
expect(result.current.suggestions).toEqual([
|
417
|
+
{ label: 'add', value: 'add', description: 'Add to memory' },
|
418
|
+
]);
|
419
|
+
});
|
420
|
+
it('should provide no suggestions for an invalid sub-command', async () => {
|
421
|
+
const slashCommands = [
|
422
|
+
{
|
423
|
+
name: 'memory',
|
424
|
+
description: 'Manage memory',
|
425
|
+
subCommands: [
|
426
|
+
{
|
427
|
+
name: 'show',
|
428
|
+
description: 'Show memory',
|
429
|
+
},
|
430
|
+
{
|
431
|
+
name: 'add',
|
432
|
+
description: 'Add to memory',
|
433
|
+
},
|
434
|
+
],
|
435
|
+
},
|
436
|
+
];
|
437
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/memory dothisnow'), testDirs, testRootDir, slashCommands, mockCommandContext));
|
438
|
+
expect(result.current.suggestions).toHaveLength(0);
|
439
|
+
expect(result.current.showSuggestions).toBe(false);
|
440
|
+
});
|
441
|
+
});
|
442
|
+
describe('Argument Completion', () => {
|
443
|
+
it('should call the command.completion function for argument suggestions', async () => {
|
444
|
+
const availableTags = [
|
445
|
+
'my-chat-tag-1',
|
446
|
+
'my-chat-tag-2',
|
447
|
+
'another-channel',
|
448
|
+
];
|
449
|
+
const mockCompletionFn = vi
|
450
|
+
.fn()
|
451
|
+
.mockImplementation(async (_context, partialArg) => availableTags.filter((tag) => tag.startsWith(partialArg)));
|
452
|
+
const slashCommands = [
|
453
|
+
{
|
454
|
+
name: 'chat',
|
455
|
+
description: 'Manage chat history',
|
456
|
+
subCommands: [
|
457
|
+
{
|
458
|
+
name: 'resume',
|
459
|
+
description: 'Resume a saved chat',
|
460
|
+
completion: mockCompletionFn,
|
461
|
+
},
|
462
|
+
],
|
463
|
+
},
|
464
|
+
];
|
465
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/chat resume my-ch'), testDirs, testRootDir, slashCommands, mockCommandContext));
|
466
|
+
await act(async () => {
|
467
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
468
|
+
});
|
469
|
+
expect(mockCompletionFn).toHaveBeenCalledWith(mockCommandContext, 'my-ch');
|
470
|
+
expect(result.current.suggestions).toEqual([
|
471
|
+
{ label: 'my-chat-tag-1', value: 'my-chat-tag-1' },
|
472
|
+
{ label: 'my-chat-tag-2', value: 'my-chat-tag-2' },
|
473
|
+
]);
|
474
|
+
});
|
475
|
+
it('should call command.completion with an empty string when args start with a space', async () => {
|
476
|
+
const mockCompletionFn = vi
|
477
|
+
.fn()
|
478
|
+
.mockResolvedValue(['my-chat-tag-1', 'my-chat-tag-2', 'my-channel']);
|
479
|
+
const slashCommands = [
|
480
|
+
{
|
481
|
+
name: 'chat',
|
482
|
+
description: 'Manage chat history',
|
483
|
+
subCommands: [
|
484
|
+
{
|
485
|
+
name: 'resume',
|
486
|
+
description: 'Resume a saved chat',
|
487
|
+
completion: mockCompletionFn,
|
488
|
+
},
|
489
|
+
],
|
490
|
+
},
|
491
|
+
];
|
492
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/chat resume '), testDirs, testRootDir, slashCommands, mockCommandContext));
|
493
|
+
await act(async () => {
|
494
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
495
|
+
});
|
496
|
+
expect(mockCompletionFn).toHaveBeenCalledWith(mockCommandContext, '');
|
497
|
+
expect(result.current.suggestions).toHaveLength(3);
|
498
|
+
expect(result.current.showSuggestions).toBe(true);
|
499
|
+
});
|
500
|
+
it('should handle completion function that returns null', async () => {
|
501
|
+
const completionFn = vi.fn().mockResolvedValue(null);
|
502
|
+
const slashCommands = [
|
503
|
+
{
|
504
|
+
name: 'chat',
|
505
|
+
description: 'Manage chat history',
|
506
|
+
subCommands: [
|
507
|
+
{
|
508
|
+
name: 'resume',
|
509
|
+
description: 'Resume a saved chat',
|
510
|
+
completion: completionFn,
|
511
|
+
},
|
512
|
+
],
|
513
|
+
},
|
514
|
+
];
|
515
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('/chat resume '), testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig));
|
516
|
+
await act(async () => {
|
517
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
518
|
+
});
|
519
|
+
expect(result.current.suggestions).toHaveLength(0);
|
520
|
+
expect(result.current.showSuggestions).toBe(false);
|
521
|
+
});
|
522
|
+
});
|
523
|
+
});
|
524
|
+
describe('File Path Completion (`@`)', () => {
|
525
|
+
describe('Basic Completion', () => {
|
526
|
+
it('should use glob for top-level @ completions when available', async () => {
|
527
|
+
await createTestFile('', 'src', 'index.ts');
|
528
|
+
await createTestFile('', 'derp', 'script.ts');
|
529
|
+
await createTestFile('', 'README.md');
|
530
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@s'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
531
|
+
await act(async () => {
|
532
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
533
|
+
});
|
534
|
+
expect(result.current.suggestions).toHaveLength(2);
|
535
|
+
expect(result.current.suggestions).toEqual(expect.arrayContaining([
|
536
|
+
{
|
537
|
+
label: 'derp/script.ts',
|
538
|
+
value: 'derp/script.ts',
|
539
|
+
},
|
540
|
+
{ label: 'src', value: 'src' },
|
541
|
+
]));
|
542
|
+
});
|
543
|
+
it('should handle directory-specific completions with git filtering', async () => {
|
544
|
+
await createEmptyDir('.git');
|
545
|
+
await createTestFile('*.log', '.gitignore');
|
546
|
+
await createTestFile('', 'src', 'component.tsx');
|
547
|
+
await createTestFile('', 'src', 'temp.log');
|
548
|
+
await createTestFile('', 'src', 'index.ts');
|
549
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@src/comp'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
550
|
+
await act(async () => {
|
551
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
552
|
+
});
|
553
|
+
// Should filter out .log files but include matching .tsx files
|
554
|
+
expect(result.current.suggestions).toEqual([
|
555
|
+
{ label: 'component.tsx', value: 'component.tsx' },
|
556
|
+
]);
|
557
|
+
});
|
558
|
+
it('should include dotfiles in glob search when input starts with a dot', async () => {
|
559
|
+
await createTestFile('', '.env');
|
560
|
+
await createTestFile('', '.gitignore');
|
561
|
+
await createTestFile('', 'src', 'index.ts');
|
562
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@.'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
563
|
+
await act(async () => {
|
564
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
565
|
+
});
|
566
|
+
expect(result.current.suggestions).toEqual([
|
567
|
+
{ label: '.env', value: '.env' },
|
568
|
+
{ label: '.gitignore', value: '.gitignore' },
|
569
|
+
]);
|
570
|
+
});
|
571
|
+
});
|
572
|
+
describe('Configuration-based Behavior', () => {
|
573
|
+
it('should not perform recursive search when disabled in config', async () => {
|
574
|
+
const mockConfigNoRecursive = {
|
575
|
+
...mockConfig,
|
576
|
+
getEnableRecursiveFileSearch: vi.fn(() => false),
|
577
|
+
};
|
578
|
+
await createEmptyDir('data');
|
579
|
+
await createEmptyDir('dist');
|
580
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@d'), testDirs, testRootDir, [], mockCommandContext, false, mockConfigNoRecursive));
|
581
|
+
await act(async () => {
|
582
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
583
|
+
});
|
584
|
+
expect(result.current.suggestions).toEqual([
|
585
|
+
{ label: 'data/', value: 'data/' },
|
586
|
+
{ label: 'dist/', value: 'dist/' },
|
587
|
+
]);
|
588
|
+
});
|
589
|
+
it('should work without config (fallback behavior)', async () => {
|
590
|
+
await createEmptyDir('src');
|
591
|
+
await createEmptyDir('node_modules');
|
592
|
+
await createTestFile('', 'README.md');
|
593
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@'), testDirs, testRootDir, [], mockCommandContext, undefined));
|
594
|
+
await act(async () => {
|
595
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
596
|
+
});
|
597
|
+
// Without config, should include all files
|
598
|
+
expect(result.current.suggestions).toHaveLength(3);
|
599
|
+
expect(result.current.suggestions).toEqual(expect.arrayContaining([
|
600
|
+
{ label: 'src/', value: 'src/' },
|
601
|
+
{ label: 'node_modules/', value: 'node_modules/' },
|
602
|
+
{ label: 'README.md', value: 'README.md' },
|
603
|
+
]));
|
604
|
+
});
|
605
|
+
it('should handle git discovery service initialization failure gracefully', async () => {
|
606
|
+
// Intentionally don't create a .git directory to cause an initialization failure.
|
607
|
+
await createEmptyDir('src');
|
608
|
+
await createTestFile('', 'README.md');
|
609
|
+
const consoleSpy = vi
|
610
|
+
.spyOn(console, 'warn')
|
611
|
+
.mockImplementation(() => { });
|
612
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
613
|
+
await act(async () => {
|
614
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
615
|
+
});
|
616
|
+
// Since we use centralized service, initialization errors are handled at config level
|
617
|
+
// This test should verify graceful fallback behavior
|
618
|
+
expect(result.current.suggestions.length).toBeGreaterThanOrEqual(0);
|
619
|
+
// Should still show completions even if git discovery fails
|
620
|
+
expect(result.current.suggestions.length).toBeGreaterThan(0);
|
621
|
+
consoleSpy.mockRestore();
|
622
|
+
});
|
623
|
+
});
|
624
|
+
describe('Git-Aware Filtering', () => {
|
625
|
+
it('should filter git-ignored entries from @ completions', async () => {
|
626
|
+
await createEmptyDir('.git');
|
627
|
+
await createTestFile('dist', '.gitignore');
|
628
|
+
await createEmptyDir('data');
|
629
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@d'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
630
|
+
// Wait for async operations to complete
|
631
|
+
await act(async () => {
|
632
|
+
await new Promise((resolve) => setTimeout(resolve, 150)); // Account for debounce
|
633
|
+
});
|
634
|
+
expect(result.current.suggestions).toEqual(expect.arrayContaining([{ label: 'data', value: 'data' }]));
|
635
|
+
expect(result.current.showSuggestions).toBe(true);
|
636
|
+
});
|
637
|
+
it('should filter git-ignored directories from @ completions', async () => {
|
638
|
+
await createEmptyDir('.git');
|
639
|
+
await createTestFile('node_modules\ndist\n.env', '.gitignore');
|
640
|
+
// gitignored entries
|
641
|
+
await createEmptyDir('node_modules');
|
642
|
+
await createEmptyDir('dist');
|
643
|
+
await createTestFile('', '.env');
|
644
|
+
// visible
|
645
|
+
await createEmptyDir('src');
|
646
|
+
await createTestFile('', 'README.md');
|
647
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
648
|
+
// Wait for async operations to complete
|
649
|
+
await act(async () => {
|
650
|
+
await new Promise((resolve) => setTimeout(resolve, 150)); // Account for debounce
|
651
|
+
});
|
652
|
+
expect(result.current.suggestions).toEqual([
|
653
|
+
{ label: 'README.md', value: 'README.md' },
|
654
|
+
{ label: 'src/', value: 'src/' },
|
655
|
+
]);
|
656
|
+
expect(result.current.showSuggestions).toBe(true);
|
657
|
+
});
|
658
|
+
it('should handle recursive search with git-aware filtering', async () => {
|
659
|
+
await createEmptyDir('.git');
|
660
|
+
await createTestFile('node_modules/\ntemp/', '.gitignore');
|
661
|
+
await createTestFile('', 'data', 'test.txt');
|
662
|
+
await createEmptyDir('dist');
|
663
|
+
await createEmptyDir('node_modules');
|
664
|
+
await createTestFile('', 'src', 'index.ts');
|
665
|
+
await createEmptyDir('src', 'components');
|
666
|
+
await createTestFile('', 'temp', 'temp.log');
|
667
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@t'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
668
|
+
await act(async () => {
|
669
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
670
|
+
});
|
671
|
+
// Should not include anything from node_modules or dist
|
672
|
+
const suggestionLabels = result.current.suggestions.map((s) => s.label);
|
673
|
+
expect(suggestionLabels).not.toContain('temp/');
|
674
|
+
expect(suggestionLabels).not.toContain('node_modules/');
|
675
|
+
});
|
676
|
+
});
|
677
|
+
});
|
678
|
+
describe('handleAutocomplete', () => {
|
679
|
+
it('should complete a partial command', () => {
|
680
|
+
const slashCommands = [
|
681
|
+
{
|
682
|
+
name: 'memory',
|
683
|
+
description: 'Manage memory',
|
684
|
+
subCommands: [
|
685
|
+
{
|
686
|
+
name: 'show',
|
687
|
+
description: 'Show memory',
|
688
|
+
},
|
689
|
+
{
|
690
|
+
name: 'add',
|
691
|
+
description: 'Add to memory',
|
692
|
+
},
|
693
|
+
],
|
694
|
+
},
|
695
|
+
];
|
696
|
+
const { result } = renderHook(() => {
|
697
|
+
const textBuffer = useTextBufferForTest('/mem');
|
698
|
+
const completion = useSlashCompletion(textBuffer, testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig);
|
699
|
+
return { ...completion, textBuffer };
|
700
|
+
});
|
701
|
+
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
702
|
+
'memory',
|
703
|
+
]);
|
704
|
+
act(() => {
|
705
|
+
result.current.handleAutocomplete(0);
|
706
|
+
});
|
707
|
+
expect(result.current.textBuffer.text).toBe('/memory ');
|
708
|
+
});
|
709
|
+
it('should append a sub-command when the parent is complete', () => {
|
710
|
+
const slashCommands = [
|
711
|
+
{
|
712
|
+
name: 'memory',
|
713
|
+
description: 'Manage memory',
|
714
|
+
subCommands: [
|
715
|
+
{
|
716
|
+
name: 'show',
|
717
|
+
description: 'Show memory',
|
718
|
+
},
|
719
|
+
{
|
720
|
+
name: 'add',
|
721
|
+
description: 'Add to memory',
|
722
|
+
},
|
723
|
+
],
|
724
|
+
},
|
725
|
+
];
|
726
|
+
const { result } = renderHook(() => {
|
727
|
+
const textBuffer = useTextBufferForTest('/memory');
|
728
|
+
const completion = useSlashCompletion(textBuffer, testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig);
|
729
|
+
return { ...completion, textBuffer };
|
730
|
+
});
|
731
|
+
// Suggestions are populated by useEffect
|
732
|
+
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
733
|
+
'show',
|
734
|
+
'add',
|
735
|
+
]);
|
736
|
+
act(() => {
|
737
|
+
result.current.handleAutocomplete(1); // index 1 is 'add'
|
738
|
+
});
|
739
|
+
expect(result.current.textBuffer.text).toBe('/memory add ');
|
740
|
+
});
|
741
|
+
it('should complete a command with an alternative name', () => {
|
742
|
+
const slashCommands = [
|
743
|
+
{
|
744
|
+
name: 'memory',
|
745
|
+
description: 'Manage memory',
|
746
|
+
subCommands: [
|
747
|
+
{
|
748
|
+
name: 'show',
|
749
|
+
description: 'Show memory',
|
750
|
+
},
|
751
|
+
{
|
752
|
+
name: 'add',
|
753
|
+
description: 'Add to memory',
|
754
|
+
},
|
755
|
+
],
|
756
|
+
},
|
757
|
+
];
|
758
|
+
const { result } = renderHook(() => {
|
759
|
+
const textBuffer = useTextBufferForTest('/?');
|
760
|
+
const completion = useSlashCompletion(textBuffer, testDirs, testRootDir, slashCommands, mockCommandContext, false, mockConfig);
|
761
|
+
return { ...completion, textBuffer };
|
762
|
+
});
|
763
|
+
result.current.suggestions.push({
|
764
|
+
label: 'help',
|
765
|
+
value: 'help',
|
766
|
+
description: 'Show help',
|
767
|
+
});
|
768
|
+
act(() => {
|
769
|
+
result.current.handleAutocomplete(0);
|
770
|
+
});
|
771
|
+
expect(result.current.textBuffer.text).toBe('/help ');
|
772
|
+
});
|
773
|
+
it('should complete a file path', () => {
|
774
|
+
const { result } = renderHook(() => {
|
775
|
+
const textBuffer = useTextBufferForTest('@src/fi');
|
776
|
+
const completion = useSlashCompletion(textBuffer, testDirs, testRootDir, [], mockCommandContext, false, mockConfig);
|
777
|
+
return { ...completion, textBuffer };
|
778
|
+
});
|
779
|
+
result.current.suggestions.push({
|
780
|
+
label: 'file1.txt',
|
781
|
+
value: 'file1.txt',
|
782
|
+
});
|
783
|
+
act(() => {
|
784
|
+
result.current.handleAutocomplete(0);
|
785
|
+
});
|
786
|
+
expect(result.current.textBuffer.text).toBe('@src/file1.txt ');
|
787
|
+
});
|
788
|
+
it('should complete a file path when cursor is not at the end of the line', () => {
|
789
|
+
const text = '@src/fi le.txt';
|
790
|
+
const cursorOffset = 7; // after "i"
|
791
|
+
const { result } = renderHook(() => {
|
792
|
+
const textBuffer = useTextBufferForTest(text, cursorOffset);
|
793
|
+
const completion = useSlashCompletion(textBuffer, testDirs, testRootDir, [], mockCommandContext, false, mockConfig);
|
794
|
+
return { ...completion, textBuffer };
|
795
|
+
});
|
796
|
+
result.current.suggestions.push({
|
797
|
+
label: 'file1.txt',
|
798
|
+
value: 'file1.txt',
|
799
|
+
});
|
800
|
+
act(() => {
|
801
|
+
result.current.handleAutocomplete(0);
|
802
|
+
});
|
803
|
+
expect(result.current.textBuffer.text).toBe('@src/file1.txt le.txt');
|
804
|
+
});
|
805
|
+
it('should complete the correct file path with multiple @-commands', () => {
|
806
|
+
const text = '@file1.txt @src/fi';
|
807
|
+
const { result } = renderHook(() => {
|
808
|
+
const textBuffer = useTextBufferForTest(text);
|
809
|
+
const completion = useSlashCompletion(textBuffer, testDirs, testRootDir, [], mockCommandContext, false, mockConfig);
|
810
|
+
return { ...completion, textBuffer };
|
811
|
+
});
|
812
|
+
result.current.suggestions.push({
|
813
|
+
label: 'file2.txt',
|
814
|
+
value: 'file2.txt',
|
815
|
+
});
|
816
|
+
act(() => {
|
817
|
+
result.current.handleAutocomplete(0);
|
818
|
+
});
|
819
|
+
expect(result.current.textBuffer.text).toBe('@file1.txt @src/file2.txt ');
|
820
|
+
});
|
821
|
+
});
|
822
|
+
describe('File Path Escaping', () => {
|
823
|
+
it('should escape special characters in file names', async () => {
|
824
|
+
await createTestFile('', 'my file.txt');
|
825
|
+
await createTestFile('', 'file(1).txt');
|
826
|
+
await createTestFile('', 'backup[old].txt');
|
827
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@my'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
828
|
+
await act(async () => {
|
829
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
830
|
+
});
|
831
|
+
const suggestion = result.current.suggestions.find((s) => s.label === 'my file.txt');
|
832
|
+
expect(suggestion).toBeDefined();
|
833
|
+
expect(suggestion.value).toBe('my\\ file.txt');
|
834
|
+
});
|
835
|
+
it('should escape parentheses in file names', async () => {
|
836
|
+
await createTestFile('', 'document(final).docx');
|
837
|
+
await createTestFile('', 'script(v2).sh');
|
838
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@doc'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
839
|
+
await act(async () => {
|
840
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
841
|
+
});
|
842
|
+
const suggestion = result.current.suggestions.find((s) => s.label === 'document(final).docx');
|
843
|
+
expect(suggestion).toBeDefined();
|
844
|
+
expect(suggestion.value).toBe('document\\(final\\).docx');
|
845
|
+
});
|
846
|
+
it('should escape square brackets in file names', async () => {
|
847
|
+
await createTestFile('', 'backup[2024-01-01].zip');
|
848
|
+
await createTestFile('', 'config[dev].json');
|
849
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@backup'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
850
|
+
await act(async () => {
|
851
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
852
|
+
});
|
853
|
+
const suggestion = result.current.suggestions.find((s) => s.label === 'backup[2024-01-01].zip');
|
854
|
+
expect(suggestion).toBeDefined();
|
855
|
+
expect(suggestion.value).toBe('backup\\[2024-01-01\\].zip');
|
856
|
+
});
|
857
|
+
it('should escape multiple special characters in file names', async () => {
|
858
|
+
await createTestFile('', 'my file (backup) [v1.2].txt');
|
859
|
+
await createTestFile('', 'data & config {prod}.json');
|
860
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@my'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
861
|
+
await act(async () => {
|
862
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
863
|
+
});
|
864
|
+
const suggestion = result.current.suggestions.find((s) => s.label === 'my file (backup) [v1.2].txt');
|
865
|
+
expect(suggestion).toBeDefined();
|
866
|
+
expect(suggestion.value).toBe('my\\ file\\ \\(backup\\)\\ \\[v1.2\\].txt');
|
867
|
+
});
|
868
|
+
it('should preserve path separators while escaping special characters', async () => {
|
869
|
+
await createTestFile('', 'projects', 'my project (2024)', 'file with spaces.txt');
|
870
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@projects/my'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
871
|
+
await act(async () => {
|
872
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
873
|
+
});
|
874
|
+
const suggestion = result.current.suggestions.find((s) => s.label.includes('my project'));
|
875
|
+
expect(suggestion).toBeDefined();
|
876
|
+
// Should escape spaces and parentheses but preserve forward slashes
|
877
|
+
expect(suggestion.value).toMatch(/my\\ project\\ \\\(2024\\\)/);
|
878
|
+
expect(suggestion.value).toContain('/'); // Should contain forward slash for path separator
|
879
|
+
});
|
880
|
+
it('should normalize Windows path separators to forward slashes while preserving escaping', async () => {
|
881
|
+
// Create test with complex nested structure
|
882
|
+
await createTestFile('', 'deep', 'nested', 'special folder', 'file with (parentheses).txt');
|
883
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@deep/nested/special'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
884
|
+
await act(async () => {
|
885
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
886
|
+
});
|
887
|
+
const suggestion = result.current.suggestions.find((s) => s.label.includes('special folder'));
|
888
|
+
expect(suggestion).toBeDefined();
|
889
|
+
// Should use forward slashes for path separators and escape spaces
|
890
|
+
expect(suggestion.value).toContain('special\\ folder/');
|
891
|
+
expect(suggestion.value).not.toContain('\\\\'); // Should not contain double backslashes for path separators
|
892
|
+
});
|
893
|
+
it('should handle directory names with special characters', async () => {
|
894
|
+
await createEmptyDir('my documents (personal)');
|
895
|
+
await createEmptyDir('config [production]');
|
896
|
+
await createEmptyDir('data & logs');
|
897
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
898
|
+
await act(async () => {
|
899
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
900
|
+
});
|
901
|
+
const suggestions = result.current.suggestions;
|
902
|
+
const docSuggestion = suggestions.find((s) => s.label === 'my documents (personal)/');
|
903
|
+
expect(docSuggestion).toBeDefined();
|
904
|
+
expect(docSuggestion.value).toBe('my\\ documents\\ \\(personal\\)/');
|
905
|
+
const configSuggestion = suggestions.find((s) => s.label === 'config [production]/');
|
906
|
+
expect(configSuggestion).toBeDefined();
|
907
|
+
expect(configSuggestion.value).toBe('config\\ \\[production\\]/');
|
908
|
+
const dataSuggestion = suggestions.find((s) => s.label === 'data & logs/');
|
909
|
+
expect(dataSuggestion).toBeDefined();
|
910
|
+
expect(dataSuggestion.value).toBe('data\\ \\&\\ logs/');
|
911
|
+
});
|
912
|
+
it('should handle files with various shell metacharacters', async () => {
|
913
|
+
await createTestFile('', 'file$var.txt');
|
914
|
+
await createTestFile('', 'important!.md');
|
915
|
+
const { result } = renderHook(() => useSlashCompletion(useTextBufferForTest('@'), testDirs, testRootDir, [], mockCommandContext, false, mockConfig));
|
916
|
+
await act(async () => {
|
917
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
918
|
+
});
|
919
|
+
const suggestions = result.current.suggestions;
|
920
|
+
const dollarSuggestion = suggestions.find((s) => s.label === 'file$var.txt');
|
921
|
+
expect(dollarSuggestion).toBeDefined();
|
922
|
+
expect(dollarSuggestion.value).toBe('file\\$var.txt');
|
923
|
+
const importantSuggestion = suggestions.find((s) => s.label === 'important!.md');
|
924
|
+
expect(importantSuggestion).toBeDefined();
|
925
|
+
expect(importantSuggestion.value).toBe('important\\!.md');
|
926
|
+
});
|
927
|
+
});
|
928
|
+
});
|
929
|
+
//# sourceMappingURL=useSlashCompletion.test.js.map
|