@google/gemini-cli 0.12.0-nightly.20251023.a7faa208 → 0.12.0-nightly.20251027.cb0947c5
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/dist/package.json +5 -4
- package/dist/src/commands/extensions/disable.js +13 -6
- package/dist/src/commands/extensions/disable.js.map +1 -1
- package/dist/src/commands/extensions/enable.js +13 -6
- package/dist/src/commands/extensions/enable.js.map +1 -1
- package/dist/src/commands/extensions/install.js +12 -2
- package/dist/src/commands/extensions/install.js.map +1 -1
- package/dist/src/commands/extensions/install.test.js +11 -3
- package/dist/src/commands/extensions/install.test.js.map +1 -1
- package/dist/src/commands/extensions/link.js +12 -2
- package/dist/src/commands/extensions/link.js.map +1 -1
- package/dist/src/commands/extensions/list.js +13 -4
- package/dist/src/commands/extensions/list.js.map +1 -1
- package/dist/src/commands/extensions/uninstall.js +12 -2
- package/dist/src/commands/extensions/uninstall.js.map +1 -1
- package/dist/src/commands/extensions/update.js +17 -13
- package/dist/src/commands/extensions/update.js.map +1 -1
- package/dist/src/commands/mcp/list.js +10 -3
- package/dist/src/commands/mcp/list.js.map +1 -1
- package/dist/src/commands/mcp/list.test.js +12 -6
- package/dist/src/commands/mcp/list.test.js.map +1 -1
- package/dist/src/config/config.d.ts +1 -0
- package/dist/src/config/config.js +15 -3
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +23 -15
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/extension-manager.d.ts +38 -0
- package/dist/src/config/extension-manager.js +411 -0
- package/dist/src/config/extension-manager.js.map +1 -0
- package/dist/src/config/extension.d.ts +4 -51
- package/dist/src/config/extension.js +1 -535
- package/dist/src/config/extension.js.map +1 -1
- package/dist/src/config/extension.test.js +343 -163
- package/dist/src/config/extension.test.js.map +1 -1
- package/dist/src/config/extensions/consent.d.ts +38 -0
- package/dist/src/config/extensions/consent.js +123 -0
- package/dist/src/config/extensions/consent.js.map +1 -0
- package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
- package/dist/src/config/extensions/extensionEnablement.js +4 -3
- package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
- package/dist/src/config/extensions/extensionEnablement.test.js +10 -10
- package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
- package/dist/src/config/extensions/extensionSettings.d.ts +15 -0
- package/dist/src/config/extensions/extensionSettings.js +63 -0
- package/dist/src/config/extensions/extensionSettings.js.map +1 -0
- package/dist/src/config/extensions/extensionSettings.test.d.ts +6 -0
- package/dist/src/config/extensions/extensionSettings.test.js +137 -0
- package/dist/src/config/extensions/extensionSettings.test.js.map +1 -0
- package/dist/src/config/extensions/github.d.ts +2 -2
- package/dist/src/config/extensions/github.js +3 -8
- package/dist/src/config/extensions/github.js.map +1 -1
- package/dist/src/config/extensions/github.test.js +25 -7
- package/dist/src/config/extensions/github.test.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.d.ts +1 -1
- package/dist/src/config/extensions/github_fetch.js +13 -1
- package/dist/src/config/extensions/github_fetch.js.map +1 -1
- package/dist/src/config/extensions/github_fetch.test.d.ts +6 -0
- package/dist/src/config/extensions/github_fetch.test.js +169 -0
- package/dist/src/config/extensions/github_fetch.test.js.map +1 -0
- package/dist/src/config/extensions/storage.d.ts +14 -0
- package/dist/src/config/extensions/storage.js +32 -0
- package/dist/src/config/extensions/storage.js.map +1 -0
- package/dist/src/config/extensions/update.d.ts +4 -4
- package/dist/src/config/extensions/update.js +11 -18
- package/dist/src/config/extensions/update.js.map +1 -1
- package/dist/src/config/extensions/update.test.js +33 -58
- package/dist/src/config/extensions/update.test.js.map +1 -1
- package/dist/src/config/extensions/variableSchema.d.ts +0 -6
- package/dist/src/config/extensions/variableSchema.js.map +1 -1
- package/dist/src/config/extensions/variables.d.ts +4 -0
- package/dist/src/config/extensions/variables.js +6 -0
- package/dist/src/config/extensions/variables.js.map +1 -1
- package/dist/src/config/settings.d.ts +2 -1
- package/dist/src/config/settings.js +4 -7
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/config/settings.test.js +86 -16
- package/dist/src/config/settings.test.js.map +1 -1
- package/dist/src/core/initializer.js +2 -1
- package/dist/src/core/initializer.js.map +1 -1
- package/dist/src/gemini.js +22 -5
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/gemini.test.js +26 -2
- package/dist/src/gemini.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/nonInteractiveCli.js +30 -5
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/nonInteractiveCli.test.js +151 -14
- package/dist/src/nonInteractiveCli.test.js.map +1 -1
- package/dist/src/test-utils/createExtension.d.ts +3 -1
- package/dist/src/test-utils/createExtension.js +3 -3
- package/dist/src/test-utils/createExtension.js.map +1 -1
- package/dist/src/ui/AppContainer.js +102 -48
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/AppContainer.test.js +138 -79
- package/dist/src/ui/AppContainer.test.js.map +1 -1
- package/dist/src/ui/commands/extensionsCommand.js +19 -10
- package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
- package/dist/src/ui/commands/extensionsCommand.test.js +8 -0
- package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
- package/dist/src/ui/components/FolderTrustDialog.test.js +1 -0
- package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +5 -6
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.test.js +668 -712
- package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.test.js +1 -0
- package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
- package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +1 -0
- package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
- package/dist/src/ui/components/SettingsDialog.test.js +2 -30
- package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
- package/dist/src/ui/components/ThemeDialog.test.js +1 -2
- package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
- package/dist/src/ui/components/shared/BaseSelectionList.test.js +1 -0
- package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
- package/dist/src/ui/components/shared/text-buffer.test.js +1 -0
- package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
- package/dist/src/ui/components/views/ExtensionsList.d.ts +7 -1
- package/dist/src/ui/components/views/ExtensionsList.js +8 -11
- package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
- package/dist/src/ui/components/views/ExtensionsList.test.js +34 -21
- package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.d.ts +3 -2
- package/dist/src/ui/contexts/KeypressContext.js +429 -386
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.test.js +162 -479
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
- package/dist/src/ui/contexts/SessionContext.test.js +1 -0
- package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
- package/dist/src/ui/hooks/shellCommandProcessor.test.js +18 -2
- package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.test.js +74 -80
- package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +1 -0
- package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useCommandCompletion.test.js +79 -78
- package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useConsoleMessages.test.js +26 -9
- package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
- package/dist/src/ui/hooks/useEditorSettings.test.js +40 -34
- package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
- package/dist/src/ui/hooks/useExtensionUpdates.d.ts +15 -5
- package/dist/src/ui/hooks/useExtensionUpdates.js +16 -18
- package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
- package/dist/src/ui/hooks/useExtensionUpdates.test.js +46 -36
- package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
- package/dist/src/ui/hooks/useFlickerDetector.test.js +1 -0
- package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
- package/dist/src/ui/hooks/useFocus.test.js +25 -9
- package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
- package/dist/src/ui/hooks/useFolderTrust.test.js +1 -0
- package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +49 -14
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +114 -34
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useGitBranchName.js +4 -0
- package/dist/src/ui/hooks/useGitBranchName.js.map +1 -1
- package/dist/src/ui/hooks/useGitBranchName.test.js +46 -35
- package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
- package/dist/src/ui/hooks/useHistoryManager.test.js +1 -0
- package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
- package/dist/src/ui/hooks/useIdeTrustListener.test.js +24 -7
- package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
- package/dist/src/ui/hooks/useInputHistory.test.js +1 -0
- package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
- package/dist/src/ui/hooks/useInputHistoryStore.test.js +1 -0
- package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
- package/dist/src/ui/hooks/useKeypress.test.js +94 -113
- package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.test.js +24 -6
- package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useMemoryMonitor.test.js +10 -5
- package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
- package/dist/src/ui/hooks/useMessageQueue.test.js +61 -45
- package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
- package/dist/src/ui/hooks/useModelCommand.test.js +18 -11
- package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +1 -0
- package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/usePhraseCycler.test.js +1 -0
- package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
- package/dist/src/ui/hooks/usePrivacySettings.test.js +26 -10
- package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.js +13 -14
- package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.test.js +32 -39
- package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.d.ts +8 -1
- package/dist/src/ui/hooks/useReactToolScheduler.js +59 -34
- package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.test.d.ts +6 -0
- package/dist/src/ui/hooks/useReactToolScheduler.test.js +66 -0
- package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -0
- package/dist/src/ui/hooks/useSelectionList.test.js +193 -132
- package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
- package/dist/src/ui/hooks/useShellHistory.test.js +1 -0
- package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
- package/dist/src/ui/hooks/useTimer.test.js +43 -14
- package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.js +122 -39
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
- package/dist/src/ui/hooks/vim.test.js +251 -356
- package/dist/src/ui/hooks/vim.test.js.map +1 -1
- package/dist/src/ui/types.d.ts +2 -1
- package/dist/src/ui/types.js.map +1 -1
- package/dist/src/ui/utils/CodeColorizer.js +2 -1
- package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
- package/dist/src/ui/utils/textOutput.d.ts +25 -0
- package/dist/src/ui/utils/textOutput.js +49 -0
- package/dist/src/ui/utils/textOutput.js.map +1 -0
- package/dist/src/ui/utils/textOutput.test.d.ts +6 -0
- package/dist/src/ui/utils/textOutput.test.js +79 -0
- package/dist/src/ui/utils/textOutput.test.js.map +1 -0
- package/dist/src/ui/utils/updateCheck.d.ts +7 -1
- package/dist/src/ui/utils/updateCheck.js +27 -26
- package/dist/src/ui/utils/updateCheck.js.map +1 -1
- package/dist/src/ui/utils/updateCheck.test.js +19 -49
- package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
- package/dist/src/utils/envVarResolver.d.ts +2 -2
- package/dist/src/utils/envVarResolver.js +10 -7
- package/dist/src/utils/envVarResolver.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.js +9 -3
- package/dist/src/utils/handleAutoUpdate.js.map +1 -1
- package/dist/src/zed-integration/schema.d.ts +4 -4
- package/dist/src/zed-integration/zedIntegration.js +8 -13
- package/dist/src/zed-integration/zedIntegration.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/dist/google-gemini-cli-0.12.0-nightly.20251022.0542de95.tgz +0 -0
|
@@ -5,7 +5,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
5
5
|
* SPDX-License-Identifier: Apache-2.0
|
|
6
6
|
*/
|
|
7
7
|
import { renderWithProviders } from '../../test-utils/render.js';
|
|
8
|
-
import {
|
|
8
|
+
import { act } from 'react';
|
|
9
9
|
import { InputPrompt } from './InputPrompt.js';
|
|
10
10
|
import { ApprovalMode } from '@google/gemini-cli-core';
|
|
11
11
|
import * as path from 'node:path';
|
|
@@ -207,63 +207,68 @@ describe('InputPrompt', () => {
|
|
|
207
207
|
streamingState: StreamingState.Idle,
|
|
208
208
|
};
|
|
209
209
|
});
|
|
210
|
-
const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
211
210
|
it('should call shellHistory.getPreviousCommand on up arrow in shell mode', async () => {
|
|
212
211
|
props.shellModeActive = true;
|
|
213
212
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
214
|
-
await
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
expect(mockShellHistory.getPreviousCommand).toHaveBeenCalled();
|
|
213
|
+
await act(async () => {
|
|
214
|
+
stdin.write('\u001B[A');
|
|
215
|
+
});
|
|
216
|
+
await vi.waitFor(() => expect(mockShellHistory.getPreviousCommand).toHaveBeenCalled());
|
|
218
217
|
unmount();
|
|
219
218
|
});
|
|
220
219
|
it('should call shellHistory.getNextCommand on down arrow in shell mode', async () => {
|
|
221
220
|
props.shellModeActive = true;
|
|
222
221
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
223
|
-
await
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
expect(mockShellHistory.getNextCommand).toHaveBeenCalled();
|
|
222
|
+
await act(async () => {
|
|
223
|
+
stdin.write('\u001B[B');
|
|
224
|
+
});
|
|
225
|
+
await vi.waitFor(() => expect(mockShellHistory.getNextCommand).toHaveBeenCalled());
|
|
227
226
|
unmount();
|
|
228
227
|
});
|
|
229
228
|
it('should set the buffer text when a shell history command is retrieved', async () => {
|
|
230
229
|
props.shellModeActive = true;
|
|
231
230
|
vi.mocked(mockShellHistory.getPreviousCommand).mockReturnValue('previous command');
|
|
232
231
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
233
|
-
await
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
await act(async () => {
|
|
233
|
+
stdin.write('\u001B[A');
|
|
234
|
+
});
|
|
235
|
+
await vi.waitFor(() => {
|
|
236
|
+
expect(mockShellHistory.getPreviousCommand).toHaveBeenCalled();
|
|
237
|
+
expect(props.buffer.setText).toHaveBeenCalledWith('previous command');
|
|
238
|
+
});
|
|
238
239
|
unmount();
|
|
239
240
|
});
|
|
240
241
|
it('should call shellHistory.addCommandToHistory on submit in shell mode', async () => {
|
|
241
242
|
props.shellModeActive = true;
|
|
242
243
|
props.buffer.setText('ls -l');
|
|
243
244
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
244
|
-
await
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
await act(async () => {
|
|
246
|
+
stdin.write('\r');
|
|
247
|
+
});
|
|
248
|
+
await vi.waitFor(() => {
|
|
249
|
+
expect(mockShellHistory.addCommandToHistory).toHaveBeenCalledWith('ls -l');
|
|
250
|
+
expect(props.onSubmit).toHaveBeenCalledWith('ls -l');
|
|
251
|
+
});
|
|
249
252
|
unmount();
|
|
250
253
|
});
|
|
251
254
|
it('should NOT call shell history methods when not in shell mode', async () => {
|
|
252
255
|
props.buffer.setText('some text');
|
|
253
256
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
254
|
-
await
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
await
|
|
259
|
-
|
|
260
|
-
|
|
257
|
+
await act(async () => {
|
|
258
|
+
stdin.write('\u001B[A'); // Up arrow
|
|
259
|
+
});
|
|
260
|
+
await vi.waitFor(() => expect(mockInputHistory.navigateUp).toHaveBeenCalled());
|
|
261
|
+
await act(async () => {
|
|
262
|
+
stdin.write('\u001B[B'); // Down arrow
|
|
263
|
+
});
|
|
264
|
+
await vi.waitFor(() => expect(mockInputHistory.navigateDown).toHaveBeenCalled());
|
|
265
|
+
await act(async () => {
|
|
266
|
+
stdin.write('\r'); // Enter
|
|
267
|
+
});
|
|
268
|
+
await vi.waitFor(() => expect(props.onSubmit).toHaveBeenCalledWith('some text'));
|
|
261
269
|
expect(mockShellHistory.getPreviousCommand).not.toHaveBeenCalled();
|
|
262
270
|
expect(mockShellHistory.getNextCommand).not.toHaveBeenCalled();
|
|
263
271
|
expect(mockShellHistory.addCommandToHistory).not.toHaveBeenCalled();
|
|
264
|
-
expect(mockInputHistory.navigateUp).toHaveBeenCalled();
|
|
265
|
-
expect(mockInputHistory.navigateDown).toHaveBeenCalled();
|
|
266
|
-
expect(props.onSubmit).toHaveBeenCalledWith('some text');
|
|
267
272
|
unmount();
|
|
268
273
|
});
|
|
269
274
|
it('should call completion.navigateUp for both up arrow and Ctrl+P when suggestions are showing', async () => {
|
|
@@ -277,13 +282,15 @@ describe('InputPrompt', () => {
|
|
|
277
282
|
});
|
|
278
283
|
props.buffer.setText('/mem');
|
|
279
284
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
280
|
-
await wait();
|
|
281
285
|
// Test up arrow
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
await
|
|
286
|
-
|
|
286
|
+
await act(async () => {
|
|
287
|
+
stdin.write('\u001B[A'); // Up arrow
|
|
288
|
+
});
|
|
289
|
+
await vi.waitFor(() => expect(mockCommandCompletion.navigateUp).toHaveBeenCalledTimes(1));
|
|
290
|
+
await act(async () => {
|
|
291
|
+
stdin.write('\u0010'); // Ctrl+P
|
|
292
|
+
});
|
|
293
|
+
await vi.waitFor(() => expect(mockCommandCompletion.navigateUp).toHaveBeenCalledTimes(2));
|
|
287
294
|
expect(mockCommandCompletion.navigateDown).not.toHaveBeenCalled();
|
|
288
295
|
unmount();
|
|
289
296
|
});
|
|
@@ -298,13 +305,15 @@ describe('InputPrompt', () => {
|
|
|
298
305
|
});
|
|
299
306
|
props.buffer.setText('/mem');
|
|
300
307
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
301
|
-
await wait();
|
|
302
308
|
// Test down arrow
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
await
|
|
307
|
-
|
|
309
|
+
await act(async () => {
|
|
310
|
+
stdin.write('\u001B[B'); // Down arrow
|
|
311
|
+
});
|
|
312
|
+
await vi.waitFor(() => expect(mockCommandCompletion.navigateDown).toHaveBeenCalledTimes(1));
|
|
313
|
+
await act(async () => {
|
|
314
|
+
stdin.write('\u000E'); // Ctrl+N
|
|
315
|
+
});
|
|
316
|
+
await vi.waitFor(() => expect(mockCommandCompletion.navigateDown).toHaveBeenCalledTimes(2));
|
|
308
317
|
expect(mockCommandCompletion.navigateUp).not.toHaveBeenCalled();
|
|
309
318
|
unmount();
|
|
310
319
|
});
|
|
@@ -315,15 +324,22 @@ describe('InputPrompt', () => {
|
|
|
315
324
|
});
|
|
316
325
|
props.buffer.setText('some text');
|
|
317
326
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
318
|
-
await
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
await
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
await
|
|
327
|
+
await act(async () => {
|
|
328
|
+
stdin.write('\u001B[A'); // Up arrow
|
|
329
|
+
});
|
|
330
|
+
await vi.waitFor(() => expect(mockInputHistory.navigateUp).toHaveBeenCalled());
|
|
331
|
+
await act(async () => {
|
|
332
|
+
stdin.write('\u001B[B'); // Down arrow
|
|
333
|
+
});
|
|
334
|
+
await vi.waitFor(() => expect(mockInputHistory.navigateDown).toHaveBeenCalled());
|
|
335
|
+
await act(async () => {
|
|
336
|
+
stdin.write('\u0010'); // Ctrl+P
|
|
337
|
+
});
|
|
338
|
+
await vi.waitFor(() => { });
|
|
339
|
+
await act(async () => {
|
|
340
|
+
stdin.write('\u000E'); // Ctrl+N
|
|
341
|
+
});
|
|
342
|
+
await vi.waitFor(() => { });
|
|
327
343
|
expect(mockCommandCompletion.navigateUp).not.toHaveBeenCalled();
|
|
328
344
|
expect(mockCommandCompletion.navigateDown).not.toHaveBeenCalled();
|
|
329
345
|
unmount();
|
|
@@ -338,23 +354,27 @@ describe('InputPrompt', () => {
|
|
|
338
354
|
vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true);
|
|
339
355
|
vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue('/test/.gemini-clipboard/clipboard-123.png');
|
|
340
356
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
341
|
-
await wait();
|
|
342
357
|
// Send Ctrl+V
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
358
|
+
await act(async () => {
|
|
359
|
+
stdin.write('\x16'); // Ctrl+V
|
|
360
|
+
});
|
|
361
|
+
await vi.waitFor(() => {
|
|
362
|
+
expect(clipboardUtils.clipboardHasImage).toHaveBeenCalled();
|
|
363
|
+
expect(clipboardUtils.saveClipboardImage).toHaveBeenCalledWith(props.config.getTargetDir());
|
|
364
|
+
expect(clipboardUtils.cleanupOldClipboardImages).toHaveBeenCalledWith(props.config.getTargetDir());
|
|
365
|
+
expect(mockBuffer.replaceRangeByOffset).toHaveBeenCalled();
|
|
366
|
+
});
|
|
349
367
|
unmount();
|
|
350
368
|
});
|
|
351
369
|
it('should not insert anything when clipboard has no image', async () => {
|
|
352
370
|
vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(false);
|
|
353
371
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
354
|
-
await
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
372
|
+
await act(async () => {
|
|
373
|
+
stdin.write('\x16'); // Ctrl+V
|
|
374
|
+
});
|
|
375
|
+
await vi.waitFor(() => {
|
|
376
|
+
expect(clipboardUtils.clipboardHasImage).toHaveBeenCalled();
|
|
377
|
+
});
|
|
358
378
|
expect(clipboardUtils.saveClipboardImage).not.toHaveBeenCalled();
|
|
359
379
|
expect(mockBuffer.setText).not.toHaveBeenCalled();
|
|
360
380
|
unmount();
|
|
@@ -363,10 +383,12 @@ describe('InputPrompt', () => {
|
|
|
363
383
|
vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true);
|
|
364
384
|
vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue(null);
|
|
365
385
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
366
|
-
await
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
386
|
+
await act(async () => {
|
|
387
|
+
stdin.write('\x16'); // Ctrl+V
|
|
388
|
+
});
|
|
389
|
+
await vi.waitFor(() => {
|
|
390
|
+
expect(clipboardUtils.saveClipboardImage).toHaveBeenCalled();
|
|
391
|
+
});
|
|
370
392
|
expect(mockBuffer.setText).not.toHaveBeenCalled();
|
|
371
393
|
unmount();
|
|
372
394
|
});
|
|
@@ -380,11 +402,13 @@ describe('InputPrompt', () => {
|
|
|
380
402
|
mockBuffer.lines = ['Hello world'];
|
|
381
403
|
mockBuffer.replaceRangeByOffset = vi.fn();
|
|
382
404
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
383
|
-
await
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
405
|
+
await act(async () => {
|
|
406
|
+
stdin.write('\x16'); // Ctrl+V
|
|
407
|
+
});
|
|
408
|
+
await vi.waitFor(() => {
|
|
409
|
+
// Should insert at cursor position with spaces
|
|
410
|
+
expect(mockBuffer.replaceRangeByOffset).toHaveBeenCalled();
|
|
411
|
+
});
|
|
388
412
|
// Get the actual call to see what path was used
|
|
389
413
|
const actualCall = vi.mocked(mockBuffer.replaceRangeByOffset).mock
|
|
390
414
|
.calls[0];
|
|
@@ -399,10 +423,12 @@ describe('InputPrompt', () => {
|
|
|
399
423
|
.mockImplementation(() => { });
|
|
400
424
|
vi.mocked(clipboardUtils.clipboardHasImage).mockRejectedValue(new Error('Clipboard error'));
|
|
401
425
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
402
|
-
await
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
426
|
+
await act(async () => {
|
|
427
|
+
stdin.write('\x16'); // Ctrl+V
|
|
428
|
+
});
|
|
429
|
+
await vi.waitFor(() => {
|
|
430
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error handling clipboard image:', expect.any(Error));
|
|
431
|
+
});
|
|
406
432
|
expect(mockBuffer.setText).not.toHaveBeenCalled();
|
|
407
433
|
consoleErrorSpy.mockRestore();
|
|
408
434
|
unmount();
|
|
@@ -418,10 +444,10 @@ describe('InputPrompt', () => {
|
|
|
418
444
|
});
|
|
419
445
|
props.buffer.setText('/mem');
|
|
420
446
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
421
|
-
await
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
|
|
447
|
+
await act(async () => {
|
|
448
|
+
stdin.write('\t'); // Press Tab
|
|
449
|
+
});
|
|
450
|
+
await vi.waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
|
|
425
451
|
unmount();
|
|
426
452
|
});
|
|
427
453
|
it('should append a sub-command when the parent command is already complete', async () => {
|
|
@@ -437,10 +463,10 @@ describe('InputPrompt', () => {
|
|
|
437
463
|
});
|
|
438
464
|
props.buffer.setText('/memory ');
|
|
439
465
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
440
|
-
await
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(1);
|
|
466
|
+
await act(async () => {
|
|
467
|
+
stdin.write('\t'); // Press Tab
|
|
468
|
+
});
|
|
469
|
+
await vi.waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(1));
|
|
444
470
|
unmount();
|
|
445
471
|
});
|
|
446
472
|
it('should handle the "backspace" edge case correctly', async () => {
|
|
@@ -457,11 +483,12 @@ describe('InputPrompt', () => {
|
|
|
457
483
|
// The user has backspaced, so the query is now just '/memory'
|
|
458
484
|
props.buffer.setText('/memory');
|
|
459
485
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
460
|
-
await
|
|
461
|
-
|
|
462
|
-
|
|
486
|
+
await act(async () => {
|
|
487
|
+
stdin.write('\t'); // Press Tab
|
|
488
|
+
});
|
|
489
|
+
await vi.waitFor(() =>
|
|
463
490
|
// It should NOT become '/show'. It should correctly become '/memory show'.
|
|
464
|
-
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
|
|
491
|
+
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
|
|
465
492
|
unmount();
|
|
466
493
|
});
|
|
467
494
|
it('should complete a partial argument for a command', async () => {
|
|
@@ -474,10 +501,10 @@ describe('InputPrompt', () => {
|
|
|
474
501
|
});
|
|
475
502
|
props.buffer.setText('/chat resume fi-');
|
|
476
503
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
477
|
-
await
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
|
|
504
|
+
await act(async () => {
|
|
505
|
+
stdin.write('\t'); // Press Tab
|
|
506
|
+
});
|
|
507
|
+
await vi.waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
|
|
481
508
|
unmount();
|
|
482
509
|
});
|
|
483
510
|
it('should autocomplete on Enter when suggestions are active, without submitting', async () => {
|
|
@@ -489,11 +516,13 @@ describe('InputPrompt', () => {
|
|
|
489
516
|
});
|
|
490
517
|
props.buffer.setText('/mem');
|
|
491
518
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
492
|
-
await
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
519
|
+
await act(async () => {
|
|
520
|
+
stdin.write('\r');
|
|
521
|
+
});
|
|
522
|
+
await vi.waitFor(() => {
|
|
523
|
+
// The app should autocomplete the text, NOT submit.
|
|
524
|
+
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
|
|
525
|
+
});
|
|
497
526
|
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
498
527
|
unmount();
|
|
499
528
|
});
|
|
@@ -514,18 +543,19 @@ describe('InputPrompt', () => {
|
|
|
514
543
|
});
|
|
515
544
|
props.buffer.setText('/?');
|
|
516
545
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
517
|
-
await
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
|
|
546
|
+
await act(async () => {
|
|
547
|
+
stdin.write('\t'); // Press Tab for autocomplete
|
|
548
|
+
});
|
|
549
|
+
await vi.waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
|
|
521
550
|
unmount();
|
|
522
551
|
});
|
|
523
552
|
it('should not submit on Enter when the buffer is empty or only contains whitespace', async () => {
|
|
524
553
|
props.buffer.setText(' '); // Set buffer to whitespace
|
|
525
554
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
526
|
-
await
|
|
527
|
-
|
|
528
|
-
|
|
555
|
+
await act(async () => {
|
|
556
|
+
stdin.write('\r'); // Press Enter
|
|
557
|
+
});
|
|
558
|
+
await vi.waitFor(() => { });
|
|
529
559
|
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
530
560
|
unmount();
|
|
531
561
|
});
|
|
@@ -537,10 +567,10 @@ describe('InputPrompt', () => {
|
|
|
537
567
|
});
|
|
538
568
|
props.buffer.setText('/clear');
|
|
539
569
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
540
|
-
await
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
expect(props.onSubmit).toHaveBeenCalledWith('/clear');
|
|
570
|
+
await act(async () => {
|
|
571
|
+
stdin.write('\r');
|
|
572
|
+
});
|
|
573
|
+
await vi.waitFor(() => expect(props.onSubmit).toHaveBeenCalledWith('/clear'));
|
|
544
574
|
unmount();
|
|
545
575
|
});
|
|
546
576
|
it('should submit directly on Enter when a complete leaf command is typed', async () => {
|
|
@@ -551,10 +581,10 @@ describe('InputPrompt', () => {
|
|
|
551
581
|
});
|
|
552
582
|
props.buffer.setText('/clear');
|
|
553
583
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
554
|
-
await
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
expect(props.onSubmit).toHaveBeenCalledWith('/clear');
|
|
584
|
+
await act(async () => {
|
|
585
|
+
stdin.write('\r');
|
|
586
|
+
});
|
|
587
|
+
await vi.waitFor(() => expect(props.onSubmit).toHaveBeenCalledWith('/clear'));
|
|
558
588
|
unmount();
|
|
559
589
|
});
|
|
560
590
|
it('should autocomplete an @-path on Enter without submitting', async () => {
|
|
@@ -566,10 +596,10 @@ describe('InputPrompt', () => {
|
|
|
566
596
|
});
|
|
567
597
|
props.buffer.setText('@src/components/');
|
|
568
598
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
569
|
-
await
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
|
|
599
|
+
await act(async () => {
|
|
600
|
+
stdin.write('\r');
|
|
601
|
+
});
|
|
602
|
+
await vi.waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
|
|
573
603
|
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
574
604
|
unmount();
|
|
575
605
|
});
|
|
@@ -579,243 +609,134 @@ describe('InputPrompt', () => {
|
|
|
579
609
|
mockBuffer.cursor = [0, 11];
|
|
580
610
|
mockBuffer.lines = ['first line\\'];
|
|
581
611
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
582
|
-
await
|
|
583
|
-
|
|
584
|
-
|
|
612
|
+
await act(async () => {
|
|
613
|
+
stdin.write('\r');
|
|
614
|
+
});
|
|
615
|
+
await vi.waitFor(() => {
|
|
616
|
+
expect(props.buffer.backspace).toHaveBeenCalled();
|
|
617
|
+
expect(props.buffer.newline).toHaveBeenCalled();
|
|
618
|
+
});
|
|
585
619
|
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
586
|
-
expect(props.buffer.backspace).toHaveBeenCalled();
|
|
587
|
-
expect(props.buffer.newline).toHaveBeenCalled();
|
|
588
620
|
unmount();
|
|
589
621
|
});
|
|
590
622
|
it('should clear the buffer on Ctrl+C if it has text', async () => {
|
|
591
623
|
props.buffer.setText('some text to clear');
|
|
592
624
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
593
|
-
await
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
625
|
+
await act(async () => {
|
|
626
|
+
stdin.write('\x03'); // Ctrl+C character
|
|
627
|
+
});
|
|
628
|
+
await vi.waitFor(() => {
|
|
629
|
+
expect(props.buffer.setText).toHaveBeenCalledWith('');
|
|
630
|
+
expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
|
|
631
|
+
});
|
|
598
632
|
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
599
633
|
unmount();
|
|
600
634
|
});
|
|
601
635
|
it('should NOT clear the buffer on Ctrl+C if it is empty', async () => {
|
|
602
636
|
props.buffer.text = '';
|
|
603
637
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
604
|
-
await
|
|
605
|
-
|
|
606
|
-
|
|
638
|
+
await act(async () => {
|
|
639
|
+
stdin.write('\x03'); // Ctrl+C character
|
|
640
|
+
});
|
|
641
|
+
await vi.waitFor(() => { });
|
|
607
642
|
expect(props.buffer.setText).not.toHaveBeenCalled();
|
|
608
643
|
unmount();
|
|
609
644
|
});
|
|
610
645
|
describe('cursor-based completion trigger', () => {
|
|
611
|
-
it(
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
617
|
-
...mockCommandCompletion,
|
|
646
|
+
it.each([
|
|
647
|
+
{
|
|
648
|
+
name: 'should trigger completion when cursor is after @ without spaces',
|
|
649
|
+
text: '@src/components',
|
|
650
|
+
cursor: [0, 15],
|
|
618
651
|
showSuggestions: true,
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
|
|
625
|
-
unmount();
|
|
626
|
-
});
|
|
627
|
-
it('should trigger completion when cursor is after / without spaces', async () => {
|
|
628
|
-
mockBuffer.text = '/memory';
|
|
629
|
-
mockBuffer.lines = ['/memory'];
|
|
630
|
-
mockBuffer.cursor = [0, 7];
|
|
631
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
632
|
-
...mockCommandCompletion,
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: 'should trigger completion when cursor is after / without spaces',
|
|
655
|
+
text: '/memory',
|
|
656
|
+
cursor: [0, 7],
|
|
633
657
|
showSuggestions: true,
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
unmount();
|
|
640
|
-
});
|
|
641
|
-
it('should NOT trigger completion when cursor is after space following @', async () => {
|
|
642
|
-
mockBuffer.text = '@src/file.ts hello';
|
|
643
|
-
mockBuffer.lines = ['@src/file.ts hello'];
|
|
644
|
-
mockBuffer.cursor = [0, 18];
|
|
645
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
646
|
-
...mockCommandCompletion,
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
name: 'should NOT trigger completion when cursor is after space following @',
|
|
661
|
+
text: '@src/file.ts hello',
|
|
662
|
+
cursor: [0, 18],
|
|
647
663
|
showSuggestions: false,
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
unmount();
|
|
654
|
-
});
|
|
655
|
-
it('should NOT trigger completion when cursor is after space following /', async () => {
|
|
656
|
-
mockBuffer.text = '/memory add';
|
|
657
|
-
mockBuffer.lines = ['/memory add'];
|
|
658
|
-
mockBuffer.cursor = [0, 11];
|
|
659
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
660
|
-
...mockCommandCompletion,
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: 'should NOT trigger completion when cursor is after space following /',
|
|
667
|
+
text: '/memory add',
|
|
668
|
+
cursor: [0, 11],
|
|
661
669
|
showSuggestions: false,
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
unmount();
|
|
668
|
-
});
|
|
669
|
-
it('should NOT trigger completion when cursor is not after @ or /', async () => {
|
|
670
|
-
mockBuffer.text = 'hello world';
|
|
671
|
-
mockBuffer.lines = ['hello world'];
|
|
672
|
-
mockBuffer.cursor = [0, 5];
|
|
673
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
674
|
-
...mockCommandCompletion,
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: 'should NOT trigger completion when cursor is not after @ or /',
|
|
673
|
+
text: 'hello world',
|
|
674
|
+
cursor: [0, 5],
|
|
675
675
|
showSuggestions: false,
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
unmount();
|
|
682
|
-
});
|
|
683
|
-
it('should handle multiline text correctly', async () => {
|
|
684
|
-
mockBuffer.text = 'first line\n/memory';
|
|
685
|
-
mockBuffer.lines = ['first line', '/memory'];
|
|
686
|
-
mockBuffer.cursor = [1, 7];
|
|
687
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
688
|
-
...mockCommandCompletion,
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
name: 'should handle multiline text correctly',
|
|
679
|
+
text: 'first line\n/memory',
|
|
680
|
+
cursor: [1, 7],
|
|
689
681
|
showSuggestions: false,
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
|
|
696
|
-
unmount();
|
|
697
|
-
});
|
|
698
|
-
it('should handle single line slash command correctly', async () => {
|
|
699
|
-
mockBuffer.text = '/memory';
|
|
700
|
-
mockBuffer.lines = ['/memory'];
|
|
701
|
-
mockBuffer.cursor = [0, 7];
|
|
702
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
703
|
-
...mockCommandCompletion,
|
|
704
|
-
showSuggestions: true,
|
|
705
|
-
suggestions: [{ label: 'show', value: 'show' }],
|
|
706
|
-
});
|
|
707
|
-
const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
708
|
-
await wait();
|
|
709
|
-
expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
|
|
710
|
-
unmount();
|
|
711
|
-
});
|
|
712
|
-
it('should handle Unicode characters (emojis) correctly in paths', async () => {
|
|
713
|
-
// Test with emoji in path after @
|
|
714
|
-
mockBuffer.text = '@src/file👍.txt';
|
|
715
|
-
mockBuffer.lines = ['@src/file👍.txt'];
|
|
716
|
-
mockBuffer.cursor = [0, 14]; // After the emoji character
|
|
717
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
718
|
-
...mockCommandCompletion,
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
name: 'should handle Unicode characters (emojis) correctly in paths',
|
|
685
|
+
text: '@src/file👍.txt',
|
|
686
|
+
cursor: [0, 14],
|
|
719
687
|
showSuggestions: true,
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
unmount();
|
|
726
|
-
});
|
|
727
|
-
it('should handle Unicode characters with spaces after them', async () => {
|
|
728
|
-
// Test with emoji followed by space - should NOT trigger completion
|
|
729
|
-
mockBuffer.text = '@src/file👍.txt hello';
|
|
730
|
-
mockBuffer.lines = ['@src/file👍.txt hello'];
|
|
731
|
-
mockBuffer.cursor = [0, 20]; // After the space
|
|
732
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
733
|
-
...mockCommandCompletion,
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
name: 'should handle Unicode characters with spaces after them',
|
|
691
|
+
text: '@src/file👍.txt hello',
|
|
692
|
+
cursor: [0, 20],
|
|
734
693
|
showSuggestions: false,
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
unmount();
|
|
741
|
-
});
|
|
742
|
-
it('should handle escaped spaces in paths correctly', async () => {
|
|
743
|
-
// Test with escaped space in path - should trigger completion
|
|
744
|
-
mockBuffer.text = '@src/my\\ file.txt';
|
|
745
|
-
mockBuffer.lines = ['@src/my\\ file.txt'];
|
|
746
|
-
mockBuffer.cursor = [0, 16]; // After the escaped space and filename
|
|
747
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
748
|
-
...mockCommandCompletion,
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
name: 'should handle escaped spaces in paths correctly',
|
|
697
|
+
text: '@src/my\\ file.txt',
|
|
698
|
+
cursor: [0, 16],
|
|
749
699
|
showSuggestions: true,
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
unmount();
|
|
756
|
-
});
|
|
757
|
-
it('should NOT trigger completion after unescaped space following escaped space', async () => {
|
|
758
|
-
// Test: @path/my\ file.txt hello (unescaped space after escaped space)
|
|
759
|
-
mockBuffer.text = '@path/my\\ file.txt hello';
|
|
760
|
-
mockBuffer.lines = ['@path/my\\ file.txt hello'];
|
|
761
|
-
mockBuffer.cursor = [0, 24]; // After "hello"
|
|
762
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
763
|
-
...mockCommandCompletion,
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
name: 'should NOT trigger completion after unescaped space following escaped space',
|
|
703
|
+
text: '@path/my\\ file.txt hello',
|
|
704
|
+
cursor: [0, 24],
|
|
764
705
|
showSuggestions: false,
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
unmount();
|
|
771
|
-
});
|
|
772
|
-
it('should handle multiple escaped spaces in paths', async () => {
|
|
773
|
-
// Test with multiple escaped spaces
|
|
774
|
-
mockBuffer.text = '@docs/my\\ long\\ file\\ name.md';
|
|
775
|
-
mockBuffer.lines = ['@docs/my\\ long\\ file\\ name.md'];
|
|
776
|
-
mockBuffer.cursor = [0, 29]; // At the end
|
|
777
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
778
|
-
...mockCommandCompletion,
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
name: 'should handle multiple escaped spaces in paths',
|
|
709
|
+
text: '@docs/my\\ long\\ file\\ name.md',
|
|
710
|
+
cursor: [0, 29],
|
|
779
711
|
showSuggestions: true,
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
await wait();
|
|
786
|
-
expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
|
|
787
|
-
unmount();
|
|
788
|
-
});
|
|
789
|
-
it('should handle escaped spaces in slash commands', async () => {
|
|
790
|
-
// Test escaped spaces with slash commands (though less common)
|
|
791
|
-
mockBuffer.text = '/memory\\ test';
|
|
792
|
-
mockBuffer.lines = ['/memory\\ test'];
|
|
793
|
-
mockBuffer.cursor = [0, 13]; // At the end
|
|
794
|
-
mockedUseCommandCompletion.mockReturnValue({
|
|
795
|
-
...mockCommandCompletion,
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: 'should handle escaped spaces in slash commands',
|
|
715
|
+
text: '/memory\\ test',
|
|
716
|
+
cursor: [0, 13],
|
|
796
717
|
showSuggestions: true,
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
mockBuffer.
|
|
807
|
-
mockBuffer.
|
|
808
|
-
mockBuffer.cursor = [0, 25]; // After the escaped space and emoji
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
name: 'should handle Unicode characters with escaped spaces',
|
|
721
|
+
text: `@${path.join('files', 'emoji\\ 👍\\ test.txt')}`,
|
|
722
|
+
cursor: [0, 25],
|
|
723
|
+
showSuggestions: true,
|
|
724
|
+
},
|
|
725
|
+
])('$name', async ({ text, cursor, showSuggestions }) => {
|
|
726
|
+
mockBuffer.text = text;
|
|
727
|
+
mockBuffer.lines = text.split('\n');
|
|
728
|
+
mockBuffer.cursor = cursor;
|
|
809
729
|
mockedUseCommandCompletion.mockReturnValue({
|
|
810
730
|
...mockCommandCompletion,
|
|
811
|
-
showSuggestions
|
|
812
|
-
suggestions:
|
|
813
|
-
{ label: '
|
|
814
|
-
|
|
731
|
+
showSuggestions,
|
|
732
|
+
suggestions: showSuggestions
|
|
733
|
+
? [{ label: 'suggestion', value: 'suggestion' }]
|
|
734
|
+
: [],
|
|
815
735
|
});
|
|
816
736
|
const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
817
|
-
await
|
|
818
|
-
|
|
737
|
+
await vi.waitFor(() => {
|
|
738
|
+
expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
|
|
739
|
+
});
|
|
819
740
|
unmount();
|
|
820
741
|
});
|
|
821
742
|
});
|
|
@@ -823,32 +744,38 @@ describe('InputPrompt', () => {
|
|
|
823
744
|
it('should not call buffer.handleInput when vim mode is enabled and vim handles the input', async () => {
|
|
824
745
|
props.vimHandleInput = vi.fn().mockReturnValue(true); // Mock that vim handled it.
|
|
825
746
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
826
|
-
await
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
747
|
+
await act(async () => {
|
|
748
|
+
stdin.write('i');
|
|
749
|
+
});
|
|
750
|
+
await vi.waitFor(() => {
|
|
751
|
+
expect(props.vimHandleInput).toHaveBeenCalled();
|
|
752
|
+
});
|
|
830
753
|
expect(mockBuffer.handleInput).not.toHaveBeenCalled();
|
|
831
754
|
unmount();
|
|
832
755
|
});
|
|
833
756
|
it('should call buffer.handleInput when vim mode is enabled but vim does not handle the input', async () => {
|
|
834
757
|
props.vimHandleInput = vi.fn().mockReturnValue(false); // Mock that vim did NOT handle it.
|
|
835
758
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
836
|
-
await
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
759
|
+
await act(async () => {
|
|
760
|
+
stdin.write('i');
|
|
761
|
+
});
|
|
762
|
+
await vi.waitFor(() => {
|
|
763
|
+
expect(props.vimHandleInput).toHaveBeenCalled();
|
|
764
|
+
expect(mockBuffer.handleInput).toHaveBeenCalled();
|
|
765
|
+
});
|
|
841
766
|
unmount();
|
|
842
767
|
});
|
|
843
768
|
it('should call handleInput when vim mode is disabled', async () => {
|
|
844
769
|
// Mock vimHandleInput to return false (vim didn't handle the input)
|
|
845
770
|
props.vimHandleInput = vi.fn().mockReturnValue(false);
|
|
846
771
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
847
|
-
await
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
772
|
+
await act(async () => {
|
|
773
|
+
stdin.write('i');
|
|
774
|
+
});
|
|
775
|
+
await vi.waitFor(() => {
|
|
776
|
+
expect(props.vimHandleInput).toHaveBeenCalled();
|
|
777
|
+
expect(mockBuffer.handleInput).toHaveBeenCalled();
|
|
778
|
+
});
|
|
852
779
|
unmount();
|
|
853
780
|
});
|
|
854
781
|
});
|
|
@@ -856,185 +783,158 @@ describe('InputPrompt', () => {
|
|
|
856
783
|
it('should handle bracketed paste when not focused', async () => {
|
|
857
784
|
props.focus = false;
|
|
858
785
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
859
|
-
await
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
786
|
+
await act(async () => {
|
|
787
|
+
stdin.write('\x1B[200~pasted text\x1B[201~');
|
|
788
|
+
});
|
|
789
|
+
await vi.waitFor(() => {
|
|
790
|
+
expect(mockBuffer.handleInput).toHaveBeenCalledWith(expect.objectContaining({
|
|
791
|
+
paste: true,
|
|
792
|
+
sequence: 'pasted text',
|
|
793
|
+
}));
|
|
794
|
+
});
|
|
866
795
|
unmount();
|
|
867
796
|
});
|
|
868
797
|
it('should ignore regular keypresses when not focused', async () => {
|
|
869
798
|
props.focus = false;
|
|
870
799
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
871
|
-
await
|
|
872
|
-
|
|
873
|
-
|
|
800
|
+
await act(async () => {
|
|
801
|
+
stdin.write('a');
|
|
802
|
+
});
|
|
803
|
+
await vi.waitFor(() => { });
|
|
874
804
|
expect(mockBuffer.handleInput).not.toHaveBeenCalled();
|
|
875
805
|
unmount();
|
|
876
806
|
});
|
|
877
807
|
});
|
|
878
808
|
describe('Highlighting and Cursor Display', () => {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
mockBuffer.viewportVisualLines = [text];
|
|
942
|
-
mockBuffer.visualCursor = [0, 8]; // cursor after '👍' (length is 6 + 2 for emoji)
|
|
943
|
-
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
944
|
-
await wait();
|
|
945
|
-
const frame = stdout.lastFrame();
|
|
946
|
-
expect(frame).toContain(`hello 👍${chalk.inverse(' ')}`);
|
|
947
|
-
unmount();
|
|
948
|
-
});
|
|
949
|
-
it('should display cursor on an empty line', async () => {
|
|
950
|
-
mockBuffer.text = '';
|
|
951
|
-
mockBuffer.lines = [''];
|
|
952
|
-
mockBuffer.viewportVisualLines = [''];
|
|
953
|
-
mockBuffer.visualCursor = [0, 0];
|
|
954
|
-
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
955
|
-
await wait();
|
|
956
|
-
const frame = stdout.lastFrame();
|
|
957
|
-
expect(frame).toContain(chalk.inverse(' '));
|
|
958
|
-
unmount();
|
|
959
|
-
});
|
|
960
|
-
it('should display cursor on a space between words', async () => {
|
|
961
|
-
mockBuffer.text = 'hello world';
|
|
962
|
-
mockBuffer.lines = ['hello world'];
|
|
963
|
-
mockBuffer.viewportVisualLines = ['hello world'];
|
|
964
|
-
mockBuffer.visualCursor = [0, 5]; // cursor on the space
|
|
965
|
-
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
966
|
-
await wait();
|
|
967
|
-
const frame = stdout.lastFrame();
|
|
968
|
-
expect(frame).toContain(`hello${chalk.inverse(' ')}world`);
|
|
969
|
-
unmount();
|
|
970
|
-
});
|
|
971
|
-
it('should display cursor in the middle of a line in a multiline block', async () => {
|
|
972
|
-
const text = 'first line\nsecond line\nthird line';
|
|
973
|
-
mockBuffer.text = text;
|
|
974
|
-
mockBuffer.lines = text.split('\n');
|
|
975
|
-
mockBuffer.viewportVisualLines = text.split('\n');
|
|
976
|
-
mockBuffer.visualCursor = [1, 3]; // cursor on 'o' in 'second'
|
|
977
|
-
mockBuffer.visualToLogicalMap = [
|
|
978
|
-
[0, 0],
|
|
979
|
-
[1, 0],
|
|
980
|
-
[2, 0],
|
|
981
|
-
];
|
|
982
|
-
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
983
|
-
await wait();
|
|
984
|
-
const frame = stdout.lastFrame();
|
|
985
|
-
expect(frame).toContain(`sec${chalk.inverse('o')}nd line`);
|
|
986
|
-
unmount();
|
|
987
|
-
});
|
|
988
|
-
it('should display cursor at the beginning of a line in a multiline block', async () => {
|
|
989
|
-
const text = 'first line\nsecond line';
|
|
990
|
-
mockBuffer.text = text;
|
|
991
|
-
mockBuffer.lines = text.split('\n');
|
|
992
|
-
mockBuffer.viewportVisualLines = text.split('\n');
|
|
993
|
-
mockBuffer.visualCursor = [1, 0]; // cursor on 's' in 'second'
|
|
994
|
-
mockBuffer.visualToLogicalMap = [
|
|
995
|
-
[0, 0],
|
|
996
|
-
[1, 0],
|
|
997
|
-
];
|
|
998
|
-
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
999
|
-
await wait();
|
|
1000
|
-
const frame = stdout.lastFrame();
|
|
1001
|
-
expect(frame).toContain(`${chalk.inverse('s')}econd line`);
|
|
1002
|
-
unmount();
|
|
1003
|
-
});
|
|
1004
|
-
it('should display cursor at the end of a line in a multiline block', async () => {
|
|
1005
|
-
const text = 'first line\nsecond line';
|
|
1006
|
-
mockBuffer.text = text;
|
|
1007
|
-
mockBuffer.lines = text.split('\n');
|
|
1008
|
-
mockBuffer.viewportVisualLines = text.split('\n');
|
|
1009
|
-
mockBuffer.visualCursor = [0, 10]; // cursor after 'first line'
|
|
1010
|
-
mockBuffer.visualToLogicalMap = [
|
|
1011
|
-
[0, 0],
|
|
1012
|
-
[1, 0],
|
|
1013
|
-
];
|
|
1014
|
-
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1015
|
-
await wait();
|
|
1016
|
-
const frame = stdout.lastFrame();
|
|
1017
|
-
expect(frame).toContain(`first line${chalk.inverse(' ')}`);
|
|
1018
|
-
unmount();
|
|
809
|
+
describe('single-line scenarios', () => {
|
|
810
|
+
it.each([
|
|
811
|
+
{
|
|
812
|
+
name: 'mid-word',
|
|
813
|
+
text: 'hello world',
|
|
814
|
+
visualCursor: [0, 3],
|
|
815
|
+
expected: `hel${chalk.inverse('l')}o world`,
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
name: 'at the beginning of the line',
|
|
819
|
+
text: 'hello',
|
|
820
|
+
visualCursor: [0, 0],
|
|
821
|
+
expected: `${chalk.inverse('h')}ello`,
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
name: 'at the end of the line',
|
|
825
|
+
text: 'hello',
|
|
826
|
+
visualCursor: [0, 5],
|
|
827
|
+
expected: `hello${chalk.inverse(' ')}`,
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
name: 'on a highlighted token',
|
|
831
|
+
text: 'run @path/to/file',
|
|
832
|
+
visualCursor: [0, 9],
|
|
833
|
+
expected: `@path/${chalk.inverse('t')}o/file`,
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
name: 'for multi-byte unicode characters',
|
|
837
|
+
text: 'hello 👍 world',
|
|
838
|
+
visualCursor: [0, 6],
|
|
839
|
+
expected: `hello ${chalk.inverse('👍')} world`,
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
name: 'at the end of a line with unicode characters',
|
|
843
|
+
text: 'hello 👍',
|
|
844
|
+
visualCursor: [0, 8],
|
|
845
|
+
expected: `hello 👍${chalk.inverse(' ')}`,
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
name: 'on an empty line',
|
|
849
|
+
text: '',
|
|
850
|
+
visualCursor: [0, 0],
|
|
851
|
+
expected: chalk.inverse(' '),
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
name: 'on a space between words',
|
|
855
|
+
text: 'hello world',
|
|
856
|
+
visualCursor: [0, 5],
|
|
857
|
+
expected: `hello${chalk.inverse(' ')}world`,
|
|
858
|
+
},
|
|
859
|
+
])('should display cursor correctly $name', async ({ text, visualCursor, expected }) => {
|
|
860
|
+
mockBuffer.text = text;
|
|
861
|
+
mockBuffer.lines = [text];
|
|
862
|
+
mockBuffer.viewportVisualLines = [text];
|
|
863
|
+
mockBuffer.visualCursor = visualCursor;
|
|
864
|
+
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
865
|
+
await vi.waitFor(() => {
|
|
866
|
+
const frame = stdout.lastFrame();
|
|
867
|
+
expect(frame).toContain(expected);
|
|
868
|
+
});
|
|
869
|
+
unmount();
|
|
870
|
+
});
|
|
1019
871
|
});
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
872
|
+
describe('multi-line scenarios', () => {
|
|
873
|
+
it.each([
|
|
874
|
+
{
|
|
875
|
+
name: 'in the middle of a line',
|
|
876
|
+
text: 'first line\nsecond line\nthird line',
|
|
877
|
+
visualCursor: [1, 3],
|
|
878
|
+
visualToLogicalMap: [
|
|
879
|
+
[0, 0],
|
|
880
|
+
[1, 0],
|
|
881
|
+
[2, 0],
|
|
882
|
+
],
|
|
883
|
+
expected: `sec${chalk.inverse('o')}nd line`,
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
name: 'at the beginning of a line',
|
|
887
|
+
text: 'first line\nsecond line',
|
|
888
|
+
visualCursor: [1, 0],
|
|
889
|
+
visualToLogicalMap: [
|
|
890
|
+
[0, 0],
|
|
891
|
+
[1, 0],
|
|
892
|
+
],
|
|
893
|
+
expected: `${chalk.inverse('s')}econd line`,
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
name: 'at the end of a line',
|
|
897
|
+
text: 'first line\nsecond line',
|
|
898
|
+
visualCursor: [0, 10],
|
|
899
|
+
visualToLogicalMap: [
|
|
900
|
+
[0, 0],
|
|
901
|
+
[1, 0],
|
|
902
|
+
],
|
|
903
|
+
expected: `first line${chalk.inverse(' ')}`,
|
|
904
|
+
},
|
|
905
|
+
])('should display cursor correctly $name in a multiline block', async ({ text, visualCursor, expected, visualToLogicalMap }) => {
|
|
906
|
+
mockBuffer.text = text;
|
|
907
|
+
mockBuffer.lines = text.split('\n');
|
|
908
|
+
mockBuffer.viewportVisualLines = text.split('\n');
|
|
909
|
+
mockBuffer.visualCursor = visualCursor;
|
|
910
|
+
mockBuffer.visualToLogicalMap = visualToLogicalMap;
|
|
911
|
+
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
912
|
+
await vi.waitFor(() => {
|
|
913
|
+
const frame = stdout.lastFrame();
|
|
914
|
+
expect(frame).toContain(expected);
|
|
915
|
+
});
|
|
916
|
+
unmount();
|
|
917
|
+
});
|
|
918
|
+
it('should display cursor on a blank line in a multiline block', async () => {
|
|
919
|
+
const text = 'first line\n\nthird line';
|
|
920
|
+
mockBuffer.text = text;
|
|
921
|
+
mockBuffer.lines = text.split('\n');
|
|
922
|
+
mockBuffer.viewportVisualLines = text.split('\n');
|
|
923
|
+
mockBuffer.visualCursor = [1, 0]; // cursor on the blank line
|
|
924
|
+
mockBuffer.visualToLogicalMap = [
|
|
925
|
+
[0, 0],
|
|
926
|
+
[1, 0],
|
|
927
|
+
[2, 0],
|
|
928
|
+
];
|
|
929
|
+
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
930
|
+
await vi.waitFor(() => {
|
|
931
|
+
const frame = stdout.lastFrame();
|
|
932
|
+
const lines = frame.split('\n');
|
|
933
|
+
// The line with the cursor should just be an inverted space inside the box border
|
|
934
|
+
expect(lines.find((l) => l.includes(chalk.inverse(' ')))).not.toBeUndefined();
|
|
935
|
+
});
|
|
936
|
+
unmount();
|
|
937
|
+
});
|
|
1038
938
|
});
|
|
1039
939
|
});
|
|
1040
940
|
describe('multiline rendering', () => {
|
|
@@ -1052,15 +952,16 @@ describe('InputPrompt', () => {
|
|
|
1052
952
|
[2, 0], // 'world' is logical line 2, col 0
|
|
1053
953
|
];
|
|
1054
954
|
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1055
|
-
await
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
955
|
+
await vi.waitFor(() => {
|
|
956
|
+
const frame = stdout.lastFrame();
|
|
957
|
+
// Check that all lines, including the empty one, are rendered.
|
|
958
|
+
// This implicitly tests that the Box wrapper provides height for the empty line.
|
|
959
|
+
expect(frame).toContain('hello');
|
|
960
|
+
expect(frame).toContain(`world${chalk.inverse(' ')}`);
|
|
961
|
+
const outputLines = frame.split('\n');
|
|
962
|
+
// The number of lines should be 2 for the border plus 3 for the content.
|
|
963
|
+
expect(outputLines.length).toBe(5);
|
|
964
|
+
});
|
|
1064
965
|
unmount();
|
|
1065
966
|
});
|
|
1066
967
|
});
|
|
@@ -1080,16 +981,18 @@ describe('InputPrompt', () => {
|
|
|
1080
981
|
},
|
|
1081
982
|
])('should handle multiline paste $description', async ({ pastedText }) => {
|
|
1082
983
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1083
|
-
await wait();
|
|
1084
984
|
// Simulate a bracketed paste event from the terminal
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
985
|
+
await act(async () => {
|
|
986
|
+
stdin.write(`\x1b[200~${pastedText}\x1b[201~`);
|
|
987
|
+
});
|
|
988
|
+
await vi.waitFor(() => {
|
|
989
|
+
// Verify that the buffer's handleInput was called once with the full text
|
|
990
|
+
expect(props.buffer.handleInput).toHaveBeenCalledTimes(1);
|
|
991
|
+
expect(props.buffer.handleInput).toHaveBeenCalledWith(expect.objectContaining({
|
|
992
|
+
paste: true,
|
|
993
|
+
sequence: pastedText,
|
|
994
|
+
}));
|
|
995
|
+
});
|
|
1093
996
|
unmount();
|
|
1094
997
|
});
|
|
1095
998
|
});
|
|
@@ -1111,10 +1014,13 @@ describe('InputPrompt', () => {
|
|
|
1111
1014
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1112
1015
|
await vi.runAllTimersAsync();
|
|
1113
1016
|
// Simulate a paste operation (this should set the paste protection)
|
|
1114
|
-
|
|
1115
|
-
|
|
1017
|
+
await act(async () => {
|
|
1018
|
+
stdin.write(`\x1b[200~pasted content\x1b[201~`);
|
|
1019
|
+
});
|
|
1116
1020
|
// Simulate an Enter key press immediately after paste
|
|
1117
|
-
|
|
1021
|
+
await act(async () => {
|
|
1022
|
+
stdin.write('\r');
|
|
1023
|
+
});
|
|
1118
1024
|
await vi.runAllTimersAsync();
|
|
1119
1025
|
// Verify that onSubmit was NOT called due to recent paste protection
|
|
1120
1026
|
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
@@ -1128,7 +1034,7 @@ describe('InputPrompt', () => {
|
|
|
1128
1034
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1129
1035
|
await vi.runAllTimersAsync();
|
|
1130
1036
|
// Simulate a paste operation (this sets the protection)
|
|
1131
|
-
act(() => {
|
|
1037
|
+
await act(async () => {
|
|
1132
1038
|
stdin.write('\x1b[200~pasted text\x1b[201~');
|
|
1133
1039
|
});
|
|
1134
1040
|
await vi.runAllTimersAsync();
|
|
@@ -1137,7 +1043,9 @@ describe('InputPrompt', () => {
|
|
|
1137
1043
|
await vi.advanceTimersByTimeAsync(50);
|
|
1138
1044
|
});
|
|
1139
1045
|
// Now Enter should work normally
|
|
1140
|
-
|
|
1046
|
+
await act(async () => {
|
|
1047
|
+
stdin.write('\r');
|
|
1048
|
+
});
|
|
1141
1049
|
await vi.runAllTimersAsync();
|
|
1142
1050
|
expect(props.onSubmit).toHaveBeenCalledWith('pasted text');
|
|
1143
1051
|
expect(props.buffer.newline).not.toHaveBeenCalled();
|
|
@@ -1158,10 +1066,14 @@ describe('InputPrompt', () => {
|
|
|
1158
1066
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: true });
|
|
1159
1067
|
await vi.runAllTimersAsync();
|
|
1160
1068
|
// Simulate a paste operation
|
|
1161
|
-
|
|
1069
|
+
await act(async () => {
|
|
1070
|
+
stdin.write('\x1b[200~some pasted stuff\x1b[201~');
|
|
1071
|
+
});
|
|
1162
1072
|
await vi.runAllTimersAsync();
|
|
1163
1073
|
// Simulate an Enter key press immediately after paste
|
|
1164
|
-
|
|
1074
|
+
await act(async () => {
|
|
1075
|
+
stdin.write('\r');
|
|
1076
|
+
});
|
|
1165
1077
|
await vi.runAllTimersAsync();
|
|
1166
1078
|
// Verify that onSubmit was called
|
|
1167
1079
|
expect(props.onSubmit).toHaveBeenCalledWith('pasted command');
|
|
@@ -1173,7 +1085,9 @@ describe('InputPrompt', () => {
|
|
|
1173
1085
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1174
1086
|
await vi.runAllTimersAsync();
|
|
1175
1087
|
// Press Enter without any recent paste
|
|
1176
|
-
|
|
1088
|
+
await act(async () => {
|
|
1089
|
+
stdin.write('\r');
|
|
1090
|
+
});
|
|
1177
1091
|
await vi.runAllTimersAsync();
|
|
1178
1092
|
// Verify that onSubmit was called normally
|
|
1179
1093
|
expect(props.onSubmit).toHaveBeenCalledWith('normal command');
|
|
@@ -1186,13 +1100,19 @@ describe('InputPrompt', () => {
|
|
|
1186
1100
|
props.onEscapePromptChange = onEscapePromptChange;
|
|
1187
1101
|
props.buffer.setText('text to clear');
|
|
1188
1102
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
|
|
1189
|
-
await
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1103
|
+
await act(async () => {
|
|
1104
|
+
stdin.write('\x1B');
|
|
1105
|
+
});
|
|
1106
|
+
await vi.waitFor(() => {
|
|
1107
|
+
expect(onEscapePromptChange).toHaveBeenCalledWith(true);
|
|
1108
|
+
});
|
|
1109
|
+
await act(async () => {
|
|
1110
|
+
stdin.write('\x1B');
|
|
1111
|
+
});
|
|
1112
|
+
await vi.waitFor(() => {
|
|
1113
|
+
expect(props.buffer.setText).toHaveBeenCalledWith('');
|
|
1114
|
+
expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
|
|
1115
|
+
});
|
|
1196
1116
|
unmount();
|
|
1197
1117
|
});
|
|
1198
1118
|
it('should reset escape state on any non-ESC key', async () => {
|
|
@@ -1200,12 +1120,16 @@ describe('InputPrompt', () => {
|
|
|
1200
1120
|
props.onEscapePromptChange = onEscapePromptChange;
|
|
1201
1121
|
props.buffer.setText('some text');
|
|
1202
1122
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
|
|
1203
|
-
|
|
1204
|
-
|
|
1123
|
+
await act(async () => {
|
|
1124
|
+
stdin.write('\x1B');
|
|
1125
|
+
});
|
|
1126
|
+
await vi.waitFor(() => {
|
|
1205
1127
|
expect(onEscapePromptChange).toHaveBeenCalledWith(true);
|
|
1206
1128
|
});
|
|
1207
|
-
|
|
1208
|
-
|
|
1129
|
+
await act(async () => {
|
|
1130
|
+
stdin.write('a');
|
|
1131
|
+
});
|
|
1132
|
+
await vi.waitFor(() => {
|
|
1209
1133
|
expect(onEscapePromptChange).toHaveBeenCalledWith(false);
|
|
1210
1134
|
});
|
|
1211
1135
|
unmount();
|
|
@@ -1213,10 +1137,10 @@ describe('InputPrompt', () => {
|
|
|
1213
1137
|
it('should handle ESC in shell mode by disabling shell mode', async () => {
|
|
1214
1138
|
props.shellModeActive = true;
|
|
1215
1139
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
|
|
1216
|
-
await
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
expect(props.setShellModeActive).toHaveBeenCalledWith(false);
|
|
1140
|
+
await act(async () => {
|
|
1141
|
+
stdin.write('\x1B');
|
|
1142
|
+
});
|
|
1143
|
+
await vi.waitFor(() => expect(props.setShellModeActive).toHaveBeenCalledWith(false));
|
|
1220
1144
|
unmount();
|
|
1221
1145
|
});
|
|
1222
1146
|
it('should handle ESC when completion suggestions are showing', async () => {
|
|
@@ -1226,10 +1150,10 @@ describe('InputPrompt', () => {
|
|
|
1226
1150
|
suggestions: [{ label: 'suggestion', value: 'suggestion' }],
|
|
1227
1151
|
});
|
|
1228
1152
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
|
|
1229
|
-
await
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
|
|
1153
|
+
await act(async () => {
|
|
1154
|
+
stdin.write('\x1B');
|
|
1155
|
+
});
|
|
1156
|
+
await vi.waitFor(() => expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled());
|
|
1233
1157
|
unmount();
|
|
1234
1158
|
});
|
|
1235
1159
|
it('should not call onEscapePromptChange when not provided', async () => {
|
|
@@ -1238,20 +1162,23 @@ describe('InputPrompt', () => {
|
|
|
1238
1162
|
props.buffer.setText('some text');
|
|
1239
1163
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
|
|
1240
1164
|
await vi.runAllTimersAsync();
|
|
1241
|
-
|
|
1165
|
+
await act(async () => {
|
|
1166
|
+
stdin.write('\x1B');
|
|
1167
|
+
});
|
|
1242
1168
|
await vi.runAllTimersAsync();
|
|
1243
1169
|
vi.useRealTimers();
|
|
1244
1170
|
unmount();
|
|
1245
1171
|
});
|
|
1246
1172
|
it('should not interfere with existing keyboard shortcuts', async () => {
|
|
1247
1173
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
|
|
1248
|
-
await
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
expect(props.onClearScreen).toHaveBeenCalled();
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1174
|
+
await act(async () => {
|
|
1175
|
+
stdin.write('\x0C');
|
|
1176
|
+
});
|
|
1177
|
+
await vi.waitFor(() => expect(props.onClearScreen).toHaveBeenCalled());
|
|
1178
|
+
await act(async () => {
|
|
1179
|
+
stdin.write('\x01');
|
|
1180
|
+
});
|
|
1181
|
+
await vi.waitFor(() => expect(props.buffer.move).toHaveBeenCalledWith('home'));
|
|
1255
1182
|
unmount();
|
|
1256
1183
|
});
|
|
1257
1184
|
});
|
|
@@ -1279,12 +1206,11 @@ describe('InputPrompt', () => {
|
|
|
1279
1206
|
activeSuggestionIndex: 0,
|
|
1280
1207
|
});
|
|
1281
1208
|
const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1282
|
-
await wait();
|
|
1283
1209
|
// Trigger reverse search with Ctrl+R
|
|
1284
|
-
act(() => {
|
|
1210
|
+
await act(async () => {
|
|
1285
1211
|
stdin.write('\x12');
|
|
1286
1212
|
});
|
|
1287
|
-
await waitFor(() => {
|
|
1213
|
+
await vi.waitFor(() => {
|
|
1288
1214
|
const frame = stdout.lastFrame();
|
|
1289
1215
|
expect(frame).toContain('(r:)');
|
|
1290
1216
|
expect(frame).toContain('echo hello');
|
|
@@ -1295,12 +1221,17 @@ describe('InputPrompt', () => {
|
|
|
1295
1221
|
});
|
|
1296
1222
|
it('resets reverse search state on Escape', async () => {
|
|
1297
1223
|
const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1298
|
-
await
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1224
|
+
await act(async () => {
|
|
1225
|
+
stdin.write('\x12');
|
|
1226
|
+
});
|
|
1227
|
+
await vi.waitFor(() => { });
|
|
1228
|
+
await act(async () => {
|
|
1229
|
+
stdin.write('\x1B');
|
|
1230
|
+
});
|
|
1231
|
+
await act(async () => {
|
|
1232
|
+
stdin.write('\u001b[27u'); // Press kitty escape key
|
|
1233
|
+
});
|
|
1234
|
+
await vi.waitFor(() => {
|
|
1304
1235
|
expect(stdout.lastFrame()).not.toContain('(r:)');
|
|
1305
1236
|
});
|
|
1306
1237
|
expect(stdout.lastFrame()).not.toContain('echo hello');
|
|
@@ -1326,20 +1257,21 @@ describe('InputPrompt', () => {
|
|
|
1326
1257
|
}));
|
|
1327
1258
|
const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1328
1259
|
// Enter reverse search mode with Ctrl+R
|
|
1329
|
-
act(() => {
|
|
1260
|
+
await act(async () => {
|
|
1330
1261
|
stdin.write('\x12');
|
|
1331
1262
|
});
|
|
1332
1263
|
// Verify reverse search is active
|
|
1333
|
-
await waitFor(() => {
|
|
1264
|
+
await vi.waitFor(() => {
|
|
1334
1265
|
expect(stdout.lastFrame()).toContain('(r:)');
|
|
1335
1266
|
});
|
|
1336
1267
|
// Press Tab to complete the highlighted entry
|
|
1337
|
-
act(() => {
|
|
1268
|
+
await act(async () => {
|
|
1338
1269
|
stdin.write('\t');
|
|
1339
1270
|
});
|
|
1340
|
-
await
|
|
1341
|
-
|
|
1342
|
-
|
|
1271
|
+
await vi.waitFor(() => {
|
|
1272
|
+
expect(mockHandleAutocomplete).toHaveBeenCalledWith(0);
|
|
1273
|
+
expect(props.buffer.setText).toHaveBeenCalledWith('echo hello');
|
|
1274
|
+
});
|
|
1343
1275
|
unmount();
|
|
1344
1276
|
}, 15000);
|
|
1345
1277
|
it('submits the highlighted entry on Enter and exits reverse-search', async () => {
|
|
@@ -1355,16 +1287,16 @@ describe('InputPrompt', () => {
|
|
|
1355
1287
|
activeSuggestionIndex: 0,
|
|
1356
1288
|
});
|
|
1357
1289
|
const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1358
|
-
act(() => {
|
|
1290
|
+
await act(async () => {
|
|
1359
1291
|
stdin.write('\x12');
|
|
1360
1292
|
});
|
|
1361
|
-
await waitFor(() => {
|
|
1293
|
+
await vi.waitFor(() => {
|
|
1362
1294
|
expect(stdout.lastFrame()).toContain('(r:)');
|
|
1363
1295
|
});
|
|
1364
|
-
act(() => {
|
|
1296
|
+
await act(async () => {
|
|
1365
1297
|
stdin.write('\r');
|
|
1366
1298
|
});
|
|
1367
|
-
await waitFor(() => {
|
|
1299
|
+
await vi.waitFor(() => {
|
|
1368
1300
|
expect(stdout.lastFrame()).not.toContain('(r:)');
|
|
1369
1301
|
});
|
|
1370
1302
|
expect(props.onSubmit).toHaveBeenCalledWith('echo hello');
|
|
@@ -1384,19 +1316,18 @@ describe('InputPrompt', () => {
|
|
|
1384
1316
|
showSuggestions: reverseSearchActiveFromInputPrompt,
|
|
1385
1317
|
}));
|
|
1386
1318
|
const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1387
|
-
await wait();
|
|
1388
1319
|
// reverse search with Ctrl+R
|
|
1389
|
-
act(() => {
|
|
1320
|
+
await act(async () => {
|
|
1390
1321
|
stdin.write('\x12');
|
|
1391
1322
|
});
|
|
1392
|
-
await waitFor(() => {
|
|
1323
|
+
await vi.waitFor(() => {
|
|
1393
1324
|
expect(stdout.lastFrame()).toContain('(r:)');
|
|
1394
1325
|
});
|
|
1395
1326
|
// Press kitty escape key
|
|
1396
|
-
act(() => {
|
|
1327
|
+
await act(async () => {
|
|
1397
1328
|
stdin.write('\u001b[27u');
|
|
1398
1329
|
});
|
|
1399
|
-
await waitFor(() => {
|
|
1330
|
+
await vi.waitFor(() => {
|
|
1400
1331
|
expect(stdout.lastFrame()).not.toContain('(r:)');
|
|
1401
1332
|
expect(props.buffer.text).toBe(initialText);
|
|
1402
1333
|
expect(props.buffer.cursor).toEqual(initialCursor);
|
|
@@ -1410,10 +1341,12 @@ describe('InputPrompt', () => {
|
|
|
1410
1341
|
props.buffer.cursor = [1, 2];
|
|
1411
1342
|
props.buffer.lines = ['line 1', 'line 2', 'line 3'];
|
|
1412
1343
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1413
|
-
await
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1344
|
+
await act(async () => {
|
|
1345
|
+
stdin.write('\x05'); // Ctrl+E
|
|
1346
|
+
});
|
|
1347
|
+
await vi.waitFor(() => {
|
|
1348
|
+
expect(props.buffer.move).toHaveBeenCalledWith('end');
|
|
1349
|
+
});
|
|
1417
1350
|
expect(props.buffer.moveToOffset).not.toHaveBeenCalled();
|
|
1418
1351
|
unmount();
|
|
1419
1352
|
});
|
|
@@ -1422,10 +1355,12 @@ describe('InputPrompt', () => {
|
|
|
1422
1355
|
props.buffer.cursor = [0, 5];
|
|
1423
1356
|
props.buffer.lines = ['single line text'];
|
|
1424
1357
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1425
|
-
await
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1358
|
+
await act(async () => {
|
|
1359
|
+
stdin.write('\x05'); // Ctrl+E
|
|
1360
|
+
});
|
|
1361
|
+
await vi.waitFor(() => {
|
|
1362
|
+
expect(props.buffer.move).toHaveBeenCalledWith('end');
|
|
1363
|
+
});
|
|
1429
1364
|
expect(props.buffer.moveToOffset).not.toHaveBeenCalled();
|
|
1430
1365
|
unmount();
|
|
1431
1366
|
});
|
|
@@ -1445,18 +1380,18 @@ describe('InputPrompt', () => {
|
|
|
1445
1380
|
activeSuggestionIndex: isActive ? 0 : -1,
|
|
1446
1381
|
}));
|
|
1447
1382
|
const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1448
|
-
await
|
|
1449
|
-
act(() => {
|
|
1383
|
+
await act(async () => {
|
|
1450
1384
|
stdin.write('\x12'); // Ctrl+R
|
|
1451
1385
|
});
|
|
1452
|
-
await
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1386
|
+
await vi.waitFor(() => {
|
|
1387
|
+
const frame = stdout.lastFrame() ?? '';
|
|
1388
|
+
expect(frame).toContain('(r:)');
|
|
1389
|
+
expect(frame).toContain('git commit');
|
|
1390
|
+
expect(frame).toContain('git push');
|
|
1391
|
+
});
|
|
1457
1392
|
unmount();
|
|
1458
1393
|
});
|
|
1459
|
-
it
|
|
1394
|
+
it('expands and collapses long suggestion via Right/Left arrows', async () => {
|
|
1460
1395
|
props.shellModeActive = false;
|
|
1461
1396
|
const longValue = 'l'.repeat(200);
|
|
1462
1397
|
vi.mocked(useReverseSearchCompletion).mockReturnValue({
|
|
@@ -1468,18 +1403,26 @@ describe('InputPrompt', () => {
|
|
|
1468
1403
|
isLoadingSuggestions: false,
|
|
1469
1404
|
});
|
|
1470
1405
|
const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1471
|
-
await
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
await
|
|
1481
|
-
|
|
1482
|
-
|
|
1406
|
+
await act(async () => {
|
|
1407
|
+
stdin.write('\x12');
|
|
1408
|
+
});
|
|
1409
|
+
await vi.waitFor(() => {
|
|
1410
|
+
expect(clean(stdout.lastFrame())).toContain('→');
|
|
1411
|
+
});
|
|
1412
|
+
await act(async () => {
|
|
1413
|
+
stdin.write('\u001B[C');
|
|
1414
|
+
});
|
|
1415
|
+
await vi.waitFor(() => {
|
|
1416
|
+
expect(clean(stdout.lastFrame())).toContain('←');
|
|
1417
|
+
});
|
|
1418
|
+
expect(stdout.lastFrame()).toMatchSnapshot('command-search-render-expanded-match');
|
|
1419
|
+
await act(async () => {
|
|
1420
|
+
stdin.write('\u001B[D');
|
|
1421
|
+
});
|
|
1422
|
+
await vi.waitFor(() => {
|
|
1423
|
+
expect(clean(stdout.lastFrame())).toContain('→');
|
|
1424
|
+
});
|
|
1425
|
+
expect(stdout.lastFrame()).toMatchSnapshot('command-search-render-collapsed-match');
|
|
1483
1426
|
unmount();
|
|
1484
1427
|
});
|
|
1485
1428
|
it('renders match window and expanded view (snapshots)', async () => {
|
|
@@ -1496,13 +1439,18 @@ describe('InputPrompt', () => {
|
|
|
1496
1439
|
isLoadingSuggestions: false,
|
|
1497
1440
|
});
|
|
1498
1441
|
const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1499
|
-
await
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1442
|
+
await act(async () => {
|
|
1443
|
+
stdin.write('\x12');
|
|
1444
|
+
});
|
|
1445
|
+
await vi.waitFor(() => {
|
|
1446
|
+
expect(stdout.lastFrame()).toMatchSnapshot('command-search-render-collapsed-match');
|
|
1447
|
+
});
|
|
1448
|
+
await act(async () => {
|
|
1449
|
+
stdin.write('\u001B[C');
|
|
1450
|
+
});
|
|
1451
|
+
await vi.waitFor(() => {
|
|
1452
|
+
expect(stdout.lastFrame()).toMatchSnapshot('command-search-render-expanded-match');
|
|
1453
|
+
});
|
|
1506
1454
|
unmount();
|
|
1507
1455
|
});
|
|
1508
1456
|
it('does not show expand/collapse indicator for short suggestions', async () => {
|
|
@@ -1517,12 +1465,16 @@ describe('InputPrompt', () => {
|
|
|
1517
1465
|
isLoadingSuggestions: false,
|
|
1518
1466
|
});
|
|
1519
1467
|
const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1520
|
-
await
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1468
|
+
await act(async () => {
|
|
1469
|
+
stdin.write('\x12');
|
|
1470
|
+
});
|
|
1471
|
+
await vi.waitFor(() => {
|
|
1472
|
+
const frame = clean(stdout.lastFrame());
|
|
1473
|
+
// Ensure it rendered the search mode
|
|
1474
|
+
expect(frame).toContain('(r:)');
|
|
1475
|
+
expect(frame).not.toContain('→');
|
|
1476
|
+
expect(frame).not.toContain('←');
|
|
1477
|
+
});
|
|
1526
1478
|
unmount();
|
|
1527
1479
|
});
|
|
1528
1480
|
});
|
|
@@ -1532,12 +1484,12 @@ describe('InputPrompt', () => {
|
|
|
1532
1484
|
props.popAllMessages = mockPopAllMessages;
|
|
1533
1485
|
props.buffer.text = '';
|
|
1534
1486
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1535
|
-
await
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
expect(mockPopAllMessages).toHaveBeenCalled();
|
|
1487
|
+
await act(async () => {
|
|
1488
|
+
stdin.write('\u001B[A');
|
|
1489
|
+
});
|
|
1490
|
+
await vi.waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
|
|
1539
1491
|
const callback = mockPopAllMessages.mock.calls[0][0];
|
|
1540
|
-
act(() => {
|
|
1492
|
+
await act(async () => {
|
|
1541
1493
|
callback('Message 1\n\nMessage 2\n\nMessage 3');
|
|
1542
1494
|
});
|
|
1543
1495
|
expect(props.buffer.setText).toHaveBeenCalledWith('Message 1\n\nMessage 2\n\nMessage 3');
|
|
@@ -1548,11 +1500,11 @@ describe('InputPrompt', () => {
|
|
|
1548
1500
|
props.popAllMessages = mockPopAllMessages;
|
|
1549
1501
|
props.buffer.text = 'some text';
|
|
1550
1502
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1551
|
-
await
|
|
1552
|
-
|
|
1553
|
-
|
|
1503
|
+
await act(async () => {
|
|
1504
|
+
stdin.write('\u001B[A');
|
|
1505
|
+
});
|
|
1506
|
+
await vi.waitFor(() => expect(mockInputHistory.navigateUp).toHaveBeenCalled());
|
|
1554
1507
|
expect(mockPopAllMessages).not.toHaveBeenCalled();
|
|
1555
|
-
expect(mockInputHistory.navigateUp).toHaveBeenCalled();
|
|
1556
1508
|
unmount();
|
|
1557
1509
|
});
|
|
1558
1510
|
it('should handle undefined messages from popAllMessages', async () => {
|
|
@@ -1560,12 +1512,12 @@ describe('InputPrompt', () => {
|
|
|
1560
1512
|
props.popAllMessages = mockPopAllMessages;
|
|
1561
1513
|
props.buffer.text = '';
|
|
1562
1514
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1563
|
-
await
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
expect(mockPopAllMessages).toHaveBeenCalled();
|
|
1515
|
+
await act(async () => {
|
|
1516
|
+
stdin.write('\u001B[A');
|
|
1517
|
+
});
|
|
1518
|
+
await vi.waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
|
|
1567
1519
|
const callback = mockPopAllMessages.mock.calls[0][0];
|
|
1568
|
-
act(() => {
|
|
1520
|
+
await act(async () => {
|
|
1569
1521
|
callback(undefined);
|
|
1570
1522
|
});
|
|
1571
1523
|
expect(props.buffer.setText).not.toHaveBeenCalled();
|
|
@@ -1580,10 +1532,10 @@ describe('InputPrompt', () => {
|
|
|
1580
1532
|
props.buffer.visualCursor = [0, 0];
|
|
1581
1533
|
props.buffer.visualScrollRow = 0;
|
|
1582
1534
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1583
|
-
await
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
expect(mockPopAllMessages).toHaveBeenCalled();
|
|
1535
|
+
await act(async () => {
|
|
1536
|
+
stdin.write('\u001B[A');
|
|
1537
|
+
});
|
|
1538
|
+
await vi.waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
|
|
1587
1539
|
unmount();
|
|
1588
1540
|
});
|
|
1589
1541
|
it('should handle single queued message', async () => {
|
|
@@ -1591,11 +1543,12 @@ describe('InputPrompt', () => {
|
|
|
1591
1543
|
props.popAllMessages = mockPopAllMessages;
|
|
1592
1544
|
props.buffer.text = '';
|
|
1593
1545
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1594
|
-
await
|
|
1595
|
-
|
|
1596
|
-
|
|
1546
|
+
await act(async () => {
|
|
1547
|
+
stdin.write('\u001B[A');
|
|
1548
|
+
});
|
|
1549
|
+
await vi.waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
|
|
1597
1550
|
const callback = mockPopAllMessages.mock.calls[0][0];
|
|
1598
|
-
act(() => {
|
|
1551
|
+
await act(async () => {
|
|
1599
1552
|
callback('Single message');
|
|
1600
1553
|
});
|
|
1601
1554
|
expect(props.buffer.setText).toHaveBeenCalledWith('Single message');
|
|
@@ -1606,20 +1559,20 @@ describe('InputPrompt', () => {
|
|
|
1606
1559
|
props.popAllMessages = mockPopAllMessages;
|
|
1607
1560
|
props.buffer.text = ' '; // Whitespace only
|
|
1608
1561
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1609
|
-
await
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
expect(mockPopAllMessages).toHaveBeenCalled();
|
|
1562
|
+
await act(async () => {
|
|
1563
|
+
stdin.write('\u001B[A');
|
|
1564
|
+
});
|
|
1565
|
+
await vi.waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
|
|
1613
1566
|
unmount();
|
|
1614
1567
|
});
|
|
1615
1568
|
it('should not call popAllMessages if it is not provided', async () => {
|
|
1616
1569
|
props.popAllMessages = undefined;
|
|
1617
1570
|
props.buffer.text = '';
|
|
1618
1571
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1619
|
-
await
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
expect(mockInputHistory.navigateUp).toHaveBeenCalled();
|
|
1572
|
+
await act(async () => {
|
|
1573
|
+
stdin.write('\u001B[A');
|
|
1574
|
+
});
|
|
1575
|
+
await vi.waitFor(() => expect(mockInputHistory.navigateUp).toHaveBeenCalled());
|
|
1623
1576
|
unmount();
|
|
1624
1577
|
});
|
|
1625
1578
|
it('should navigate input history on fresh start when no queued messages exist', async () => {
|
|
@@ -1627,12 +1580,12 @@ describe('InputPrompt', () => {
|
|
|
1627
1580
|
props.popAllMessages = mockPopAllMessages;
|
|
1628
1581
|
props.buffer.text = '';
|
|
1629
1582
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1630
|
-
await
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
expect(mockPopAllMessages).toHaveBeenCalled();
|
|
1583
|
+
await act(async () => {
|
|
1584
|
+
stdin.write('\u001B[A');
|
|
1585
|
+
});
|
|
1586
|
+
await vi.waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
|
|
1634
1587
|
const callback = mockPopAllMessages.mock.calls[0][0];
|
|
1635
|
-
act(() => {
|
|
1588
|
+
await act(async () => {
|
|
1636
1589
|
callback(undefined);
|
|
1637
1590
|
});
|
|
1638
1591
|
expect(mockInputHistory.navigateUp).toHaveBeenCalled();
|
|
@@ -1644,33 +1597,31 @@ describe('InputPrompt', () => {
|
|
|
1644
1597
|
it('should render correctly in shell mode', async () => {
|
|
1645
1598
|
props.shellModeActive = true;
|
|
1646
1599
|
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1647
|
-
await
|
|
1648
|
-
expect(stdout.lastFrame()).toMatchSnapshot();
|
|
1600
|
+
await vi.waitFor(() => expect(stdout.lastFrame()).toMatchSnapshot());
|
|
1649
1601
|
unmount();
|
|
1650
1602
|
});
|
|
1651
1603
|
it('should render correctly when accepting edits', async () => {
|
|
1652
1604
|
props.approvalMode = ApprovalMode.AUTO_EDIT;
|
|
1653
1605
|
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1654
|
-
await
|
|
1655
|
-
expect(stdout.lastFrame()).toMatchSnapshot();
|
|
1606
|
+
await vi.waitFor(() => expect(stdout.lastFrame()).toMatchSnapshot());
|
|
1656
1607
|
unmount();
|
|
1657
1608
|
});
|
|
1658
1609
|
it('should render correctly in yolo mode', async () => {
|
|
1659
1610
|
props.approvalMode = ApprovalMode.YOLO;
|
|
1660
1611
|
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1661
|
-
await
|
|
1662
|
-
expect(stdout.lastFrame()).toMatchSnapshot();
|
|
1612
|
+
await vi.waitFor(() => expect(stdout.lastFrame()).toMatchSnapshot());
|
|
1663
1613
|
unmount();
|
|
1664
1614
|
});
|
|
1665
1615
|
it('should not show inverted cursor when shell is focused', async () => {
|
|
1666
1616
|
props.isEmbeddedShellFocused = true;
|
|
1667
1617
|
props.focus = false;
|
|
1668
1618
|
const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1669
|
-
await
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1619
|
+
await vi.waitFor(() => {
|
|
1620
|
+
expect(stdout.lastFrame()).not.toContain(`{chalk.inverse(' ')}`);
|
|
1621
|
+
// This snapshot is good to make sure there was an input prompt but does
|
|
1622
|
+
// not show the inverted cursor because snapshots do not show colors.
|
|
1623
|
+
expect(stdout.lastFrame()).toMatchSnapshot();
|
|
1624
|
+
});
|
|
1674
1625
|
unmount();
|
|
1675
1626
|
});
|
|
1676
1627
|
});
|
|
@@ -1678,54 +1629,59 @@ describe('InputPrompt', () => {
|
|
|
1678
1629
|
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), {
|
|
1679
1630
|
shellFocus: false,
|
|
1680
1631
|
});
|
|
1681
|
-
await
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
expect(mockBuffer.handleInput).toHaveBeenCalled();
|
|
1685
|
-
unmount();
|
|
1686
|
-
});
|
|
1687
|
-
it('should prevent slash commands from being queued while streaming', async () => {
|
|
1688
|
-
props.onSubmit = vi.fn();
|
|
1689
|
-
props.buffer.text = '/help';
|
|
1690
|
-
props.setQueueErrorMessage = vi.fn();
|
|
1691
|
-
props.streamingState = StreamingState.Responding;
|
|
1692
|
-
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1693
|
-
await wait();
|
|
1694
|
-
stdin.write('/help');
|
|
1695
|
-
stdin.write('\r');
|
|
1696
|
-
await wait();
|
|
1697
|
-
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
1698
|
-
expect(props.setQueueErrorMessage).toHaveBeenCalledWith('Slash commands cannot be queued');
|
|
1699
|
-
unmount();
|
|
1700
|
-
});
|
|
1701
|
-
it('should prevent shell commands from being queued while streaming', async () => {
|
|
1702
|
-
props.onSubmit = vi.fn();
|
|
1703
|
-
props.buffer.text = 'ls';
|
|
1704
|
-
props.setQueueErrorMessage = vi.fn();
|
|
1705
|
-
props.streamingState = StreamingState.Responding;
|
|
1706
|
-
props.shellModeActive = true;
|
|
1707
|
-
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1708
|
-
await wait();
|
|
1709
|
-
stdin.write('ls');
|
|
1710
|
-
stdin.write('\r');
|
|
1711
|
-
await wait();
|
|
1712
|
-
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
1713
|
-
expect(props.setQueueErrorMessage).toHaveBeenCalledWith('Shell commands cannot be queued');
|
|
1632
|
+
await act(async () => {
|
|
1633
|
+
stdin.write('a');
|
|
1634
|
+
});
|
|
1635
|
+
await vi.waitFor(() => expect(mockBuffer.handleInput).toHaveBeenCalled());
|
|
1714
1636
|
unmount();
|
|
1715
1637
|
});
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1638
|
+
describe('command queuing while streaming', () => {
|
|
1639
|
+
beforeEach(() => {
|
|
1640
|
+
props.streamingState = StreamingState.Responding;
|
|
1641
|
+
props.setQueueErrorMessage = vi.fn();
|
|
1642
|
+
props.onSubmit = vi.fn();
|
|
1643
|
+
});
|
|
1644
|
+
it.each([
|
|
1645
|
+
{
|
|
1646
|
+
name: 'should prevent slash commands',
|
|
1647
|
+
bufferText: '/help',
|
|
1648
|
+
shellMode: false,
|
|
1649
|
+
shouldSubmit: false,
|
|
1650
|
+
errorMessage: 'Slash commands cannot be queued',
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
name: 'should prevent shell commands',
|
|
1654
|
+
bufferText: 'ls',
|
|
1655
|
+
shellMode: true,
|
|
1656
|
+
shouldSubmit: false,
|
|
1657
|
+
errorMessage: 'Shell commands cannot be queued',
|
|
1658
|
+
},
|
|
1659
|
+
{
|
|
1660
|
+
name: 'should allow regular messages',
|
|
1661
|
+
bufferText: 'regular message',
|
|
1662
|
+
shellMode: false,
|
|
1663
|
+
shouldSubmit: true,
|
|
1664
|
+
errorMessage: null,
|
|
1665
|
+
},
|
|
1666
|
+
])('$name', async ({ bufferText, shellMode, shouldSubmit, errorMessage }) => {
|
|
1667
|
+
props.buffer.text = bufferText;
|
|
1668
|
+
props.shellModeActive = shellMode;
|
|
1669
|
+
const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
|
|
1670
|
+
await act(async () => {
|
|
1671
|
+
stdin.write('\r');
|
|
1672
|
+
});
|
|
1673
|
+
await vi.waitFor(() => {
|
|
1674
|
+
if (shouldSubmit) {
|
|
1675
|
+
expect(props.onSubmit).toHaveBeenCalledWith(bufferText);
|
|
1676
|
+
expect(props.setQueueErrorMessage).not.toHaveBeenCalled();
|
|
1677
|
+
}
|
|
1678
|
+
else {
|
|
1679
|
+
expect(props.onSubmit).not.toHaveBeenCalled();
|
|
1680
|
+
expect(props.setQueueErrorMessage).toHaveBeenCalledWith(errorMessage);
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
unmount();
|
|
1684
|
+
});
|
|
1729
1685
|
});
|
|
1730
1686
|
});
|
|
1731
1687
|
function clean(str) {
|