@google/gemini-cli 0.13.0-nightly.20251030.42c79c64 → 0.13.0-nightly.20251101.caf2ca14

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 (216) hide show
  1. package/dist/google-gemini-cli-0.13.0-nightly.20251031.c89bc30d.tgz +0 -0
  2. package/dist/package.json +8 -10
  3. package/dist/src/commands/extensions/install.js +2 -2
  4. package/dist/src/commands/extensions/install.js.map +1 -1
  5. package/dist/src/commands/extensions/install.test.js +27 -16
  6. package/dist/src/commands/extensions/install.test.js.map +1 -1
  7. package/dist/src/commands/extensions/link.js +2 -2
  8. package/dist/src/commands/extensions/link.js.map +1 -1
  9. package/dist/src/commands/extensions/update.js +3 -2
  10. package/dist/src/commands/extensions/update.js.map +1 -1
  11. package/dist/src/commands/extensions/validate.d.ts +12 -0
  12. package/dist/src/commands/extensions/validate.js +83 -0
  13. package/dist/src/commands/extensions/validate.js.map +1 -0
  14. package/dist/src/commands/extensions/validate.test.d.ts +6 -0
  15. package/dist/src/commands/extensions/validate.test.js +93 -0
  16. package/dist/src/commands/extensions/validate.test.js.map +1 -0
  17. package/dist/src/commands/extensions.js +2 -0
  18. package/dist/src/commands/extensions.js.map +1 -1
  19. package/dist/src/config/config.js +2 -1
  20. package/dist/src/config/config.js.map +1 -1
  21. package/dist/src/config/config.test.js +6 -10
  22. package/dist/src/config/config.test.js.map +1 -1
  23. package/dist/src/config/extension-manager.js +8 -2
  24. package/dist/src/config/extension-manager.js.map +1 -1
  25. package/dist/src/config/extension.test.js +61 -1
  26. package/dist/src/config/extension.test.js.map +1 -1
  27. package/dist/src/config/extensions/update.d.ts +2 -2
  28. package/dist/src/config/extensions/update.js +28 -22
  29. package/dist/src/config/extensions/update.js.map +1 -1
  30. package/dist/src/config/settings.js +1 -1
  31. package/dist/src/config/settings.js.map +1 -1
  32. package/dist/src/config/settings.test.js +13 -53
  33. package/dist/src/config/settings.test.js.map +1 -1
  34. package/dist/src/config/settingsSchema.d.ts +26 -8
  35. package/dist/src/config/settingsSchema.js +25 -7
  36. package/dist/src/config/settingsSchema.js.map +1 -1
  37. package/dist/src/config/settingsSchema.test.js +2 -0
  38. package/dist/src/config/settingsSchema.test.js.map +1 -1
  39. package/dist/src/config/trustedFolders.d.ts +1 -1
  40. package/dist/src/config/trustedFolders.js +4 -2
  41. package/dist/src/config/trustedFolders.js.map +1 -1
  42. package/dist/src/gemini.d.ts +1 -1
  43. package/dist/src/gemini.js +8 -1
  44. package/dist/src/gemini.js.map +1 -1
  45. package/dist/src/gemini.test.js +58 -28
  46. package/dist/src/gemini.test.js.map +1 -1
  47. package/dist/src/generated/git-commit.d.ts +2 -2
  48. package/dist/src/generated/git-commit.js +2 -2
  49. package/dist/src/test-utils/async.d.ts +9 -0
  50. package/dist/src/test-utils/async.js +29 -0
  51. package/dist/src/test-utils/async.js.map +1 -0
  52. package/dist/src/test-utils/render.d.ts +2 -1
  53. package/dist/src/test-utils/render.js +36 -2
  54. package/dist/src/test-utils/render.js.map +1 -1
  55. package/dist/src/test-utils/render.test.js +29 -4
  56. package/dist/src/test-utils/render.test.js.map +1 -1
  57. package/dist/src/ui/App.test.js +1 -1
  58. package/dist/src/ui/App.test.js.map +1 -1
  59. package/dist/src/ui/AppContainer.js +1 -1
  60. package/dist/src/ui/AppContainer.js.map +1 -1
  61. package/dist/src/ui/AppContainer.test.js +205 -99
  62. package/dist/src/ui/AppContainer.test.js.map +1 -1
  63. package/dist/src/ui/auth/ApiAuthDialog.test.js +1 -1
  64. package/dist/src/ui/auth/ApiAuthDialog.test.js.map +1 -1
  65. package/dist/src/ui/components/AnsiOutput.test.js +1 -1
  66. package/dist/src/ui/components/AnsiOutput.test.js.map +1 -1
  67. package/dist/src/ui/components/Composer.test.js +1 -1
  68. package/dist/src/ui/components/Composer.test.js.map +1 -1
  69. package/dist/src/ui/components/ConsentPrompt.test.js +18 -8
  70. package/dist/src/ui/components/ConsentPrompt.test.js.map +1 -1
  71. package/dist/src/ui/components/ContextSummaryDisplay.test.js +11 -6
  72. package/dist/src/ui/components/ContextSummaryDisplay.test.js.map +1 -1
  73. package/dist/src/ui/components/FolderTrustDialog.test.js +4 -3
  74. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  75. package/dist/src/ui/components/Footer.js +3 -2
  76. package/dist/src/ui/components/Footer.js.map +1 -1
  77. package/dist/src/ui/components/Footer.test.js +59 -0
  78. package/dist/src/ui/components/Footer.test.js.map +1 -1
  79. package/dist/src/ui/components/Header.test.js +9 -5
  80. package/dist/src/ui/components/Header.test.js.map +1 -1
  81. package/dist/src/ui/components/Help.test.js +5 -3
  82. package/dist/src/ui/components/Help.test.js.map +1 -1
  83. package/dist/src/ui/components/InputPrompt.test.js +139 -111
  84. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  85. package/dist/src/ui/components/LoadingIndicator.js +2 -2
  86. package/dist/src/ui/components/LoadingIndicator.js.map +1 -1
  87. package/dist/src/ui/components/LoadingIndicator.test.js +28 -15
  88. package/dist/src/ui/components/LoadingIndicator.test.js.map +1 -1
  89. package/dist/src/ui/components/LoopDetectionConfirmation.js +1 -1
  90. package/dist/src/ui/components/LoopDetectionConfirmation.js.map +1 -1
  91. package/dist/src/ui/components/LoopDetectionConfirmation.test.js +2 -2
  92. package/dist/src/ui/components/LoopDetectionConfirmation.test.js.map +1 -1
  93. package/dist/src/ui/components/ModelDialog.js +1 -1
  94. package/dist/src/ui/components/ModelDialog.js.map +1 -1
  95. package/dist/src/ui/components/ModelDialog.test.js +21 -11
  96. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  97. package/dist/src/ui/components/ModelStatsDisplay.test.js +1 -1
  98. package/dist/src/ui/components/ModelStatsDisplay.test.js.map +1 -1
  99. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +11 -10
  100. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
  101. package/dist/src/ui/components/PrepareLabel.test.js +14 -8
  102. package/dist/src/ui/components/PrepareLabel.test.js.map +1 -1
  103. package/dist/src/ui/components/ProQuotaDialog.test.js +14 -6
  104. package/dist/src/ui/components/ProQuotaDialog.test.js.map +1 -1
  105. package/dist/src/ui/components/QueuedMessageDisplay.test.js +11 -6
  106. package/dist/src/ui/components/QueuedMessageDisplay.test.js.map +1 -1
  107. package/dist/src/ui/components/SessionSummaryDisplay.test.js +1 -1
  108. package/dist/src/ui/components/SessionSummaryDisplay.test.js.map +1 -1
  109. package/dist/src/ui/components/SettingsDialog.test.js +438 -512
  110. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  111. package/dist/src/ui/components/ShellConfirmationDialog.js +1 -1
  112. package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -1
  113. package/dist/src/ui/components/ShellConfirmationDialog.test.js +2 -2
  114. package/dist/src/ui/components/ShellConfirmationDialog.test.js.map +1 -1
  115. package/dist/src/ui/components/StatsDisplay.test.js +1 -1
  116. package/dist/src/ui/components/StatsDisplay.test.js.map +1 -1
  117. package/dist/src/ui/components/SuggestionsDisplay.js +1 -1
  118. package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
  119. package/dist/src/ui/components/ThemeDialog.test.js +3 -2
  120. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  121. package/dist/src/ui/components/ToolStatsDisplay.test.js +1 -1
  122. package/dist/src/ui/components/ToolStatsDisplay.test.js.map +1 -1
  123. package/dist/src/ui/components/messages/CompressionMessage.test.js +25 -17
  124. package/dist/src/ui/components/messages/CompressionMessage.test.js.map +1 -1
  125. package/dist/src/ui/components/messages/DiffRenderer.test.js +1 -1
  126. package/dist/src/ui/components/messages/DiffRenderer.test.js.map +1 -1
  127. package/dist/src/ui/components/messages/Todo.test.js +1 -1
  128. package/dist/src/ui/components/messages/Todo.test.js.map +1 -1
  129. package/dist/src/ui/components/messages/ToolGroupMessage.test.js +29 -15
  130. package/dist/src/ui/components/messages/ToolGroupMessage.test.js.map +1 -1
  131. package/dist/src/ui/components/shared/BaseSelectionList.test.js +12 -11
  132. package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
  133. package/dist/src/ui/components/shared/MaxSizedBox.test.js +43 -22
  134. package/dist/src/ui/components/shared/MaxSizedBox.test.js.map +1 -1
  135. package/dist/src/ui/components/shared/TextInput.test.js +1 -1
  136. package/dist/src/ui/components/shared/TextInput.test.js.map +1 -1
  137. package/dist/src/ui/components/views/ChatList.test.js +7 -4
  138. package/dist/src/ui/components/views/ChatList.test.js.map +1 -1
  139. package/dist/src/ui/components/views/ExtensionsList.js +1 -0
  140. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  141. package/dist/src/ui/components/views/ExtensionsList.test.js +13 -5
  142. package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
  143. package/dist/src/ui/components/views/McpStatus.test.js +23 -12
  144. package/dist/src/ui/components/views/McpStatus.test.js.map +1 -1
  145. package/dist/src/ui/contexts/KeypressContext.js +43 -45
  146. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  147. package/dist/src/ui/contexts/KeypressContext.test.js +136 -252
  148. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  149. package/dist/src/ui/contexts/SessionContext.test.js +9 -5
  150. package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
  151. package/dist/src/ui/hooks/atCommandProcessor.test.js +1 -0
  152. package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -1
  153. package/dist/src/ui/hooks/shellCommandProcessor.test.js +47 -34
  154. package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
  155. package/dist/src/ui/hooks/slashCommandProcessor.test.js +141 -104
  156. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  157. package/dist/src/ui/hooks/useAtCompletion.test.js +23 -21
  158. package/dist/src/ui/hooks/useAtCompletion.test.js.map +1 -1
  159. package/dist/src/ui/hooks/useCommandCompletion.test.js +16 -15
  160. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  161. package/dist/src/ui/hooks/useConsoleMessages.test.js +1 -1
  162. package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
  163. package/dist/src/ui/hooks/useEditorSettings.test.js +1 -1
  164. package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
  165. package/dist/src/ui/hooks/useExtensionUpdates.d.ts +1 -1
  166. package/dist/src/ui/hooks/useExtensionUpdates.js +9 -3
  167. package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
  168. package/dist/src/ui/hooks/useExtensionUpdates.test.js +10 -9
  169. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  170. package/dist/src/ui/hooks/useFocus.test.js +1 -1
  171. package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
  172. package/dist/src/ui/hooks/useFolderTrust.test.js +7 -6
  173. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
  174. package/dist/src/ui/hooks/useGeminiStream.test.js +39 -38
  175. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  176. package/dist/src/ui/hooks/useGitBranchName.test.js +5 -4
  177. package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
  178. package/dist/src/ui/hooks/useIdeTrustListener.test.js +25 -11
  179. package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
  180. package/dist/src/ui/hooks/useKeypress.test.js +1 -1
  181. package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
  182. package/dist/src/ui/hooks/useLoadingIndicator.test.js +1 -1
  183. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  184. package/dist/src/ui/hooks/useMemoryMonitor.test.js +1 -1
  185. package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
  186. package/dist/src/ui/hooks/useMessageQueue.test.js +5 -4
  187. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  188. package/dist/src/ui/hooks/useModelCommand.test.js +7 -4
  189. package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
  190. package/dist/src/ui/hooks/usePhraseCycler.test.js +46 -16
  191. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
  192. package/dist/src/ui/hooks/usePrivacySettings.test.js +11 -7
  193. package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
  194. package/dist/src/ui/hooks/usePromptCompletion.js +2 -2
  195. package/dist/src/ui/hooks/usePromptCompletion.js.map +1 -1
  196. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +28 -14
  197. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  198. package/dist/src/ui/hooks/useSelectionList.test.js +116 -109
  199. package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
  200. package/dist/src/ui/hooks/useShellHistory.test.js +25 -17
  201. package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
  202. package/dist/src/ui/hooks/useSlashCompletion.js +16 -5
  203. package/dist/src/ui/hooks/useSlashCompletion.js.map +1 -1
  204. package/dist/src/ui/hooks/useSlashCompletion.test.js +244 -111
  205. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
  206. package/dist/src/ui/hooks/useTimer.test.js +1 -1
  207. package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
  208. package/dist/src/ui/hooks/useToolScheduler.test.js +6 -2
  209. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  210. package/dist/src/ui/hooks/vim.test.js +6 -21
  211. package/dist/src/ui/hooks/vim.test.js.map +1 -1
  212. package/dist/src/ui/state/extensions.d.ts +1 -0
  213. package/dist/src/ui/state/extensions.js +1 -0
  214. package/dist/src/ui/state/extensions.js.map +1 -1
  215. package/dist/tsconfig.tsbuildinfo +1 -1
  216. package/package.json +9 -11
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { act } from 'react';
3
3
  import { renderHook } from '../../test-utils/render.js';
