@machina.ai/cell-cli 1.11.0-rc1 → 1.13.0-rc2

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 (538) hide show
  1. package/dist/package.json +16 -10
  2. package/dist/src/commands/extensions/disable.d.ts +1 -1
  3. package/dist/src/commands/extensions/disable.js +15 -7
  4. package/dist/src/commands/extensions/disable.js.map +1 -1
  5. package/dist/src/commands/extensions/enable.d.ts +1 -1
  6. package/dist/src/commands/extensions/enable.js +15 -7
  7. package/dist/src/commands/extensions/enable.js.map +1 -1
  8. package/dist/src/commands/extensions/install.js +14 -3
  9. package/dist/src/commands/extensions/install.js.map +1 -1
  10. package/dist/src/commands/extensions/install.test.js +39 -19
  11. package/dist/src/commands/extensions/install.test.js.map +1 -1
  12. package/dist/src/commands/extensions/link.js +14 -3
  13. package/dist/src/commands/extensions/link.js.map +1 -1
  14. package/dist/src/commands/extensions/list.js +13 -4
  15. package/dist/src/commands/extensions/list.js.map +1 -1
  16. package/dist/src/commands/extensions/uninstall.js +13 -2
  17. package/dist/src/commands/extensions/uninstall.js.map +1 -1
  18. package/dist/src/commands/extensions/update.js +18 -13
  19. package/dist/src/commands/extensions/update.js.map +1 -1
  20. package/dist/src/commands/extensions/validate.d.ts +12 -0
  21. package/dist/src/commands/extensions/validate.js +83 -0
  22. package/dist/src/commands/extensions/validate.js.map +1 -0
  23. package/dist/src/commands/extensions/validate.test.js +93 -0
  24. package/dist/src/commands/extensions/validate.test.js.map +1 -0
  25. package/dist/src/commands/extensions.js +3 -0
  26. package/dist/src/commands/extensions.js.map +1 -1
  27. package/dist/src/commands/mcp/add.test.js +3 -0
  28. package/dist/src/commands/mcp/add.test.js.map +1 -1
  29. package/dist/src/commands/mcp/list.js +10 -3
  30. package/dist/src/commands/mcp/list.js.map +1 -1
  31. package/dist/src/commands/mcp/list.test.js +37 -27
  32. package/dist/src/commands/mcp/list.test.js.map +1 -1
  33. package/dist/src/config/auth.js +0 -5
  34. package/dist/src/config/auth.js.map +1 -1
  35. package/dist/src/config/config.d.ts +6 -3
  36. package/dist/src/config/config.js +65 -80
  37. package/dist/src/config/config.js.map +1 -1
  38. package/dist/src/config/config.test.js +235 -212
  39. package/dist/src/config/config.test.js.map +1 -1
  40. package/dist/src/config/extension-manager.d.ts +63 -0
  41. package/dist/src/config/extension-manager.js +450 -0
  42. package/dist/src/config/extension-manager.js.map +1 -0
  43. package/dist/src/config/extension.d.ts +4 -51
  44. package/dist/src/config/extension.js +1 -535
  45. package/dist/src/config/extension.js.map +1 -1
  46. package/dist/src/config/extension.test.js +525 -201
  47. package/dist/src/config/extension.test.js.map +1 -1
  48. package/dist/src/config/extensions/consent.d.ts +38 -0
  49. package/dist/src/config/extensions/consent.js +123 -0
  50. package/dist/src/config/extensions/consent.js.map +1 -0
  51. package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
  52. package/dist/src/config/extensions/extensionEnablement.js +4 -3
  53. package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
  54. package/dist/src/config/extensions/extensionEnablement.test.js +10 -10
  55. package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
  56. package/dist/src/config/extensions/extensionSettings.d.ts +15 -0
  57. package/dist/src/config/extensions/extensionSettings.js +113 -0
  58. package/dist/src/config/extensions/extensionSettings.js.map +1 -0
  59. package/dist/src/config/extensions/extensionSettings.test.d.ts +6 -0
  60. package/dist/src/config/extensions/extensionSettings.test.js +254 -0
  61. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -0
  62. package/dist/src/config/extensions/github.d.ts +2 -2
  63. package/dist/src/config/extensions/github.js +5 -10
  64. package/dist/src/config/extensions/github.js.map +1 -1
  65. package/dist/src/config/extensions/github.test.js +153 -167
  66. package/dist/src/config/extensions/github.test.js.map +1 -1
  67. package/dist/src/config/extensions/github_fetch.d.ts +1 -1
  68. package/dist/src/config/extensions/github_fetch.js +13 -1
  69. package/dist/src/config/extensions/github_fetch.js.map +1 -1
  70. package/dist/src/config/extensions/github_fetch.test.d.ts +6 -0
  71. package/dist/src/config/extensions/github_fetch.test.js +169 -0
  72. package/dist/src/config/extensions/github_fetch.test.js.map +1 -0
  73. package/dist/src/config/extensions/storage.d.ts +14 -0
  74. package/dist/src/config/extensions/storage.js +32 -0
  75. package/dist/src/config/extensions/storage.js.map +1 -0
  76. package/dist/src/config/extensions/update.d.ts +4 -4
  77. package/dist/src/config/extensions/update.js +39 -39
  78. package/dist/src/config/extensions/update.js.map +1 -1
  79. package/dist/src/config/extensions/update.test.js +72 -74
  80. package/dist/src/config/extensions/update.test.js.map +1 -1
  81. package/dist/src/config/extensions/variableSchema.d.ts +0 -6
  82. package/dist/src/config/extensions/variableSchema.js.map +1 -1
  83. package/dist/src/config/extensions/variables.d.ts +4 -0
  84. package/dist/src/config/extensions/variables.js +6 -0
  85. package/dist/src/config/extensions/variables.js.map +1 -1
  86. package/dist/src/config/keyBindings.d.ts +3 -0
  87. package/dist/src/config/keyBindings.js +30 -8
  88. package/dist/src/config/keyBindings.js.map +1 -1
  89. package/dist/src/config/keyBindings.test.js +17 -0
  90. package/dist/src/config/keyBindings.test.js.map +1 -1
  91. package/dist/src/config/policies/read-only.toml +56 -0
  92. package/dist/src/config/policies/write.toml +63 -0
  93. package/dist/src/config/policies/yolo.toml +31 -0
  94. package/dist/src/config/policy-engine.integration.test.js +41 -38
  95. package/dist/src/config/policy-engine.integration.test.js.map +1 -1
  96. package/dist/src/config/policy.d.ts +2 -2
  97. package/dist/src/config/policy.js +10 -148
  98. package/dist/src/config/policy.js.map +1 -1
  99. package/dist/src/config/sandboxConfig.d.ts +1 -1
  100. package/dist/src/config/sandboxConfig.js +6 -3
  101. package/dist/src/config/sandboxConfig.js.map +1 -1
  102. package/dist/src/config/settings.d.ts +2 -1
  103. package/dist/src/config/settings.js +58 -18
  104. package/dist/src/config/settings.js.map +1 -1
  105. package/dist/src/config/settings.test.js +128 -69
  106. package/dist/src/config/settings.test.js.map +1 -1
  107. package/dist/src/config/settingsSchema.d.ts +170 -28
  108. package/dist/src/config/settingsSchema.js +418 -27
  109. package/dist/src/config/settingsSchema.js.map +1 -1
  110. package/dist/src/config/settingsSchema.test.js +42 -1
  111. package/dist/src/config/settingsSchema.test.js.map +1 -1
  112. package/dist/src/config/trustedFolders.d.ts +1 -1
  113. package/dist/src/config/trustedFolders.js +4 -2
  114. package/dist/src/config/trustedFolders.js.map +1 -1
  115. package/dist/src/core/initializer.js +2 -1
  116. package/dist/src/core/initializer.js.map +1 -1
  117. package/dist/src/gemini.d.ts +1 -1
  118. package/dist/src/gemini.js +46 -16
  119. package/dist/src/gemini.js.map +1 -1
  120. package/dist/src/gemini.test.js +88 -30
  121. package/dist/src/gemini.test.js.map +1 -1
  122. package/dist/src/generated/git-commit.d.ts +2 -2
  123. package/dist/src/generated/git-commit.js +2 -2
  124. package/dist/src/nonInteractiveCli.d.ts +9 -1
  125. package/dist/src/nonInteractiveCli.js +114 -7
  126. package/dist/src/nonInteractiveCli.js.map +1 -1
  127. package/dist/src/nonInteractiveCli.test.js +355 -112
  128. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  129. package/dist/src/services/BuiltinCommandLoader.js +4 -0
  130. package/dist/src/services/BuiltinCommandLoader.js.map +1 -1
  131. package/dist/src/services/BuiltinCommandLoader.test.js +22 -0
  132. package/dist/src/services/BuiltinCommandLoader.test.js.map +1 -1
  133. package/dist/src/services/FeedbackService.js +2 -2
  134. package/dist/src/services/FeedbackService.js.map +1 -1
  135. package/dist/src/services/McpPromptLoader.js +2 -2
  136. package/dist/src/services/McpPromptLoader.js.map +1 -1
  137. package/dist/src/services/McpPromptLoader.test.js +4 -2
  138. package/dist/src/services/McpPromptLoader.test.js.map +1 -1
  139. package/dist/src/test-utils/async.d.ts +9 -0
  140. package/dist/src/test-utils/async.js +29 -0
  141. package/dist/src/test-utils/async.js.map +1 -0
  142. package/dist/src/test-utils/createExtension.d.ts +3 -1
  143. package/dist/src/test-utils/createExtension.js +3 -3
  144. package/dist/src/test-utils/createExtension.js.map +1 -1
  145. package/dist/src/test-utils/render.d.ts +16 -2
  146. package/dist/src/test-utils/render.js +66 -4
  147. package/dist/src/test-utils/render.js.map +1 -1
  148. package/dist/src/test-utils/render.test.d.ts +6 -0
  149. package/dist/src/test-utils/render.test.js +79 -0
  150. package/dist/src/test-utils/render.test.js.map +1 -0
  151. package/dist/src/ui/App.test.js +1 -1
  152. package/dist/src/ui/App.test.js.map +1 -1
  153. package/dist/src/ui/AppContainer.js +181 -65
  154. package/dist/src/ui/AppContainer.js.map +1 -1
  155. package/dist/src/ui/AppContainer.test.js +505 -147
  156. package/dist/src/ui/AppContainer.test.js.map +1 -1
  157. package/dist/src/ui/IdeIntegrationNudge.js +1 -1
  158. package/dist/src/ui/IdeIntegrationNudge.js.map +1 -1
  159. package/dist/src/ui/auth/ApiAuthDialog.d.ts +14 -0
  160. package/dist/src/ui/auth/ApiAuthDialog.js +26 -0
  161. package/dist/src/ui/auth/ApiAuthDialog.js.map +1 -0
  162. package/dist/src/ui/auth/ApiAuthDialog.test.d.ts +6 -0
  163. package/dist/src/ui/auth/ApiAuthDialog.test.js +91 -0
  164. package/dist/src/ui/auth/ApiAuthDialog.test.js.map +1 -0
  165. package/dist/src/ui/auth/AuthDialog.js +7 -3
  166. package/dist/src/ui/auth/AuthDialog.js.map +1 -1
  167. package/dist/src/ui/auth/useAuth.d.ts +2 -0
  168. package/dist/src/ui/auth/useAuth.js +31 -2
  169. package/dist/src/ui/auth/useAuth.js.map +1 -1
  170. package/dist/src/ui/colors.js +3 -0
  171. package/dist/src/ui/colors.js.map +1 -1
  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/extensionsCommand.js +64 -11
  175. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  176. package/dist/src/ui/commands/extensionsCommand.test.js +72 -1
  177. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  178. package/dist/src/ui/commands/mcpCommand.js +14 -14
  179. package/dist/src/ui/commands/mcpCommand.js.map +1 -1
  180. package/dist/src/ui/commands/mcpCommand.test.js +4 -0
  181. package/dist/src/ui/commands/mcpCommand.test.js.map +1 -1
  182. package/dist/src/ui/commands/memoryCommand.js +1 -1
  183. package/dist/src/ui/commands/memoryCommand.js.map +1 -1
  184. package/dist/src/ui/commands/memoryCommand.test.js +3 -1
  185. package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
  186. package/dist/src/ui/commands/policiesCommand.d.ts +7 -0
  187. package/dist/src/ui/commands/policiesCommand.js +59 -0
  188. package/dist/src/ui/commands/policiesCommand.js.map +1 -0
  189. package/dist/src/ui/commands/policiesCommand.test.d.ts +6 -0
  190. package/dist/src/ui/commands/policiesCommand.test.js +83 -0
  191. package/dist/src/ui/commands/policiesCommand.test.js.map +1 -0
  192. package/dist/src/ui/components/AnsiOutput.test.js +1 -1
  193. package/dist/src/ui/components/AnsiOutput.test.js.map +1 -1
  194. package/dist/src/ui/components/AsciiArt.d.ts +3 -3
  195. package/dist/src/ui/components/AsciiArt.js +3 -3
  196. package/dist/src/ui/components/Composer.js +1 -1
  197. package/dist/src/ui/components/Composer.js.map +1 -1
  198. package/dist/src/ui/components/Composer.test.js +5 -2
  199. package/dist/src/ui/components/Composer.test.js.map +1 -1
  200. package/dist/src/ui/components/ConfigInitDisplay.js +4 -6
  201. package/dist/src/ui/components/ConfigInitDisplay.js.map +1 -1
  202. package/dist/src/ui/components/ConsentPrompt.test.js +18 -8
  203. package/dist/src/ui/components/ConsentPrompt.test.js.map +1 -1
  204. package/dist/src/ui/components/ConsoleSummaryDisplay.js +1 -1
  205. package/dist/src/ui/components/ConsoleSummaryDisplay.js.map +1 -1
  206. package/dist/src/ui/components/ContextSummaryDisplay.test.js +11 -6
  207. package/dist/src/ui/components/ContextSummaryDisplay.test.js.map +1 -1
  208. package/dist/src/ui/components/DetailedMessagesDisplay.js +1 -1
  209. package/dist/src/ui/components/DetailedMessagesDisplay.js.map +1 -1
  210. package/dist/src/ui/components/DialogManager.js +4 -0
  211. package/dist/src/ui/components/DialogManager.js.map +1 -1
  212. package/dist/src/ui/components/FolderTrustDialog.test.js +2 -1
  213. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  214. package/dist/src/ui/components/Footer.js +4 -3
  215. package/dist/src/ui/components/Footer.js.map +1 -1
  216. package/dist/src/ui/components/Footer.test.js +83 -0
  217. package/dist/src/ui/components/Footer.test.js.map +1 -1
  218. package/dist/src/ui/components/Header.test.js +13 -5
  219. package/dist/src/ui/components/Header.test.js.map +1 -1
  220. package/dist/src/ui/components/Help.test.js +5 -4
  221. package/dist/src/ui/components/Help.test.js.map +1 -1
  222. package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
  223. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  224. package/dist/src/ui/components/InputPrompt.js +27 -8
  225. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  226. package/dist/src/ui/components/InputPrompt.test.js +776 -727
  227. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  228. package/dist/src/ui/components/LoadingIndicator.js +2 -2
  229. package/dist/src/ui/components/LoadingIndicator.js.map +1 -1
  230. package/dist/src/ui/components/LoadingIndicator.test.js +28 -15
  231. package/dist/src/ui/components/LoadingIndicator.test.js.map +1 -1
  232. package/dist/src/ui/components/LoopDetectionConfirmation.js +1 -1
  233. package/dist/src/ui/components/LoopDetectionConfirmation.js.map +1 -1
  234. package/dist/src/ui/components/LoopDetectionConfirmation.test.js +2 -2
  235. package/dist/src/ui/components/LoopDetectionConfirmation.test.js.map +1 -1
  236. package/dist/src/ui/components/MainContent.js +15 -4
  237. package/dist/src/ui/components/MainContent.js.map +1 -1
  238. package/dist/src/ui/components/ModelDialog.js +1 -1
  239. package/dist/src/ui/components/ModelDialog.js.map +1 -1
  240. package/dist/src/ui/components/ModelDialog.test.js +23 -13
  241. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  242. package/dist/src/ui/components/ModelStatsDisplay.test.js +1 -1
  243. package/dist/src/ui/components/ModelStatsDisplay.test.js.map +1 -1
  244. package/dist/src/ui/components/Notifications.js +38 -5
  245. package/dist/src/ui/components/Notifications.js.map +1 -1
  246. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +2 -2
  247. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
  248. package/dist/src/ui/components/PrepareLabel.test.js +14 -8
  249. package/dist/src/ui/components/PrepareLabel.test.js.map +1 -1
  250. package/dist/src/ui/components/ProQuotaDialog.test.js +14 -6
  251. package/dist/src/ui/components/ProQuotaDialog.test.js.map +1 -1
  252. package/dist/src/ui/components/QueuedMessageDisplay.test.js +11 -6
  253. package/dist/src/ui/components/QueuedMessageDisplay.test.js.map +1 -1
  254. package/dist/src/ui/components/SessionSummaryDisplay.test.js +1 -1
  255. package/dist/src/ui/components/SessionSummaryDisplay.test.js.map +1 -1
  256. package/dist/src/ui/components/SettingsDialog.js +32 -25
  257. package/dist/src/ui/components/SettingsDialog.js.map +1 -1
  258. package/dist/src/ui/components/SettingsDialog.test.js +428 -532
  259. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  260. package/dist/src/ui/components/ShellConfirmationDialog.js +1 -1
  261. package/dist/src/ui/components/ShellConfirmationDialog.js.map +1 -1
  262. package/dist/src/ui/components/ShellConfirmationDialog.test.js +2 -2
  263. package/dist/src/ui/components/ShellConfirmationDialog.test.js.map +1 -1
  264. package/dist/src/ui/components/StatsDisplay.test.js +1 -1
  265. package/dist/src/ui/components/StatsDisplay.test.js.map +1 -1
  266. package/dist/src/ui/components/SuggestionsDisplay.js +1 -1
  267. package/dist/src/ui/components/SuggestionsDisplay.js.map +1 -1
  268. package/dist/src/ui/components/ThemeDialog.test.js +2 -2
  269. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  270. package/dist/src/ui/components/ToolStatsDisplay.test.js +1 -1
  271. package/dist/src/ui/components/ToolStatsDisplay.test.js.map +1 -1
  272. package/dist/src/ui/components/messages/CompressionMessage.test.js +25 -17
  273. package/dist/src/ui/components/messages/CompressionMessage.test.js.map +1 -1
  274. package/dist/src/ui/components/messages/DiffRenderer.test.js +1 -1
  275. package/dist/src/ui/components/messages/DiffRenderer.test.js.map +1 -1
  276. package/dist/src/ui/components/messages/InfoMessage.js +1 -1
  277. package/dist/src/ui/components/messages/InfoMessage.js.map +1 -1
  278. package/dist/src/ui/components/messages/Todo.js +27 -5
  279. package/dist/src/ui/components/messages/Todo.js.map +1 -1
  280. package/dist/src/ui/components/messages/Todo.test.js +20 -8
  281. package/dist/src/ui/components/messages/Todo.test.js.map +1 -1
  282. package/dist/src/ui/components/messages/ToolConfirmationMessage.js +1 -1
  283. package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
  284. package/dist/src/ui/components/messages/ToolGroupMessage.test.js +29 -15
  285. package/dist/src/ui/components/messages/ToolGroupMessage.test.js.map +1 -1
  286. package/dist/src/ui/components/messages/WarningMessage.js +2 -2
  287. package/dist/src/ui/components/messages/WarningMessage.js.map +1 -1
  288. package/dist/src/ui/components/shared/BaseSelectionList.test.js +1 -1
  289. package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
  290. package/dist/src/ui/components/shared/MaxSizedBox.test.js +43 -22
  291. package/dist/src/ui/components/shared/MaxSizedBox.test.js.map +1 -1
  292. package/dist/src/ui/components/shared/TextInput.d.ts +15 -0
  293. package/dist/src/ui/components/shared/TextInput.js +38 -0
  294. package/dist/src/ui/components/shared/TextInput.js.map +1 -0
  295. package/dist/src/ui/components/shared/TextInput.test.d.ts +6 -0
  296. package/dist/src/ui/components/shared/TextInput.test.js +242 -0
  297. package/dist/src/ui/components/shared/TextInput.test.js.map +1 -0
  298. package/dist/src/ui/components/shared/text-buffer.d.ts +9 -2
  299. package/dist/src/ui/components/shared/text-buffer.js +51 -13
  300. package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
  301. package/dist/src/ui/components/shared/text-buffer.test.js +385 -202
  302. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
  303. package/dist/src/ui/components/views/ChatList.test.js +7 -4
  304. package/dist/src/ui/components/views/ChatList.test.js.map +1 -1
  305. package/dist/src/ui/components/views/ExtensionsList.d.ts +7 -1
  306. package/dist/src/ui/components/views/ExtensionsList.js +9 -11
  307. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  308. package/dist/src/ui/components/views/ExtensionsList.test.js +43 -22
  309. package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
  310. package/dist/src/ui/components/views/McpStatus.test.js +23 -12
  311. package/dist/src/ui/components/views/McpStatus.test.js.map +1 -1
  312. package/dist/src/ui/contexts/KeypressContext.d.ts +3 -2
  313. package/dist/src/ui/contexts/KeypressContext.js +610 -540
  314. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  315. package/dist/src/ui/contexts/KeypressContext.test.js +438 -718
  316. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  317. package/dist/src/ui/contexts/MouseContext.d.ts +21 -0
  318. package/dist/src/ui/contexts/MouseContext.js +89 -0
  319. package/dist/src/ui/contexts/MouseContext.js.map +1 -0
  320. package/dist/src/ui/contexts/MouseContext.test.d.ts +6 -0
  321. package/dist/src/ui/contexts/MouseContext.test.js +164 -0
  322. package/dist/src/ui/contexts/MouseContext.test.js.map +1 -0
  323. package/dist/src/ui/contexts/SessionContext.test.js +35 -17
  324. package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
  325. package/dist/src/ui/contexts/UIActionsContext.d.ts +2 -0
  326. package/dist/src/ui/contexts/UIActionsContext.js.map +1 -1
  327. package/dist/src/ui/contexts/UIStateContext.d.ts +2 -0
  328. package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
  329. package/dist/src/ui/hooks/atCommandProcessor.js +31 -9
  330. package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
  331. package/dist/src/ui/hooks/atCommandProcessor.test.js +163 -64
  332. package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -1
  333. package/dist/src/ui/hooks/shellCommandProcessor.test.js +64 -35
  334. package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
  335. package/dist/src/ui/hooks/slashCommandProcessor.test.js +193 -165
  336. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  337. package/dist/src/ui/hooks/useAtCompletion.test.js +16 -5
  338. package/dist/src/ui/hooks/useAtCompletion.test.js.map +1 -1
  339. package/dist/src/ui/hooks/useAutoAcceptIndicator.js +10 -0
  340. package/dist/src/ui/hooks/useAutoAcceptIndicator.js.map +1 -1
  341. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +32 -1
  342. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
  343. package/dist/src/ui/hooks/useCommandCompletion.test.js +66 -64
  344. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  345. package/dist/src/ui/hooks/useConsoleMessages.test.js +26 -9
  346. package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
  347. package/dist/src/ui/hooks/useEditorSettings.test.js +40 -34
  348. package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
  349. package/dist/src/ui/hooks/useExtensionUpdates.d.ts +14 -5
  350. package/dist/src/ui/hooks/useExtensionUpdates.js +18 -13
  351. package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
  352. package/dist/src/ui/hooks/useExtensionUpdates.test.js +49 -44
  353. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  354. package/dist/src/ui/hooks/useFlickerDetector.test.js +9 -5
  355. package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
  356. package/dist/src/ui/hooks/useFocus.test.js +25 -9
  357. package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
  358. package/dist/src/ui/hooks/useFolderTrust.test.js +46 -22
  359. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
  360. package/dist/src/ui/hooks/useGeminiStream.js +56 -19
  361. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  362. package/dist/src/ui/hooks/useGeminiStream.test.js +260 -411
  363. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  364. package/dist/src/ui/hooks/useGitBranchName.js +4 -0
  365. package/dist/src/ui/hooks/useGitBranchName.js.map +1 -1
  366. package/dist/src/ui/hooks/useGitBranchName.test.js +46 -34
  367. package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
  368. package/dist/src/ui/hooks/useHistoryManager.test.js +2 -1
  369. package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
  370. package/dist/src/ui/hooks/useIdeTrustListener.test.js +40 -9
  371. package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
  372. package/dist/src/ui/hooks/useInputHistory.test.js +2 -1
  373. package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
  374. package/dist/src/ui/hooks/useInputHistoryStore.test.js +2 -1
  375. package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
  376. package/dist/src/ui/hooks/useKeypress.test.js +103 -114
  377. package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
  378. package/dist/src/ui/hooks/useLoadingIndicator.test.js +24 -6
  379. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  380. package/dist/src/ui/hooks/useMemoryMonitor.test.js +10 -5
  381. package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
  382. package/dist/src/ui/hooks/useMessageQueue.test.js +62 -45
  383. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  384. package/dist/src/ui/hooks/useModelCommand.test.js +21 -11
  385. package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
  386. package/dist/src/ui/hooks/useMouse.d.ts +17 -0
  387. package/dist/src/ui/hooks/useMouse.js +27 -0
  388. package/dist/src/ui/hooks/useMouse.js.map +1 -0
  389. package/dist/src/ui/hooks/useMouse.test.d.ts +6 -0
  390. package/dist/src/ui/hooks/useMouse.test.js +57 -0
  391. package/dist/src/ui/hooks/useMouse.test.js.map +1 -0
  392. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +2 -2
  393. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
  394. package/dist/src/ui/hooks/usePhraseCycler.js +1 -1
  395. package/dist/src/ui/hooks/usePhraseCycler.js.map +1 -1
  396. package/dist/src/ui/hooks/usePhraseCycler.test.js +109 -106
  397. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
  398. package/dist/src/ui/hooks/usePrivacySettings.test.js +26 -6
  399. package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
  400. package/dist/src/ui/hooks/usePromptCompletion.js +2 -2
  401. package/dist/src/ui/hooks/usePromptCompletion.js.map +1 -1
  402. package/dist/src/ui/hooks/useQuotaAndFallback.js +13 -14
  403. package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
  404. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +55 -48
  405. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  406. package/dist/src/ui/hooks/useReactToolScheduler.d.ts +8 -1
  407. package/dist/src/ui/hooks/useReactToolScheduler.js +59 -34
  408. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  409. package/dist/src/ui/hooks/useReactToolScheduler.test.d.ts +6 -0
  410. package/dist/src/ui/hooks/useReactToolScheduler.test.js +65 -0
  411. package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -0
  412. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +2 -2
  413. package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -1
  414. package/dist/src/ui/hooks/useSelectionList.js +5 -4
  415. package/dist/src/ui/hooks/useSelectionList.js.map +1 -1
  416. package/dist/src/ui/hooks/useSelectionList.test.js +272 -183
  417. package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
  418. package/dist/src/ui/hooks/useShellHistory.test.js +52 -20
  419. package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
  420. package/dist/src/ui/hooks/useSlashCompletion.js +18 -7
  421. package/dist/src/ui/hooks/useSlashCompletion.js.map +1 -1
  422. package/dist/src/ui/hooks/useSlashCompletion.test.js +275 -137
  423. package/dist/src/ui/hooks/useSlashCompletion.test.js.map +1 -1
  424. package/dist/src/ui/hooks/useTimer.test.js +43 -14
  425. package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
  426. package/dist/src/ui/hooks/useToolScheduler.test.js +226 -242
  427. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  428. package/dist/src/ui/hooks/vim.test.js +235 -355
  429. package/dist/src/ui/hooks/vim.test.js.map +1 -1
  430. package/dist/src/ui/keyMatchers.test.js +30 -3
  431. package/dist/src/ui/keyMatchers.test.js.map +1 -1
  432. package/dist/src/ui/state/extensions.d.ts +1 -0
  433. package/dist/src/ui/state/extensions.js +1 -0
  434. package/dist/src/ui/state/extensions.js.map +1 -1
  435. package/dist/src/ui/themes/ansi-light.js +1 -0
  436. package/dist/src/ui/themes/ansi-light.js.map +1 -1
  437. package/dist/src/ui/themes/ansi.js +1 -0
  438. package/dist/src/ui/themes/ansi.js.map +1 -1
  439. package/dist/src/ui/themes/atom-one-dark.js +2 -0
  440. package/dist/src/ui/themes/atom-one-dark.js.map +1 -1
  441. package/dist/src/ui/themes/ayu-light.js +2 -0
  442. package/dist/src/ui/themes/ayu-light.js.map +1 -1
  443. package/dist/src/ui/themes/ayu.js +2 -0
  444. package/dist/src/ui/themes/ayu.js.map +1 -1
  445. package/dist/src/ui/themes/color-utils.d.ts +1 -0
  446. package/dist/src/ui/themes/color-utils.js +6 -0
  447. package/dist/src/ui/themes/color-utils.js.map +1 -1
  448. package/dist/src/ui/themes/color-utils.test.js +13 -1
  449. package/dist/src/ui/themes/color-utils.test.js.map +1 -1
  450. package/dist/src/ui/themes/dracula.js +2 -0
  451. package/dist/src/ui/themes/dracula.js.map +1 -1
  452. package/dist/src/ui/themes/github-dark.js +2 -0
  453. package/dist/src/ui/themes/github-dark.js.map +1 -1
  454. package/dist/src/ui/themes/github-light.js +2 -0
  455. package/dist/src/ui/themes/github-light.js.map +1 -1
  456. package/dist/src/ui/themes/googlecode.js +2 -0
  457. package/dist/src/ui/themes/googlecode.js.map +1 -1
  458. package/dist/src/ui/themes/no-color.js +3 -0
  459. package/dist/src/ui/themes/no-color.js.map +1 -1
  460. package/dist/src/ui/themes/semantic-tokens.d.ts +2 -0
  461. package/dist/src/ui/themes/semantic-tokens.js +6 -0
  462. package/dist/src/ui/themes/semantic-tokens.js.map +1 -1
  463. package/dist/src/ui/themes/shades-of-purple.js +2 -0
  464. package/dist/src/ui/themes/shades-of-purple.js.map +1 -1
  465. package/dist/src/ui/themes/theme.d.ts +3 -0
  466. package/dist/src/ui/themes/theme.js +14 -3
  467. package/dist/src/ui/themes/theme.js.map +1 -1
  468. package/dist/src/ui/themes/theme.test.js +67 -1
  469. package/dist/src/ui/themes/theme.test.js.map +1 -1
  470. package/dist/src/ui/themes/xcode.js +2 -0
  471. package/dist/src/ui/themes/xcode.js.map +1 -1
  472. package/dist/src/ui/types.d.ts +3 -1
  473. package/dist/src/ui/types.js +2 -0
  474. package/dist/src/ui/types.js.map +1 -1
  475. package/dist/src/ui/utils/CodeColorizer.js +2 -1
  476. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  477. package/dist/src/ui/utils/InlineMarkdownRenderer.d.ts +1 -0
  478. package/dist/src/ui/utils/InlineMarkdownRenderer.js +11 -10
  479. package/dist/src/ui/utils/InlineMarkdownRenderer.js.map +1 -1
  480. package/dist/src/ui/utils/MarkdownDisplay.js +11 -9
  481. package/dist/src/ui/utils/MarkdownDisplay.js.map +1 -1
  482. package/dist/src/ui/utils/clipboardUtils.js +2 -2
  483. package/dist/src/ui/utils/clipboardUtils.js.map +1 -1
  484. package/dist/src/ui/utils/input.d.ts +17 -0
  485. package/dist/src/ui/utils/input.js +51 -0
  486. package/dist/src/ui/utils/input.js.map +1 -0
  487. package/dist/src/ui/utils/input.test.d.ts +6 -0
  488. package/dist/src/ui/utils/input.test.js +44 -0
  489. package/dist/src/ui/utils/input.test.js.map +1 -0
  490. package/dist/src/ui/utils/kittyProtocolDetector.js +13 -4
  491. package/dist/src/ui/utils/kittyProtocolDetector.js.map +1 -1
  492. package/dist/src/ui/utils/mouse.d.ts +31 -0
  493. package/dist/src/ui/utils/mouse.js +164 -0
  494. package/dist/src/ui/utils/mouse.js.map +1 -0
  495. package/dist/src/ui/utils/mouse.test.d.ts +6 -0
  496. package/dist/src/ui/utils/mouse.test.js +131 -0
  497. package/dist/src/ui/utils/mouse.test.js.map +1 -0
  498. package/dist/src/ui/utils/textOutput.d.ts +25 -0
  499. package/dist/src/ui/utils/textOutput.js +49 -0
  500. package/dist/src/ui/utils/textOutput.js.map +1 -0
  501. package/dist/src/ui/utils/textOutput.test.d.ts +6 -0
  502. package/dist/src/ui/utils/textOutput.test.js +79 -0
  503. package/dist/src/ui/utils/textOutput.test.js.map +1 -0
  504. package/dist/src/ui/utils/updateCheck.d.ts +7 -1
  505. package/dist/src/ui/utils/updateCheck.js +33 -29
  506. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  507. package/dist/src/ui/utils/updateCheck.test.js +24 -50
  508. package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
  509. package/dist/src/utils/commentJson.js +2 -2
  510. package/dist/src/utils/commentJson.js.map +1 -1
  511. package/dist/src/utils/commentJson.test.js +7 -6
  512. package/dist/src/utils/commentJson.test.js.map +1 -1
  513. package/dist/src/utils/envVarResolver.d.ts +2 -2
  514. package/dist/src/utils/envVarResolver.js +10 -7
  515. package/dist/src/utils/envVarResolver.js.map +1 -1
  516. package/dist/src/utils/events.d.ts +11 -2
  517. package/dist/src/utils/events.js +1 -0
  518. package/dist/src/utils/events.js.map +1 -1
  519. package/dist/src/utils/handleAutoUpdate.js +9 -3
  520. package/dist/src/utils/handleAutoUpdate.js.map +1 -1
  521. package/dist/src/utils/sandbox.js +16 -18
  522. package/dist/src/utils/sandbox.js.map +1 -1
  523. package/dist/src/utils/version.js +6 -2
  524. package/dist/src/utils/version.js.map +1 -1
  525. package/dist/src/zed-integration/acp.js +2 -1
  526. package/dist/src/zed-integration/acp.js.map +1 -1
  527. package/dist/src/zed-integration/schema.d.ts +4 -4
  528. package/dist/src/zed-integration/zedIntegration.d.ts +2 -2
  529. package/dist/src/zed-integration/zedIntegration.js +12 -19
  530. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  531. package/dist/tsconfig.tsbuildinfo +1 -1
  532. package/package.json +18 -14
  533. package/dist/src/config/policy.test.js +0 -360
  534. package/dist/src/config/policy.test.js.map +0 -1
  535. package/dist/src/utils/package.d.ts +0 -12
  536. package/dist/src/utils/package.js +0 -24
  537. package/dist/src/utils/package.js.map +0 -1
  538. /package/dist/src/{config/policy.test.d.ts → commands/extensions/validate.test.d.ts} +0 -0
