@google/gemini-cli 0.10.0-preview.3 → 0.11.0-nightly.20251021.e72c00cf

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 (472) hide show
  1. package/README.md +9 -0
  2. package/dist/google-gemini-cli-0.11.0-nightly.20251020.a96f0659.tgz +0 -0
  3. package/dist/index.js +5 -5
  4. package/dist/index.js.map +1 -1
  5. package/dist/package.json +2 -2
  6. package/dist/src/commands/extensions/disable.js +8 -5
  7. package/dist/src/commands/extensions/disable.js.map +1 -1
  8. package/dist/src/commands/extensions/enable.js +5 -3
  9. package/dist/src/commands/extensions/enable.js.map +1 -1
  10. package/dist/src/commands/extensions/examples/mcp-server/example.d.ts +6 -0
  11. package/dist/src/commands/extensions/examples/mcp-server/example.js +46 -0
  12. package/dist/src/commands/extensions/examples/mcp-server/example.js.map +1 -0
  13. package/dist/src/commands/extensions/install.d.ts +1 -0
  14. package/dist/src/commands/extensions/install.js +18 -4
  15. package/dist/src/commands/extensions/install.js.map +1 -1
  16. package/dist/src/commands/extensions/link.js +3 -2
  17. package/dist/src/commands/extensions/link.js.map +1 -1
  18. package/dist/src/commands/extensions/list.js +7 -5
  19. package/dist/src/commands/extensions/list.js.map +1 -1
  20. package/dist/src/commands/extensions/new.js +14 -20
  21. package/dist/src/commands/extensions/new.js.map +1 -1
  22. package/dist/src/commands/extensions/uninstall.js +3 -2
  23. package/dist/src/commands/extensions/uninstall.js.map +1 -1
  24. package/dist/src/commands/extensions/update.js +17 -17
  25. package/dist/src/commands/extensions/update.js.map +1 -1
  26. package/dist/src/commands/mcp/add.js +7 -4
  27. package/dist/src/commands/mcp/add.js.map +1 -1
  28. package/dist/src/commands/mcp/add.test.d.ts +6 -0
  29. package/dist/src/commands/mcp/add.test.js +244 -0
  30. package/dist/src/commands/mcp/add.test.js.map +1 -0
  31. package/dist/src/commands/mcp/list.js +6 -4
  32. package/dist/src/commands/mcp/list.js.map +1 -1
  33. package/dist/src/commands/mcp/list.test.d.ts +6 -0
  34. package/dist/src/commands/mcp/list.test.js +117 -0
  35. package/dist/src/commands/mcp/list.test.js.map +1 -0
  36. package/dist/src/commands/mcp/remove.test.d.ts +6 -0
  37. package/dist/src/commands/mcp/remove.test.js +175 -0
  38. package/dist/src/commands/mcp/remove.test.js.map +1 -0
  39. package/dist/src/commands/mcp.test.d.ts +6 -0
  40. package/dist/src/commands/mcp.test.js +62 -0
  41. package/dist/src/commands/mcp.test.js.map +1 -0
  42. package/dist/src/config/auth.js +3 -1
  43. package/dist/src/config/auth.js.map +1 -1
  44. package/dist/src/config/auth.test.js +3 -1
  45. package/dist/src/config/auth.test.js.map +1 -1
  46. package/dist/src/config/config.d.ts +2 -14
  47. package/dist/src/config/config.integration.test.d.ts +6 -0
  48. package/dist/src/config/config.integration.test.js +321 -0
  49. package/dist/src/config/config.integration.test.js.map +1 -0
  50. package/dist/src/config/config.js +38 -102
  51. package/dist/src/config/config.js.map +1 -1
  52. package/dist/src/config/config.test.d.ts +6 -0
  53. package/dist/src/config/config.test.js +1961 -0
  54. package/dist/src/config/config.test.js.map +1 -0
  55. package/dist/src/config/extension.d.ts +4 -15
  56. package/dist/src/config/extension.js +84 -97
  57. package/dist/src/config/extension.js.map +1 -1
  58. package/dist/src/config/extension.test.d.ts +6 -0
  59. package/dist/src/config/extension.test.js +1076 -0
  60. package/dist/src/config/extension.test.js.map +1 -0
  61. package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
  62. package/dist/src/config/extensions/extensionEnablement.js +4 -3
  63. package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
  64. package/dist/src/config/extensions/extensionEnablement.test.js +21 -18
  65. package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
  66. package/dist/src/config/extensions/github.d.ts +18 -9
  67. package/dist/src/config/extensions/github.js +106 -29
  68. package/dist/src/config/extensions/github.js.map +1 -1
  69. package/dist/src/config/extensions/github.test.js +20 -17
  70. package/dist/src/config/extensions/github.test.js.map +1 -1
  71. package/dist/src/config/extensions/update.d.ts +5 -4
  72. package/dist/src/config/extensions/update.js +10 -6
  73. package/dist/src/config/extensions/update.js.map +1 -1
  74. package/dist/src/config/extensions/update.test.js +57 -57
  75. package/dist/src/config/extensions/update.test.js.map +1 -1
  76. package/dist/src/config/extensions/variableSchema.d.ts +2 -0
  77. package/dist/src/config/extensions/variableSchema.js.map +1 -1
  78. package/dist/src/config/keyBindings.d.ts +2 -1
  79. package/dist/src/config/keyBindings.js +4 -2
  80. package/dist/src/config/keyBindings.js.map +1 -1
  81. package/dist/src/config/policy.d.ts +3 -2
  82. package/dist/src/config/policy.js +32 -23
  83. package/dist/src/config/policy.js.map +1 -1
  84. package/dist/src/config/policy.test.js +60 -36
  85. package/dist/src/config/policy.test.js.map +1 -1
  86. package/dist/src/config/sandboxConfig.d.ts +0 -1
  87. package/dist/src/config/sandboxConfig.js +1 -3
  88. package/dist/src/config/sandboxConfig.js.map +1 -1
  89. package/dist/src/config/settings.js +3 -1
  90. package/dist/src/config/settings.js.map +1 -1
  91. package/dist/src/config/settings.test.d.ts +6 -0
  92. package/dist/src/config/settings.test.js +1937 -0
  93. package/dist/src/config/settings.test.js.map +1 -0
  94. package/dist/src/config/settingsSchema.d.ts +1 -1
  95. package/dist/src/config/settingsSchema.js +1 -1
  96. package/dist/src/config/settingsSchema.js.map +1 -1
  97. package/dist/src/config/settingsSchema.test.js +1 -1
  98. package/dist/src/config/settingsSchema.test.js.map +1 -1
  99. package/dist/src/gemini.js +30 -24
  100. package/dist/src/gemini.js.map +1 -1
  101. package/dist/src/gemini.test.js +42 -11
  102. package/dist/src/gemini.test.js.map +1 -1
  103. package/dist/src/generated/git-commit.d.ts +2 -2
  104. package/dist/src/generated/git-commit.js +2 -2
  105. package/dist/src/generated/git-commit.js.map +1 -1
  106. package/dist/src/nonInteractiveCli.js +92 -4
  107. package/dist/src/nonInteractiveCli.js.map +1 -1
  108. package/dist/src/nonInteractiveCli.test.d.ts +6 -0
  109. package/dist/src/nonInteractiveCli.test.js +711 -0
  110. package/dist/src/nonInteractiveCli.test.js.map +1 -0
  111. package/dist/src/services/FileCommandLoader.test.d.ts +6 -0
  112. package/dist/src/services/FileCommandLoader.test.js +971 -0
  113. package/dist/src/services/FileCommandLoader.test.js.map +1 -0
  114. package/dist/src/services/prompt-processors/argumentProcessor.test.d.ts +6 -0
  115. package/dist/src/services/prompt-processors/argumentProcessor.test.js +40 -0
  116. package/dist/src/services/prompt-processors/argumentProcessor.test.js.map +1 -0
  117. package/dist/src/services/prompt-processors/atFileProcessor.js +3 -2
  118. package/dist/src/services/prompt-processors/atFileProcessor.js.map +1 -1
  119. package/dist/src/services/prompt-processors/shellProcessor.test.d.ts +6 -0
  120. package/dist/src/services/prompt-processors/shellProcessor.test.js +482 -0
  121. package/dist/src/services/prompt-processors/shellProcessor.test.js.map +1 -0
  122. package/dist/src/test-utils/render.d.ts +2 -1
  123. package/dist/src/test-utils/render.js +5 -2
  124. package/dist/src/test-utils/render.js.map +1 -1
  125. package/dist/src/ui/App.test.d.ts +6 -0
  126. package/dist/src/ui/App.test.js +110 -0
  127. package/dist/src/ui/App.test.js.map +1 -0
  128. package/dist/src/ui/AppContainer.js +43 -28
  129. package/dist/src/ui/AppContainer.js.map +1 -1
  130. package/dist/src/ui/AppContainer.test.js +35 -9
  131. package/dist/src/ui/AppContainer.test.js.map +1 -1
  132. package/dist/src/ui/auth/AuthDialog.d.ts +1 -1
  133. package/dist/src/ui/auth/AuthDialog.js +3 -1
  134. package/dist/src/ui/auth/AuthDialog.js.map +1 -1
  135. package/dist/src/ui/auth/useAuth.d.ts +1 -1
  136. package/dist/src/ui/auth/useAuth.js +3 -1
  137. package/dist/src/ui/auth/useAuth.js.map +1 -1
  138. package/dist/src/ui/commands/aboutCommand.js +1 -1
  139. package/dist/src/ui/commands/aboutCommand.test.d.ts +6 -0
  140. package/dist/src/ui/commands/aboutCommand.test.js +130 -0
  141. package/dist/src/ui/commands/aboutCommand.test.js.map +1 -0
  142. package/dist/src/ui/commands/authCommand.js +1 -1
  143. package/dist/src/ui/commands/authCommand.test.d.ts +6 -0
  144. package/dist/src/ui/commands/authCommand.test.js +30 -0
  145. package/dist/src/ui/commands/authCommand.test.js.map +1 -0
  146. package/dist/src/ui/commands/bugCommand.js +1 -1
  147. package/dist/src/ui/commands/bugCommand.test.d.ts +6 -0
  148. package/dist/src/ui/commands/bugCommand.test.js +105 -0
  149. package/dist/src/ui/commands/bugCommand.test.js.map +1 -0
  150. package/dist/src/ui/commands/chatCommand.js +1 -1
  151. package/dist/src/ui/commands/chatCommand.js.map +1 -1
  152. package/dist/src/ui/commands/chatCommand.test.d.ts +6 -0
  153. package/dist/src/ui/commands/chatCommand.test.js +555 -0
  154. package/dist/src/ui/commands/chatCommand.test.js.map +1 -0
  155. package/dist/src/ui/commands/clearCommand.js +1 -1
  156. package/dist/src/ui/commands/clearCommand.test.d.ts +6 -0
  157. package/dist/src/ui/commands/clearCommand.test.js +76 -0
  158. package/dist/src/ui/commands/clearCommand.test.js.map +1 -0
  159. package/dist/src/ui/commands/compressCommand.js +1 -1
  160. package/dist/src/ui/commands/compressCommand.js.map +1 -1
  161. package/dist/src/ui/commands/compressCommand.test.d.ts +6 -0
  162. package/dist/src/ui/commands/compressCommand.test.js +98 -0
  163. package/dist/src/ui/commands/compressCommand.test.js.map +1 -0
  164. package/dist/src/ui/commands/copyCommand.test.d.ts +6 -0
  165. package/dist/src/ui/commands/copyCommand.test.js +242 -0
  166. package/dist/src/ui/commands/copyCommand.test.js.map +1 -0
  167. package/dist/src/ui/commands/corgiCommand.js +1 -1
  168. package/dist/src/ui/commands/corgiCommand.js.map +1 -1
  169. package/dist/src/ui/commands/corgiCommand.test.d.ts +6 -0
  170. package/dist/src/ui/commands/corgiCommand.test.js +28 -0
  171. package/dist/src/ui/commands/corgiCommand.test.js.map +1 -0
  172. package/dist/src/ui/commands/directoryCommand.js +1 -1
  173. package/dist/src/ui/commands/directoryCommand.js.map +1 -1
  174. package/dist/src/ui/commands/directoryCommand.test.d.ts +6 -0
  175. package/dist/src/ui/commands/directoryCommand.test.js +144 -0
  176. package/dist/src/ui/commands/directoryCommand.test.js.map +1 -0
  177. package/dist/src/ui/commands/docsCommand.js +1 -1
  178. package/dist/src/ui/commands/docsCommand.test.d.ts +6 -0
  179. package/dist/src/ui/commands/docsCommand.test.js +72 -0
  180. package/dist/src/ui/commands/docsCommand.test.js.map +1 -0
  181. package/dist/src/ui/commands/editorCommand.js +1 -1
  182. package/dist/src/ui/commands/editorCommand.test.d.ts +6 -0
  183. package/dist/src/ui/commands/editorCommand.test.js +27 -0
  184. package/dist/src/ui/commands/editorCommand.test.js.map +1 -0
  185. package/dist/src/ui/commands/extensionsCommand.test.d.ts +6 -0
  186. package/dist/src/ui/commands/extensionsCommand.test.js +241 -0
  187. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -0
  188. package/dist/src/ui/commands/helpCommand.js +1 -1
  189. package/dist/src/ui/commands/helpCommand.test.d.ts +6 -0
  190. package/dist/src/ui/commands/helpCommand.test.js +42 -0
  191. package/dist/src/ui/commands/helpCommand.test.js.map +1 -0
  192. package/dist/src/ui/commands/ideCommand.js +6 -6
  193. package/dist/src/ui/commands/ideCommand.test.d.ts +6 -0
  194. package/dist/src/ui/commands/ideCommand.test.js +203 -0
  195. package/dist/src/ui/commands/ideCommand.test.js.map +1 -0
  196. package/dist/src/ui/commands/initCommand.js +1 -1
  197. package/dist/src/ui/commands/initCommand.js.map +1 -1
  198. package/dist/src/ui/commands/initCommand.test.d.ts +6 -0
  199. package/dist/src/ui/commands/initCommand.test.js +80 -0
  200. package/dist/src/ui/commands/initCommand.test.js.map +1 -0
  201. package/dist/src/ui/commands/mcpCommand.js +98 -88
  202. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  203. package/dist/src/ui/commands/mcpCommand.test.d.ts +6 -0
  204. package/dist/src/ui/commands/mcpCommand.test.js +148 -0
  205. package/dist/src/ui/commands/mcpCommand.test.js.map +1 -0
  206. package/dist/src/ui/commands/memoryCommand.js +6 -6
  207. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  208. package/dist/src/ui/commands/memoryCommand.test.d.ts +6 -0
  209. package/dist/src/ui/commands/memoryCommand.test.js +266 -0
  210. package/dist/src/ui/commands/memoryCommand.test.js.map +1 -0
  211. package/dist/src/ui/commands/privacyCommand.js +1 -1
  212. package/dist/src/ui/commands/privacyCommand.test.d.ts +6 -0
  213. package/dist/src/ui/commands/privacyCommand.test.js +32 -0
  214. package/dist/src/ui/commands/privacyCommand.test.js.map +1 -0
  215. package/dist/src/ui/commands/quitCommand.js +1 -1
  216. package/dist/src/ui/commands/quitCommand.test.d.ts +6 -0
  217. package/dist/src/ui/commands/quitCommand.test.js +50 -0
  218. package/dist/src/ui/commands/quitCommand.test.js.map +1 -0
  219. package/dist/src/ui/commands/restoreCommand.test.d.ts +6 -0
  220. package/dist/src/ui/commands/restoreCommand.test.js +190 -0
  221. package/dist/src/ui/commands/restoreCommand.test.js.map +1 -0
  222. package/dist/src/ui/commands/settingsCommand.test.d.ts +6 -0
  223. package/dist/src/ui/commands/settingsCommand.test.js +30 -0
  224. package/dist/src/ui/commands/settingsCommand.test.js.map +1 -0
  225. package/dist/src/ui/commands/setupGithubCommand.test.js +1 -2
  226. package/dist/src/ui/commands/setupGithubCommand.test.js.map +1 -1
  227. package/dist/src/ui/commands/statsCommand.js +3 -3
  228. package/dist/src/ui/commands/statsCommand.js.map +1 -1
  229. package/dist/src/ui/commands/statsCommand.test.d.ts +6 -0
  230. package/dist/src/ui/commands/statsCommand.test.js +53 -0
  231. package/dist/src/ui/commands/statsCommand.test.js.map +1 -0
  232. package/dist/src/ui/commands/terminalSetupCommand.test.d.ts +6 -0
  233. package/dist/src/ui/commands/terminalSetupCommand.test.js +66 -0
  234. package/dist/src/ui/commands/terminalSetupCommand.test.js.map +1 -0
  235. package/dist/src/ui/commands/themeCommand.js +1 -1
  236. package/dist/src/ui/commands/themeCommand.test.d.ts +6 -0
  237. package/dist/src/ui/commands/themeCommand.test.js +32 -0
  238. package/dist/src/ui/commands/themeCommand.test.js.map +1 -0
  239. package/dist/src/ui/commands/toolsCommand.js +1 -1
  240. package/dist/src/ui/commands/toolsCommand.test.d.ts +6 -0
  241. package/dist/src/ui/commands/toolsCommand.test.js +100 -0
  242. package/dist/src/ui/commands/toolsCommand.test.js.map +1 -0
  243. package/dist/src/ui/commands/vimCommand.js +1 -1
  244. package/dist/src/ui/components/Composer.js +5 -3
  245. package/dist/src/ui/components/Composer.js.map +1 -1
  246. package/dist/src/ui/components/Composer.test.js +16 -1
  247. package/dist/src/ui/components/Composer.test.js.map +1 -1
  248. package/dist/src/ui/components/ContextSummaryDisplay.d.ts +0 -1
  249. package/dist/src/ui/components/ContextSummaryDisplay.js +2 -12
  250. package/dist/src/ui/components/ContextSummaryDisplay.js.map +1 -1
  251. package/dist/src/ui/components/ContextSummaryDisplay.test.d.ts +6 -0
  252. package/dist/src/ui/components/ContextSummaryDisplay.test.js +66 -0
  253. package/dist/src/ui/components/ContextSummaryDisplay.test.js.map +1 -0
  254. package/dist/src/ui/components/DialogManager.js +1 -5
  255. package/dist/src/ui/components/DialogManager.js.map +1 -1
  256. package/dist/src/ui/components/EditorSettingsDialog.js +1 -1
  257. package/dist/src/ui/components/EditorSettingsDialog.js.map +1 -1
  258. package/dist/src/ui/components/FolderTrustDialog.test.js +7 -3
  259. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  260. package/dist/src/ui/components/Footer.js +1 -1
  261. package/dist/src/ui/components/Footer.js.map +1 -1
  262. package/dist/src/ui/components/Footer.test.d.ts +6 -0
  263. package/dist/src/ui/components/Footer.test.js +231 -0
  264. package/dist/src/ui/components/Footer.test.js.map +1 -0
  265. package/dist/src/ui/components/InputPrompt.d.ts +4 -0
  266. package/dist/src/ui/components/InputPrompt.js +53 -4
  267. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  268. package/dist/src/ui/components/InputPrompt.test.d.ts +6 -0
  269. package/dist/src/ui/components/InputPrompt.test.js +1716 -0
  270. package/dist/src/ui/components/InputPrompt.test.js.map +1 -0
  271. package/dist/src/ui/components/ModelStatsDisplay.test.d.ts +6 -0
  272. package/dist/src/ui/components/ModelStatsDisplay.test.js +285 -0
  273. package/dist/src/ui/components/ModelStatsDisplay.test.js.map +1 -0
  274. package/dist/src/ui/components/PermissionsModifyTrustDialog.js +22 -18
  275. package/dist/src/ui/components/PermissionsModifyTrustDialog.js.map +1 -1
  276. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +10 -2
  277. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
  278. package/dist/src/ui/components/QueuedMessageDisplay.js +3 -3
  279. package/dist/src/ui/components/QueuedMessageDisplay.js.map +1 -1
  280. package/dist/src/ui/components/QueuedMessageDisplay.test.js +4 -0
  281. package/dist/src/ui/components/QueuedMessageDisplay.test.js.map +1 -1
  282. package/dist/src/ui/components/RawMarkdownIndicator.d.ts +7 -0
  283. package/dist/src/ui/components/RawMarkdownIndicator.js +8 -0
  284. package/dist/src/ui/components/RawMarkdownIndicator.js.map +1 -0
  285. package/dist/src/ui/components/SessionSummaryDisplay.test.d.ts +6 -0
  286. package/dist/src/ui/components/SessionSummaryDisplay.test.js +74 -0
  287. package/dist/src/ui/components/SessionSummaryDisplay.test.js.map +1 -0
  288. package/dist/src/ui/components/SettingsDialog.js +8 -8
  289. package/dist/src/ui/components/SettingsDialog.js.map +1 -1
  290. package/dist/src/ui/components/SettingsDialog.test.js +189 -76
  291. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  292. package/dist/src/ui/components/StatsDisplay.test.d.ts +6 -0
  293. package/dist/src/ui/components/StatsDisplay.test.js +351 -0
  294. package/dist/src/ui/components/StatsDisplay.test.js.map +1 -0
  295. package/dist/src/ui/components/ThemeDialog.d.ts +4 -2
  296. package/dist/src/ui/components/ThemeDialog.js +3 -3
  297. package/dist/src/ui/components/ThemeDialog.js.map +1 -1
  298. package/dist/src/ui/components/ThemeDialog.test.js +13 -0
  299. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  300. package/dist/src/ui/components/ToolStatsDisplay.test.d.ts +6 -0
  301. package/dist/src/ui/components/ToolStatsDisplay.test.js +227 -0
  302. package/dist/src/ui/components/ToolStatsDisplay.test.js.map +1 -0
  303. package/dist/src/ui/components/messages/GeminiMessage.js +3 -1
  304. package/dist/src/ui/components/messages/GeminiMessage.js.map +1 -1
  305. package/dist/src/ui/components/messages/GeminiMessage.test.d.ts +6 -0
  306. package/dist/src/ui/components/messages/GeminiMessage.test.js +35 -0
  307. package/dist/src/ui/components/messages/GeminiMessage.test.js.map +1 -0
  308. package/dist/src/ui/components/messages/GeminiMessageContent.js +3 -1
  309. package/dist/src/ui/components/messages/GeminiMessageContent.js.map +1 -1
  310. package/dist/src/ui/components/messages/Todo.d.ts +7 -0
  311. package/dist/src/ui/components/messages/Todo.js +59 -0
  312. package/dist/src/ui/components/messages/Todo.js.map +1 -0
  313. package/dist/src/ui/components/messages/Todo.test.d.ts +6 -0
  314. package/dist/src/ui/components/messages/Todo.test.js +113 -0
  315. package/dist/src/ui/components/messages/Todo.test.js.map +1 -0
  316. package/dist/src/ui/components/messages/ToolGroupMessage.js +1 -1
  317. package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
  318. package/dist/src/ui/components/messages/ToolMessage.js +8 -3
  319. package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
  320. package/dist/src/ui/components/messages/ToolMessage.test.js +2 -2
  321. package/dist/src/ui/components/messages/ToolMessage.test.js.map +1 -1
  322. package/dist/src/ui/components/messages/ToolMessageRawMarkdown.test.d.ts +6 -0
  323. package/dist/src/ui/components/messages/ToolMessageRawMarkdown.test.js +30 -0
  324. package/dist/src/ui/components/messages/ToolMessageRawMarkdown.test.js.map +1 -0
  325. package/dist/src/ui/components/messages/UserShellMessage.js +1 -1
  326. package/dist/src/ui/components/messages/UserShellMessage.js.map +1 -1
  327. package/dist/src/ui/components/shared/BaseSelectionList.test.js +1 -1
  328. package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
  329. package/dist/src/ui/components/shared/text-buffer.test.d.ts +6 -0
  330. package/dist/src/ui/components/shared/text-buffer.test.js +1554 -0
  331. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -0
  332. package/dist/src/ui/components/shared/vim-buffer-actions.test.d.ts +6 -0
  333. package/dist/src/ui/components/shared/vim-buffer-actions.test.js +951 -0
  334. package/dist/src/ui/components/shared/vim-buffer-actions.test.js.map +1 -0
  335. package/dist/src/ui/components/views/ExtensionsList.js +3 -4
  336. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  337. package/dist/src/ui/components/views/ExtensionsList.test.js +2 -9
  338. package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
  339. package/dist/src/ui/components/views/McpStatus.d.ts +0 -1
  340. package/dist/src/ui/components/views/McpStatus.js +4 -4
  341. package/dist/src/ui/components/views/McpStatus.js.map +1 -1
  342. package/dist/src/ui/components/views/McpStatus.test.js +0 -5
  343. package/dist/src/ui/components/views/McpStatus.test.js.map +1 -1
  344. package/dist/src/ui/components/views/ToolsList.test.js +4 -4
  345. package/dist/src/ui/components/views/ToolsList.test.js.map +1 -1
  346. package/dist/src/ui/contexts/KeypressContext.d.ts +1 -0
  347. package/dist/src/ui/contexts/KeypressContext.js +176 -50
  348. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  349. package/dist/src/ui/contexts/KeypressContext.test.js +413 -14
  350. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  351. package/dist/src/ui/contexts/SessionContext.test.d.ts +6 -0
  352. package/dist/src/ui/contexts/SessionContext.test.js +177 -0
  353. package/dist/src/ui/contexts/SessionContext.test.js.map +1 -0
  354. package/dist/src/ui/contexts/UIActionsContext.d.ts +5 -4
  355. package/dist/src/ui/contexts/UIActionsContext.js.map +1 -1
  356. package/dist/src/ui/contexts/UIStateContext.d.ts +3 -3
  357. package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
  358. package/dist/src/ui/hooks/slashCommandProcessor.test.d.ts +6 -0
  359. package/dist/src/ui/hooks/slashCommandProcessor.test.js +779 -0
  360. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -0
  361. package/dist/src/ui/hooks/useAtCompletion.js +2 -2
  362. package/dist/src/ui/hooks/useAtCompletion.js.map +1 -1
  363. package/dist/src/ui/hooks/useAtCompletion.test.d.ts +6 -0
  364. package/dist/src/ui/hooks/useAtCompletion.test.js +385 -0
  365. package/dist/src/ui/hooks/useAtCompletion.test.js.map +1 -0
  366. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +0 -1
  367. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
  368. package/dist/src/ui/hooks/useCommandCompletion.d.ts +1 -1
  369. package/dist/src/ui/hooks/useCommandCompletion.js +5 -3
  370. package/dist/src/ui/hooks/useCommandCompletion.js.map +1 -1
  371. package/dist/src/ui/hooks/useCommandCompletion.test.d.ts +6 -0
  372. package/dist/src/ui/hooks/useCommandCompletion.test.js +375 -0
  373. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -0
  374. package/dist/src/ui/hooks/useConsoleMessages.test.d.ts +6 -0
  375. package/dist/src/ui/hooks/useConsoleMessages.test.js +110 -0
  376. package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -0
  377. package/dist/src/ui/hooks/useExtensionUpdates.d.ts +2 -1
  378. package/dist/src/ui/hooks/useExtensionUpdates.js +5 -3
  379. package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
  380. package/dist/src/ui/hooks/useExtensionUpdates.test.js +22 -13
  381. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  382. package/dist/src/ui/hooks/useFocus.test.d.ts +6 -0
  383. package/dist/src/ui/hooks/useFocus.test.js +115 -0
  384. package/dist/src/ui/hooks/useFocus.test.js.map +1 -0
  385. package/dist/src/ui/hooks/useFolderTrust.test.d.ts +6 -0
  386. package/dist/src/ui/hooks/useFolderTrust.test.js +164 -0
  387. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -0
  388. package/dist/src/ui/hooks/useGeminiStream.js +33 -31
  389. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  390. package/dist/src/ui/hooks/useGeminiStream.test.d.ts +6 -0
  391. package/dist/src/ui/hooks/useGeminiStream.test.js +1936 -0
  392. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -0
  393. package/dist/src/ui/hooks/useKeypress.test.d.ts +6 -0
  394. package/dist/src/ui/hooks/useKeypress.test.js +234 -0
  395. package/dist/src/ui/hooks/useKeypress.test.js.map +1 -0
  396. package/dist/src/ui/hooks/useLoadingIndicator.test.js +5 -0
  397. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  398. package/dist/src/ui/hooks/useMessageQueue.d.ts +1 -0
  399. package/dist/src/ui/hooks/useMessageQueue.js +14 -0
  400. package/dist/src/ui/hooks/useMessageQueue.js.map +1 -1
  401. package/dist/src/ui/hooks/useMessageQueue.test.js +121 -0
  402. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  403. package/dist/src/ui/hooks/usePhraseCycler.d.ts +1 -0
  404. package/dist/src/ui/hooks/usePhraseCycler.js +156 -5
  405. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  406. package/dist/src/ui/hooks/usePhraseCycler.test.d.ts +6 -0
  407. package/dist/src/ui/hooks/usePhraseCycler.test.js +155 -0
  408. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -0
  409. package/dist/src/ui/hooks/useThemeCommand.d.ts +2 -1
  410. package/dist/src/ui/hooks/useThemeCommand.js +6 -0
  411. package/dist/src/ui/hooks/useThemeCommand.js.map +1 -1
  412. package/dist/src/ui/hooks/useToolScheduler.test.js +3 -0
  413. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  414. package/dist/src/ui/hooks/vim.test.d.ts +6 -0
  415. package/dist/src/ui/hooks/vim.test.js +1389 -0
  416. package/dist/src/ui/hooks/vim.test.js.map +1 -0
  417. package/dist/src/ui/keyMatchers.test.js +9 -3
  418. package/dist/src/ui/keyMatchers.test.js.map +1 -1
  419. package/dist/src/ui/themes/theme.test.d.ts +6 -0
  420. package/dist/src/ui/themes/theme.test.js +85 -0
  421. package/dist/src/ui/themes/theme.test.js.map +1 -0
  422. package/dist/src/ui/types.d.ts +0 -1
  423. package/dist/src/ui/types.js.map +1 -1
  424. package/dist/src/ui/utils/CodeColorizer.d.ts +1 -1
  425. package/dist/src/ui/utils/CodeColorizer.js +4 -2
  426. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  427. package/dist/src/ui/utils/MarkdownDisplay.d.ts +1 -0
  428. package/dist/src/ui/utils/MarkdownDisplay.js +8 -1
  429. package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
  430. package/dist/src/ui/utils/commandUtils.js +18 -2
  431. package/dist/src/ui/utils/commandUtils.js.map +1 -1
  432. package/dist/src/ui/utils/commandUtils.test.js +61 -6
  433. package/dist/src/ui/utils/commandUtils.test.js.map +1 -1
  434. package/dist/src/ui/utils/computeStats.js +5 -2
  435. package/dist/src/ui/utils/computeStats.js.map +1 -1
  436. package/dist/src/ui/utils/computeStats.test.d.ts +6 -0
  437. package/dist/src/ui/utils/computeStats.test.js +262 -0
  438. package/dist/src/ui/utils/computeStats.test.js.map +1 -0
  439. package/dist/src/ui/utils/updateCheck.d.ts +2 -1
  440. package/dist/src/ui/utils/updateCheck.js +4 -1
  441. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  442. package/dist/src/ui/utils/updateCheck.test.js +25 -10
  443. package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
  444. package/dist/src/utils/cleanup.test.d.ts +6 -0
  445. package/dist/src/utils/cleanup.test.js +49 -0
  446. package/dist/src/utils/cleanup.test.js.map +1 -0
  447. package/dist/src/utils/errors.d.ts +1 -0
  448. package/dist/src/utils/errors.js +66 -5
  449. package/dist/src/utils/errors.js.map +1 -1
  450. package/dist/src/utils/handleAutoUpdate.test.d.ts +6 -0
  451. package/dist/src/utils/handleAutoUpdate.test.js +225 -0
  452. package/dist/src/utils/handleAutoUpdate.test.js.map +1 -0
  453. package/dist/src/utils/sandbox-macos-permissive-open.sb +3 -1
  454. package/dist/src/utils/startupWarnings.test.d.ts +6 -0
  455. package/dist/src/utils/startupWarnings.test.js +61 -0
  456. package/dist/src/utils/startupWarnings.test.js.map +1 -0
  457. package/dist/src/validateNonInterActiveAuth.js +2 -2
  458. package/dist/src/validateNonInterActiveAuth.js.map +1 -1
  459. package/dist/src/validateNonInterActiveAuth.test.d.ts +6 -0
  460. package/dist/src/validateNonInterActiveAuth.test.js +336 -0
  461. package/dist/src/validateNonInterActiveAuth.test.js.map +1 -0
  462. package/dist/src/zed-integration/zedIntegration.js +1 -3
  463. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  464. package/dist/tsconfig.tsbuildinfo +1 -1
  465. package/package.json +3 -3
  466. package/dist/google-gemini-cli-0.10.0-preview.2.tgz +0 -0
  467. package/dist/src/ui/components/WorkspaceMigrationDialog.d.ts +0 -11
  468. package/dist/src/ui/components/WorkspaceMigrationDialog.js +0 -44
  469. package/dist/src/ui/components/WorkspaceMigrationDialog.js.map +0 -1
  470. package/dist/src/ui/hooks/useWorkspaceMigration.d.ts +0 -13
  471. package/dist/src/ui/hooks/useWorkspaceMigration.js +0 -59
  472. package/dist/src/ui/hooks/useWorkspaceMigration.js.map +0 -1