4
+ import { waitFor } from '../../test-utils/async.js';
4
5
  import { vi } from 'vitest';
5
6
  import { KeypressProvider, useKeypressContext, DRAG_COMPLETION_TIMEOUT_MS, KITTY_SEQUENCE_TIMEOUT_MS,
6
7
  // CSI_END_O,
@@ -32,6 +33,14 @@ class MockStdin extends EventEmitter {
32
33
  this.emit('data', text);
33
34
  }
34
35
  }
36
+ // Helper function to setup keypress test with standard configuration
37
+ const setupKeypressTest = (kittyProtocolEnabled = true) => {
38
+ const keyHandler = vi.fn();
39
+ const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kittyProtocolEnabled, children: children }));
40
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
41
+ act(() => result.current.subscribe(keyHandler));
42
+ return { result, keyHandler };
43
+ };
35
44
  describe('KeypressContext - Kitty Protocol', () => {
36
45
  let stdin;
37
46
  const mockSetRawMode = vi.fn();
@@ -45,100 +54,55 @@ describe('KeypressContext - Kitty Protocol', () => {
45
54
  });
46
55
  });
47
56
  describe('Enter key handling', () => {
48
- it('should recognize regular enter key (keycode 13) in kitty protocol', async () => {
49
- const keyHandler = vi.fn();
50
- const { result } = renderHook(() => useKeypressContext(), {
51
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
52
- });
53
- act(() => result.current.subscribe(keyHandler));
54
- // Send kitty protocol sequence for regular enter: ESC[13u
55
- act(() => {
56
- stdin.write(`\x1b[13u`);
57
- });
58
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
59
- name: 'return',
60
- kittyProtocol: true,
61
- ctrl: false,
62
- meta: false,
63
- shift: false,
64
- }));
65
- });
66
- it('should recognize numpad enter key (keycode 57414) in kitty protocol', async () => {
67
- const keyHandler = vi.fn();
68
- const { result } = renderHook(() => useKeypressContext(), {
69
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
70
- });
71
- act(() => result.current.subscribe(keyHandler));
72
- // Send kitty protocol sequence for numpad enter: ESC[57414u
73
- act(() => {
74
- stdin.write(`\x1b[57414u`);
75
- });
76
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
77
- name: 'return',
78
- kittyProtocol: true,
79
- ctrl: false,
80
- meta: false,
81
- shift: false,
82
- }));
83
- });
84
- it('should handle numpad enter with modifiers', async () => {
85
- const keyHandler = vi.fn();
86
- const { result } = renderHook(() => useKeypressContext(), {
87
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
88
- });
89
- act(() => result.current.subscribe(keyHandler));
90
- // Send kitty protocol sequence for numpad enter with Shift (modifier 2): ESC[57414;2u
57
+ it.each([
58
+ {
59
+ name: 'regular enter key (keycode 13)',
60
+ sequence: '\x1b[13u',
61
+ },
62
+ {
63
+ name: 'numpad enter key (keycode 57414)',
64
+ sequence: '\x1b[57414u',
65
+ },
66
+ ])('should recognize $name in kitty protocol', async ({ sequence }) => {
67
+ const { keyHandler } = setupKeypressTest(true);
91
68
  act(() => {
92
- stdin.write(`\x1b[57414;2u`);
69
+ stdin.write(sequence);
93
70
  });
94
71
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
95
72
  name: 'return',
96
73
  kittyProtocol: true,
97
74
  ctrl: false,
98
75
  meta: false,
99
- shift: true,
100
- }));
101
- });
102
- it('should handle numpad enter with Ctrl modifier', async () => {
103
- const keyHandler = vi.fn();
104
- const { result } = renderHook(() => useKeypressContext(), {
105
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
106
- });
107
- act(() => result.current.subscribe(keyHandler));
108
- // Send kitty protocol sequence for numpad enter with Ctrl (modifier 5): ESC[57414;5u
109
- act(() => stdin.write(`\x1b[57414;5u`));
110
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
111
- name: 'return',
112
- kittyProtocol: true,
113
- ctrl: true,
114
- meta: false,
115
76
  shift: false,
116
77
  }));