@@ -5,7 +5,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
5
5
  * SPDX-License-Identifier: Apache-2.0
6
6
  */
7
7
  import { renderWithProviders } from '../../test-utils/render.js';
8
- import { waitFor, act } from '@testing-library/react';
8
+ import { waitFor } from '../../test-utils/async.js';
9
+ import { act } from 'react';
9
10
  import { InputPrompt } from './InputPrompt.js';
10
11
  import { ApprovalMode } from '@google/gemini-cli-core';
11
12
  import * as path from 'node:path';
@@ -112,6 +113,7 @@ describe('InputPrompt', () => {
112
113
  moveToOffset: vi.fn((offset) => {
113
114
  mockBuffer.cursor = [0, offset];
114
115
  }),
116
+ moveToVisualPosition: vi.fn(),
115
117
  killLineRight: vi.fn(),
116
118
  killLineLeft: vi.fn(),
117
119
  openInExternalEditor: vi.fn(),
@@ -207,63 +209,68 @@ describe('InputPrompt', () => {
207
209
  streamingState: StreamingState.Idle,
208
210
  };
209
211
  });
210
- const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms));
211
212
  it('should call shellHistory.getPreviousCommand on up arrow in shell mode', async () => {
212
213
  props.shellModeActive = true;
213
214
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
214
- await wait();
215
- stdin.write('\u001B[A');
216
- await wait();
217
- expect(mockShellHistory.getPreviousCommand).toHaveBeenCalled();
215
+ await act(async () => {
216
+ stdin.write('\u001B[A');
217
+ });
218
+ await waitFor(() => expect(mockShellHistory.getPreviousCommand).toHaveBeenCalled());
218
219
  unmount();
219
220
  });