@@ -0,0 +1,1716 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * @license
4
+ * Copyright 2025 Google LLC
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
7
+ import { renderWithProviders } from '../../test-utils/render.js';
8
+ import { waitFor, act } from '@testing-library/react';
9
+ import { InputPrompt } from './InputPrompt.js';
10
+ import { ApprovalMode } from '@google/gemini-cli-core';
11
+ import * as path from 'node:path';
12
+ import { CommandKind } from '../commands/types.js';
13
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
14
+ import { useShellHistory } from '../hooks/useShellHistory.js';
15
+ import { useCommandCompletion } from '../hooks/useCommandCompletion.js';
16
+ import { useInputHistory } from '../hooks/useInputHistory.js';
17
+ import { useReverseSearchCompletion } from '../hooks/useReverseSearchCompletion.js';
18
+ import * as clipboardUtils from '../utils/clipboardUtils.js';
19
+ import { useKittyKeyboardProtocol } from '../hooks/useKittyKeyboardProtocol.js';
20
+ import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
21
+ import stripAnsi from 'strip-ansi';
22
+ import chalk from 'chalk';
23
+ import { StreamingState } from '../types.js';
24
+ vi.mock('../hooks/useShellHistory.js');
25
+ vi.mock('../hooks/useCommandCompletion.js');
26
+ vi.mock('../hooks/useInputHistory.js');
27
+ vi.mock('../hooks/useReverseSearchCompletion.js');
28
+ vi.mock('../utils/clipboardUtils.js');
29
+ vi.mock('../hooks/useKittyKeyboardProtocol.js');
30
+ const mockSlashCommands = [
31
+ {
32
+ name: 'clear',
33
+ kind: CommandKind.BUILT_IN,
34
+ description: 'Clear screen',
35
+ action: vi.fn(),
36
+ },
37
+ {
38
+ name: 'memory',
39
+ kind: CommandKind.BUILT_IN,
40
+ description: 'Manage memory',
41
+ subCommands: [
42
+ {
43
+ name: 'show',
44
+ kind: CommandKind.BUILT_IN,
45
+ description: 'Show memory',
46
+ action: vi.fn(),
47
+ },
48
+ {
49
+ name: 'add',
50
+ kind: CommandKind.BUILT_IN,
51
+ description: 'Add to memory',
52
+ action: vi.fn(),
53
+ },
54
+ {
55
+ name: 'refresh',
56
+ kind: CommandKind.BUILT_IN,
57
+ description: 'Refresh memory',
58
+ action: vi.fn(),
59
+ },
60
+ ],
61
+ },
62
+ {
63
+ name: 'chat',
64
+ description: 'Manage chats',
65
+ kind: CommandKind.BUILT_IN,
66
+ subCommands: [
67
+ {
68
+ name: 'resume',
69
+ description: 'Resume a chat',
70
+ kind: CommandKind.BUILT_IN,
71
+ action: vi.fn(),
72
+ completion: async () => ['fix-foo', 'fix-bar'],
73
+ },
74
+ ],
75
+ },
76
+ ];
77
+ describe('InputPrompt', () => {
78
+ let props;
79
+ let mockShellHistory;
80
+ let mockCommandCompletion;
81
+ let mockInputHistory;
82
+ let mockReverseSearchCompletion;
83
+ let mockBuffer;
84
+ let mockCommandContext;
85
+ const mockedUseShellHistory = vi.mocked(useShellHistory);
86
+ const mockedUseCommandCompletion = vi.mocked(useCommandCompletion);
87
+ const mockedUseInputHistory = vi.mocked(useInputHistory);
88
+ const mockedUseReverseSearchCompletion = vi.mocked(useReverseSearchCompletion);
89
+ const mockedUseKittyKeyboardProtocol = vi.mocked(useKittyKeyboardProtocol);
90
+ beforeEach(() => {
91
+ vi.resetAllMocks();
92
+ mockCommandContext = createMockCommandContext();
93
+ mockBuffer = {
94
+ text: '',
95
+ cursor: [0, 0],
96
+ lines: [''],
97
+ setText: vi.fn((newText) => {
98
+ mockBuffer.text = newText;
99
+ mockBuffer.lines = [newText];
100
+ mockBuffer.cursor = [0, newText.length];
101
+ mockBuffer.viewportVisualLines = [newText];
102
+ mockBuffer.allVisualLines = [newText];
103
+ mockBuffer.visualToLogicalMap = [[0, 0]];
104
+ }),
105
+ replaceRangeByOffset: vi.fn(),
106
+ viewportVisualLines: [''],
107
+ allVisualLines: [''],
108
+ visualCursor: [0, 0],
109
+ visualScrollRow: 0,
110
+ handleInput: vi.fn(),
111
+ move: vi.fn(),
112
+ moveToOffset: vi.fn((offset) => {
113
+ mockBuffer.cursor = [0, offset];
114
+ }),
115
+ killLineRight: vi.fn(),
116
+ killLineLeft: vi.fn(),
117
+ openInExternalEditor: vi.fn(),
118
+ newline: vi.fn(),
119
+ undo: vi.fn(),
120
+ redo: vi.fn(),
121
+ backspace: vi.fn(),
122
+ preferredCol: null,
123
+ selectionAnchor: null,
124
+ insert: vi.fn(),
125
+ del: vi.fn(),
126
+ replaceRange: vi.fn(),
127
+ deleteWordLeft: vi.fn(),
128
+ deleteWordRight: vi.fn(),
129
+ visualToLogicalMap: [[0, 0]],
130
+ };
131
+ mockShellHistory = {
132
+ history: [],
133
+ addCommandToHistory: vi.fn(),
134
+ getPreviousCommand: vi.fn().mockReturnValue(null),
135
+ getNextCommand: vi.fn().mockReturnValue(null),
136
+ resetHistoryPosition: vi.fn(),
137
+ };
138
+ mockedUseShellHistory.mockReturnValue(mockShellHistory);
139
+ mockCommandCompletion = {
140
+ suggestions: [],
141
+ activeSuggestionIndex: -1,
142
+ isLoadingSuggestions: false,
143
+ showSuggestions: false,
144
+ visibleStartIndex: 0,
145
+ isPerfectMatch: false,
146
+ navigateUp: vi.fn(),
147
+ navigateDown: vi.fn(),
148
+ resetCompletionState: vi.fn(),
149
+ setActiveSuggestionIndex: vi.fn(),
150
+ setShowSuggestions: vi.fn(),
151
+ handleAutocomplete: vi.fn(),
152
+ promptCompletion: {
153
+ text: '',
154
+ accept: vi.fn(),
155
+ clear: vi.fn(),
156
+ isLoading: false,
157
+ isActive: false,
158
+ markSelected: vi.fn(),
159
+ },
160
+ };
161
+ mockedUseCommandCompletion.mockReturnValue(mockCommandCompletion);
162
+ mockInputHistory = {
163
+ navigateUp: vi.fn(),
164
+ navigateDown: vi.fn(),
165
+ handleSubmit: vi.fn(),
166
+ };
167
+ mockedUseInputHistory.mockReturnValue(mockInputHistory);
168
+ mockReverseSearchCompletion = {
169
+ suggestions: [],
170
+ activeSuggestionIndex: -1,
171
+ visibleStartIndex: 0,
172
+ showSuggestions: false,
173
+ isLoadingSuggestions: false,
174
+ navigateUp: vi.fn(),
175
+ navigateDown: vi.fn(),
176
+ handleAutocomplete: vi.fn(),
177
+ resetCompletionState: vi.fn(),
178
+ };
179
+ mockedUseReverseSearchCompletion.mockReturnValue(mockReverseSearchCompletion);
180
+ mockedUseKittyKeyboardProtocol.mockReturnValue({
181
+ supported: false,
182
+ enabled: false,
183
+ checking: false,
184
+ });
185
+ props = {
186
+ buffer: mockBuffer,
187
+ onSubmit: vi.fn(),
188
+ userMessages: [],
189
+ onClearScreen: vi.fn(),
190
+ config: {
191
+ getProjectRoot: () => path.join('test', 'project'),
192
+ getTargetDir: () => path.join('test', 'project', 'src'),
193
+ getVimMode: () => false,
194
+ getWorkspaceContext: () => ({
195
+ getDirectories: () => ['/test/project/src'],
196
+ }),
197
+ },
198
+ slashCommands: mockSlashCommands,
199
+ commandContext: mockCommandContext,
200
+ shellModeActive: false,
201
+ setShellModeActive: vi.fn(),
202
+ approvalMode: ApprovalMode.DEFAULT,
203
+ inputWidth: 80,
204
+ suggestionsWidth: 80,
205
+ focus: true,
206
+ setQueueErrorMessage: vi.fn(),
207
+ streamingState: StreamingState.Idle,
208
+ };
209
+ });
210
+ const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
211
+ it('should call shellHistory.getPreviousCommand on up arrow in shell mode', async () => {
212
+ props.shellModeActive = true;
213
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
214
+ await wait();
215
+ stdin.write('\u001B[A');
216
+ await wait();
217
+ expect(mockShellHistory.getPreviousCommand).toHaveBeenCalled();
218
+ unmount();
219
+ });
220
+ it('should call shellHistory.getNextCommand on down arrow in shell mode', async () => {
221
+ props.shellModeActive = true;
222
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
223
+ await wait();
224
+ stdin.write('\u001B[B');
225
+ await wait();
226
+ expect(mockShellHistory.getNextCommand).toHaveBeenCalled();
227
+ unmount();
228
+ });
229
+ it('should set the buffer text when a shell history command is retrieved', async () => {
230
+ props.shellModeActive = true;
231
+ vi.mocked(mockShellHistory.getPreviousCommand).mockReturnValue('previous command');
232
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
233
+ await wait();
234
+ stdin.write('\u001B[A');
235
+ await wait();
236
+ expect(mockShellHistory.getPreviousCommand).toHaveBeenCalled();
237
+ expect(props.buffer.setText).toHaveBeenCalledWith('previous command');
238
+ unmount();
239
+ });
240
+ it('should call shellHistory.addCommandToHistory on submit in shell mode', async () => {
241
+ props.shellModeActive = true;
242
+ props.buffer.setText('ls -l');
243
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
244
+ await wait();
245
+ stdin.write('\r');
246
+ await wait();
247
+ expect(mockShellHistory.addCommandToHistory).toHaveBeenCalledWith('ls -l');
248
+ expect(props.onSubmit).toHaveBeenCalledWith('ls -l');
249
+ unmount();
250
+ });
251
+ it('should NOT call shell history methods when not in shell mode', async () => {
252
+ props.buffer.setText('some text');
253
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
254
+ await wait();
255
+ stdin.write('\u001B[A'); // Up arrow
256
+ await wait();
257
+ stdin.write('\u001B[B'); // Down arrow
258
+ await wait();
259
+ stdin.write('\r'); // Enter
260
+ await wait();
261
+ expect(mockShellHistory.getPreviousCommand).not.toHaveBeenCalled();
262
+ expect(mockShellHistory.getNextCommand).not.toHaveBeenCalled();
263
+ expect(mockShellHistory.addCommandToHistory).not.toHaveBeenCalled();
264
+ expect(mockInputHistory.navigateUp).toHaveBeenCalled();
265
+ expect(mockInputHistory.navigateDown).toHaveBeenCalled();
266
+ expect(props.onSubmit).toHaveBeenCalledWith('some text');
267
+ unmount();
268
+ });
269
+ it('should call completion.navigateUp for both up arrow and Ctrl+P when suggestions are showing', async () => {
270
+ mockedUseCommandCompletion.mockReturnValue({
271
+ ...mockCommandCompletion,
272
+ showSuggestions: true,
273
+ suggestions: [
274
+ { label: 'memory', value: 'memory' },
275
+ { label: 'memcache', value: 'memcache' },
276
+ ],
277
+ });
278
+ props.buffer.setText('/mem');
279
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
280
+ await wait();
281
+ // Test up arrow
282
+ stdin.write('\u001B[A'); // Up arrow
283
+ await wait();
284
+ stdin.write('\u0010'); // Ctrl+P
285
+ await wait();
286
+ expect(mockCommandCompletion.navigateUp).toHaveBeenCalledTimes(2);
287
+ expect(mockCommandCompletion.navigateDown).not.toHaveBeenCalled();
288
+ unmount();
289
+ });
290
+ it('should call completion.navigateDown for both down arrow and Ctrl+N when suggestions are showing', async () => {
291
+ mockedUseCommandCompletion.mockReturnValue({
292
+ ...mockCommandCompletion,
293
+ showSuggestions: true,
294
+ suggestions: [
295
+ { label: 'memory', value: 'memory' },
296
+ { label: 'memcache', value: 'memcache' },
297
+ ],
298
+ });
299
+ props.buffer.setText('/mem');
300
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
301
+ await wait();
302
+ // Test down arrow
303
+ stdin.write('\u001B[B'); // Down arrow
304
+ await wait();
305
+ stdin.write('\u000E'); // Ctrl+N
306
+ await wait();
307
+ expect(mockCommandCompletion.navigateDown).toHaveBeenCalledTimes(2);
308
+ expect(mockCommandCompletion.navigateUp).not.toHaveBeenCalled();
309
+ unmount();
310
+ });
311
+ it('should NOT call completion navigation when suggestions are not showing', async () => {
312
+ mockedUseCommandCompletion.mockReturnValue({
313
+ ...mockCommandCompletion,
314
+ showSuggestions: false,
315
+ });
316
+ props.buffer.setText('some text');
317
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
318
+ await wait();
319
+ stdin.write('\u001B[A'); // Up arrow
320
+ await wait();
321
+ stdin.write('\u001B[B'); // Down arrow
322
+ await wait();
323
+ stdin.write('\u0010'); // Ctrl+P
324
+ await wait();
325
+ stdin.write('\u000E'); // Ctrl+N
326
+ await wait();
327
+ expect(mockCommandCompletion.navigateUp).not.toHaveBeenCalled();
328
+ expect(mockCommandCompletion.navigateDown).not.toHaveBeenCalled();
329
+ unmount();
330
+ });
331
+ describe('clipboard image paste', () => {
332
+ beforeEach(() => {
333
+ vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(false);
334
+ vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue(null);
335
+ vi.mocked(clipboardUtils.cleanupOldClipboardImages).mockResolvedValue(undefined);
336
+ });
337
+ it('should handle Ctrl+V when clipboard has an image', async () => {
338
+ vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true);
339
+ vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue('/test/.gemini-clipboard/clipboard-123.png');
340
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
341
+ await wait();
342
+ // Send Ctrl+V
343
+ stdin.write('\x16'); // Ctrl+V
344
+ await wait();
345
+ expect(clipboardUtils.clipboardHasImage).toHaveBeenCalled();
346
+ expect(clipboardUtils.saveClipboardImage).toHaveBeenCalledWith(props.config.getTargetDir());
347
+ expect(clipboardUtils.cleanupOldClipboardImages).toHaveBeenCalledWith(props.config.getTargetDir());
348
+ expect(mockBuffer.replaceRangeByOffset).toHaveBeenCalled();
349
+ unmount();
350
+ });
351
+ it('should not insert anything when clipboard has no image', async () => {
352
+ vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(false);
353
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
354
+ await wait();
355
+ stdin.write('\x16'); // Ctrl+V
356
+ await wait();
357
+ expect(clipboardUtils.clipboardHasImage).toHaveBeenCalled();
358
+ expect(clipboardUtils.saveClipboardImage).not.toHaveBeenCalled();
359
+ expect(mockBuffer.setText).not.toHaveBeenCalled();
360
+ unmount();
361
+ });
362
+ it('should handle image save failure gracefully', async () => {
363
+ vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true);
364
+ vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue(null);
365
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
366
+ await wait();
367
+ stdin.write('\x16'); // Ctrl+V
368
+ await wait();
369
+ expect(clipboardUtils.saveClipboardImage).toHaveBeenCalled();
370
+ expect(mockBuffer.setText).not.toHaveBeenCalled();
371
+ unmount();
372
+ });
373
+ it('should insert image path at cursor position with proper spacing', async () => {
374
+ const imagePath = path.join('test', '.gemini-clipboard', 'clipboard-456.png');
375
+ vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true);
376
+ vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue(imagePath);
377
+ // Set initial text and cursor position
378
+ mockBuffer.text = 'Hello world';
379
+ mockBuffer.cursor = [0, 5]; // Cursor after "Hello"
380
+ mockBuffer.lines = ['Hello world'];
381
+ mockBuffer.replaceRangeByOffset = vi.fn();
382
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
383
+ await wait();
384
+ stdin.write('\x16'); // Ctrl+V
385
+ await wait();
386
+ // Should insert at cursor position with spaces
387
+ expect(mockBuffer.replaceRangeByOffset).toHaveBeenCalled();
388
+ // Get the actual call to see what path was used
389
+ const actualCall = vi.mocked(mockBuffer.replaceRangeByOffset).mock
390
+ .calls[0];
391
+ expect(actualCall[0]).toBe(5); // start offset
392
+ expect(actualCall[1]).toBe(5); // end offset
393
+ expect(actualCall[2]).toBe(' @' + path.relative(path.join('test', 'project', 'src'), imagePath));
394
+ unmount();
395
+ });
396
+ it('should handle errors during clipboard operations', async () => {
397
+ const consoleErrorSpy = vi
398
+ .spyOn(console, 'error')
399
+ .mockImplementation(() => { });
400
+ vi.mocked(clipboardUtils.clipboardHasImage).mockRejectedValue(new Error('Clipboard error'));
401
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
402
+ await wait();
403
+ stdin.write('\x16'); // Ctrl+V
404
+ await wait();
405
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Error handling clipboard image:', expect.any(Error));
406
+ expect(mockBuffer.setText).not.toHaveBeenCalled();
407
+ consoleErrorSpy.mockRestore();
408
+ unmount();
409
+ });
410
+ });
411
+ it('should complete a partial parent command', async () => {
412
+ // SCENARIO: /mem -> Tab
413
+ mockedUseCommandCompletion.mockReturnValue({
414
+ ...mockCommandCompletion,
415
+ showSuggestions: true,
416
+ suggestions: [{ label: 'memory', value: 'memory', description: '...' }],
417
+ activeSuggestionIndex: 0,
418
+ });
419
+ props.buffer.setText('/mem');
420
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
421
+ await wait();
422
+ stdin.write('\t'); // Press Tab
423
+ await wait();
424
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
425
+ unmount();
426
+ });
427
+ it('should append a sub-command when the parent command is already complete', async () => {
428
+ // SCENARIO: /memory -> Tab (to accept 'add')
429
+ mockedUseCommandCompletion.mockReturnValue({
430
+ ...mockCommandCompletion,
431
+ showSuggestions: true,
432
+ suggestions: [
433
+ { label: 'show', value: 'show' },
434
+ { label: 'add', value: 'add' },
435
+ ],
436
+ activeSuggestionIndex: 1, // 'add' is highlighted
437
+ });
438
+ props.buffer.setText('/memory ');
439
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
440
+ await wait();
441
+ stdin.write('\t'); // Press Tab
442
+ await wait();
443
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(1);
444
+ unmount();
445
+ });
446
+ it('should handle the "backspace" edge case correctly', async () => {
447
+ // SCENARIO: /memory -> Backspace -> /memory -> Tab (to accept 'show')
448
+ mockedUseCommandCompletion.mockReturnValue({
449
+ ...mockCommandCompletion,
450
+ showSuggestions: true,
451
+ suggestions: [
452
+ { label: 'show', value: 'show' },
453
+ { label: 'add', value: 'add' },
454
+ ],
455
+ activeSuggestionIndex: 0, // 'show' is highlighted
456
+ });
457
+ // The user has backspaced, so the query is now just '/memory'
458
+ props.buffer.setText('/memory');
459
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
460
+ await wait();
461
+ stdin.write('\t'); // Press Tab
462
+ await wait();
463
+ // It should NOT become '/show'. It should correctly become '/memory show'.
464
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
465
+ unmount();
466
+ });
467
+ it('should complete a partial argument for a command', async () => {
468
+ // SCENARIO: /chat resume fi- -> Tab
469
+ mockedUseCommandCompletion.mockReturnValue({
470
+ ...mockCommandCompletion,
471
+ showSuggestions: true,
472
+ suggestions: [{ label: 'fix-foo', value: 'fix-foo' }],
473
+ activeSuggestionIndex: 0,
474
+ });
475
+ props.buffer.setText('/chat resume fi-');
476
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
477
+ await wait();
478
+ stdin.write('\t'); // Press Tab
479
+ await wait();
480
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
481
+ unmount();
482
+ });
483
+ it('should autocomplete on Enter when suggestions are active, without submitting', async () => {
484
+ mockedUseCommandCompletion.mockReturnValue({
485
+ ...mockCommandCompletion,
486
+ showSuggestions: true,
487
+ suggestions: [{ label: 'memory', value: 'memory' }],
488
+ activeSuggestionIndex: 0,
489
+ });
490
+ props.buffer.setText('/mem');
491
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
492
+ await wait();
493
+ stdin.write('\r');
494
+ await wait();
495
+ // The app should autocomplete the text, NOT submit.
496
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
497
+ expect(props.onSubmit).not.toHaveBeenCalled();
498
+ unmount();
499
+ });
500
+ it('should complete a command based on its altNames', async () => {
501
+ props.slashCommands = [
502
+ {
503
+ name: 'help',
504
+ altNames: ['?'],
505
+ kind: CommandKind.BUILT_IN,
506
+ description: '...',
507
+ },
508
+ ];
509
+ mockedUseCommandCompletion.mockReturnValue({
510
+ ...mockCommandCompletion,
511
+ showSuggestions: true,
512
+ suggestions: [{ label: 'help', value: 'help' }],
513
+ activeSuggestionIndex: 0,
514
+ });
515
+ props.buffer.setText('/?');
516
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
517
+ await wait();
518
+ stdin.write('\t'); // Press Tab for autocomplete
519
+ await wait();
520
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
521
+ unmount();
522
+ });
523
+ it('should not submit on Enter when the buffer is empty or only contains whitespace', async () => {
524
+ props.buffer.setText(' '); // Set buffer to whitespace
525
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
526
+ await wait();
527
+ stdin.write('\r'); // Press Enter
528
+ await wait();
529
+ expect(props.onSubmit).not.toHaveBeenCalled();
530
+ unmount();
531
+ });
532
+ it('should submit directly on Enter when isPerfectMatch is true', async () => {
533
+ mockedUseCommandCompletion.mockReturnValue({
534
+ ...mockCommandCompletion,
535
+ showSuggestions: false,
536
+ isPerfectMatch: true,
537
+ });
538
+ props.buffer.setText('/clear');
539
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
540
+ await wait();
541
+ stdin.write('\r');
542
+ await wait();
543
+ expect(props.onSubmit).toHaveBeenCalledWith('/clear');
544
+ unmount();
545
+ });
546
+ it('should submit directly on Enter when a complete leaf command is typed', async () => {
547
+ mockedUseCommandCompletion.mockReturnValue({
548
+ ...mockCommandCompletion,
549
+ showSuggestions: false,
550
+ isPerfectMatch: false, // Added explicit isPerfectMatch false
551
+ });
552
+ props.buffer.setText('/clear');
553
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
554
+ await wait();
555
+ stdin.write('\r');
556
+ await wait();
557
+ expect(props.onSubmit).toHaveBeenCalledWith('/clear');
558
+ unmount();
559
+ });
560
+ it('should autocomplete an @-path on Enter without submitting', async () => {
561
+ mockedUseCommandCompletion.mockReturnValue({
562
+ ...mockCommandCompletion,
563
+ showSuggestions: true,
564
+ suggestions: [{ label: 'index.ts', value: 'index.ts' }],
565
+ activeSuggestionIndex: 0,
566
+ });
567
+ props.buffer.setText('@src/components/');
568
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
569
+ await wait();
570
+ stdin.write('\r');
571
+ await wait();
572
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
573
+ expect(props.onSubmit).not.toHaveBeenCalled();
574
+ unmount();
575
+ });
576
+ it('should add a newline on enter when the line ends with a backslash', async () => {
577
+ // This test simulates multi-line input, not submission
578
+ mockBuffer.text = 'first line\\';
579
+ mockBuffer.cursor = [0, 11];
580
+ mockBuffer.lines = ['first line\\'];
581
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
582
+ await wait();
583
+ stdin.write('\r');
584
+ await wait();
585
+ expect(props.onSubmit).not.toHaveBeenCalled();
586
+ expect(props.buffer.backspace).toHaveBeenCalled();
587
+ expect(props.buffer.newline).toHaveBeenCalled();
588
+ unmount();
589
+ });
590
+ it('should clear the buffer on Ctrl+C if it has text', async () => {
591
+ props.buffer.setText('some text to clear');
592
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
593
+ await wait();
594
+ stdin.write('\x03'); // Ctrl+C character
595
+ await wait();
596
+ expect(props.buffer.setText).toHaveBeenCalledWith('');
597
+ expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
598
+ expect(props.onSubmit).not.toHaveBeenCalled();
599
+ unmount();
600
+ });
601
+ it('should NOT clear the buffer on Ctrl+C if it is empty', async () => {
602
+ props.buffer.text = '';
603
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
604
+ await wait();
605
+ stdin.write('\x03'); // Ctrl+C character
606
+ await wait();
607
+ expect(props.buffer.setText).not.toHaveBeenCalled();
608
+ unmount();
609
+ });
610
+ describe('cursor-based completion trigger', () => {
611
+ it('should trigger completion when cursor is after @ without spaces', async () => {
612
+ // Set up buffer state
613
+ mockBuffer.text = '@src/components';
614
+ mockBuffer.lines = ['@src/components'];
615
+ mockBuffer.cursor = [0, 15];
616
+ mockedUseCommandCompletion.mockReturnValue({
617
+ ...mockCommandCompletion,
618
+ showSuggestions: true,
619
+ suggestions: [{ label: 'Button.tsx', value: 'Button.tsx' }],
620
+ });
621
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
622
+ await wait();
623
+ // Verify useCompletion was called with correct signature
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,
633
+ showSuggestions: true,
634
+ suggestions: [{ label: 'show', value: 'show' }],
635
+ });
636
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
637
+ await wait();
638
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
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,
647
+ showSuggestions: false,
648
+ suggestions: [],
649
+ });
650
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
651
+ await wait();
652
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
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,
661
+ showSuggestions: false,
662
+ suggestions: [],
663
+ });
664
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
665
+ await wait();
666
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
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,
675
+ showSuggestions: false,
676
+ suggestions: [],
677
+ });
678
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
679
+ await wait();
680
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
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,
689
+ showSuggestions: false,
690
+ suggestions: [],
691
+ });
692
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
693
+ await wait();
694
+ // Verify useCompletion was called with the buffer
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,
719
+ showSuggestions: true,
720
+ suggestions: [{ label: 'file👍.txt', value: 'file👍.txt' }],
721
+ });
722
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
723
+ await wait();
724
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
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,
734
+ showSuggestions: false,
735
+ suggestions: [],
736
+ });
737
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
738
+ await wait();
739
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
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,
749
+ showSuggestions: true,
750
+ suggestions: [{ label: 'my file.txt', value: 'my file.txt' }],
751
+ });
752
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
753
+ await wait();
754
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
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,
764
+ showSuggestions: false,
765
+ suggestions: [],
766
+ });
767
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
768
+ await wait();
769
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
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,
779
+ showSuggestions: true,
780
+ suggestions: [
781
+ { label: 'my long file name.md', value: 'my long file name.md' },
782
+ ],
783
+ });
784
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
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,
796
+ showSuggestions: true,
797
+ suggestions: [{ label: 'test-command', value: 'test-command' }],
798
+ });
799
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
800
+ await wait();
801
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
802
+ unmount();
803
+ });
804
+ it('should handle Unicode characters with escaped spaces', async () => {
805
+ // Test combining Unicode and escaped spaces
806
+ mockBuffer.text = '@' + path.join('files', 'emoji\\ 👍\\ test.txt');
807
+ mockBuffer.lines = ['@' + path.join('files', 'emoji\\ 👍\\ test.txt')];
808
+ mockBuffer.cursor = [0, 25]; // After the escaped space and emoji
809
+ mockedUseCommandCompletion.mockReturnValue({
810
+ ...mockCommandCompletion,
811
+ showSuggestions: true,
812
+ suggestions: [
813
+ { label: 'emoji 👍 test.txt', value: 'emoji 👍 test.txt' },
814
+ ],
815
+ });
816
+ const { unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
817
+ await wait();
818
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
819
+ unmount();
820
+ });
821
+ });
822
+ describe('vim mode', () => {
823
+ it('should not call buffer.handleInput when vim mode is enabled and vim handles the input', async () => {
824
+ props.vimHandleInput = vi.fn().mockReturnValue(true); // Mock that vim handled it.
825
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
826
+ await wait();
827
+ stdin.write('i');
828
+ await wait();
829
+ expect(props.vimHandleInput).toHaveBeenCalled();
830
+ expect(mockBuffer.handleInput).not.toHaveBeenCalled();
831
+ unmount();
832
+ });
833
+ it('should call buffer.handleInput when vim mode is enabled but vim does not handle the input', async () => {
834
+ props.vimHandleInput = vi.fn().mockReturnValue(false); // Mock that vim did NOT handle it.
835
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
836
+ await wait();
837
+ stdin.write('i');
838
+ await wait();
839
+ expect(props.vimHandleInput).toHaveBeenCalled();
840
+ expect(mockBuffer.handleInput).toHaveBeenCalled();
841
+ unmount();
842
+ });
843
+ it('should call handleInput when vim mode is disabled', async () => {
844
+ // Mock vimHandleInput to return false (vim didn't handle the input)
845
+ props.vimHandleInput = vi.fn().mockReturnValue(false);
846
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
847
+ await wait();
848
+ stdin.write('i');
849
+ await wait();
850
+ expect(props.vimHandleInput).toHaveBeenCalled();
851
+ expect(mockBuffer.handleInput).toHaveBeenCalled();
852
+ unmount();
853
+ });
854
+ });
855
+ describe('unfocused paste', () => {
856
+ it('should handle bracketed paste when not focused', async () => {
857
+ props.focus = false;
858
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
859
+ await wait();
860
+ stdin.write('\x1B[200~pasted text\x1B[201~');
861
+ await wait();
862
+ expect(mockBuffer.handleInput).toHaveBeenCalledWith(expect.objectContaining({
863
+ paste: true,
864
+ sequence: 'pasted text',
865
+ }));
866
+ unmount();
867
+ });
868
+ it('should ignore regular keypresses when not focused', async () => {
869
+ props.focus = false;
870
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
871
+ await wait();
872
+ stdin.write('a');
873
+ await wait();
874
+ expect(mockBuffer.handleInput).not.toHaveBeenCalled();
875
+ unmount();
876
+ });
877
+ });
878
+ describe('Highlighting and Cursor Display', () => {
879
+ it('should display cursor mid-word by highlighting the character', async () => {
880
+ mockBuffer.text = 'hello world';
881
+ mockBuffer.lines = ['hello world'];
882
+ mockBuffer.viewportVisualLines = ['hello world'];
883
+ mockBuffer.visualCursor = [0, 3]; // cursor on the second 'l'
884
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
885
+ await wait();
886
+ const frame = stdout.lastFrame();
887
+ // The component will render the text with the character at the cursor inverted.
888
+ expect(frame).toContain(`hel${chalk.inverse('l')}o world`);
889
+ unmount();
890
+ });
891
+ it('should display cursor at the beginning of the line', async () => {
892
+ mockBuffer.text = 'hello';
893
+ mockBuffer.lines = ['hello'];
894
+ mockBuffer.viewportVisualLines = ['hello'];
895
+ mockBuffer.visualCursor = [0, 0]; // cursor on 'h'
896
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
897
+ await wait();
898
+ const frame = stdout.lastFrame();
899
+ expect(frame).toContain(`${chalk.inverse('h')}ello`);
900
+ unmount();
901
+ });
902
+ it('should display cursor at the end of the line as an inverted space', async () => {
903
+ mockBuffer.text = 'hello';
904
+ mockBuffer.lines = ['hello'];
905
+ mockBuffer.viewportVisualLines = ['hello'];
906
+ mockBuffer.visualCursor = [0, 5]; // cursor after 'o'
907
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
908
+ await wait();
909
+ const frame = stdout.lastFrame();
910
+ expect(frame).toContain(`hello${chalk.inverse(' ')}`);
911
+ unmount();
912
+ });
913
+ it('should display cursor correctly on a highlighted token', async () => {
914
+ mockBuffer.text = 'run @path/to/file';
915
+ mockBuffer.lines = ['run @path/to/file'];
916
+ mockBuffer.viewportVisualLines = ['run @path/to/file'];
917
+ mockBuffer.visualCursor = [0, 9]; // cursor on 't' in 'to'
918
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
919
+ await wait();
920
+ const frame = stdout.lastFrame();
921
+ // The token '@path/to/file' is colored, and the cursor highlights one char inside it.
922
+ expect(frame).toContain(`@path/${chalk.inverse('t')}o/file`);
923
+ unmount();
924
+ });
925
+ it('should display cursor correctly for multi-byte unicode characters', async () => {
926
+ const text = 'hello 👍 world';
927
+ mockBuffer.text = text;
928
+ mockBuffer.lines = [text];
929
+ mockBuffer.viewportVisualLines = [text];
930
+ mockBuffer.visualCursor = [0, 6]; // cursor on '👍'
931
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
932
+ await wait();
933
+ const frame = stdout.lastFrame();
934
+ expect(frame).toContain(`hello ${chalk.inverse('👍')} world`);
935
+ unmount();
936
+ });
937
+ it('should display cursor at the end of a line with unicode characters', async () => {
938
+ const text = 'hello 👍';
939
+ mockBuffer.text = text;
940
+ mockBuffer.lines = [text];
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();
1019
+ });
1020
+ it('should display cursor on a blank line in a multiline block', async () => {
1021
+ const text = 'first line\n\nthird line';
1022
+ mockBuffer.text = text;
1023
+ mockBuffer.lines = text.split('\n');
1024
+ mockBuffer.viewportVisualLines = text.split('\n');
1025
+ mockBuffer.visualCursor = [1, 0]; // cursor on the blank line
1026
+ mockBuffer.visualToLogicalMap = [
1027
+ [0, 0],
1028
+ [1, 0],
1029
+ [2, 0],
1030
+ ];
1031
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1032
+ await wait();
1033
+ const frame = stdout.lastFrame();
1034
+ const lines = frame.split('\n');
1035
+ // The line with the cursor should just be an inverted space inside the box border
1036
+ expect(lines.find((l) => l.includes(chalk.inverse(' ')))).not.toBeUndefined();
1037
+ unmount();
1038
+ });
1039
+ });
1040
+ describe('multiline rendering', () => {
1041
+ it('should correctly render multiline input including blank lines', async () => {
1042
+ const text = 'hello\n\nworld';
1043
+ mockBuffer.text = text;
1044
+ mockBuffer.lines = text.split('\n');
1045
+ mockBuffer.viewportVisualLines = text.split('\n');
1046
+ mockBuffer.allVisualLines = text.split('\n');
1047
+ mockBuffer.visualCursor = [2, 5]; // cursor at the end of "world"
1048
+ // Provide a visual-to-logical mapping for each visual line
1049
+ mockBuffer.visualToLogicalMap = [
1050
+ [0, 0], // 'hello' starts at col 0 of logical line 0
1051
+ [1, 0], // '' (blank) is logical line 1, col 0
1052
+ [2, 0], // 'world' is logical line 2, col 0
1053
+ ];
1054
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1055
+ await wait();
1056
+ const frame = stdout.lastFrame();
1057
+ // Check that all lines, including the empty one, are rendered.
1058
+ // This implicitly tests that the Box wrapper provides height for the empty line.
1059
+ expect(frame).toContain('hello');
1060
+ expect(frame).toContain(`world${chalk.inverse(' ')}`);
1061
+ const outputLines = frame.split('\n');
1062
+ // The number of lines should be 2 for the border plus 3 for the content.
1063
+ expect(outputLines.length).toBe(5);
1064
+ unmount();
1065
+ });
1066
+ });
1067
+ describe('multiline paste', () => {
1068
+ it.each([
1069
+ {
1070
+ description: 'with \n newlines',
1071
+ pastedText: 'This \n is \n a \n multiline \n paste.',
1072
+ },
1073
+ {
1074
+ description: 'with extra slashes before \n newlines',
1075
+ pastedText: 'This \\\n is \\\n a \\\n multiline \\\n paste.',
1076
+ },
1077
+ {
1078
+ description: 'with \r\n newlines',
1079
+ pastedText: 'This\r\nis\r\na\r\nmultiline\r\npaste.',
1080
+ },
1081
+ ])('should handle multiline paste $description', async ({ pastedText }) => {
1082
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1083
+ await wait();
1084
+ // Simulate a bracketed paste event from the terminal
1085
+ stdin.write(`\x1b[200~${pastedText}\x1b[201~`);
1086
+ await wait();
1087
+ // Verify that the buffer's handleInput was called once with the full text
1088
+ expect(props.buffer.handleInput).toHaveBeenCalledTimes(1);
1089
+ expect(props.buffer.handleInput).toHaveBeenCalledWith(expect.objectContaining({
1090
+ paste: true,
1091
+ sequence: pastedText,
1092
+ }));
1093
+ unmount();
1094
+ });
1095
+ });
1096
+ describe('paste auto-submission protection', () => {
1097
+ beforeEach(() => {
1098
+ vi.useFakeTimers();
1099
+ mockedUseKittyKeyboardProtocol.mockReturnValue({
1100
+ supported: false,
1101
+ enabled: false,
1102
+ checking: false,
1103
+ });
1104
+ });
1105
+ afterEach(() => {
1106
+ vi.useRealTimers();
1107
+ });
1108
+ it('should prevent auto-submission immediately after an unsafe paste', async () => {
1109
+ // isTerminalPasteTrusted will be false due to beforeEach setup.
1110
+ props.buffer.text = 'some command';
1111
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1112
+ await vi.runAllTimersAsync();
1113
+ // Simulate a paste operation (this should set the paste protection)
1114
+ stdin.write(`\x1b[200~pasted content\x1b[201~`);
1115
+ await vi.runAllTimersAsync();
1116
+ // Simulate an Enter key press immediately after paste
1117
+ stdin.write('\r');
1118
+ await vi.runAllTimersAsync();
1119
+ // Verify that onSubmit was NOT called due to recent paste protection
1120
+ expect(props.onSubmit).not.toHaveBeenCalled();
1121
+ // It should call newline() instead
1122
+ expect(props.buffer.newline).toHaveBeenCalled();
1123
+ unmount();
1124
+ });
1125
+ it('should allow submission after unsafe paste protection timeout', async () => {
1126
+ // isTerminalPasteTrusted will be false due to beforeEach setup.
1127
+ props.buffer.text = 'pasted text';
1128
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1129
+ await vi.runAllTimersAsync();
1130
+ // Simulate a paste operation (this sets the protection)
1131
+ act(() => {
1132
+ stdin.write('\x1b[200~pasted text\x1b[201~');
1133
+ });
1134
+ await vi.runAllTimersAsync();
1135
+ // Advance timers past the protection timeout
1136
+ await act(async () => {
1137
+ await vi.advanceTimersByTimeAsync(50);
1138
+ });
1139
+ // Now Enter should work normally
1140
+ stdin.write('\r');
1141
+ await vi.runAllTimersAsync();
1142
+ expect(props.onSubmit).toHaveBeenCalledWith('pasted text');
1143
+ expect(props.buffer.newline).not.toHaveBeenCalled();
1144
+ unmount();
1145
+ });
1146
+ it.each([
1147
+ {
1148
+ name: 'kitty',
1149
+ setup: () => mockedUseKittyKeyboardProtocol.mockReturnValue({
1150
+ supported: true,
1151
+ enabled: true,
1152
+ checking: false,
1153
+ }),
1154
+ },
1155
+ ])('should allow immediate submission for a trusted paste ($name)', async ({ setup }) => {
1156
+ setup();
1157
+ props.buffer.text = 'pasted command';
1158
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: true });
1159
+ await vi.runAllTimersAsync();
1160
+ // Simulate a paste operation
1161
+ stdin.write('\x1b[200~some pasted stuff\x1b[201~');
1162
+ await vi.runAllTimersAsync();
1163
+ // Simulate an Enter key press immediately after paste
1164
+ stdin.write('\r');
1165
+ await vi.runAllTimersAsync();
1166
+ // Verify that onSubmit was called
1167
+ expect(props.onSubmit).toHaveBeenCalledWith('pasted command');
1168
+ unmount();
1169
+ });
1170
+ it('should not interfere with normal Enter key submission when no recent paste', async () => {
1171
+ // Set up buffer with text before rendering to ensure submission works
1172
+ props.buffer.text = 'normal command';
1173
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1174
+ await vi.runAllTimersAsync();
1175
+ // Press Enter without any recent paste
1176
+ stdin.write('\r');
1177
+ await vi.runAllTimersAsync();
1178
+ // Verify that onSubmit was called normally
1179
+ expect(props.onSubmit).toHaveBeenCalledWith('normal command');
1180
+ unmount();
1181
+ });
1182
+ });
1183
+ describe('enhanced input UX - double ESC clear functionality', () => {
1184
+ it('should clear buffer on second ESC press', async () => {
1185
+ const onEscapePromptChange = vi.fn();
1186
+ props.onEscapePromptChange = onEscapePromptChange;
1187
+ props.buffer.setText('text to clear');
1188
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
1189
+ await wait();
1190
+ stdin.write('\x1B');
1191
+ await wait();
1192
+ stdin.write('\x1B');
1193
+ await wait();
1194
+ expect(props.buffer.setText).toHaveBeenCalledWith('');
1195
+ expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
1196
+ unmount();
1197
+ });
1198
+ it('should reset escape state on any non-ESC key', async () => {
1199
+ const onEscapePromptChange = vi.fn();
1200
+ props.onEscapePromptChange = onEscapePromptChange;
1201
+ props.buffer.setText('some text');
1202
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
1203
+ stdin.write('\x1B');
1204
+ await waitFor(() => {
1205
+ expect(onEscapePromptChange).toHaveBeenCalledWith(true);
1206
+ });
1207
+ stdin.write('a');
1208
+ await waitFor(() => {
1209
+ expect(onEscapePromptChange).toHaveBeenCalledWith(false);
1210
+ });
1211
+ unmount();
1212
+ });
1213
+ it('should handle ESC in shell mode by disabling shell mode', async () => {
1214
+ props.shellModeActive = true;
1215
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
1216
+ await wait();
1217
+ stdin.write('\x1B');
1218
+ await wait();
1219
+ expect(props.setShellModeActive).toHaveBeenCalledWith(false);
1220
+ unmount();
1221
+ });
1222
+ it('should handle ESC when completion suggestions are showing', async () => {
1223
+ mockedUseCommandCompletion.mockReturnValue({
1224
+ ...mockCommandCompletion,
1225
+ showSuggestions: true,
1226
+ suggestions: [{ label: 'suggestion', value: 'suggestion' }],
1227
+ });
1228
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
1229
+ await wait();
1230
+ stdin.write('\x1B');
1231
+ await wait();
1232
+ expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
1233
+ unmount();
1234
+ });
1235
+ it('should not call onEscapePromptChange when not provided', async () => {
1236
+ vi.useFakeTimers();
1237
+ props.onEscapePromptChange = undefined;
1238
+ props.buffer.setText('some text');
1239
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
1240
+ await vi.runAllTimersAsync();
1241
+ stdin.write('\x1B');
1242
+ await vi.runAllTimersAsync();
1243
+ vi.useRealTimers();
1244
+ unmount();
1245
+ });
1246
+ it('should not interfere with existing keyboard shortcuts', async () => {
1247
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
1248
+ await wait();
1249
+ stdin.write('\x0C');
1250
+ await wait();
1251
+ expect(props.onClearScreen).toHaveBeenCalled();
1252
+ stdin.write('\x01');
1253
+ await wait();
1254
+ expect(props.buffer.move).toHaveBeenCalledWith('home');
1255
+ unmount();
1256
+ });
1257
+ });
1258
+ describe('reverse search', () => {
1259
+ beforeEach(async () => {
1260
+ props.shellModeActive = true;
1261
+ vi.mocked(useShellHistory).mockReturnValue({
1262
+ history: ['echo hello', 'echo world', 'ls'],
1263
+ getPreviousCommand: vi.fn(),
1264
+ getNextCommand: vi.fn(),
1265
+ addCommandToHistory: vi.fn(),
1266
+ resetHistoryPosition: vi.fn(),
1267
+ });
1268
+ });
1269
+ it('invokes reverse search on Ctrl+R', async () => {
1270
+ // Mock the reverse search completion to return suggestions
1271
+ mockedUseReverseSearchCompletion.mockReturnValue({
1272
+ ...mockReverseSearchCompletion,
1273
+ suggestions: [
1274
+ { label: 'echo hello', value: 'echo hello' },
1275
+ { label: 'echo world', value: 'echo world' },
1276
+ { label: 'ls', value: 'ls' },
1277
+ ],
1278
+ showSuggestions: true,
1279
+ activeSuggestionIndex: 0,
1280
+ });
1281
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1282
+ await wait();
1283
+ // Trigger reverse search with Ctrl+R
1284
+ act(() => {
1285
+ stdin.write('\x12');
1286
+ });
1287
+ await wait();
1288
+ const frame = stdout.lastFrame();
1289
+ expect(frame).toContain('(r:)');
1290
+ expect(frame).toContain('echo hello');
1291
+ expect(frame).toContain('echo world');
1292
+ expect(frame).toContain('ls');
1293
+ unmount();
1294
+ });
1295
+ it('resets reverse search state on Escape', async () => {
1296
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1297
+ await wait();
1298
+ stdin.write('\x12');
1299
+ await wait();
1300
+ stdin.write('\x1B');
1301
+ stdin.write('\u001b[27u'); // Press kitty escape key
1302
+ await waitFor(() => {
1303
+ expect(stdout.lastFrame()).not.toContain('(r:)');
1304
+ });
1305
+ expect(stdout.lastFrame()).not.toContain('echo hello');
1306
+ unmount();
1307
+ });
1308
+ it('completes the highlighted entry on Tab and exits reverse-search', async () => {
1309
+ // Mock the reverse search completion
1310
+ const mockHandleAutocomplete = vi.fn(() => {
1311
+ props.buffer.setText('echo hello');
1312
+ });
1313
+ mockedUseReverseSearchCompletion.mockImplementation((buffer, shellHistory, reverseSearchActive) => ({
1314
+ ...mockReverseSearchCompletion,
1315
+ suggestions: reverseSearchActive
1316
+ ? [
1317
+ { label: 'echo hello', value: 'echo hello' },
1318
+ { label: 'echo world', value: 'echo world' },
1319
+ { label: 'ls', value: 'ls' },
1320
+ ]
1321
+ : [],
1322
+ showSuggestions: reverseSearchActive,
1323
+ activeSuggestionIndex: reverseSearchActive ? 0 : -1,
1324
+ handleAutocomplete: mockHandleAutocomplete,
1325
+ }));
1326
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1327
+ // Enter reverse search mode with Ctrl+R
1328
+ act(() => {
1329
+ stdin.write('\x12');
1330
+ });
1331
+ await wait();
1332
+ // Verify reverse search is active
1333
+ expect(stdout.lastFrame()).toContain('(r:)');
1334
+ // Press Tab to complete the highlighted entry
1335
+ act(() => {
1336
+ stdin.write('\t');
1337
+ });
1338
+ await wait();
1339
+ expect(mockHandleAutocomplete).toHaveBeenCalledWith(0);
1340
+ expect(props.buffer.setText).toHaveBeenCalledWith('echo hello');
1341
+ unmount();
1342
+ }, 15000);
1343
+ it('submits the highlighted entry on Enter and exits reverse-search', async () => {
1344
+ // Mock the reverse search completion to return suggestions
1345
+ mockedUseReverseSearchCompletion.mockReturnValue({
1346
+ ...mockReverseSearchCompletion,
1347
+ suggestions: [
1348
+ { label: 'echo hello', value: 'echo hello' },
1349
+ { label: 'echo world', value: 'echo world' },
1350
+ { label: 'ls', value: 'ls' },
1351
+ ],
1352
+ showSuggestions: true,
1353
+ activeSuggestionIndex: 0,
1354
+ });
1355
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1356
+ act(() => {
1357
+ stdin.write('\x12');
1358
+ });
1359
+ await wait();
1360
+ expect(stdout.lastFrame()).toContain('(r:)');
1361
+ act(() => {
1362
+ stdin.write('\r');
1363
+ });
1364
+ await waitFor(() => {
1365
+ expect(stdout.lastFrame()).not.toContain('(r:)');
1366
+ });
1367
+ expect(props.onSubmit).toHaveBeenCalledWith('echo hello');
1368
+ unmount();
1369
+ });
1370
+ it.skip('text and cursor position should be restored after reverse search', async () => {
1371
+ props.buffer.setText('initial text');
1372
+ props.buffer.cursor = [0, 3];
1373
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1374
+ stdin.write('\x12');
1375
+ await wait();
1376
+ expect(stdout.lastFrame()).toContain('(r:)');
1377
+ stdin.write('\u001b[27u'); // Press kitty escape key
1378
+ await waitFor(() => {
1379
+ expect(stdout.lastFrame()).not.toContain('(r:)');
1380
+ });
1381
+ expect(props.buffer.text).toBe('initial text');
1382
+ expect(props.buffer.cursor).toEqual([0, 3]);
1383
+ unmount();
1384
+ });
1385
+ });
1386
+ describe('Ctrl+E keyboard shortcut', () => {
1387
+ it('should move cursor to end of current line in multiline input', async () => {
1388
+ props.buffer.text = 'line 1\nline 2\nline 3';
1389
+ props.buffer.cursor = [1, 2];
1390
+ props.buffer.lines = ['line 1', 'line 2', 'line 3'];
1391
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1392
+ await wait();
1393
+ stdin.write('\x05'); // Ctrl+E
1394
+ await wait();
1395
+ expect(props.buffer.move).toHaveBeenCalledWith('end');
1396
+ expect(props.buffer.moveToOffset).not.toHaveBeenCalled();
1397
+ unmount();
1398
+ });
1399
+ it('should move cursor to end of current line for single line input', async () => {
1400
+ props.buffer.text = 'single line text';
1401
+ props.buffer.cursor = [0, 5];
1402
+ props.buffer.lines = ['single line text'];
1403
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1404
+ await wait();
1405
+ stdin.write('\x05'); // Ctrl+E
1406
+ await wait();
1407
+ expect(props.buffer.move).toHaveBeenCalledWith('end');
1408
+ expect(props.buffer.moveToOffset).not.toHaveBeenCalled();
1409
+ unmount();
1410
+ });
1411
+ });
1412
+ describe('command search (Ctrl+R when not in shell)', () => {
1413
+ it('enters command search on Ctrl+R and shows suggestions', async () => {
1414
+ props.shellModeActive = false;
1415
+ vi.mocked(useReverseSearchCompletion).mockImplementation((buffer, data, isActive) => ({
1416
+ ...mockReverseSearchCompletion,
1417
+ suggestions: isActive
1418
+ ? [
1419
+ { label: 'git commit -m "msg"', value: 'git commit -m "msg"' },
1420
+ { label: 'git push', value: 'git push' },
1421
+ ]
1422
+ : [],
1423
+ showSuggestions: !!isActive,
1424
+ activeSuggestionIndex: isActive ? 0 : -1,
1425
+ }));
1426
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1427
+ await wait();
1428
+ act(() => {
1429
+ stdin.write('\x12'); // Ctrl+R
1430
+ });
1431
+ await wait();
1432
+ const frame = stdout.lastFrame() ?? '';
1433
+ expect(frame).toContain('(r:)');
1434
+ expect(frame).toContain('git commit');
1435
+ expect(frame).toContain('git push');
1436
+ unmount();
1437
+ });
1438
+ it.skip('expands and collapses long suggestion via Right/Left arrows', async () => {
1439
+ props.shellModeActive = false;
1440
+ const longValue = 'l'.repeat(200);
1441
+ vi.mocked(useReverseSearchCompletion).mockReturnValue({
1442
+ ...mockReverseSearchCompletion,
1443
+ suggestions: [{ label: longValue, value: longValue, matchedIndex: 0 }],
1444
+ showSuggestions: true,
1445
+ activeSuggestionIndex: 0,
1446
+ visibleStartIndex: 0,
1447
+ isLoadingSuggestions: false,
1448
+ });
1449
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1450
+ await wait();
1451
+ stdin.write('\x12');
1452
+ await wait();
1453
+ expect(clean(stdout.lastFrame())).toContain('→');
1454
+ stdin.write('\u001B[C');
1455
+ await wait(200);
1456
+ expect(clean(stdout.lastFrame())).toContain('←');
1457
+ expect(stdout.lastFrame()).toMatchSnapshot('command-search-expanded-match');
1458
+ stdin.write('\u001B[D');
1459
+ await wait();
1460
+ expect(clean(stdout.lastFrame())).toContain('→');
1461
+ expect(stdout.lastFrame()).toMatchSnapshot('command-search-collapsed-match');
1462
+ unmount();
1463
+ });
1464
+ it('renders match window and expanded view (snapshots)', async () => {
1465
+ props.shellModeActive = false;
1466
+ props.buffer.setText('commit');
1467
+ const label = 'git commit -m "feat: add search" in src/app';
1468
+ const matchedIndex = label.indexOf('commit');
1469
+ vi.mocked(useReverseSearchCompletion).mockReturnValue({
1470
+ ...mockReverseSearchCompletion,
1471
+ suggestions: [{ label, value: label, matchedIndex }],
1472
+ showSuggestions: true,
1473
+ activeSuggestionIndex: 0,
1474
+ visibleStartIndex: 0,
1475
+ isLoadingSuggestions: false,
1476
+ });
1477
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1478
+ await wait();
1479
+ stdin.write('\x12');
1480
+ await wait();
1481
+ expect(stdout.lastFrame()).toMatchSnapshot('command-search-collapsed-match');
1482
+ stdin.write('\u001B[C');
1483
+ await wait();
1484
+ expect(stdout.lastFrame()).toMatchSnapshot('command-search-expanded-match');
1485
+ unmount();
1486
+ });
1487
+ it('does not show expand/collapse indicator for short suggestions', async () => {
1488
+ props.shellModeActive = false;
1489
+ const shortValue = 'echo hello';
1490
+ vi.mocked(useReverseSearchCompletion).mockReturnValue({
1491
+ ...mockReverseSearchCompletion,
1492
+ suggestions: [{ label: shortValue, value: shortValue }],
1493
+ showSuggestions: true,
1494
+ activeSuggestionIndex: 0,
1495
+ visibleStartIndex: 0,
1496
+ isLoadingSuggestions: false,
1497
+ });
1498
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1499
+ await wait();
1500
+ stdin.write('\x12');
1501
+ await wait();
1502
+ const frame = clean(stdout.lastFrame());
1503
+ expect(frame).not.toContain('→');
1504
+ expect(frame).not.toContain('←');
1505
+ unmount();
1506
+ });
1507
+ });
1508
+ describe('queued message editing', () => {
1509
+ it('should load all queued messages when up arrow is pressed with empty input', async () => {
1510
+ const mockPopAllMessages = vi.fn();
1511
+ props.popAllMessages = mockPopAllMessages;
1512
+ props.buffer.text = '';
1513
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1514
+ await wait();
1515
+ stdin.write('\u001B[A');
1516
+ await wait();
1517
+ expect(mockPopAllMessages).toHaveBeenCalled();
1518
+ const callback = mockPopAllMessages.mock.calls[0][0];
1519
+ act(() => {
1520
+ callback('Message 1\n\nMessage 2\n\nMessage 3');
1521
+ });
1522
+ expect(props.buffer.setText).toHaveBeenCalledWith('Message 1\n\nMessage 2\n\nMessage 3');
1523
+ unmount();
1524
+ });
1525
+ it('should not load queued messages when input is not empty', async () => {
1526
+ const mockPopAllMessages = vi.fn();
1527
+ props.popAllMessages = mockPopAllMessages;
1528
+ props.buffer.text = 'some text';
1529
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1530
+ await wait();
1531
+ stdin.write('\u001B[A');
1532
+ await wait();
1533
+ expect(mockPopAllMessages).not.toHaveBeenCalled();
1534
+ expect(mockInputHistory.navigateUp).toHaveBeenCalled();
1535
+ unmount();
1536
+ });
1537
+ it('should handle undefined messages from popAllMessages', async () => {
1538
+ const mockPopAllMessages = vi.fn();
1539
+ props.popAllMessages = mockPopAllMessages;
1540
+ props.buffer.text = '';
1541
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1542
+ await wait();
1543
+ stdin.write('\u001B[A');
1544
+ await wait();
1545
+ expect(mockPopAllMessages).toHaveBeenCalled();
1546
+ const callback = mockPopAllMessages.mock.calls[0][0];
1547
+ act(() => {
1548
+ callback(undefined);
1549
+ });
1550
+ expect(props.buffer.setText).not.toHaveBeenCalled();
1551
+ expect(mockInputHistory.navigateUp).toHaveBeenCalled();
1552
+ unmount();
1553
+ });
1554
+ it('should work with NAVIGATION_UP key as well', async () => {
1555
+ const mockPopAllMessages = vi.fn();
1556
+ props.popAllMessages = mockPopAllMessages;
1557
+ props.buffer.text = '';
1558
+ props.buffer.allVisualLines = [''];
1559
+ props.buffer.visualCursor = [0, 0];
1560
+ props.buffer.visualScrollRow = 0;
1561
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1562
+ await wait();
1563
+ stdin.write('\u001B[A');
1564
+ await wait();
1565
+ expect(mockPopAllMessages).toHaveBeenCalled();
1566
+ unmount();
1567
+ });
1568
+ it('should handle single queued message', async () => {
1569
+ const mockPopAllMessages = vi.fn();
1570
+ props.popAllMessages = mockPopAllMessages;
1571
+ props.buffer.text = '';
1572
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1573
+ await wait();
1574
+ stdin.write('\u001B[A');
1575
+ await wait();
1576
+ const callback = mockPopAllMessages.mock.calls[0][0];
1577
+ act(() => {
1578
+ callback('Single message');
1579
+ });
1580
+ expect(props.buffer.setText).toHaveBeenCalledWith('Single message');
1581
+ unmount();
1582
+ });
1583
+ it('should only check for queued messages when buffer text is trimmed empty', async () => {
1584
+ const mockPopAllMessages = vi.fn();
1585
+ props.popAllMessages = mockPopAllMessages;
1586
+ props.buffer.text = ' '; // Whitespace only
1587
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1588
+ await wait();
1589
+ stdin.write('\u001B[A');
1590
+ await wait();
1591
+ expect(mockPopAllMessages).toHaveBeenCalled();
1592
+ unmount();
1593
+ });
1594
+ it('should not call popAllMessages if it is not provided', async () => {
1595
+ props.popAllMessages = undefined;
1596
+ props.buffer.text = '';
1597
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1598
+ await wait();
1599
+ stdin.write('\u001B[A');
1600
+ await wait();
1601
+ expect(mockInputHistory.navigateUp).toHaveBeenCalled();
1602
+ unmount();
1603
+ });
1604
+ it('should navigate input history on fresh start when no queued messages exist', async () => {
1605
+ const mockPopAllMessages = vi.fn();
1606
+ props.popAllMessages = mockPopAllMessages;
1607
+ props.buffer.text = '';
1608
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1609
+ await wait();
1610
+ stdin.write('\u001B[A');
1611
+ await wait();
1612
+ expect(mockPopAllMessages).toHaveBeenCalled();
1613
+ const callback = mockPopAllMessages.mock.calls[0][0];
1614
+ act(() => {
1615
+ callback(undefined);
1616
+ });
1617
+ expect(mockInputHistory.navigateUp).toHaveBeenCalled();
1618
+ expect(props.buffer.setText).not.toHaveBeenCalled();
1619
+ unmount();
1620
+ });
1621
+ });
1622
+ describe('snapshots', () => {
1623
+ it('should render correctly in shell mode', async () => {
1624
+ props.shellModeActive = true;
1625
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1626
+ await wait();
1627
+ expect(stdout.lastFrame()).toMatchSnapshot();
1628
+ unmount();
1629
+ });
1630
+ it('should render correctly when accepting edits', async () => {
1631
+ props.approvalMode = ApprovalMode.AUTO_EDIT;
1632
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1633
+ await wait();
1634
+ expect(stdout.lastFrame()).toMatchSnapshot();
1635
+ unmount();
1636
+ });
1637
+ it('should render correctly in yolo mode', async () => {
1638
+ props.approvalMode = ApprovalMode.YOLO;
1639
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1640
+ await wait();
1641
+ expect(stdout.lastFrame()).toMatchSnapshot();
1642
+ unmount();
1643
+ });
1644
+ it('should not show inverted cursor when shell is focused', async () => {
1645
+ props.isEmbeddedShellFocused = true;
1646
+ props.focus = false;
1647
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1648
+ await wait();
1649
+ expect(stdout.lastFrame()).not.toContain(`{chalk.inverse(' ')}`);
1650
+ // This snapshot is good to make sure there was an input prompt but does
1651
+ // not show the inverted cursor because snapshots do not show colors.
1652
+ expect(stdout.lastFrame()).toMatchSnapshot();
1653
+ unmount();
1654
+ });
1655
+ });
1656
+ it('should still allow input when shell is not focused', async () => {
1657
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), {
1658
+ shellFocus: false,
1659
+ });
1660
+ await wait();
1661
+ stdin.write('a');
1662
+ await wait();
1663
+ expect(mockBuffer.handleInput).toHaveBeenCalled();
1664
+ unmount();
1665
+ });
1666
+ it('should prevent slash commands from being queued while streaming', async () => {
1667
+ props.onSubmit = vi.fn();
1668
+ props.buffer.text = '/help';
1669
+ props.setQueueErrorMessage = vi.fn();
1670
+ props.streamingState = StreamingState.Responding;
1671
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1672
+ await wait();
1673
+ stdin.write('/help');
1674
+ stdin.write('\r');
1675
+ await wait();
1676
+ expect(props.onSubmit).not.toHaveBeenCalled();
1677
+ expect(props.setQueueErrorMessage).toHaveBeenCalledWith('Slash commands cannot be queued');
1678
+ unmount();
1679
+ });
1680
+ it('should prevent shell commands from being queued while streaming', async () => {
1681
+ props.onSubmit = vi.fn();
1682
+ props.buffer.text = 'ls';
1683
+ props.setQueueErrorMessage = vi.fn();
1684
+ props.streamingState = StreamingState.Responding;
1685
+ props.shellModeActive = true;
1686
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1687
+ await wait();
1688
+ stdin.write('ls');
1689
+ stdin.write('\r');
1690
+ await wait();
1691
+ expect(props.onSubmit).not.toHaveBeenCalled();
1692
+ expect(props.setQueueErrorMessage).toHaveBeenCalledWith('Shell commands cannot be queued');
1693
+ unmount();
1694
+ });
1695
+ it('should allow regular messages to be queued while streaming', async () => {
1696
+ props.onSubmit = vi.fn();
1697
+ props.buffer.text = 'regular message';
1698
+ props.setQueueErrorMessage = vi.fn();
1699
+ props.streamingState = StreamingState.Responding;
1700
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1701
+ await wait();
1702
+ stdin.write('regular message');
1703
+ stdin.write('\r');
1704
+ await wait();
1705
+ expect(props.onSubmit).toHaveBeenCalledWith('regular message');
1706
+ expect(props.setQueueErrorMessage).not.toHaveBeenCalled();
1707
+ unmount();
1708
+ });
1709
+ });
1710
+ function clean(str) {
1711
+ if (!str)
1712
+ return '';
1713
+ // Remove ANSI escape codes and trim whitespace
1714
+ return stripAnsi(str).trim();
1715
+ }
1716
+ //# sourceMappingURL=InputPrompt.test.js.map