@google/gemini-cli 0.12.0-nightly.20251023.c4c0c0d1 → 0.12.0-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/README.md +7 -5
  2. package/dist/package.json +3 -3
  3. package/dist/src/commands/extensions/disable.d.ts +1 -1
  4. package/dist/src/commands/extensions/disable.js +5 -4
  5. package/dist/src/commands/extensions/disable.js.map +1 -1
  6. package/dist/src/commands/extensions/enable.d.ts +1 -1
  7. package/dist/src/commands/extensions/enable.js +3 -2
  8. package/dist/src/commands/extensions/enable.js.map +1 -1
  9. package/dist/src/commands/extensions/install.js +2 -1
  10. package/dist/src/commands/extensions/install.js.map +1 -1
  11. package/dist/src/commands/extensions/install.test.js +1 -0
  12. package/dist/src/commands/extensions/install.test.js.map +1 -1
  13. package/dist/src/commands/extensions/link.js +2 -1
  14. package/dist/src/commands/extensions/link.js.map +1 -1
  15. package/dist/src/commands/extensions/list.js +2 -2
  16. package/dist/src/commands/extensions/list.js.map +1 -1
  17. package/dist/src/commands/extensions/uninstall.js +2 -1
  18. package/dist/src/commands/extensions/uninstall.js.map +1 -1
  19. package/dist/src/commands/extensions/update.js +2 -2
  20. package/dist/src/commands/extensions/update.js.map +1 -1
  21. package/dist/src/commands/mcp/list.js +2 -2
  22. package/dist/src/commands/mcp/list.js.map +1 -1
  23. package/dist/src/config/config.d.ts +6 -3
  24. package/dist/src/config/config.js +56 -11
  25. package/dist/src/config/config.js.map +1 -1
  26. package/dist/src/config/config.test.js +208 -175
  27. package/dist/src/config/config.test.js.map +1 -1
  28. package/dist/src/config/extension-manager.d.ts +23 -10
  29. package/dist/src/config/extension-manager.js +90 -64
  30. package/dist/src/config/extension-manager.js.map +1 -1
  31. package/dist/src/config/extension.test.js +183 -76
  32. package/dist/src/config/extension.test.js.map +1 -1
  33. package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
  34. package/dist/src/config/extensions/extensionEnablement.js +3 -2
  35. package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
  36. package/dist/src/config/extensions/extensionEnablement.test.js +10 -10
  37. package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
  38. package/dist/src/config/extensions/extensionSettings.d.ts +3 -3
  39. package/dist/src/config/extensions/extensionSettings.js +74 -24
  40. package/dist/src/config/extensions/extensionSettings.js.map +1 -1
  41. package/dist/src/config/extensions/extensionSettings.test.js +145 -24
  42. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -1
  43. package/dist/src/config/extensions/github.js +3 -3
  44. package/dist/src/config/extensions/github.js.map +1 -1
  45. package/dist/src/config/extensions/github.test.js +1 -1
  46. package/dist/src/config/extensions/github.test.js.map +1 -1
  47. package/dist/src/config/extensions/github_fetch.d.ts +1 -1
  48. package/dist/src/config/extensions/github_fetch.js +13 -1
  49. package/dist/src/config/extensions/github_fetch.js.map +1 -1
  50. package/dist/src/config/extensions/github_fetch.test.d.ts +6 -0
  51. package/dist/src/config/extensions/github_fetch.test.js +169 -0
  52. package/dist/src/config/extensions/github_fetch.test.js.map +1 -0
  53. package/dist/src/config/extensions/update.js +7 -6
  54. package/dist/src/config/extensions/update.js.map +1 -1
  55. package/dist/src/config/extensions/update.test.js +54 -30
  56. package/dist/src/config/extensions/update.test.js.map +1 -1
  57. package/dist/src/config/keyBindings.js +1 -1
  58. package/dist/src/config/keyBindings.js.map +1 -1
  59. package/dist/src/config/policies/read-only.toml +56 -0
  60. package/dist/src/config/policies/write.toml +63 -0
  61. package/dist/src/config/policies/yolo.toml +31 -0
  62. package/dist/src/config/policy-engine.integration.test.js +41 -38
  63. package/dist/src/config/policy-engine.integration.test.js.map +1 -1
  64. package/dist/src/config/policy-toml-loader.d.ts +46 -0
  65. package/dist/src/config/policy-toml-loader.js +314 -0
  66. package/dist/src/config/policy-toml-loader.js.map +1 -0
  67. package/dist/src/config/policy-toml-loader.test.d.ts +6 -0
  68. package/dist/src/config/policy-toml-loader.test.js +626 -0
  69. package/dist/src/config/policy-toml-loader.test.js.map +1 -0
  70. package/dist/src/config/policy.d.ts +9 -2
  71. package/dist/src/config/policy.js +139 -110
  72. package/dist/src/config/policy.js.map +1 -1
  73. package/dist/src/config/policy.test.js +780 -82
  74. package/dist/src/config/policy.test.js.map +1 -1
  75. package/dist/src/config/settings.test.js +6 -6
  76. package/dist/src/config/settings.test.js.map +1 -1
  77. package/dist/src/core/initializer.js +2 -1
  78. package/dist/src/core/initializer.js.map +1 -1
  79. package/dist/src/gemini.js +6 -17
  80. package/dist/src/gemini.js.map +1 -1
  81. package/dist/src/gemini.test.js +27 -2
  82. package/dist/src/gemini.test.js.map +1 -1
  83. package/dist/src/generated/git-commit.d.ts +2 -2
  84. package/dist/src/generated/git-commit.js +2 -2
  85. package/dist/src/generated/git-commit.js.map +1 -1
  86. package/dist/src/nonInteractiveCli.js +16 -4
  87. package/dist/src/nonInteractiveCli.js.map +1 -1
  88. package/dist/src/nonInteractiveCli.test.js +67 -12
  89. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  90. package/dist/src/test-utils/render.d.ts +12 -0
  91. package/dist/src/test-utils/render.js +28 -1
  92. package/dist/src/test-utils/render.js.map +1 -1
  93. package/dist/src/test-utils/render.test.d.ts +6 -0
  94. package/dist/src/test-utils/render.test.js +54 -0
  95. package/dist/src/test-utils/render.test.js.map +1 -0
  96. package/dist/src/ui/AppContainer.js +29 -23
  97. package/dist/src/ui/AppContainer.js.map +1 -1
  98. package/dist/src/ui/AppContainer.test.js +8 -0
  99. package/dist/src/ui/AppContainer.test.js.map +1 -1
  100. package/dist/src/ui/commands/directoryCommand.js +1 -1
  101. package/dist/src/ui/commands/directoryCommand.js.map +1 -1
  102. package/dist/src/ui/commands/extensionsCommand.js +45 -1
  103. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  104. package/dist/src/ui/commands/extensionsCommand.test.js +64 -1
  105. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  106. package/dist/src/ui/commands/memoryCommand.js +1 -1
  107. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  108. package/dist/src/ui/commands/memoryCommand.test.js +3 -1
  109. package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
  110. package/dist/src/ui/components/ConsoleSummaryDisplay.js +1 -1
  111. package/dist/src/ui/components/ConsoleSummaryDisplay.js.map +1 -1
  112. package/dist/src/ui/components/DetailedMessagesDisplay.js +1 -1
  113. package/dist/src/ui/components/DetailedMessagesDisplay.js.map +1 -1
  114. package/dist/src/ui/components/FolderTrustDialog.test.js +4 -4
  115. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  116. package/dist/src/ui/components/Footer.js +1 -1
  117. package/dist/src/ui/components/Footer.js.map +1 -1
  118. package/dist/src/ui/components/Footer.test.js +24 -0
  119. package/dist/src/ui/components/Footer.test.js.map +1 -1
  120. package/dist/src/ui/components/Help.test.js +0 -1
  121. package/dist/src/ui/components/Help.test.js.map +1 -1
  122. package/dist/src/ui/components/InputPrompt.test.js +442 -342
  123. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  124. package/dist/src/ui/components/ModelDialog.test.js +5 -5
  125. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  126. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +11 -12
  127. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
  128. package/dist/src/ui/components/SettingsDialog.test.js +13 -14
  129. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  130. package/dist/src/ui/components/ThemeDialog.test.js +1 -2
  131. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  132. package/dist/src/ui/components/shared/BaseSelectionList.test.js +11 -12
  133. package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
  134. package/dist/src/ui/components/shared/text-buffer.test.js +2 -1
  135. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
  136. package/dist/src/ui/components/views/ExtensionsList.d.ts +1 -1
  137. package/dist/src/ui/components/views/ExtensionsList.js +4 -1
  138. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  139. package/dist/src/ui/contexts/KeypressContext.d.ts +3 -2
  140. package/dist/src/ui/contexts/KeypressContext.js +114 -64
  141. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  142. package/dist/src/ui/contexts/KeypressContext.test.js +166 -482
  143. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  144. package/dist/src/ui/contexts/SessionContext.test.js +27 -13
  145. package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
  146. package/dist/src/ui/hooks/atCommandProcessor.js +2 -2
  147. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  148. package/dist/src/ui/hooks/shellCommandProcessor.test.js +18 -2
  149. package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
  150. package/dist/src/ui/hooks/slashCommandProcessor.test.js +74 -80
  151. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  152. package/dist/src/ui/hooks/useAtCompletion.test.js +32 -23
  153. package/dist/src/ui/hooks/useAtCompletion.test.js.map +1 -1
  154. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +2 -1
  155. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
  156. package/dist/src/ui/hooks/useCommandCompletion.test.js +79 -78
  157. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  158. package/dist/src/ui/hooks/useConsoleMessages.test.js +26 -9
  159. package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
  160. package/dist/src/ui/hooks/useEditorSettings.test.js +40 -34
  161. package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
  162. package/dist/src/ui/hooks/useExtensionUpdates.d.ts +1 -2
  163. package/dist/src/ui/hooks/useExtensionUpdates.js +4 -2
  164. package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
  165. package/dist/src/ui/hooks/useExtensionUpdates.test.js +37 -26
  166. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  167. package/dist/src/ui/hooks/useFlickerDetector.test.js +9 -5
  168. package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
  169. package/dist/src/ui/hooks/useFocus.test.js +25 -9
  170. package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
  171. package/dist/src/ui/hooks/useFolderTrust.test.js +45 -22
  172. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
  173. package/dist/src/ui/hooks/useGeminiStream.js +56 -19
  174. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  175. package/dist/src/ui/hooks/useGeminiStream.test.js +155 -74
  176. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  177. package/dist/src/ui/hooks/useGitBranchName.test.js +29 -16
  178. package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
  179. package/dist/src/ui/hooks/useHistoryManager.test.js +2 -1
  180. package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
  181. package/dist/src/ui/hooks/useIdeTrustListener.test.js +24 -7
  182. package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
  183. package/dist/src/ui/hooks/useInputHistory.test.js +2 -1
  184. package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
  185. package/dist/src/ui/hooks/useInputHistoryStore.test.js +2 -1
  186. package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
  187. package/dist/src/ui/hooks/useKeypress.test.js +94 -113
  188. package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
  189. package/dist/src/ui/hooks/useLoadingIndicator.test.js +24 -6
  190. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  191. package/dist/src/ui/hooks/useMemoryMonitor.test.js +10 -5
  192. package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
  193. package/dist/src/ui/hooks/useMessageQueue.test.js +61 -45
  194. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  195. package/dist/src/ui/hooks/useModelCommand.test.js +18 -11
  196. package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
  197. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +2 -2
  198. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
  199. package/dist/src/ui/hooks/usePhraseCycler.js +1 -1
  200. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  201. package/dist/src/ui/hooks/usePhraseCycler.test.js +83 -110
  202. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
  203. package/dist/src/ui/hooks/usePrivacySettings.test.js +26 -10
  204. package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
  205. package/dist/src/ui/hooks/useQuotaAndFallback.js +13 -14
  206. package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
  207. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +33 -40
  208. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  209. package/dist/src/ui/hooks/useReactToolScheduler.d.ts +8 -1
  210. package/dist/src/ui/hooks/useReactToolScheduler.js +37 -26
  211. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  212. package/dist/src/ui/hooks/useReactToolScheduler.test.js +1 -1
  213. package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -1
  214. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +2 -2
  215. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -1
  216. package/dist/src/ui/hooks/useSelectionList.test.js +193 -132
  217. package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
  218. package/dist/src/ui/hooks/useShellHistory.test.js +40 -16
  219. package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
  220. package/dist/src/ui/hooks/useSlashCompletion.test.js +54 -49
  221. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
  222. package/dist/src/ui/hooks/useTimer.test.js +43 -14
  223. package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
  224. package/dist/src/ui/hooks/useToolScheduler.test.js +163 -74
  225. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  226. package/dist/src/ui/hooks/vim.test.js +251 -356
  227. package/dist/src/ui/hooks/vim.test.js.map +1 -1
  228. package/dist/src/ui/keyMatchers.test.js +3 -3
  229. package/dist/src/ui/keyMatchers.test.js.map +1 -1
  230. package/dist/src/ui/utils/textOutput.d.ts +25 -0
  231. package/dist/src/ui/utils/textOutput.js +49 -0
  232. package/dist/src/ui/utils/textOutput.js.map +1 -0
  233. package/dist/src/ui/utils/textOutput.test.d.ts +6 -0
  234. package/dist/src/ui/utils/textOutput.test.js +79 -0
  235. package/dist/src/ui/utils/textOutput.test.js.map +1 -0
  236. package/dist/src/ui/utils/updateCheck.d.ts +7 -1
  237. package/dist/src/ui/utils/updateCheck.js +27 -26
  238. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  239. package/dist/src/ui/utils/updateCheck.test.js +19 -49
  240. package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
  241. package/dist/src/utils/handleAutoUpdate.js +9 -3
  242. package/dist/src/utils/handleAutoUpdate.js.map +1 -1
  243. package/dist/src/zed-integration/zedIntegration.d.ts +2 -2
  244. package/dist/src/zed-integration/zedIntegration.js +9 -16
  245. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  246. package/dist/tsconfig.tsbuildinfo +1 -1
  247. package/package.json +4 -4
  248. package/dist/google-gemini-cli-0.12.0-nightly.20251022.0542de95.tgz +0 -0