220
221
  it('should call shellHistory.getNextCommand on down arrow in shell mode', async () => {
221
222
  props.shellModeActive = true;
222
223
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
223
- await wait();
224
- stdin.write('\u001B[B');
225
- await wait();
226
- expect(mockShellHistory.getNextCommand).toHaveBeenCalled();
224
+ await act(async () => {
225
+ stdin.write('\u001B[B');
226
+ await waitFor(() => expect(mockShellHistory.getNextCommand).toHaveBeenCalled());
227
+ });
227
228
  unmount();
228
229
  });
229
230
  it('should set the buffer text when a shell history command is retrieved', async () => {
230
231
  props.shellModeActive = true;
231
232
  vi.mocked(mockShellHistory.getPreviousCommand).mockReturnValue('previous command');
232
233
  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');
234
+ await act(async () => {
235
+ stdin.write('\u001B[A');
236
+ });
237
+ await waitFor(() => {
238
+ expect(mockShellHistory.getPreviousCommand).toHaveBeenCalled();
239
+ expect(props.buffer.setText).toHaveBeenCalledWith('previous command');
240
+ });
238
241
  unmount();
239
242
  });
240
243
  it('should call shellHistory.addCommandToHistory on submit in shell mode', async () => {
241
244
  props.shellModeActive = true;
242
245
  props.buffer.setText('ls -l');
243
246
  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');
247
+ await act(async () => {
248
+ stdin.write('\r');
249
+ });
250
+ await waitFor(() => {
251
+ expect(mockShellHistory.addCommandToHistory).toHaveBeenCalledWith('ls -l');
252
+ expect(props.onSubmit).toHaveBeenCalledWith('ls -l');
253
+ });
249
254
  unmount();
250
255
  });