117
78
  });
118
- it('should handle numpad enter with Alt modifier', async () => {
119
- const keyHandler = vi.fn();
120
- const { result } = renderHook(() => useKeypressContext(), {
121
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
122
- });
123
- act(() => result.current.subscribe(keyHandler));
124
- // Send kitty protocol sequence for numpad enter with Alt (modifier 3): ESC[57414;3u
125
- act(() => {
126
- stdin.write(`\x1b[57414;3u`);
127
- });
79
+ it.each([
80
+ {
81
+ modifier: 'Shift',
82
+ sequence: '\x1b[57414;2u',
83
+ expected: { ctrl: false, meta: false, shift: true },
84
+ },
85
+ {
86
+ modifier: 'Ctrl',
87
+ sequence: '\x1b[57414;5u',
88
+ expected: { ctrl: true, meta: false, shift: false },
89
+ },
90
+ {
91
+ modifier: 'Alt',
92
+ sequence: '\x1b[57414;3u',
93
+ expected: { ctrl: false, meta: true, shift: false },
94
+ },
95
+ ])('should handle numpad enter with $modifier modifier', async ({ sequence, expected }) => {
96
+ const { keyHandler } = setupKeypressTest(true);
97
+ act(() => stdin.write(sequence));
128
98
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
129
99
  name: 'return',
130
100
  kittyProtocol: true,
131
- ctrl: false,
132
- meta: true,
133
- shift: false,
101
+ ...expected,
134
102
  }));
135
103
  });