@@ -1,10 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
1
2
  /**
2
3
  * @license
3
4
  * Copyright 2025 Google LLC
4
5
  * SPDX-License-Identifier: Apache-2.0
5
6
  */
6
7
  import { describe, it, expect, vi, beforeEach } from 'vitest';
7
- import { renderHook, act } from '@testing-library/react';
8
+ import { act } from 'react';
9
+ import { render } from 'ink-testing-library';
8
10
  import { useVim } from './vim.js';
9
11
  import { textBufferReducer } from '../components/shared/text-buffer.js';
10
12
  // Mock the VimModeContext
@@ -154,7 +156,22 @@ describe('useVim hook', () => {
154
156
  }),
155
157
  };
156
158
  };
157
- const renderVimHook = (buffer) => renderHook(() => useVim((buffer || mockBuffer), mockHandleFinalSubmit));
159
+ const renderVimHook = (buffer) => {
160
+ let hookResult;
161
+ function TestComponent() {
162
+ hookResult = useVim((buffer || mockBuffer), mockHandleFinalSubmit);
163
+ return null;
164
+ }
165
+ const { rerender } = render(_jsx(TestComponent, {}));
166
+ return {
167
+ result: {
168
+ get current() {
169
+ return hookResult;
170
+ },
171
+ },
172
+ rerender: () => rerender(_jsx(TestComponent, {})),
173
+ };
174
+ };
158
175
  const exitInsertMode = (result) => {
159
176
  act(() => {
160
177
  result.current.handleInput(TEST_SEQUENCES.ESCAPE);
@@ -1006,23 +1023,32 @@ describe('useVim hook', () => {
1006
1023
  });
1007
1024
  });
1008
1025
  describe('Shell command pass-through', () => {
1009
- it('should pass through ctrl+r in INSERT mode', () => {
1026
+ it('should pass through ctrl+r in INSERT mode', async () => {
1010
1027
  mockVimContext.vimMode = 'INSERT';
1011
1028
  const { result } = renderVimHook();
1029
+ await vi.waitFor(() => {
1030
+ expect(result.current.mode).toBe('INSERT');
1031
+ });
1012
1032
  const handled = result.current.handleInput(createKey({ name: 'r', ctrl: true }));
1013
1033
  expect(handled).toBe(false);
1014
1034
  });
1015
- it('should pass through ! in INSERT mode when buffer is empty', () => {
1035
+ it('should pass through ! in INSERT mode when buffer is empty', async () => {
1016
1036
  mockVimContext.vimMode = 'INSERT';
1017
1037
  const emptyBuffer = createMockBuffer('');
1018
1038
  const { result } = renderVimHook(emptyBuffer);
1039
+ await vi.waitFor(() => {
1040
+ expect(result.current.mode).toBe('INSERT');
1041
+ });
1019
1042
  const handled = result.current.handleInput(createKey({ sequence: '!' }));
1020
1043
  expect(handled).toBe(false);
1021
1044
  });
1022
- it('should handle ! as input in INSERT mode when buffer is not empty', () => {
1045
+ it('should handle ! as input in INSERT mode when buffer is not empty', async () => {
1023
1046
  mockVimContext.vimMode = 'INSERT';
1024
1047
  const nonEmptyBuffer = createMockBuffer('not empty');
1025
1048
  const { result } = renderVimHook(nonEmptyBuffer);
1049
+ await vi.waitFor(() => {
1050
+ expect(result.current.mode).toBe('INSERT');
1051
+ });
1026
1052
  const key = createKey({ sequence: '!', name: '!' });
1027
1053
  act(() => {
1028
1054
  result.current.handleInput(key);
@@ -1032,357 +1058,226 @@ describe('useVim hook', () => {
1032
1058
  });
1033
1059
  // Line operations (dd, cc) are tested in text-buffer.test.ts
1034
1060
  describe('Reducer-based integration tests', () => {
1035
- describe('de (delete word end)', () => {
1036
- it('should delete from cursor to end of current word', () => {
1037
- const initialState = createMockTextBufferState({
1038
- lines: ['hello world test'],
1039
- cursorRow: 0,
1040
- cursorCol: 1, // cursor on 'e' in "hello"
1041
- preferredCol: null,
1042
- undoStack: [],
1043
- redoStack: [],
1044
- clipboard: null,
1045
- selectionAnchor: null,
1046
- });
1047
- const result = textBufferReducer(initialState, {
1048
- type: 'vim_delete_word_end',
1049
- payload: { count: 1 },
1050
- });
1051
- // Should delete "ello" (from cursor to end of word), leaving "h world test"
1052
- expect(result.lines).toEqual(['h world test']);
1053
- expect(result.cursorRow).toBe(0);
1054
- expect(result.cursorCol).toBe(1);
1055
- });
1056
- it('should delete multiple word ends with count', () => {
1057
- const initialState = createMockTextBufferState({
1058
- lines: ['hello world test more'],
1059
- cursorRow: 0,
1060
- cursorCol: 1, // cursor on 'e' in "hello"
1061
- preferredCol: null,
1062
- undoStack: [],
1063
- redoStack: [],
1064
- clipboard: null,
1065
- selectionAnchor: null,
1066
- });
1067
- const result = textBufferReducer(initialState, {
1068
- type: 'vim_delete_word_end',
1069
- payload: { count: 2 },
1070
- });
1071
- // Should delete "ello world" (to end of second word), leaving "h test more"
1072
- expect(result.lines).toEqual(['h test more']);
1073
- expect(result.cursorRow).toBe(0);
1074
- expect(result.cursorCol).toBe(1);
1075
- });
1076
- });
1077
- describe('db (delete word backward)', () => {
1078
- it('should delete from cursor to start of previous word', () => {
1079
- const initialState = createMockTextBufferState({
1080
- lines: ['hello world test'],
1081
- cursorRow: 0,
1082
- cursorCol: 11, // cursor on 't' in "test"
1083
- preferredCol: null,
1084
- undoStack: [],
1085
- redoStack: [],
1086
- clipboard: null,
1087
- selectionAnchor: null,
1088
- });
1089
- const result = textBufferReducer(initialState, {
1090
- type: 'vim_delete_word_backward',
1091
- payload: { count: 1 },
1092
- });
1093
- // Should delete "world" (previous word only), leaving "hello test"
1094
- expect(result.lines).toEqual(['hello test']);
1095
- expect(result.cursorRow).toBe(0);
1096
- expect(result.cursorCol).toBe(6);
1097
- });
1098
- it('should delete multiple words backward with count', () => {
1099
- const initialState = createMockTextBufferState({
1100
- lines: ['hello world test more'],
1101
- cursorRow: 0,
1102
- cursorCol: 17, // cursor on 'm' in "more"
1103
- preferredCol: null,
1104
- undoStack: [],
1105
- redoStack: [],
1106
- clipboard: null,
1107
- selectionAnchor: null,
1108
- });
1109
- const result = textBufferReducer(initialState, {
1110
- type: 'vim_delete_word_backward',
1111
- payload: { count: 2 },
1112
- });
1113
- // Should delete "world test " (two words backward), leaving "hello more"
1114
- expect(result.lines).toEqual(['hello more']);
1115
- expect(result.cursorRow).toBe(0);
1116
- expect(result.cursorCol).toBe(6);
1117
- });
1118
- });
1119
- describe('cw (change word forward)', () => {
1120
- it('should delete from cursor to start of next word', () => {
1121
- const initialState = createMockTextBufferState({
1122
- lines: ['hello world test'],
1123
- cursorRow: 0,
1124
- cursorCol: 0, // cursor on 'h' in "hello"
1125
- preferredCol: null,
1126
- undoStack: [],
1127
- redoStack: [],
1128
- clipboard: null,
1129
- selectionAnchor: null,
1130
- });
1131
- const result = textBufferReducer(initialState, {
1132
- type: 'vim_change_word_forward',
1133
- payload: { count: 1 },
1134
- });
1135
- // Should delete "hello " (word + space), leaving "world test"
1136
- expect(result.lines).toEqual(['world test']);
1137
- expect(result.cursorRow).toBe(0);
1138
- expect(result.cursorCol).toBe(0);
1139
- });
1140
- it('should change multiple words with count', () => {
1141
- const initialState = createMockTextBufferState({
1142
- lines: ['hello world test more'],
1143
- cursorRow: 0,
1144
- cursorCol: 0,
1145
- preferredCol: null,
1146
- undoStack: [],
1147
- redoStack: [],
1148
- clipboard: null,
1149
- selectionAnchor: null,
1150
- });
1151
- const result = textBufferReducer(initialState, {
1152
- type: 'vim_change_word_forward',
1153
- payload: { count: 2 },
1154
- });
1155
- // Should delete "hello world " (two words), leaving "test more"
1156
- expect(result.lines).toEqual(['test more']);
1157
- expect(result.cursorRow).toBe(0);
1158
- expect(result.cursorCol).toBe(0);
1159
- });
1160
- });
1161
- describe('ce (change word end)', () => {
1162
- it('should change from cursor to end of current word', () => {
1163
- const initialState = createMockTextBufferState({
1164
- lines: ['hello world test'],
1165
- cursorRow: 0,
1166
- cursorCol: 1, // cursor on 'e' in "hello"
1167
- preferredCol: null,
1168
- undoStack: [],
1169
- redoStack: [],
1170
- clipboard: null,
1171
- selectionAnchor: null,
1172
- });
1173
- const result = textBufferReducer(initialState, {
1174
- type: 'vim_change_word_end',
1175
- payload: { count: 1 },
1176
- });
1177
- // Should delete "ello" (from cursor to end of word), leaving "h world test"
1178
- expect(result.lines).toEqual(['h world test']);
1179
- expect(result.cursorRow).toBe(0);
1180
- expect(result.cursorCol).toBe(1);
1181
- });
1182
- it('should change multiple word ends with count', () => {
1183
- const initialState = createMockTextBufferState({
1184
- lines: ['hello world test'],
1185
- cursorRow: 0,
1186
- cursorCol: 1, // cursor on 'e' in "hello"
1187
- preferredCol: null,
1188
- undoStack: [],
1189
- redoStack: [],
1190
- clipboard: null,
1191
- selectionAnchor: null,
1192
- });
1193
- const result = textBufferReducer(initialState, {
1194
- type: 'vim_change_word_end',
1195
- payload: { count: 2 },
1196
- });
1197
- // Should delete "ello world" (to end of second word), leaving "h test"
1198
- expect(result.lines).toEqual(['h test']);
1199
- expect(result.cursorRow).toBe(0);
1200
- expect(result.cursorCol).toBe(1);
1201
- });
1202
- });
1203
- describe('cb (change word backward)', () => {
1204
- it('should change from cursor to start of previous word', () => {
1205
- const initialState = createMockTextBufferState({
1206
- lines: ['hello world test'],
1207
- cursorRow: 0,
1208
- cursorCol: 11, // cursor on 't' in "test"
1209
- preferredCol: null,
1210
- undoStack: [],
1211
- redoStack: [],
1212
- clipboard: null,
1213
- selectionAnchor: null,
1214
- });
1215
- const result = textBufferReducer(initialState, {
1216
- type: 'vim_change_word_backward',
1217
- payload: { count: 1 },
1218
- });
1219
- // Should delete "world" (previous word only), leaving "hello test"
1220
- expect(result.lines).toEqual(['hello test']);
1221
- expect(result.cursorRow).toBe(0);
1222
- expect(result.cursorCol).toBe(6);
1223
- });
1224
- });
1225
- describe('cc (change line)', () => {
1226
- it('should clear the line and place cursor at the start', () => {
1227
- const initialState = createMockTextBufferState({
1228
- lines: [' hello world'],
1229
- cursorRow: 0,
1230
- cursorCol: 5, // cursor on 'o'
1231
- preferredCol: null,
1232
- undoStack: [],
1233
- redoStack: [],
1234
- clipboard: null,
1235
- selectionAnchor: null,
1236
- });
1237
- const result = textBufferReducer(initialState, {
1238
- type: 'vim_change_line',
1239
- payload: { count: 1 },
1240
- });
1241
- expect(result.lines).toEqual(['']);
1242
- expect(result.cursorRow).toBe(0);
1243
- expect(result.cursorCol).toBe(0);
1244
- });
1245
- });
1246
- describe('dd (delete line)', () => {
1247
- it('should delete the current line', () => {
1248
- const initialState = createMockTextBufferState({
1249
- lines: ['line1', 'line2', 'line3'],
1250
- cursorRow: 1,
1251
- cursorCol: 2,
1252
- preferredCol: null,
1253
- undoStack: [],
1254
- redoStack: [],
1255
- clipboard: null,
1256
- selectionAnchor: null,
1257
- });
1258
- const result = textBufferReducer(initialState, {
1259
- type: 'vim_delete_line',
1260
- payload: { count: 1 },
1261
- });
1262
- expect(result.lines).toEqual(['line1', 'line3']);
1263
- expect(result.cursorRow).toBe(1);
1264
- expect(result.cursorCol).toBe(0);
1265
- });
1266
- it('should delete multiple lines with count', () => {
1267
- const initialState = createMockTextBufferState({
1268
- lines: ['line1', 'line2', 'line3', 'line4'],
1269
- cursorRow: 1,
1270
- cursorCol: 2,
1271
- preferredCol: null,
1272
- undoStack: [],
1273
- redoStack: [],
1274
- clipboard: null,
1275
- selectionAnchor: null,
1276
- });
1277
- const result = textBufferReducer(initialState, {
1278
- type: 'vim_delete_line',
1279
- payload: { count: 2 },
1280
- });
1281
- // Should delete lines 1 and 2
1282
- expect(result.lines).toEqual(['line1', 'line4']);
1283
- expect(result.cursorRow).toBe(1);
1284
- expect(result.cursorCol).toBe(0);
1285
- });
1286
- it('should handle deleting last line', () => {
1287
- const initialState = createMockTextBufferState({
1288
- lines: ['only line'],
1289
- cursorRow: 0,
1290
- cursorCol: 3,
1291
- preferredCol: null,
1292
- undoStack: [],
1293
- redoStack: [],
1294
- clipboard: null,
1295
- selectionAnchor: null,
1296
- });
1297
- const result = textBufferReducer(initialState, {
1298
- type: 'vim_delete_line',
1299
- payload: { count: 1 },
1300
- });
1301
- // Should leave an empty line when deleting the only line
1302
- expect(result.lines).toEqual(['']);
1303
- expect(result.cursorRow).toBe(0);
1304
- expect(result.cursorCol).toBe(0);
1305
- });
1306
- });
1307
- describe('D (delete to end of line)', () => {
1308
- it('should delete from cursor to end of line', () => {
1309
- const initialState = createMockTextBufferState({
1310
- lines: ['hello world test'],
1311
- cursorRow: 0,
1312
- cursorCol: 6, // cursor on 'w' in "world"
1313
- preferredCol: null,
1314
- undoStack: [],
1315
- redoStack: [],
1316
- clipboard: null,
1317
- selectionAnchor: null,
1318
- });
1319
- const result = textBufferReducer(initialState, {
1320
- type: 'vim_delete_to_end_of_line',
1321
- });
1322
- // Should delete "world test", leaving "hello "
1323
- expect(result.lines).toEqual(['hello ']);
1324
- expect(result.cursorRow).toBe(0);
1325
- expect(result.cursorCol).toBe(6);
1326
- });
1327
- it('should handle D at end of line', () => {
1328
- const initialState = createMockTextBufferState({
1329
- lines: ['hello world'],
1330
- cursorRow: 0,
1331
- cursorCol: 11, // cursor at end
1332
- preferredCol: null,
1333
- undoStack: [],
1334
- redoStack: [],
1335
- clipboard: null,
1336
- selectionAnchor: null,
1337
- });
1338
- const result = textBufferReducer(initialState, {
1339
- type: 'vim_delete_to_end_of_line',
1340
- });
1341
- // Should not change anything when at end of line
1342
- expect(result.lines).toEqual(['hello world']);
1343
- expect(result.cursorRow).toBe(0);
1344
- expect(result.cursorCol).toBe(11);
1345
- });
1346
- });
1347
- describe('C (change to end of line)', () => {
1348
- it('should change from cursor to end of line', () => {
1349
- const initialState = createMockTextBufferState({
1350
- lines: ['hello world test'],
1351
- cursorRow: 0,
1352
- cursorCol: 6, // cursor on 'w' in "world"
1353
- preferredCol: null,
1354
- undoStack: [],
1355
- redoStack: [],
1356
- clipboard: null,
1357
- selectionAnchor: null,
1358
- });
1359
- const result = textBufferReducer(initialState, {
1360
- type: 'vim_change_to_end_of_line',
1361
- });
1362
- // Should delete "world test", leaving "hello "
1363
- expect(result.lines).toEqual(['hello ']);
1364
- expect(result.cursorRow).toBe(0);
1365
- expect(result.cursorCol).toBe(6);
1366
- });
1367
- it('should handle C at beginning of line', () => {
1368
- const initialState = createMockTextBufferState({
1369
- lines: ['hello world'],
1370
- cursorRow: 0,
1371
- cursorCol: 0,
1372
- preferredCol: null,
1373
- undoStack: [],
1374
- redoStack: [],
1375
- clipboard: null,
1376
- selectionAnchor: null,
1377
- });
1378
- const result = textBufferReducer(initialState, {
1379
- type: 'vim_change_to_end_of_line',
1380
- });
1381
- // Should delete entire line content
1382
- expect(result.lines).toEqual(['']);
1383
- expect(result.cursorRow).toBe(0);
1384
- expect(result.cursorCol).toBe(0);
1385
- });
1061
+ const testCases = [
1062
+ {
1063
+ command: 'de',
1064
+ desc: 'delete from cursor to end of current word',
1065
+ lines: ['hello world test'],
1066
+ cursorRow: 0,
1067
+ cursorCol: 1,
1068
+ actionType: 'vim_delete_word_end',
1069
+ count: 1,
1070
+ expectedLines: ['h world test'],
1071
+ expectedCursorRow: 0,
1072
+ expectedCursorCol: 1,
1073
+ },
1074
+ {
1075
+ command: 'de',
1076
+ desc: 'delete multiple word ends with count',
1077
+ lines: ['hello world test more'],
1078
+ cursorRow: 0,
1079
+ cursorCol: 1,
1080
+ actionType: 'vim_delete_word_end',
1081
+ count: 2,
1082
+ expectedLines: ['h test more'],
1083
+ expectedCursorRow: 0,
1084
+ expectedCursorCol: 1,
1085
+ },
1086
+ {
1087
+ command: 'db',
1088
+ desc: 'delete from cursor to start of previous word',
1089
+ lines: ['hello world test'],
1090
+ cursorRow: 0,
1091
+ cursorCol: 11,
1092
+ actionType: 'vim_delete_word_backward',
1093
+ count: 1,
1094
+ expectedLines: ['hello test'],
1095
+ expectedCursorRow: 0,
1096
+ expectedCursorCol: 6,
1097
+ },
1098
+ {
1099
+ command: 'db',
1100
+ desc: 'delete multiple words backward with count',
1101
+ lines: ['hello world test more'],
1102
+ cursorRow: 0,
1103
+ cursorCol: 17,
1104
+ actionType: 'vim_delete_word_backward',
1105
+ count: 2,
1106
+ expectedLines: ['hello more'],
1107
+ expectedCursorRow: 0,
1108
+ expectedCursorCol: 6,
1109
+ },
1110
+ {
1111
+ command: 'cw',
1112
+ desc: 'delete from cursor to start of next word',
1113
+ lines: ['hello world test'],
1114
+ cursorRow: 0,
1115
+ cursorCol: 0,
1116
+ actionType: 'vim_change_word_forward',
1117
+ count: 1,
1118
+ expectedLines: ['world test'],
1119
+ expectedCursorRow: 0,
1120
+ expectedCursorCol: 0,
1121
+ },
1122
+ {
1123
+ command: 'cw',
1124
+ desc: 'change multiple words with count',
1125
+ lines: ['hello world test more'],
1126
+ cursorRow: 0,
1127
+ cursorCol: 0,
1128
+ actionType: 'vim_change_word_forward',
1129
+ count: 2,
1130
+ expectedLines: ['test more'],
1131
+ expectedCursorRow: 0,
1132
+ expectedCursorCol: 0,
1133
+ },
1134
+ {
1135
+ command: 'ce',
1136
+ desc: 'change from cursor to end of current word',
1137
+ lines: ['hello world test'],
1138
+ cursorRow: 0,
1139
+ cursorCol: 1,
1140
+ actionType: 'vim_change_word_end',
1141
+ count: 1,
1142
+ expectedLines: ['h world test'],
1143
+ expectedCursorRow: 0,
1144
+ expectedCursorCol: 1,
1145
+ },
1146
+ {
1147
+ command: 'ce',
1148
+ desc: 'change multiple word ends with count',
1149
+ lines: ['hello world test'],
1150
+ cursorRow: 0,
1151
+ cursorCol: 1,
1152
+ actionType: 'vim_change_word_end',
1153
+ count: 2,
1154
+ expectedLines: ['h test'],
1155
+ expectedCursorRow: 0,
1156
+ expectedCursorCol: 1,
1157
+ },
1158
+ {
1159
+ command: 'cb',
1160
+ desc: 'change from cursor to start of previous word',
1161
+ lines: ['hello world test'],
1162
+ cursorRow: 0,
1163
+ cursorCol: 11,
1164
+ actionType: 'vim_change_word_backward',
1165
+ count: 1,
1166
+ expectedLines: ['hello test'],
1167
+ expectedCursorRow: 0,
1168
+ expectedCursorCol: 6,
1169
+ },
1170
+ {
1171
+ command: 'cc',
1172
+ desc: 'clear the line and place cursor at the start',
1173
+ lines: [' hello world'],
1174
+ cursorRow: 0,
1175
+ cursorCol: 5,
1176
+ actionType: 'vim_change_line',
1177
+ count: 1,
1178
+ expectedLines: [''],
1179
+ expectedCursorRow: 0,
1180
+ expectedCursorCol: 0,
1181
+ },
1182
+ {
1183
+ command: 'dd',
1184
+ desc: 'delete the current line',
1185
+ lines: ['line1', 'line2', 'line3'],
1186
+ cursorRow: 1,
1187
+ cursorCol: 2,
1188
+ actionType: 'vim_delete_line',
1189
+ count: 1,
1190
+ expectedLines: ['line1', 'line3'],
1191
+ expectedCursorRow: 1,
1192
+ expectedCursorCol: 0,
1193
+ },
1194
+ {
1195
+ command: 'dd',
1196
+ desc: 'delete multiple lines with count',
1197
+ lines: ['line1', 'line2', 'line3', 'line4'],
1198
+ cursorRow: 1,
1199
+ cursorCol: 2,
1200
+ actionType: 'vim_delete_line',
1201
+ count: 2,
1202
+ expectedLines: ['line1', 'line4'],
1203
+ expectedCursorRow: 1,
1204
+ expectedCursorCol: 0,
1205
+ },
1206
+ {
1207
+ command: 'dd',
1208
+ desc: 'handle deleting last line',
1209
+ lines: ['only line'],
1210
+ cursorRow: 0,
1211
+ cursorCol: 3,
1212
+ actionType: 'vim_delete_line',
1213
+ count: 1,
1214
+ expectedLines: [''],
1215
+ expectedCursorRow: 0,
1216
+ expectedCursorCol: 0,
1217
+ },
1218
+ {
1219
+ command: 'D',
1220
+ desc: 'delete from cursor to end of line',
1221
+ lines: ['hello world test'],
1222
+ cursorRow: 0,
1223
+ cursorCol: 6,
1224
+ actionType: 'vim_delete_to_end_of_line',
1225
+ expectedLines: ['hello '],
1226
+ expectedCursorRow: 0,
1227
+ expectedCursorCol: 6,
1228
+ },
1229
+ {
1230
+ command: 'D',
1231
+ desc: 'handle D at end of line',
1232
+ lines: ['hello world'],
1233
+ cursorRow: 0,
1234
+ cursorCol: 11,
1235
+ actionType: 'vim_delete_to_end_of_line',
1236
+ expectedLines: ['hello world'],
1237
+ expectedCursorRow: 0,
1238
+ expectedCursorCol: 11,
1239
+ },
1240
+ {
1241
+ command: 'C',
1242
+ desc: 'change from cursor to end of line',
1243
+ lines: ['hello world test'],
1244
+ cursorRow: 0,
1245
+ cursorCol: 6,
1246
+ actionType: 'vim_change_to_end_of_line',
1247
+ expectedLines: ['hello '],
1248
+ expectedCursorRow: 0,
1249
+ expectedCursorCol: 6,
1250
+ },
1251
+ {
1252
+ command: 'C',
1253
+ desc: 'handle C at beginning of line',
1254
+ lines: ['hello world'],
1255
+ cursorRow: 0,
1256
+ cursorCol: 0,
1257
+ actionType: 'vim_change_to_end_of_line',
1258
+ expectedLines: [''],
1259
+ expectedCursorRow: 0,
1260
+ expectedCursorCol: 0,
1261
+ },
1262
+ ];
1263
+ it.each(testCases)('$command: should $desc', ({ lines, cursorRow, cursorCol, actionType, count, expectedLines, expectedCursorRow, expectedCursorCol, }) => {
1264
+ const initialState = createMockTextBufferState({
1265
+ lines,
1266
+ cursorRow,
1267
+ cursorCol,
1268
+ preferredCol: null,
1269
+ undoStack: [],
1270
+ redoStack: [],
1271
+ clipboard: null,
1272
+ selectionAnchor: null,
1273
+ });
1274
+ const action = (count
1275
+ ? { type: actionType, payload: { count } }
1276
+ : { type: actionType });
1277
+ const result = textBufferReducer(initialState, action);
1278
+ expect(result.lines).toEqual(expectedLines);
1279
+ expect(result.cursorRow).toBe(expectedCursorRow);
1280
+ expect(result.cursorCol).toBe(expectedCursorCol);
1386
1281
  });
1387
1282
  });
1388
1283
  });