251
256
  it('should NOT call shell history methods when not in shell mode', async () => {
252
257
  props.buffer.setText('some text');
253
258
  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();
259
+ await act(async () => {
260
+ stdin.write('\u001B[A'); // Up arrow
261
+ });
262
+ await waitFor(() => expect(mockInputHistory.navigateUp).toHaveBeenCalled());
263
+ await act(async () => {
264
+ stdin.write('\u001B[B'); // Down arrow
265
+ });
266
+ await waitFor(() => expect(mockInputHistory.navigateDown).toHaveBeenCalled());
267
+ await act(async () => {
268
+ stdin.write('\r'); // Enter
269
+ });
270
+ await waitFor(() => expect(props.onSubmit).toHaveBeenCalledWith('some text'));
261
271
  expect(mockShellHistory.getPreviousCommand).not.toHaveBeenCalled();
262
272
  expect(mockShellHistory.getNextCommand).not.toHaveBeenCalled();
263
273
  expect(mockShellHistory.addCommandToHistory).not.toHaveBeenCalled();
264
- expect(mockInputHistory.navigateUp).toHaveBeenCalled();
265
- expect(mockInputHistory.navigateDown).toHaveBeenCalled();
266
- expect(props.onSubmit).toHaveBeenCalledWith('some text');
267
274
  unmount();
268
275
  });
269
276
  it('should call completion.navigateUp for both up arrow and Ctrl+P when suggestions are showing', async () => {
@@ -277,13 +284,15 @@ describe('InputPrompt', () => {
277
284
  });
278
285
  props.buffer.setText('/mem');
279
286
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
280
- await wait();
281
287
  // 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);
288
+ await act(async () => {
289
+ stdin.write('\u001B[A'); // Up arrow
290
+ });
291
+ await waitFor(() => expect(mockCommandCompletion.navigateUp).toHaveBeenCalledTimes(1));
292
+ await act(async () => {
293
+ stdin.write('\u0010'); // Ctrl+P
294
+ });
295
+ await waitFor(() => expect(mockCommandCompletion.navigateUp).toHaveBeenCalledTimes(2));
287
296
  expect(mockCommandCompletion.navigateDown).not.toHaveBeenCalled();
288
297
  unmount();
289
298
  });
@@ -298,13 +307,15 @@ describe('InputPrompt', () => {
298
307
  });
299
308
  props.buffer.setText('/mem');
300
309
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
301
- await wait();
302
310
  // 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);
311
+ await act(async () => {
312
+ stdin.write('\u001B[B'); // Down arrow
313
+ });
314
+ await waitFor(() => expect(mockCommandCompletion.navigateDown).toHaveBeenCalledTimes(1));
315
+ await act(async () => {
316
+ stdin.write('\u000E'); // Ctrl+N
317
+ });
318
+ await waitFor(() => expect(mockCommandCompletion.navigateDown).toHaveBeenCalledTimes(2));
308
319
  expect(mockCommandCompletion.navigateUp).not.toHaveBeenCalled();
309
320
  unmount();
310
321
  });
@@ -315,17 +326,24 @@ describe('InputPrompt', () => {
315
326
  });
316
327
  props.buffer.setText('some text');
317
328
  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
+ await act(async () => {
330
+ stdin.write('\u001B[A'); // Up arrow
331
+ });
332
+ await waitFor(() => expect(mockInputHistory.navigateUp).toHaveBeenCalled());
333
+ await act(async () => {
334
+ stdin.write('\u001B[B'); // Down arrow
335
+ });
336
+ await waitFor(() => expect(mockInputHistory.navigateDown).toHaveBeenCalled());
337
+ await act(async () => {
338
+ stdin.write('\u0010'); // Ctrl+P
339
+ });
340
+ await act(async () => {
341
+ stdin.write('\u000E'); // Ctrl+N
342
+ });
343
+ await waitFor(() => {
344
+ expect(mockCommandCompletion.navigateUp).not.toHaveBeenCalled();
345
+ expect(mockCommandCompletion.navigateDown).not.toHaveBeenCalled();
346
+ });
329
347
  unmount();
330
348
  });
331
349
  describe('clipboard image paste', () => {
@@ -338,23 +356,27 @@ describe('InputPrompt', () => {
338
356
  vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true);
339
357
  vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue('/test/.cell-cli-clipboard/clipboard-123.png');
340
358
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
341
- await wait();
342
359
  // 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();
360
+ await act(async () => {
361
+ stdin.write('\x16'); // Ctrl+V
362
+ });
363
+ await waitFor(() => {
364
+ expect(clipboardUtils.clipboardHasImage).toHaveBeenCalled();
365
+ expect(clipboardUtils.saveClipboardImage).toHaveBeenCalledWith(props.config.getTargetDir());
366
+ expect(clipboardUtils.cleanupOldClipboardImages).toHaveBeenCalledWith(props.config.getTargetDir());
367
+ expect(mockBuffer.replaceRangeByOffset).toHaveBeenCalled();
368
+ });
349
369
  unmount();
350
370
  });
351
371
  it('should not insert anything when clipboard has no image', async () => {
352
372
  vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(false);
353
373
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
354
- await wait();
355
- stdin.write('\x16'); // Ctrl+V
356
- await wait();
357
- expect(clipboardUtils.clipboardHasImage).toHaveBeenCalled();
374
+ await act(async () => {
375
+ stdin.write('\x16'); // Ctrl+V
376
+ });
377
+ await waitFor(() => {
378
+ expect(clipboardUtils.clipboardHasImage).toHaveBeenCalled();
379
+ });
358
380
  expect(clipboardUtils.saveClipboardImage).not.toHaveBeenCalled();
359
381
  expect(mockBuffer.setText).not.toHaveBeenCalled();
360
382
  unmount();
@@ -363,10 +385,12 @@ describe('InputPrompt', () => {
363
385
  vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true);
364
386
  vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue(null);
365
387
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
366
- await wait();
367
- stdin.write('\x16'); // Ctrl+V
368
- await wait();
369
- expect(clipboardUtils.saveClipboardImage).toHaveBeenCalled();
388
+ await act(async () => {
389
+ stdin.write('\x16'); // Ctrl+V
390
+ });
391
+ await waitFor(() => {
392
+ expect(clipboardUtils.saveClipboardImage).toHaveBeenCalled();
393
+ });
370
394
  expect(mockBuffer.setText).not.toHaveBeenCalled();
371
395
  unmount();
372
396
  });
@@ -380,11 +404,13 @@ describe('InputPrompt', () => {
380
404
  mockBuffer.lines = ['Hello world'];
381
405
  mockBuffer.replaceRangeByOffset = vi.fn();
382
406
  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();
407
+ await act(async () => {
408
+ stdin.write('\x16'); // Ctrl+V
409
+ });
410
+ await waitFor(() => {
411
+ // Should insert at cursor position with spaces
412
+ expect(mockBuffer.replaceRangeByOffset).toHaveBeenCalled();
413
+ });
388
414
  // Get the actual call to see what path was used
389
415
  const actualCall = vi.mocked(mockBuffer.replaceRangeByOffset).mock
390
416
  .calls[0];
@@ -399,10 +425,12 @@ describe('InputPrompt', () => {
399
425
  .mockImplementation(() => { });
400
426
  vi.mocked(clipboardUtils.clipboardHasImage).mockRejectedValue(new Error('Clipboard error'));
401
427
  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));
428
+ await act(async () => {
429
+ stdin.write('\x16'); // Ctrl+V
430
+ });
431
+ await waitFor(() => {
432
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Error handling clipboard image:', expect.any(Error));
433
+ });
406
434
  expect(mockBuffer.setText).not.toHaveBeenCalled();
407
435
  consoleErrorSpy.mockRestore();
408
436
  unmount();
@@ -418,10 +446,10 @@ describe('InputPrompt', () => {
418
446
  });
419
447
  props.buffer.setText('/mem');
420
448
  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);
449
+ await act(async () => {
450
+ stdin.write('\t'); // Press Tab
451
+ });
452
+ await waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
425
453
  unmount();
426
454
  });
427
455
  it('should append a sub-command when the parent command is already complete', async () => {
@@ -437,10 +465,10 @@ describe('InputPrompt', () => {
437
465
  });
438
466
  props.buffer.setText('/memory ');
439
467
  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);
468
+ await act(async () => {
469
+ stdin.write('\t'); // Press Tab
470
+ });
471
+ await waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(1));
444
472
  unmount();
445
473
  });
446
474
  it('should handle the "backspace" edge case correctly', async () => {
@@ -457,11 +485,12 @@ describe('InputPrompt', () => {
457
485
  // The user has backspaced, so the query is now just '/memory'
458
486
  props.buffer.setText('/memory');
459
487
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
460
- await wait();
461
- stdin.write('\t'); // Press Tab
462
- await wait();
488
+ await act(async () => {
489
+ stdin.write('\t'); // Press Tab
490
+ });
491
+ await waitFor(() =>
463
492
  // It should NOT become '/show'. It should correctly become '/memory show'.
464
- expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
493
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
465
494
  unmount();
466
495
  });
467
496
  it('should complete a partial argument for a command', async () => {
@@ -474,10 +503,10 @@ describe('InputPrompt', () => {
474
503
  });
475
504
  props.buffer.setText('/chat resume fi-');
476
505
  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);
506
+ await act(async () => {
507
+ stdin.write('\t'); // Press Tab
508
+ });
509
+ await waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
481
510
  unmount();
482
511
  });
483
512
  it('should autocomplete on Enter when suggestions are active, without submitting', async () => {
@@ -489,11 +518,13 @@ describe('InputPrompt', () => {
489
518
  });
490
519
  props.buffer.setText('/mem');
491
520
  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);
521
+ await act(async () => {
522
+ stdin.write('\r');
523
+ });
524
+ await waitFor(() => {
525
+ // The app should autocomplete the text, NOT submit.
526
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
527
+ });
497
528
  expect(props.onSubmit).not.toHaveBeenCalled();
498
529
  unmount();
499
530
  });
@@ -514,19 +545,21 @@ describe('InputPrompt', () => {
514
545
  });
515
546
  props.buffer.setText('/?');
516
547
  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);
548
+ await act(async () => {
549
+ stdin.write('\t'); // Press Tab for autocomplete
550
+ });
551
+ await waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
521
552
  unmount();
522
553
  });
523
554
  it('should not submit on Enter when the buffer is empty or only contains whitespace', async () => {
524
555
  props.buffer.setText(' '); // Set buffer to whitespace
525
556
  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();
557
+ await act(async () => {
558
+ stdin.write('\r'); // Press Enter
559
+ });
560
+ await waitFor(() => {
561
+ expect(props.onSubmit).not.toHaveBeenCalled();
562
+ });
530
563
  unmount();
531
564
  });
532
565
  it('should submit directly on Enter when isPerfectMatch is true', async () => {
@@ -537,10 +570,10 @@ describe('InputPrompt', () => {
537
570
  });
538
571
  props.buffer.setText('/clear');
539
572
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
540
- await wait();
541
- stdin.write('\r');
542
- await wait();
543
- expect(props.onSubmit).toHaveBeenCalledWith('/clear');
573
+ await act(async () => {
574
+ stdin.write('\r');
575
+ });
576
+ await waitFor(() => expect(props.onSubmit).toHaveBeenCalledWith('/clear'));
544
577
  unmount();
545
578
  });
546
579
  it('should submit directly on Enter when a complete leaf command is typed', async () => {
@@ -551,10 +584,10 @@ describe('InputPrompt', () => {
551
584
  });
552
585
  props.buffer.setText('/clear');
553
586
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
554
- await wait();
555
- stdin.write('\r');
556
- await wait();
557
- expect(props.onSubmit).toHaveBeenCalledWith('/clear');
587
+ await act(async () => {
588
+ stdin.write('\r');
589
+ });
590
+ await waitFor(() => expect(props.onSubmit).toHaveBeenCalledWith('/clear'));
558
591
  unmount();
559
592
  });