136
104
  it('should not process kitty sequences when kitty protocol is disabled', async () => {
137
- const keyHandler = vi.fn();
138
- const { result } = renderHook(() => useKeypressContext(), {
139
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
140
- });
141
- act(() => result.current.subscribe(keyHandler));
105
+ const { keyHandler } = setupKeypressTest(false);
142
106
  // Send kitty protocol sequence for numpad enter
143
107
  act(() => {
144
108
  stdin.write(`\x1b[57414u`);
@@ -153,11 +117,7 @@ describe('KeypressContext - Kitty Protocol', () => {
153
117
  });
154
118
  describe('Escape key handling', () => {
155
119
  it('should recognize escape key (keycode 27) in kitty protocol', async () => {
156
- const keyHandler = vi.fn();
157
- const { result } = renderHook(() => useKeypressContext(), {
158
- wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
159
- });
160
- act(() => result.current.subscribe(keyHandler));
120
+ const { keyHandler } = setupKeypressTest(true);
161
121
  // Send kitty protocol sequence for escape: ESC[27u
162
122
  act(() => {
163
123
  stdin.write('\x1b[27u');
@@ -169,132 +129,80 @@ describe('KeypressContext - Kitty Protocol', () => {
169
129
  });
170
130
  });
171
131
  describe('Tab and Backspace handling', () => {
172
- it('should recognize Tab key in kitty protocol', async () => {
173
- const keyHandler = vi.fn();
174
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
175
- act(() => result.current.subscribe(keyHandler));
176
- act(() => {
177
- stdin.write(`\x1b[9u`);
178
- });
179
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
180
- name: 'tab',
181
- kittyProtocol: true,
182
- shift: false,
183
- }));
184
- });
185
- it('should recognize Shift+Tab in kitty protocol', async () => {
186
- const keyHandler = vi.fn();
187
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
188
- act(() => result.current.subscribe(keyHandler));
189
- // Modifier 2 is Shift
190
- act(() => {
191
- stdin.write(`\x1b[9;2u`);
192
- });
193
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
194
- name: 'tab',
195
- kittyProtocol: true,
196
- shift: true,
197
- }));
198
- });
199
- it('should recognize Backspace key in kitty protocol', async () => {
200
- const keyHandler = vi.fn();
201
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
202
- act(() => result.current.subscribe(keyHandler));
203
- act(() => {
204
- stdin.write(`\x1b[127u`);
205
- });
206
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
207
- name: 'backspace',
208
- kittyProtocol: true,
209
- meta: false,
210
- }));
211
- });
212
- it('should recognize Option+Backspace in kitty protocol', async () => {
213
- const keyHandler = vi.fn();
214
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
215
- act(() => result.current.subscribe(keyHandler));
216
- // Modifier 3 is Alt/Option
217
- act(() => {
218
- stdin.write(`\x1b[127;3u`);
219
- });
220
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
221
- name: 'backspace',
222
- kittyProtocol: true,
223
- meta: true,
224
- }));
225
- });
226
- it('should recognize Ctrl+Backspace in kitty protocol', async () => {
227
- const keyHandler = vi.fn();
228
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
229
- act(() => result.current.subscribe(keyHandler));
230
- // Modifier 5 is Ctrl
132
+ it.each([
133
+ {
134
+ name: 'Tab key',
135
+ sequence: '\x1b[9u',
136
+ expected: { name: 'tab', shift: false },
137
+ },
138
+ {
139
+ name: 'Shift+Tab',
140
+ sequence: '\x1b[9;2u',
141
+ expected: { name: 'tab', shift: true },
142
+ },
143
+ {
144
+ name: 'Backspace',
145
+ sequence: '\x1b[127u',
146
+ expected: { name: 'backspace', meta: false },
147
+ },
148
+ {
149
+ name: 'Option+Backspace',
150
+ sequence: '\x1b[127;3u',
151
+ expected: { name: 'backspace', meta: true },
152
+ },
153
+ {
154
+ name: 'Ctrl+Backspace',
155
+ sequence: '\x1b[127;5u',
156
+ expected: { name: 'backspace', ctrl: true },
157
+ },
158
+ ])('should recognize $name in kitty protocol', async ({ sequence, expected }) => {
159
+ const { keyHandler } = setupKeypressTest(true);
231
160
  act(() => {
232
- stdin.write(`\x1b[127;5u`);
161
+ stdin.write(sequence);
233
162
  });
234
163
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
235
- name: 'backspace',
164
+ ...expected,
236
165
  kittyProtocol: true,
237
- ctrl: true,
238
166
  }));
239
167
  });