560
593
  it('should autocomplete an @-path on Enter without submitting', async () => {
@@ -566,10 +599,10 @@ describe('InputPrompt', () => {
566
599
  });
567
600
  props.buffer.setText('@src/components/');
568
601
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
569
- await wait();
570
- stdin.write('\r');
571
- await wait();
572
- expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0);
602
+ await act(async () => {
603
+ stdin.write('\r');
604
+ });
605
+ await waitFor(() => expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0));
573
606
  expect(props.onSubmit).not.toHaveBeenCalled();
574
607
  unmount();
575
608
  });
@@ -579,243 +612,137 @@ describe('InputPrompt', () => {
579
612
  mockBuffer.cursor = [0, 11];
580
613
  mockBuffer.lines = ['first line\\'];
581
614
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
582
- await wait();
583
- stdin.write('\r');
584
- await wait();
615
+ await act(async () => {
616
+ stdin.write('\r');
617
+ });
618
+ await waitFor(() => {
619
+ expect(props.buffer.backspace).toHaveBeenCalled();
620
+ expect(props.buffer.newline).toHaveBeenCalled();
621
+ });
585
622
  expect(props.onSubmit).not.toHaveBeenCalled();
586
- expect(props.buffer.backspace).toHaveBeenCalled();
587
- expect(props.buffer.newline).toHaveBeenCalled();
588
623
  unmount();
589
624
  });
590
625
  it('should clear the buffer on Ctrl+C if it has text', async () => {
591
- props.buffer.setText('some text to clear');
626
+ await act(async () => {
627
+ props.buffer.setText('some text to clear');
628
+ });
592
629
  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();
630
+ await act(async () => {
631
+ stdin.write('\x03'); // Ctrl+C character
632
+ });
633
+ await waitFor(() => {
634
+ expect(props.buffer.setText).toHaveBeenCalledWith('');
635
+ expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
636
+ });
598
637
  expect(props.onSubmit).not.toHaveBeenCalled();
599
638
  unmount();
600
639
  });
601
640
  it('should NOT clear the buffer on Ctrl+C if it is empty', async () => {
602
641
  props.buffer.text = '';
603
642
  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();
643
+ await act(async () => {
644
+ stdin.write('\x03'); // Ctrl+C character
645
+ });
646
+ await waitFor(() => {
647
+ expect(props.buffer.setText).not.toHaveBeenCalled();
648
+ });
608
649
  unmount();
609
650
  });
610
651
  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,
652
+ it.each([
653
+ {
654
+ name: 'should trigger completion when cursor is after @ without spaces',
655
+ text: '@src/components',
656
+ cursor: [0, 15],
618
657
  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,
658
+ },
659
+ {
660
+ name: 'should trigger completion when cursor is after / without spaces',
661
+ text: '/memory',
662
+ cursor: [0, 7],
633
663
  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,
664
+ },
665
+ {
666
+ name: 'should NOT trigger completion when cursor is after space following @',
667
+ text: '@src/file.ts hello',
668
+ cursor: [0, 18],
647
669
  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,
670
+ },
671
+ {
672
+ name: 'should NOT trigger completion when cursor is after space following /',
673
+ text: '/memory add',
674
+ cursor: [0, 11],
661
675
  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,
676
+ },
677
+ {
678
+ name: 'should NOT trigger completion when cursor is not after @ or /',
679
+ text: 'hello world',
680
+ cursor: [0, 5],
675
681
  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,
682
+ },
683
+ {
684
+ name: 'should handle multiline text correctly',
685
+ text: 'first line\n/memory',
686
+ cursor: [1, 7],
689
687
  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,
688
+ },
689
+ {
690
+ name: 'should handle Unicode characters (emojis) correctly in paths',
691
+ text: '@src/file👍.txt',
692
+ cursor: [0, 14],
719
693
  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,
694
+ },
695
+ {
696
+ name: 'should handle Unicode characters with spaces after them',
697
+ text: '@src/file👍.txt hello',
698
+ cursor: [0, 20],
734
699
  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,
700
+ },
701
+ {
702
+ name: 'should handle escaped spaces in paths correctly',
703
+ text: '@src/my\\ file.txt',
704
+ cursor: [0, 16],
749
705
  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,
706
+ },
707
+ {
708
+ name: 'should NOT trigger completion after unescaped space following escaped space',
709
+ text: '@path/my\\ file.txt hello',
710
+ cursor: [0, 24],
764
711
  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,
712
+ },
713
+ {
714
+ name: 'should handle multiple escaped spaces in paths',
715
+ text: '@docs/my\\ long\\ file\\ name.md',
716
+ cursor: [0, 29],
779
717
  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,
718
+ },
719
+ {
720
+ name: 'should handle escaped spaces in slash commands',
721
+ text: '/memory\\ test',
722
+ cursor: [0, 13],
796
723
  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
724
+ },
725
+ {
726
+ name: 'should handle Unicode characters with escaped spaces',
727
+ text: `@${path.join('files', 'emoji\\ 👍\\ test.txt')}`,
728
+ cursor: [0, 25],
729
+ showSuggestions: true,
730
+ },
731
+ ])('$name', async ({ text, cursor, showSuggestions }) => {
732
+ mockBuffer.text = text;
733
+ mockBuffer.lines = text.split('\n');
734
+ mockBuffer.cursor = cursor;
809
735
  mockedUseCommandCompletion.mockReturnValue({
810
736
  ...mockCommandCompletion,
811
- showSuggestions: true,
812
- suggestions: [
813
- { label: 'emoji 👍 test.txt', value: 'emoji 👍 test.txt' },
814
- ],
737
+ showSuggestions,
738
+ suggestions: showSuggestions
739
+ ? [{ label: 'suggestion', value: 'suggestion' }]
740
+ : [],
815
741
  });
816
742
  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));
743
+ await waitFor(() => {
744
+ expect(mockedUseCommandCompletion).toHaveBeenCalledWith(mockBuffer, ['/test/project/src'], path.join('test', 'project', 'src'), mockSlashCommands, mockCommandContext, false, false, expect.any(Object));
745
+ });
819
746
  unmount();
820
747
  });
821
748
  });
@@ -823,32 +750,38 @@ describe('InputPrompt', () => {
823
750
  it('should not call buffer.handleInput when vim mode is enabled and vim handles the input', async () => {
824
751
  props.vimHandleInput = vi.fn().mockReturnValue(true); // Mock that vim handled it.
825
752
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
826
- await wait();
827
- stdin.write('i');
828
- await wait();
829
- expect(props.vimHandleInput).toHaveBeenCalled();
753
+ await act(async () => {
754
+ stdin.write('i');
755
+ });
756
+ await waitFor(() => {
757
+ expect(props.vimHandleInput).toHaveBeenCalled();
758
+ });
830
759
  expect(mockBuffer.handleInput).not.toHaveBeenCalled();
831
760
  unmount();
832
761
  });
833
762
  it('should call buffer.handleInput when vim mode is enabled but vim does not handle the input', async () => {
834
763
  props.vimHandleInput = vi.fn().mockReturnValue(false); // Mock that vim did NOT handle it.
835
764
  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();
765
+ await act(async () => {
766
+ stdin.write('i');
767
+ });
768
+ await waitFor(() => {
769
+ expect(props.vimHandleInput).toHaveBeenCalled();
770
+ expect(mockBuffer.handleInput).toHaveBeenCalled();
771
+ });
841
772
  unmount();
842
773
  });
843
774
  it('should call handleInput when vim mode is disabled', async () => {
844
775
  // Mock vimHandleInput to return false (vim didn't handle the input)
845
776
  props.vimHandleInput = vi.fn().mockReturnValue(false);
846
777
  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();
778
+ await act(async () => {
779
+ stdin.write('i');
780
+ });
781
+ await waitFor(() => {
782
+ expect(props.vimHandleInput).toHaveBeenCalled();
783
+ expect(mockBuffer.handleInput).toHaveBeenCalled();
784
+ });
852
785
  unmount();
853
786
  });
854
787
  });
@@ -856,185 +789,158 @@ describe('InputPrompt', () => {
856
789
  it('should handle bracketed paste when not focused', async () => {
857
790
  props.focus = false;
858
791
  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
- }));
792
+ await act(async () => {
793
+ stdin.write('\x1B[200~pasted text\x1B[201~');
794
+ });
795
+ await waitFor(() => {
796
+ expect(mockBuffer.handleInput).toHaveBeenCalledWith(expect.objectContaining({
797
+ paste: true,
798
+ sequence: 'pasted text',
799
+ }));
800
+ });
866
801
  unmount();
867
802
  });
868
803
  it('should ignore regular keypresses when not focused', async () => {
869
804
  props.focus = false;
870
805
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
871
- await wait();
872
- stdin.write('a');
873
- await wait();
806
+ await act(async () => {
807
+ stdin.write('a');
808
+ });
809
+ await waitFor(() => { });
874
810
  expect(mockBuffer.handleInput).not.toHaveBeenCalled();
875
811
  unmount();
876
812
  });
877
813
  });
878
814
  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();
815
+ describe('single-line scenarios', () => {
816
+ it.each([
817
+ {
818
+ name: 'mid-word',
819
+ text: 'hello world',
820
+ visualCursor: [0, 3],
821
+ expected: `hel${chalk.inverse('l')}o world`,
822
+ },
823
+ {
824
+ name: 'at the beginning of the line',
825
+ text: 'hello',
826
+ visualCursor: [0, 0],
827
+ expected: `${chalk.inverse('h')}ello`,
828
+ },
829
+ {
830
+ name: 'at the end of the line',
831
+ text: 'hello',
832
+ visualCursor: [0, 5],
833
+ expected: `hello${chalk.inverse(' ')}`,
834
+ },
835
+ {
836
+ name: 'on a highlighted token',
837
+ text: 'run @path/to/file',
838
+ visualCursor: [0, 9],
839
+ expected: `@path/${chalk.inverse('t')}o/file`,
840
+ },
841
+ {
842
+ name: 'for multi-byte unicode characters',
843
+ text: 'hello 👍 world',
844
+ visualCursor: [0, 6],
845
+ expected: `hello ${chalk.inverse('👍')} world`,
846
+ },
847
+ {
848
+ name: 'at the end of a line with unicode characters',
849
+ text: 'hello 👍',
850
+ visualCursor: [0, 8],
851
+ expected: `hello 👍${chalk.inverse(' ')}`,
852
+ },
853
+ {
854
+ name: 'on an empty line',
855
+ text: '',
856
+ visualCursor: [0, 0],
857
+ expected: chalk.inverse(' '),
858
+ },
859
+ {
860
+ name: 'on a space between words',
861
+ text: 'hello world',
862
+ visualCursor: [0, 5],
863
+ expected: `hello${chalk.inverse(' ')}world`,
864
+ },
865
+ ])('should display cursor correctly $name', async ({ text, visualCursor, expected }) => {
866
+ mockBuffer.text = text;
867
+ mockBuffer.lines = [text];
868
+ mockBuffer.viewportVisualLines = [text];
869
+ mockBuffer.visualCursor = visualCursor;
870
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
871
+ await waitFor(() => {
872
+ const frame = stdout.lastFrame();
873
+ expect(frame).toContain(expected);
874
+ });
875
+ unmount();
876
+ });
1019
877
  });
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();
878
+ describe('multi-line scenarios', () => {
879
+ it.each([
880
+ {
881
+ name: 'in the middle of a line',
882
+ text: 'first line\nsecond line\nthird line',
883
+ visualCursor: [1, 3],
884
+ visualToLogicalMap: [
885
+ [0, 0],
886
+ [1, 0],
887
+ [2, 0],
888
+ ],
889
+ expected: `sec${chalk.inverse('o')}nd line`,
890
+ },
891
+ {
892
+ name: 'at the beginning of a line',
893
+ text: 'first line\nsecond line',
894
+ visualCursor: [1, 0],
895
+ visualToLogicalMap: [
896
+ [0, 0],
897
+ [1, 0],
898
+ ],
899
+ expected: `${chalk.inverse('s')}econd line`,
900
+ },
901
+ {
902
+ name: 'at the end of a line',
903
+ text: 'first line\nsecond line',
904
+ visualCursor: [0, 10],
905
+ visualToLogicalMap: [
906
+ [0, 0],
907
+ [1, 0],
908
+ ],
909
+ expected: `first line${chalk.inverse(' ')}`,
910
+ },
911
+ ])('should display cursor correctly $name in a multiline block', async ({ text, visualCursor, expected, visualToLogicalMap }) => {
912
+ mockBuffer.text = text;
913
+ mockBuffer.lines = text.split('\n');
914
+ mockBuffer.viewportVisualLines = text.split('\n');
915
+ mockBuffer.visualCursor = visualCursor;
916
+ mockBuffer.visualToLogicalMap = visualToLogicalMap;
917
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
918
+ await waitFor(() => {
919
+ const frame = stdout.lastFrame();
920
+ expect(frame).toContain(expected);
921
+ });
922
+ unmount();
923
+ });
924
+ it('should display cursor on a blank line in a multiline block', async () => {
925
+ const text = 'first line\n\nthird line';
926
+ mockBuffer.text = text;
927
+ mockBuffer.lines = text.split('\n');
928
+ mockBuffer.viewportVisualLines = text.split('\n');
929
+ mockBuffer.visualCursor = [1, 0]; // cursor on the blank line
930
+ mockBuffer.visualToLogicalMap = [
931
+ [0, 0],
932
+ [1, 0],
933
+ [2, 0],
934
+ ];
935
+ const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
936
+ await waitFor(() => {
937
+ const frame = stdout.lastFrame();
938
+ const lines = frame.split('\n');
939
+ // The line with the cursor should just be an inverted space inside the box border
940
+ expect(lines.find((l) => l.includes(chalk.inverse(' ')))).not.toBeUndefined();
941
+ });
942
+ unmount();
943
+ });
1038
944
  });
1039
945
  });
1040
946
  describe('multiline rendering', () => {
@@ -1052,15 +958,16 @@ describe('InputPrompt', () => {
1052
958
  [2, 0], // 'world' is logical line 2, col 0
1053
959
  ];
1054
960
  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);
961
+ await waitFor(() => {
962
+ const frame = stdout.lastFrame();
963
+ // Check that all lines, including the empty one, are rendered.
964
+ // This implicitly tests that the Box wrapper provides height for the empty line.
965
+ expect(frame).toContain('hello');
966
+ expect(frame).toContain(`world${chalk.inverse(' ')}`);
967
+ const outputLines = frame.split('\n');
968
+ // The number of lines should be 2 for the border plus 3 for the content.
969
+ expect(outputLines.length).toBe(5);
970
+ });
1064
971
  unmount();
1065
972
  });
1066
973
  });
@@ -1080,16 +987,18 @@ describe('InputPrompt', () => {
1080
987
  },
1081
988
  ])('should handle multiline paste $description', async ({ pastedText }) => {
1082
989
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1083
- await wait();
1084
990
  // 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
- }));
991
+ await act(async () => {
992
+ stdin.write(`\x1b[200~${pastedText}\x1b[201~`);
993
+ });
994
+ await waitFor(() => {
995
+ // Verify that the buffer's handleInput was called once with the full text
996
+ expect(props.buffer.handleInput).toHaveBeenCalledTimes(1);
997
+ expect(props.buffer.handleInput).toHaveBeenCalledWith(expect.objectContaining({
998
+ paste: true,
999
+ sequence: pastedText,
1000
+ }));
1001
+ });
1093
1002
  unmount();
1094
1003
  });
1095
1004
  });