240
168
  });
241
169
  describe('paste mode', () => {
242
- it('should handle multiline paste as a single event', async () => {
243
- const keyHandler = vi.fn();
244
- const pastedText = 'This \n is \n a \n multiline \n paste.';
245
- const { result } = renderHook(() => useKeypressContext(), {
246
- wrapper,
247
- });
248
- act(() => result.current.subscribe(keyHandler));
249
- // Simulate a bracketed paste event
250
- act(() => {
251
- stdin.write(PASTE_START);
252
- stdin.write(pastedText);
253
- stdin.write(PASTE_END);
254
- });
255
- await vi.waitFor(() => {
256
- // Expect the handler to be called exactly once for the entire paste
257
- expect(keyHandler).toHaveBeenCalledTimes(1);
258
- });
259
- // Verify the single event contains the full pasted text
260
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
261
- paste: true,
262
- sequence: pastedText,
263
- }));
264
- });
265
- it('should paste start code split over multiple writes', async () => {
266
- const keyHandler = vi.fn();
267
- const pastedText = 'pasted content';
268
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
269
- act(() => result.current.subscribe(keyHandler));
270
- act(() => {
271
- // Split PASTE_START into two parts
272
- stdin.write(PASTE_START.slice(0, 3));
273
- stdin.write(PASTE_START.slice(3));
274
- stdin.write(pastedText);
275
- stdin.write(PASTE_END);
276
- });
277
- await vi.waitFor(() => {
278
- expect(keyHandler).toHaveBeenCalledTimes(1);
279
- });
280
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
281
- paste: true,
282
- sequence: pastedText,
283
- }));
284
- });
285
- it('should paste end code split over multiple writes', async () => {
170
+ it.each([
171
+ {
172
+ name: 'handle multiline paste as a single event',
173
+ pastedText: 'This \n is \n a \n multiline \n paste.',
174
+ writeSequence: (text) => {
175
+ stdin.write(PASTE_START);
176
+ stdin.write(text);
177
+ stdin.write(PASTE_END);
178
+ },
179
+ },
180
+ {
181
+ name: 'handle paste start code split over multiple writes',
182
+ pastedText: 'pasted content',
183
+ writeSequence: (text) => {
184
+ stdin.write(PASTE_START.slice(0, 3));
185
+ stdin.write(PASTE_START.slice(3));
186
+ stdin.write(text);
187
+ stdin.write(PASTE_END);
188
+ },
189
+ },
190
+ {
191
+ name: 'handle paste end code split over multiple writes',
192
+ pastedText: 'pasted content',
193
+ writeSequence: (text) => {
194
+ stdin.write(PASTE_START);
195
+ stdin.write(text);
196
+ stdin.write(PASTE_END.slice(0, 3));
197
+ stdin.write(PASTE_END.slice(3));
198
+ },
199
+ },
200
+ ])('should $name', async ({ pastedText, writeSequence }) => {
286
201
  const keyHandler = vi.fn();
287
- const pastedText = 'pasted content';
288
202
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
289
203
  act(() => result.current.subscribe(keyHandler));
290
- act(() => {
291
- stdin.write(PASTE_START);
292
- stdin.write(pastedText);
293
- // Split PASTE_END into two parts
294
- stdin.write(PASTE_END.slice(0, 3));
295
- stdin.write(PASTE_END.slice(3));
296
- });
297
- await vi.waitFor(() => {
204
+ act(() => writeSequence(pastedText));
205
+ await waitFor(() => {
298
206
  expect(keyHandler).toHaveBeenCalledTimes(1);
299
207
  });
300
208
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
@@ -390,6 +298,10 @@ describe('KeypressContext - Kitty Protocol', () => {
390
298
  { sequence: `\x1b[1~`, expected: { name: 'home' } },
391
299
  { sequence: `\x1b[4~`, expected: { name: 'end' } },
392
300
  { sequence: `\x1b[2~`, expected: { name: 'insert' } },
301
+ { sequence: `\x1b[11~`, expected: { name: 'f1' } },
302
+ { sequence: `\x1b[17~`, expected: { name: 'f6' } },
303
+ { sequence: `\x1b[23~`, expected: { name: 'f11' } },
304
+ { sequence: `\x1b[24~`, expected: { name: 'f12' } },
393
305
  // Reverse tabs
394
306
  { sequence: `\x1b[Z`, expected: { name: 'tab', shift: true } },
395
307
  { sequence: `\x1b[1;2Z`, expected: { name: 'tab', shift: true } },
@@ -429,26 +341,20 @@ describe('KeypressContext - Kitty Protocol', () => {
429
341
  });
430
342
  describe('Double-tap and batching', () => {
431
343
  it('should emit two delete events for double-tap CSI[3~', async () => {
432
- const keyHandler = vi.fn();
433
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
434
- act(() => result.current.subscribe(keyHandler));
344
+ const { keyHandler } = setupKeypressTest(true);
435
345
  act(() => stdin.write(`\x1b[3~`));
436
346
  act(() => stdin.write(`\x1b[3~`));
437
347
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'delete' }));
438
348
  expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'delete' }));
439
349
  });
440
350
  it('should parse two concatenated tilde-coded sequences in one chunk', async () => {
441
- const keyHandler = vi.fn();
442
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
443
- act(() => result.current.subscribe(keyHandler));
351
+ const { keyHandler } = setupKeypressTest(true);
444
352
  act(() => stdin.write(`\x1b[3~\x1b[5~`));
445
353
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
446
354
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'pageup' }));
447
355
  });
448
356
  it('should ignore incomplete CSI then parse the next complete sequence', async () => {
449
- const keyHandler = vi.fn();
450
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
451
- act(() => result.current.subscribe(keyHandler));
357
+ const { keyHandler } = setupKeypressTest(true);
452
358
  // Incomplete ESC sequence then a complete Delete
453
359
  act(() => {
454
360
  // Provide an incomplete ESC sequence chunk with a real ESC character
@@ -477,65 +383,45 @@ describe('Drag and Drop Handling', () => {
477
383
  vi.useRealTimers();
478
384
  });
479
385
  describe('drag start by quotes', () => {
480
- it('should start collecting when single quote arrives and not broadcast immediately', async () => {
481
- const keyHandler = vi.fn();
482
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
483
- act(() => result.current.subscribe(keyHandler));
484
- act(() => stdin.write(SINGLE_QUOTE));
485
- expect(keyHandler).not.toHaveBeenCalled();
486
- });
487
- it('should start collecting when double quote arrives and not broadcast immediately', async () => {
386
+ it.each([
387
+ { name: 'single quote', quote: SINGLE_QUOTE },
388
+ { name: 'double quote', quote: DOUBLE_QUOTE },
389
+ ])('should start collecting when $name arrives and not broadcast immediately', async ({ quote }) => {
488
390
  const keyHandler = vi.fn();
489
391
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
490
392
  act(() => result.current.subscribe(keyHandler));
491
- act(() => stdin.write(DOUBLE_QUOTE));
393
+ act(() => stdin.write(quote));
492
394
  expect(keyHandler).not.toHaveBeenCalled();
493
395
  });
494
396
  });
495
397
  describe('drag collection and completion', () => {
496
- it('should collect single character inputs during drag mode', async () => {
398
+ it.each([
399
+ {
400
+ name: 'collect single character inputs during drag mode',
401
+ characters: ['a'],
402
+ expectedText: 'a',
403
+ },
404
+ {
405
+ name: 'collect multiple characters and complete on timeout',
406
+ characters: ['p', 'a', 't', 'h'],
407
+ expectedText: 'path',
408
+ },
409
+ ])('should $name', async ({ characters, expectedText }) => {
497
410
  const keyHandler = vi.fn();
498
411
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
499
412
  act(() => result.current.subscribe(keyHandler));
500
- // Start by single quote
501
413
  act(() => stdin.write(SINGLE_QUOTE));
502
- // Send single character
503
- act(() => stdin.write('a'));
504
- // Character should not be immediately broadcast
505
- expect(keyHandler).not.toHaveBeenCalled();
506
- // Fast-forward to completion timeout
507
- act(() => {
508
- vi.advanceTimersByTime(DRAG_COMPLETION_TIMEOUT_MS + 10);
414
+ characters.forEach((char) => {
415
+ act(() => stdin.write(char));
509
416
  });
510
- // Should broadcast the collected path as paste (includes starting quote)
511
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
512
- name: '',
513
- paste: true,
514
- sequence: `${SINGLE_QUOTE}a`,
515
- }));
516
- });
517
- it('should collect multiple characters and complete on timeout', async () => {
518
- const keyHandler = vi.fn();
519
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
520
- act(() => result.current.subscribe(keyHandler));
521
- // Start by single quote
522
- act(() => stdin.write(SINGLE_QUOTE));
523
- // Send multiple characters
524
- act(() => stdin.write('p'));
525
- act(() => stdin.write('a'));
526
- act(() => stdin.write('t'));
527
- act(() => stdin.write('h'));
528
- // Characters should not be immediately broadcast
529
417
  expect(keyHandler).not.toHaveBeenCalled();
530
- // Fast-forward to completion timeout
531
418
  act(() => {
532
419
  vi.advanceTimersByTime(DRAG_COMPLETION_TIMEOUT_MS + 10);
533
420
  });
534
- // Should broadcast the collected path as paste (includes starting quote)
535
421
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
536
422
  name: '',
537
423
  paste: true,
538
- sequence: `${SINGLE_QUOTE}path`,
424
+ sequence: `${SINGLE_QUOTE}${expectedText}`,
539
425
  }));
540
426
  });
541
427
  });
@@ -650,9 +536,7 @@ describe('Kitty Sequence Parsing', () => {
650
536
  vi.useRealTimers();
651
537
  });
652
538
  it('should treat backslash as a regular keystroke', () => {
653
- const keyHandler = vi.fn();
654
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
655
- act(() => result.current.subscribe(keyHandler));
539
+ const { keyHandler } = setupKeypressTest(true);
656
540
  act(() => stdin.write('\\'));
657
541
  // Advance timers to trigger the backslash timeout
658
542
  act(() => {
@@ -794,10 +678,10 @@ describe('Kitty Sequence Parsing', () => {
794
678
  act(() => {
795
679
  stdin.emit('data', Buffer.from(char));
796
680
  });
797
- await new Promise((resolve) => setTimeout(resolve, 0));
681
+ await new Promise((resolve) => setImmediate(resolve));
798
682
  }
799
683
  // Should parse once complete
800
- await vi.waitFor(() => {
684
+ await waitFor(() => {
801
685
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
802
686
  name: 'escape',
803
687
  kittyProtocol: true,