@@ -1109,13 +1018,20 @@ describe('InputPrompt', () => {
1109
1018
  // isTerminalPasteTrusted will be false due to beforeEach setup.
1110
1019
  props.buffer.text = 'some command';
1111
1020
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1112
- await vi.runAllTimersAsync();
1021
+ await act(async () => {
1022
+ await vi.runAllTimersAsync();
1023
+ });
1113
1024
  // Simulate a paste operation (this should set the paste protection)
1114
- stdin.write(`\x1b[200~pasted content\x1b[201~`);
1115
- await vi.runAllTimersAsync();
1025
+ await act(async () => {
1026
+ stdin.write(`\x1b[200~pasted content\x1b[201~`);
1027
+ });
1116
1028
  // Simulate an Enter key press immediately after paste
1117
- stdin.write('\r');
1118
- await vi.runAllTimersAsync();
1029
+ await act(async () => {
1030
+ stdin.write('\r');
1031
+ });
1032
+ await act(async () => {
1033
+ await vi.runAllTimersAsync();
1034
+ });
1119
1035
  // Verify that onSubmit was NOT called due to recent paste protection
1120
1036
  expect(props.onSubmit).not.toHaveBeenCalled();
1121
1037
  // It should call newline() instead
@@ -1126,19 +1042,27 @@ describe('InputPrompt', () => {
1126
1042
  // isTerminalPasteTrusted will be false due to beforeEach setup.
1127
1043
  props.buffer.text = 'pasted text';
1128
1044
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1129
- await vi.runAllTimersAsync();
1045
+ await act(async () => {
1046
+ await vi.runAllTimersAsync();
1047
+ });
1130
1048
  // Simulate a paste operation (this sets the protection)
1131
- act(() => {
1049
+ await act(async () => {
1132
1050
  stdin.write('\x1b[200~pasted text\x1b[201~');
1133
1051
  });
1134
- await vi.runAllTimersAsync();
1052
+ await act(async () => {
1053
+ await vi.runAllTimersAsync();
1054
+ });
1135
1055
  // Advance timers past the protection timeout
1136
1056
  await act(async () => {
1137
1057
  await vi.advanceTimersByTimeAsync(50);
1138
1058
  });
1139
1059
  // Now Enter should work normally
1140
- stdin.write('\r');
1141
- await vi.runAllTimersAsync();
1060
+ await act(async () => {
1061
+ stdin.write('\r');
1062
+ });
1063
+ await act(async () => {
1064
+ await vi.runAllTimersAsync();
1065
+ });
1142
1066
  expect(props.onSubmit).toHaveBeenCalledWith('pasted text');
1143
1067
  expect(props.buffer.newline).not.toHaveBeenCalled();
1144
1068
  unmount();
@@ -1156,13 +1080,23 @@ describe('InputPrompt', () => {
1156
1080
  setup();
1157
1081
  props.buffer.text = 'pasted command';
1158
1082
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: true });
1159
- await vi.runAllTimersAsync();
1083
+ await act(async () => {
1084
+ await vi.runAllTimersAsync();
1085
+ });
1160
1086
  // Simulate a paste operation
1161
- stdin.write('\x1b[200~some pasted stuff\x1b[201~');
1162
- await vi.runAllTimersAsync();
1087
+ await act(async () => {
1088
+ stdin.write('\x1b[200~some pasted stuff\x1b[201~');
1089
+ });
1090
+ await act(async () => {
1091
+ await vi.runAllTimersAsync();
1092
+ });
1163
1093
  // Simulate an Enter key press immediately after paste
1164
- stdin.write('\r');
1165
- await vi.runAllTimersAsync();
1094
+ await act(async () => {
1095
+ stdin.write('\r');
1096
+ });
1097
+ await act(async () => {
1098
+ await vi.runAllTimersAsync();
1099
+ });
1166
1100
  // Verify that onSubmit was called
1167
1101
  expect(props.onSubmit).toHaveBeenCalledWith('pasted command');
1168
1102
  unmount();
@@ -1171,10 +1105,16 @@ describe('InputPrompt', () => {
1171
1105
  // Set up buffer with text before rendering to ensure submission works
1172
1106
  props.buffer.text = 'normal command';
1173
1107
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1174
- await vi.runAllTimersAsync();
1108
+ await act(async () => {
1109
+ await vi.runAllTimersAsync();
1110
+ });
1175
1111
  // Press Enter without any recent paste
1176
- stdin.write('\r');
1177
- await vi.runAllTimersAsync();
1112
+ await act(async () => {
1113
+ stdin.write('\r');
1114
+ });
1115
+ await act(async () => {
1116
+ await vi.runAllTimersAsync();
1117
+ });
1178
1118
  // Verify that onSubmit was called normally
1179
1119
  expect(props.onSubmit).toHaveBeenCalledWith('normal command');
1180
1120
  unmount();
@@ -1186,13 +1126,19 @@ describe('InputPrompt', () => {
1186
1126
  props.onEscapePromptChange = onEscapePromptChange;
1187
1127
  props.buffer.setText('text to clear');
1188
1128
  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();
1129
+ await act(async () => {
1130
+ stdin.write('\x1B');
1131
+ await waitFor(() => {
1132
+ expect(onEscapePromptChange).toHaveBeenCalledWith(false);
1133
+ });
1134
+ });
1135
+ await act(async () => {
1136
+ stdin.write('\x1B');
1137
+ await waitFor(() => {
1138
+ expect(props.buffer.setText).toHaveBeenCalledWith('');
1139
+ expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
1140
+ });
1141
+ });
1196
1142
  unmount();
1197
1143
  });
1198
1144
  it('should reset escape state on any non-ESC key', async () => {
@@ -1200,23 +1146,27 @@ describe('InputPrompt', () => {
1200
1146
  props.onEscapePromptChange = onEscapePromptChange;
1201
1147
  props.buffer.setText('some text');
1202
1148
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
1203
- stdin.write('\x1B');
1204
- await waitFor(() => {
1205
- expect(onEscapePromptChange).toHaveBeenCalledWith(true);
1149
+ await act(async () => {
1150
+ stdin.write('\x1B');
1151
+ await waitFor(() => {
1152
+ expect(onEscapePromptChange).toHaveBeenCalledWith(false);
1153
+ });
1206
1154
  });
1207
- stdin.write('a');
1208
- await waitFor(() => {
1209
- expect(onEscapePromptChange).toHaveBeenCalledWith(false);
1155
+ await act(async () => {
1156
+ stdin.write('a');
1157
+ await waitFor(() => {
1158
+ expect(onEscapePromptChange).toHaveBeenCalledWith(false);
1159
+ });
1210
1160
  });
1211
1161
  unmount();
1212
1162
  });
1213
1163
  it('should handle ESC in shell mode by disabling shell mode', async () => {
1214
1164
  props.shellModeActive = true;
1215
1165
  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);
1166
+ await act(async () => {
1167
+ stdin.write('\x1B');
1168
+ await waitFor(() => expect(props.setShellModeActive).toHaveBeenCalledWith(false));
1169
+ });
1220
1170
  unmount();
1221
1171
  });
1222
1172
  it('should handle ESC when completion suggestions are showing', async () => {
@@ -1226,10 +1176,10 @@ describe('InputPrompt', () => {
1226
1176
  suggestions: [{ label: 'suggestion', value: 'suggestion' }],
1227
1177
  });
1228
1178
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
1229
- await wait();
1230
- stdin.write('\x1B');
1231
- await wait();
1232
- expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled();
1179
+ await act(async () => {
1180
+ stdin.write('\x1B');
1181
+ });
1182
+ await waitFor(() => expect(mockCommandCompletion.resetCompletionState).toHaveBeenCalled());
1233
1183
  unmount();
1234
1184
  });
1235
1185
  it('should not call onEscapePromptChange when not provided', async () => {
@@ -1237,21 +1187,28 @@ describe('InputPrompt', () => {
1237
1187
  props.onEscapePromptChange = undefined;
1238
1188
  props.buffer.setText('some text');
1239
1189
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled: false });
1240
- await vi.runAllTimersAsync();
1241
- stdin.write('\x1B');
1242
- await vi.runAllTimersAsync();
1190
+ await act(async () => {
1191
+ await vi.runAllTimersAsync();
1192
+ });
1193
+ await act(async () => {
1194
+ stdin.write('\x1B');
1195
+ });
1196
+ await act(async () => {
1197
+ await vi.runAllTimersAsync();
1198
+ });
1243
1199
  vi.useRealTimers();
1244
1200
  unmount();
1245
1201
  });
1246
1202
  it('should not interfere with existing keyboard shortcuts', async () => {
1247
1203
  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');
1204
+ await act(async () => {
1205
+ stdin.write('\x0C');
1206
+ });
1207
+ await waitFor(() => expect(props.onClearScreen).toHaveBeenCalled());
1208
+ await act(async () => {
1209
+ stdin.write('\x01');
1210
+ });
1211
+ await waitFor(() => expect(props.buffer.move).toHaveBeenCalledWith('home'));
1255
1212
  unmount();
1256
1213
  });
1257
1214
  });
@@ -1279,9 +1236,8 @@ describe('InputPrompt', () => {
1279
1236
  activeSuggestionIndex: 0,
1280
1237
  });
1281
1238
  const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1282
- await wait();
1283
1239
  // Trigger reverse search with Ctrl+R
1284
- act(() => {
1240
+ await act(async () => {
1285
1241
  stdin.write('\x12');
1286
1242
  });
1287
1243
  await waitFor(() => {
@@ -1293,17 +1249,29 @@ describe('InputPrompt', () => {
1293
1249
  });
1294
1250
  unmount();
1295
1251
  });
1296
- it('resets reverse search state on Escape', async () => {
1297
- const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1298
- await wait();
1299
- stdin.write('\x12');
1300
- await wait();
1301
- stdin.write('\x1B');
1302
- stdin.write('\u001b[27u'); // Press kitty escape key
1252
+ it.each([
1253
+ { name: 'standard', kittyProtocolEnabled: false, escapeSequence: '\x1B' },
1254
+ {
1255
+ name: 'kitty',
1256
+ kittyProtocolEnabled: true,
1257
+ escapeSequence: '\u001b[27u',
1258
+ },
1259
+ ])('resets reverse search state on Escape ($name)', async ({ kittyProtocolEnabled, escapeSequence }) => {
1260
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { kittyProtocolEnabled });
1261
+ await act(async () => {
1262
+ stdin.write('\x12');
1263
+ });
1264
+ // Wait for reverse search to be active
1265
+ await waitFor(() => {
1266
+ expect(stdout.lastFrame()).toContain('(r:)');
1267
+ });
1268
+ await act(async () => {
1269
+ stdin.write(escapeSequence);
1270
+ });
1303
1271
  await waitFor(() => {
1304
1272
  expect(stdout.lastFrame()).not.toContain('(r:)');
1273
+ expect(stdout.lastFrame()).not.toContain('echo hello');
1305
1274
  });
1306
- expect(stdout.lastFrame()).not.toContain('echo hello');
1307
1275
  unmount();
1308
1276
  });
1309
1277
  it('completes the highlighted entry on Tab and exits reverse-search', async () => {
@@ -1326,7 +1294,7 @@ describe('InputPrompt', () => {
1326
1294
  }));
1327
1295
  const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1328
1296
  // Enter reverse search mode with Ctrl+R
1329
- act(() => {
1297
+ await act(async () => {
1330
1298
  stdin.write('\x12');
1331
1299
  });
1332
1300
  // Verify reverse search is active
@@ -1334,12 +1302,13 @@ describe('InputPrompt', () => {
1334
1302
  expect(stdout.lastFrame()).toContain('(r:)');
1335
1303
  });
1336
1304
  // Press Tab to complete the highlighted entry
1337
- act(() => {
1305
+ await act(async () => {
1338
1306
  stdin.write('\t');
1339
1307
  });
1340
- await wait();
1341
- expect(mockHandleAutocomplete).toHaveBeenCalledWith(0);
1342
- expect(props.buffer.setText).toHaveBeenCalledWith('echo hello');
1308
+ await waitFor(() => {
1309
+ expect(mockHandleAutocomplete).toHaveBeenCalledWith(0);
1310
+ expect(props.buffer.setText).toHaveBeenCalledWith('echo hello');
1311
+ });
1343
1312
  unmount();
1344
1313
  }, 15000);
1345
1314
  it('submits the highlighted entry on Enter and exits reverse-search', async () => {
@@ -1355,13 +1324,13 @@ describe('InputPrompt', () => {
1355
1324
  activeSuggestionIndex: 0,
1356
1325
  });
1357
1326
  const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1358
- act(() => {
1327
+ await act(async () => {
1359
1328
  stdin.write('\x12');
1360
1329
  });
1361
1330
  await waitFor(() => {
1362
1331
  expect(stdout.lastFrame()).toContain('(r:)');
1363
1332
  });
1364
- act(() => {
1333
+ await act(async () => {
1365
1334
  stdin.write('\r');
1366
1335
  });
1367
1336
  await waitFor(() => {
@@ -1384,16 +1353,15 @@ describe('InputPrompt', () => {
1384
1353
  showSuggestions: reverseSearchActiveFromInputPrompt,
1385
1354
  }));
1386
1355
  const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1387
- await wait();
1388
1356
  // reverse search with Ctrl+R
1389
- act(() => {
1357
+ await act(async () => {
1390
1358
  stdin.write('\x12');
1391
1359
  });
1392
1360
  await waitFor(() => {
1393
1361
  expect(stdout.lastFrame()).toContain('(r:)');
1394
1362
  });
1395
1363
  // Press kitty escape key
1396
- act(() => {
1364
+ await act(async () => {
1397
1365
  stdin.write('\u001b[27u');
1398
1366
  });
1399
1367
  await waitFor(() => {
@@ -1410,10 +1378,12 @@ describe('InputPrompt', () => {
1410
1378
  props.buffer.cursor = [1, 2];
1411
1379
  props.buffer.lines = ['line 1', 'line 2', 'line 3'];
1412
1380
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1413
- await wait();
1414
- stdin.write('\x05'); // Ctrl+E
1415
- await wait();
1416
- expect(props.buffer.move).toHaveBeenCalledWith('end');
1381
+ await act(async () => {
1382
+ stdin.write('\x05'); // Ctrl+E
1383
+ });
1384
+ await waitFor(() => {
1385
+ expect(props.buffer.move).toHaveBeenCalledWith('end');
1386
+ });
1417
1387
  expect(props.buffer.moveToOffset).not.toHaveBeenCalled();
1418
1388
  unmount();
1419
1389
  });
@@ -1422,10 +1392,12 @@ describe('InputPrompt', () => {
1422
1392
  props.buffer.cursor = [0, 5];
1423
1393
  props.buffer.lines = ['single line text'];
1424
1394
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1425
- await wait();
1426
- stdin.write('\x05'); // Ctrl+E
1427
- await wait();
1428
- expect(props.buffer.move).toHaveBeenCalledWith('end');
1395
+ await act(async () => {
1396
+ stdin.write('\x05'); // Ctrl+E
1397
+ });
1398
+ await waitFor(() => {
1399
+ expect(props.buffer.move).toHaveBeenCalledWith('end');
1400
+ });
1429
1401
  expect(props.buffer.moveToOffset).not.toHaveBeenCalled();
1430
1402
  unmount();
1431
1403
  });
@@ -1445,18 +1417,18 @@ describe('InputPrompt', () => {
1445
1417
  activeSuggestionIndex: isActive ? 0 : -1,
1446
1418
  }));
1447
1419
  const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1448
- await wait();
1449
- act(() => {
1420
+ await act(async () => {
1450
1421
  stdin.write('\x12'); // Ctrl+R
1451
1422
  });
1452
- await wait();
1453
- const frame = stdout.lastFrame() ?? '';
1454
- expect(frame).toContain('(r:)');
1455
- expect(frame).toContain('git commit');
1456
- expect(frame).toContain('git push');
1423
+ await waitFor(() => {
1424
+ const frame = stdout.lastFrame() ?? '';
1425
+ expect(frame).toContain('(r:)');
1426
+ expect(frame).toContain('git commit');
1427
+ expect(frame).toContain('git push');
1428
+ });
1457
1429
  unmount();
1458
1430
  });
1459
- it.skip('expands and collapses long suggestion via Right/Left arrows', async () => {
1431
+ it('expands and collapses long suggestion via Right/Left arrows', async () => {
1460
1432
  props.shellModeActive = false;
1461
1433
  const longValue = 'l'.repeat(200);
1462
1434
  vi.mocked(useReverseSearchCompletion).mockReturnValue({
@@ -1468,18 +1440,26 @@ describe('InputPrompt', () => {
1468
1440
  isLoadingSuggestions: false,
1469
1441
  });
1470
1442
  const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1471
- await wait();
1472
- stdin.write('\x12');
1473
- await wait();
1474
- expect(clean(stdout.lastFrame())).toContain('→');
1475
- stdin.write('\u001B[C');
1476
- await wait(200);
1477
- expect(clean(stdout.lastFrame())).toContain('←');
1478
- expect(stdout.lastFrame()).toMatchSnapshot('command-search-expanded-match');
1479
- stdin.write('\u001B[D');
1480
- await wait();
1481
- expect(clean(stdout.lastFrame())).toContain('');
1482
- expect(stdout.lastFrame()).toMatchSnapshot('command-search-collapsed-match');
1443
+ await act(async () => {
1444
+ stdin.write('\x12');
1445
+ });
1446
+ await waitFor(() => {
1447
+ expect(clean(stdout.lastFrame())).toContain('');
1448
+ });
1449
+ await act(async () => {
1450
+ stdin.write('\u001B[C');
1451
+ });
1452
+ await waitFor(() => {
1453
+ expect(clean(stdout.lastFrame())).toContain('');
1454
+ });
1455
+ expect(stdout.lastFrame()).toMatchSnapshot('command-search-render-expanded-match');
1456
+ await act(async () => {
1457
+ stdin.write('\u001B[D');
1458
+ });
1459
+ await waitFor(() => {
1460
+ expect(clean(stdout.lastFrame())).toContain('→');
1461
+ });
1462
+ expect(stdout.lastFrame()).toMatchSnapshot('command-search-render-collapsed-match');
1483
1463
  unmount();
1484
1464
  });
1485
1465
  it('renders match window and expanded view (snapshots)', async () => {
@@ -1496,13 +1476,18 @@ describe('InputPrompt', () => {
1496
1476
  isLoadingSuggestions: false,
1497
1477
  });
1498
1478
  const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1499
- await wait();
1500
- stdin.write('\x12');
1501
- await wait();
1502
- expect(stdout.lastFrame()).toMatchSnapshot('command-search-collapsed-match');
1503
- stdin.write('\u001B[C');
1504
- await wait();
1505
- expect(stdout.lastFrame()).toMatchSnapshot('command-search-expanded-match');
1479
+ await act(async () => {
1480
+ stdin.write('\x12');
1481
+ });
1482
+ await waitFor(() => {
1483
+ expect(stdout.lastFrame()).toMatchSnapshot('command-search-render-collapsed-match');
1484
+ });
1485
+ await act(async () => {
1486
+ stdin.write('\u001B[C');
1487
+ });
1488
+ await waitFor(() => {
1489
+ expect(stdout.lastFrame()).toMatchSnapshot('command-search-render-expanded-match');
1490
+ });
1506
1491
  unmount();
1507
1492
  });
1508
1493
  it('does not show expand/collapse indicator for short suggestions', async () => {
@@ -1517,12 +1502,72 @@ describe('InputPrompt', () => {
1517
1502
  isLoadingSuggestions: false,
1518
1503
  });
1519
1504
  const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1520
- await wait();
1521
- stdin.write('\x12');
1522
- await wait();
1523
- const frame = clean(stdout.lastFrame());
1524
- expect(frame).not.toContain('→');
1525
- expect(frame).not.toContain('←');
1505
+ await act(async () => {
1506
+ stdin.write('\x12');
1507
+ });
1508
+ await waitFor(() => {
1509
+ const frame = clean(stdout.lastFrame());
1510
+ // Ensure it rendered the search mode
1511
+ expect(frame).toContain('(r:)');
1512
+ expect(frame).not.toContain('→');
1513
+ expect(frame).not.toContain('←');
1514
+ });
1515
+ unmount();
1516
+ });
1517
+ });
1518
+ describe('mouse interaction', () => {
1519
+ it.each([
1520
+ {
1521
+ name: 'first line, first char',
1522
+ relX: 0,
1523
+ relY: 0,
1524
+ mouseCol: 5,
1525
+ mouseRow: 2,
1526
+ },
1527
+ {
1528
+ name: 'first line, middle char',
1529
+ relX: 6,
1530
+ relY: 0,
1531
+ mouseCol: 11,
1532
+ mouseRow: 2,
1533
+ },
1534
+ {
1535
+ name: 'second line, first char',
1536
+ relX: 0,
1537
+ relY: 1,
1538
+ mouseCol: 5,
1539
+ mouseRow: 3,
1540
+ },
1541
+ {
1542
+ name: 'second line, end char',
1543
+ relX: 5,
1544
+ relY: 1,
1545
+ mouseCol: 10,
1546
+ mouseRow: 3,
1547
+ },
1548
+ ])('should move cursor on mouse click - $name', async ({ relX, relY, mouseCol, mouseRow }) => {
1549
+ props.buffer.text = 'hello world\nsecond line';
1550
+ props.buffer.lines = ['hello world', 'second line'];
1551
+ props.buffer.viewportVisualLines = ['hello world', 'second line'];
1552
+ props.buffer.visualToLogicalMap = [
1553
+ [0, 0],
1554
+ [1, 0],
1555
+ ];
1556
+ props.buffer.visualCursor = [0, 11];
1557
+ props.buffer.visualScrollRow = 0;
1558
+ const { stdin, stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), { mouseEventsEnabled: true });
1559
+ // Wait for initial render
1560
+ await waitFor(() => {
1561
+ expect(stdout.lastFrame()).toContain('hello world');
1562
+ });
1563
+ // Simulate left mouse press at calculated coordinates.
1564
+ // Assumes inner box is at x=4, y=1 based on border(1)+padding(1)+prompt(2) and border-top(1).
1565
+ await act(async () => {
1566
+ stdin.write(`\x1b[<0;${mouseCol};${mouseRow}M`);
1567
+ });
1568
+ await waitFor(() => {
1569
+ expect(props.buffer.moveToVisualPosition).toHaveBeenCalledWith(relY, relX);
1570
+ });
1526
1571
  unmount();
1527
1572
  });
1528
1573
  });
@@ -1532,12 +1577,12 @@ describe('InputPrompt', () => {
1532
1577
  props.popAllMessages = mockPopAllMessages;
1533
1578
  props.buffer.text = '';
1534
1579
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1535
- await wait();
1536
- stdin.write('\u001B[A');
1537
- await wait();
1538
- expect(mockPopAllMessages).toHaveBeenCalled();
1580
+ await act(async () => {
1581
+ stdin.write('\u001B[A');
1582
+ });
1583
+ await waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
1539
1584
  const callback = mockPopAllMessages.mock.calls[0][0];
1540
- act(() => {
1585
+ await act(async () => {
1541
1586
  callback('Message 1\n\nMessage 2\n\nMessage 3');
1542
1587
  });
1543
1588
  expect(props.buffer.setText).toHaveBeenCalledWith('Message 1\n\nMessage 2\n\nMessage 3');
@@ -1548,11 +1593,11 @@ describe('InputPrompt', () => {
1548
1593
  props.popAllMessages = mockPopAllMessages;
1549
1594
  props.buffer.text = 'some text';
1550
1595
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1551
- await wait();
1552
- stdin.write('\u001B[A');
1553
- await wait();
1596
+ await act(async () => {
1597
+ stdin.write('\u001B[A');
1598
+ });
1599
+ await waitFor(() => expect(mockInputHistory.navigateUp).toHaveBeenCalled());
1554
1600
  expect(mockPopAllMessages).not.toHaveBeenCalled();
1555
- expect(mockInputHistory.navigateUp).toHaveBeenCalled();
1556
1601
  unmount();
1557
1602
  });
1558
1603
  it('should handle undefined messages from popAllMessages', async () => {
@@ -1560,12 +1605,12 @@ describe('InputPrompt', () => {
1560
1605
  props.popAllMessages = mockPopAllMessages;
1561
1606
  props.buffer.text = '';
1562
1607
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1563
- await wait();
1564
- stdin.write('\u001B[A');
1565
- await wait();
1566
- expect(mockPopAllMessages).toHaveBeenCalled();
1608
+ await act(async () => {
1609
+ stdin.write('\u001B[A');
1610
+ });
1611
+ await waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
1567
1612
  const callback = mockPopAllMessages.mock.calls[0][0];
1568
- act(() => {
1613
+ await act(async () => {
1569
1614
  callback(undefined);
1570
1615
  });
1571
1616
  expect(props.buffer.setText).not.toHaveBeenCalled();
@@ -1580,10 +1625,10 @@ describe('InputPrompt', () => {
1580
1625
  props.buffer.visualCursor = [0, 0];
1581
1626
  props.buffer.visualScrollRow = 0;
1582
1627
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1583
- await wait();
1584
- stdin.write('\u001B[A');
1585
- await wait();
1586
- expect(mockPopAllMessages).toHaveBeenCalled();
1628
+ await act(async () => {
1629
+ stdin.write('\u001B[A');
1630
+ });
1631
+ await waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
1587
1632
  unmount();
1588
1633
  });
1589
1634
  it('should handle single queued message', async () => {
@@ -1591,11 +1636,12 @@ describe('InputPrompt', () => {
1591
1636
  props.popAllMessages = mockPopAllMessages;
1592
1637
  props.buffer.text = '';
1593
1638
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1594
- await wait();
1595
- stdin.write('\u001B[A');
1596
- await wait();
1639
+ await act(async () => {
1640
+ stdin.write('\u001B[A');
1641
+ });
1642
+ await waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
1597
1643
  const callback = mockPopAllMessages.mock.calls[0][0];
1598
- act(() => {
1644
+ await act(async () => {
1599
1645
  callback('Single message');
1600
1646
  });
1601
1647
  expect(props.buffer.setText).toHaveBeenCalledWith('Single message');
@@ -1606,20 +1652,20 @@ describe('InputPrompt', () => {
1606
1652
  props.popAllMessages = mockPopAllMessages;
1607
1653
  props.buffer.text = ' '; // Whitespace only
1608
1654
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1609
- await wait();
1610
- stdin.write('\u001B[A');
1611
- await wait();
1612
- expect(mockPopAllMessages).toHaveBeenCalled();
1655
+ await act(async () => {
1656
+ stdin.write('\u001B[A');
1657
+ });
1658
+ await waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
1613
1659
  unmount();
1614
1660
  });
1615
1661
  it('should not call popAllMessages if it is not provided', async () => {
1616
1662
  props.popAllMessages = undefined;
1617
1663
  props.buffer.text = '';
1618
1664
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1619
- await wait();
1620
- stdin.write('\u001B[A');
1621
- await wait();
1622
- expect(mockInputHistory.navigateUp).toHaveBeenCalled();
1665
+ await act(async () => {
1666
+ stdin.write('\u001B[A');
1667
+ });
1668
+ await waitFor(() => expect(mockInputHistory.navigateUp).toHaveBeenCalled());
1623
1669
  unmount();
1624
1670
  });
1625
1671
  it('should navigate input history on fresh start when no queued messages exist', async () => {
@@ -1627,12 +1673,12 @@ describe('InputPrompt', () => {
1627
1673
  props.popAllMessages = mockPopAllMessages;
1628
1674
  props.buffer.text = '';
1629
1675
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1630
- await wait();
1631
- stdin.write('\u001B[A');
1632
- await wait();
1633
- expect(mockPopAllMessages).toHaveBeenCalled();
1676
+ await act(async () => {
1677
+ stdin.write('\u001B[A');
1678
+ });
1679
+ await waitFor(() => expect(mockPopAllMessages).toHaveBeenCalled());
1634
1680
  const callback = mockPopAllMessages.mock.calls[0][0];
1635
- act(() => {
1681
+ await act(async () => {
1636
1682
  callback(undefined);
1637
1683
  });
1638
1684
  expect(mockInputHistory.navigateUp).toHaveBeenCalled();
@@ -1644,33 +1690,31 @@ describe('InputPrompt', () => {
1644
1690
  it('should render correctly in shell mode', async () => {
1645
1691
  props.shellModeActive = true;
1646
1692
  const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1647
- await wait();
1648
- expect(stdout.lastFrame()).toMatchSnapshot();
1693
+ await waitFor(() => expect(stdout.lastFrame()).toMatchSnapshot());
1649
1694
  unmount();
1650
1695
  });
1651
1696
  it('should render correctly when accepting edits', async () => {
1652
1697
  props.approvalMode = ApprovalMode.AUTO_EDIT;
1653
1698
  const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1654
- await wait();
1655
- expect(stdout.lastFrame()).toMatchSnapshot();
1699
+ await waitFor(() => expect(stdout.lastFrame()).toMatchSnapshot());
1656
1700
  unmount();
1657
1701
  });
1658
1702
  it('should render correctly in yolo mode', async () => {
1659
1703
  props.approvalMode = ApprovalMode.YOLO;
1660
1704
  const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1661
- await wait();
1662
- expect(stdout.lastFrame()).toMatchSnapshot();
1705
+ await waitFor(() => expect(stdout.lastFrame()).toMatchSnapshot());
1663
1706
  unmount();
1664
1707
  });
1665
1708
  it('should not show inverted cursor when shell is focused', async () => {
1666
1709
  props.isEmbeddedShellFocused = true;
1667
1710
  props.focus = false;
1668
1711
  const { stdout, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1669
- await wait();
1670
- expect(stdout.lastFrame()).not.toContain(`{chalk.inverse(' ')}`);
1671
- // This snapshot is good to make sure there was an input prompt but does
1672
- // not show the inverted cursor because snapshots do not show colors.
1673
- expect(stdout.lastFrame()).toMatchSnapshot();
1712
+ await waitFor(() => {
1713
+ expect(stdout.lastFrame()).not.toContain(`{chalk.inverse(' ')}`);
1714
+ // This snapshot is good to make sure there was an input prompt but does
1715
+ // not show the inverted cursor because snapshots do not show colors.
1716
+ expect(stdout.lastFrame()).toMatchSnapshot();
1717
+ });
1674
1718
  unmount();
1675
1719
  });
1676
1720
  });
@@ -1678,54 +1722,59 @@ describe('InputPrompt', () => {
1678
1722
  const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }), {
1679
1723
  shellFocus: false,
1680
1724
  });
1681
- await wait();
1682
- stdin.write('a');
1683
- await wait();
1684
- expect(mockBuffer.handleInput).toHaveBeenCalled();
1685
- unmount();
1686
- });
1687
- it('should prevent slash commands from being queued while streaming', async () => {
1688
- props.onSubmit = vi.fn();
1689
- props.buffer.text = '/help';
1690
- props.setQueueErrorMessage = vi.fn();
1691
- props.streamingState = StreamingState.Responding;
1692
- const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1693
- await wait();
1694
- stdin.write('/help');
1695
- stdin.write('\r');
1696
- await wait();
1697
- expect(props.onSubmit).not.toHaveBeenCalled();
1698
- expect(props.setQueueErrorMessage).toHaveBeenCalledWith('Slash commands cannot be queued');
1699
- unmount();
1700
- });
1701
- it('should prevent shell commands from being queued while streaming', async () => {
1702
- props.onSubmit = vi.fn();
1703
- props.buffer.text = 'ls';
1704
- props.setQueueErrorMessage = vi.fn();
1705
- props.streamingState = StreamingState.Responding;
1706
- props.shellModeActive = true;
1707
- const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1708
- await wait();
1709
- stdin.write('ls');
1710
- stdin.write('\r');
1711
- await wait();
1712
- expect(props.onSubmit).not.toHaveBeenCalled();
1713
- expect(props.setQueueErrorMessage).toHaveBeenCalledWith('Shell commands cannot be queued');
1725
+ await act(async () => {
1726
+ stdin.write('a');
1727
+ });
1728
+ await waitFor(() => expect(mockBuffer.handleInput).toHaveBeenCalled());
1714
1729
  unmount();
1715
1730
  });
1716
- it('should allow regular messages to be queued while streaming', async () => {
1717
- props.onSubmit = vi.fn();
1718
- props.buffer.text = 'regular message';
1719
- props.setQueueErrorMessage = vi.fn();
1720
- props.streamingState = StreamingState.Responding;
1721
- const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1722
- await wait();
1723
- stdin.write('regular message');
1724
- stdin.write('\r');
1725
- await wait();
1726
- expect(props.onSubmit).toHaveBeenCalledWith('regular message');
1727
- expect(props.setQueueErrorMessage).not.toHaveBeenCalled();
1728
- unmount();
1731
+ describe('command queuing while streaming', () => {
1732
+ beforeEach(() => {
1733
+ props.streamingState = StreamingState.Responding;
1734
+ props.setQueueErrorMessage = vi.fn();
1735
+ props.onSubmit = vi.fn();
1736
+ });
1737
+ it.each([
1738
+ {
1739
+ name: 'should prevent slash commands',
1740
+ bufferText: '/help',
1741
+ shellMode: false,
1742
+ shouldSubmit: false,
1743
+ errorMessage: 'Slash commands cannot be queued',
1744
+ },
1745
+ {
1746
+ name: 'should prevent shell commands',
1747
+ bufferText: 'ls',
1748
+ shellMode: true,
1749
+ shouldSubmit: false,
1750
+ errorMessage: 'Shell commands cannot be queued',
1751
+ },
1752
+ {
1753
+ name: 'should allow regular messages',
1754
+ bufferText: 'regular message',
1755
+ shellMode: false,
1756
+ shouldSubmit: true,
1757
+ errorMessage: null,
1758
+ },
1759
+ ])('$name', async ({ bufferText, shellMode, shouldSubmit, errorMessage }) => {
1760
+ props.buffer.text = bufferText;
1761
+ props.shellModeActive = shellMode;
1762
+ const { stdin, unmount } = renderWithProviders(_jsx(InputPrompt, { ...props }));
1763
+ await act(async () => {
1764
+ stdin.write('\r');
1765
+ });
1766
+ await waitFor(() => {
1767
+ if (shouldSubmit) {
1768
+ expect(props.onSubmit).toHaveBeenCalledWith(bufferText);
1769
+ expect(props.setQueueErrorMessage).not.toHaveBeenCalled();
1770
+ }
1771
+ else {
1772
+ expect(props.onSubmit).not.toHaveBeenCalled();
1773
+ expect(props.setQueueErrorMessage).toHaveBeenCalledWith(errorMessage);
1774
+ }
1775
+ });
1776
+ unmount();
1777
+ });
1729
1778
  });
1730
1779
  });
1731
1780
  function clean(str) {