@navios/commander-tui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +275 -0
  3. package/coverage/__tests__/utils/factories.ts.html +1147 -0
  4. package/coverage/__tests__/utils/index.html +131 -0
  5. package/coverage/__tests__/utils/render-utils.tsx.html +202 -0
  6. package/coverage/base.css +224 -0
  7. package/coverage/block-navigation.js +87 -0
  8. package/coverage/clover.xml +959 -0
  9. package/coverage/components/filter/filter_bar.tsx.html +322 -0
  10. package/coverage/components/filter/index.html +116 -0
  11. package/coverage/components/log/index.html +131 -0
  12. package/coverage/components/log/index.ts.html +88 -0
  13. package/coverage/components/log/log_message.tsx.html +391 -0
  14. package/coverage/components/prompt/index.html +116 -0
  15. package/coverage/components/prompt/prompt_renderer.tsx.html +1123 -0
  16. package/coverage/components/screen/index.html +131 -0
  17. package/coverage/components/screen/loading_message.tsx.html +217 -0
  18. package/coverage/components/screen/progress_message.tsx.html +265 -0
  19. package/coverage/components/sidebar/index.html +146 -0
  20. package/coverage/components/sidebar/sidebar.tsx.html +391 -0
  21. package/coverage/components/sidebar/sidebar_item.tsx.html +235 -0
  22. package/coverage/components/sidebar/sidebar_separator.tsx.html +124 -0
  23. package/coverage/context/index.html +131 -0
  24. package/coverage/context/index.ts.html +88 -0
  25. package/coverage/context/logger_context.tsx.html +412 -0
  26. package/coverage/coverage-final.json +55 -0
  27. package/coverage/favicon.png +0 -0
  28. package/coverage/filter/filter_engine.ts.html +424 -0
  29. package/coverage/filter/index.html +116 -0
  30. package/coverage/hooks/index.html +131 -0
  31. package/coverage/hooks/index.ts.html +88 -0
  32. package/coverage/hooks/use_theme.ts.html +121 -0
  33. package/coverage/index.html +356 -0
  34. package/coverage/keyboard/index.html +116 -0
  35. package/coverage/keyboard/keyboard_manager.ts.html +784 -0
  36. package/coverage/prettify.css +1 -0
  37. package/coverage/prettify.js +2 -0
  38. package/coverage/schemas/index.html +161 -0
  39. package/coverage/schemas/index.ts.html +94 -0
  40. package/coverage/schemas/logger-options.ts.html +124 -0
  41. package/coverage/schemas/prompt-options.ts.html +112 -0
  42. package/coverage/schemas/screen-options.ts.html +127 -0
  43. package/coverage/services/index.html +146 -0
  44. package/coverage/services/logger.ts.html +1192 -0
  45. package/coverage/services/prompt.ts.html +568 -0
  46. package/coverage/services/screen.ts.html +1804 -0
  47. package/coverage/sort-arrow-sprite.png +0 -0
  48. package/coverage/sorter.js +210 -0
  49. package/coverage/themes/dark.ts.html +604 -0
  50. package/coverage/themes/high-contrast.ts.html +619 -0
  51. package/coverage/themes/index.html +176 -0
  52. package/coverage/themes/index.ts.html +97 -0
  53. package/coverage/themes/light.ts.html +601 -0
  54. package/coverage/themes/utils.ts.html +334 -0
  55. package/coverage/tokens/index.html +161 -0
  56. package/coverage/tokens/index.ts.html +94 -0
  57. package/coverage/tokens/logger.ts.html +115 -0
  58. package/coverage/tokens/prompt.ts.html +115 -0
  59. package/coverage/tokens/screen.ts.html +115 -0
  60. package/coverage/types/file.types.ts.html +265 -0
  61. package/coverage/types/filter.types.ts.html +238 -0
  62. package/coverage/types/index.html +236 -0
  63. package/coverage/types/index.ts.html +151 -0
  64. package/coverage/types/keyboard.types.ts.html +364 -0
  65. package/coverage/types/log.types.ts.html +268 -0
  66. package/coverage/types/message.types.ts.html +445 -0
  67. package/coverage/types/prompt.types.ts.html +403 -0
  68. package/coverage/types/screen.types.ts.html +451 -0
  69. package/coverage/types/theme.types.ts.html +841 -0
  70. package/coverage/utils/colors/file-colors.ts.html +112 -0
  71. package/coverage/utils/colors/helpers.ts.html +235 -0
  72. package/coverage/utils/colors/index.html +221 -0
  73. package/coverage/utils/colors/index.ts.html +145 -0
  74. package/coverage/utils/colors/log-colors.ts.html +253 -0
  75. package/coverage/utils/colors/progress-colors.ts.html +160 -0
  76. package/coverage/utils/colors/prompt-colors.ts.html +175 -0
  77. package/coverage/utils/colors/sidebar-colors.ts.html +241 -0
  78. package/coverage/utils/colors/table-colors.ts.html +118 -0
  79. package/coverage/utils/filetype.ts.html +277 -0
  80. package/coverage/utils/format.ts.html +241 -0
  81. package/coverage/utils/index.html +161 -0
  82. package/coverage/utils/index.ts.html +118 -0
  83. package/coverage/utils/stdout-printer.ts.html +523 -0
  84. package/dist/src/components/file/file_log.d.ts +35 -0
  85. package/dist/src/components/file/file_log.d.ts.map +1 -0
  86. package/dist/src/components/file/index.d.ts +2 -0
  87. package/dist/src/components/file/index.d.ts.map +1 -0
  88. package/dist/src/components/filter/filter_bar.d.ts +10 -0
  89. package/dist/src/components/filter/filter_bar.d.ts.map +1 -0
  90. package/dist/src/components/filter/index.d.ts +2 -0
  91. package/dist/src/components/filter/index.d.ts.map +1 -0
  92. package/dist/src/components/help/help_overlay.d.ts +10 -0
  93. package/dist/src/components/help/help_overlay.d.ts.map +1 -0
  94. package/dist/src/components/help/index.d.ts +2 -0
  95. package/dist/src/components/help/index.d.ts.map +1 -0
  96. package/dist/src/components/index.d.ts +27 -0
  97. package/dist/src/components/index.d.ts.map +1 -0
  98. package/dist/src/components/log/debug_log.d.ts +3 -0
  99. package/dist/src/components/log/debug_log.d.ts.map +1 -0
  100. package/dist/src/components/log/error_log.d.ts +3 -0
  101. package/dist/src/components/log/error_log.d.ts.map +1 -0
  102. package/dist/src/components/log/fatal_log.d.ts +3 -0
  103. package/dist/src/components/log/fatal_log.d.ts.map +1 -0
  104. package/dist/src/components/log/index.d.ts +9 -0
  105. package/dist/src/components/log/index.d.ts.map +1 -0
  106. package/dist/src/components/log/info_log.d.ts +3 -0
  107. package/dist/src/components/log/info_log.d.ts.map +1 -0
  108. package/dist/src/components/log/log_message.d.ts +33 -0
  109. package/dist/src/components/log/log_message.d.ts.map +1 -0
  110. package/dist/src/components/log/success_log.d.ts +3 -0
  111. package/dist/src/components/log/success_log.d.ts.map +1 -0
  112. package/dist/src/components/log/trace_log.d.ts +3 -0
  113. package/dist/src/components/log/trace_log.d.ts.map +1 -0
  114. package/dist/src/components/log/warning_log.d.ts +3 -0
  115. package/dist/src/components/log/warning_log.d.ts.map +1 -0
  116. package/dist/src/components/prompt/index.d.ts +3 -0
  117. package/dist/src/components/prompt/index.d.ts.map +1 -0
  118. package/dist/src/components/prompt/prompt_renderer.d.ts +6 -0
  119. package/dist/src/components/prompt/prompt_renderer.d.ts.map +1 -0
  120. package/dist/src/components/screen/group_renderer.d.ts +19 -0
  121. package/dist/src/components/screen/group_renderer.d.ts.map +1 -0
  122. package/dist/src/components/screen/index.d.ts +13 -0
  123. package/dist/src/components/screen/index.d.ts.map +1 -0
  124. package/dist/src/components/screen/loading_message.d.ts +6 -0
  125. package/dist/src/components/screen/loading_message.d.ts.map +1 -0
  126. package/dist/src/components/screen/message_renderer.d.ts +8 -0
  127. package/dist/src/components/screen/message_renderer.d.ts.map +1 -0
  128. package/dist/src/components/screen/progress_message.d.ts +8 -0
  129. package/dist/src/components/screen/progress_message.d.ts.map +1 -0
  130. package/dist/src/components/screen/screen_bridge.d.ts +20 -0
  131. package/dist/src/components/screen/screen_bridge.d.ts.map +1 -0
  132. package/dist/src/components/screen/table_message.d.ts +6 -0
  133. package/dist/src/components/screen/table_message.d.ts.map +1 -0
  134. package/dist/src/components/screen_manager_bridge.d.ts +11 -0
  135. package/dist/src/components/screen_manager_bridge.d.ts.map +1 -0
  136. package/dist/src/components/sidebar/index.d.ts +6 -0
  137. package/dist/src/components/sidebar/index.d.ts.map +1 -0
  138. package/dist/src/components/sidebar/sidebar.d.ts +18 -0
  139. package/dist/src/components/sidebar/sidebar.d.ts.map +1 -0
  140. package/dist/src/components/sidebar/sidebar_item.d.ts +14 -0
  141. package/dist/src/components/sidebar/sidebar_item.d.ts.map +1 -0
  142. package/dist/src/components/sidebar/sidebar_separator.d.ts +2 -0
  143. package/dist/src/components/sidebar/sidebar_separator.d.ts.map +1 -0
  144. package/dist/src/context/logger_context.d.ts +60 -0
  145. package/dist/src/context/logger_context.d.ts.map +1 -0
  146. package/dist/src/filter/filter_engine.d.ts +20 -0
  147. package/dist/src/filter/filter_engine.d.ts.map +1 -0
  148. package/dist/src/filter/index.d.ts +4 -0
  149. package/dist/src/filter/index.d.ts.map +1 -0
  150. package/dist/src/hooks/index.d.ts +2 -0
  151. package/dist/src/hooks/index.d.ts.map +1 -0
  152. package/dist/src/hooks/use_theme.d.ts +7 -0
  153. package/dist/src/hooks/use_theme.d.ts.map +1 -0
  154. package/dist/src/index.d.ts +88 -0
  155. package/dist/src/index.d.ts.map +1 -0
  156. package/dist/src/keyboard/create_bindings.d.ts +31 -0
  157. package/dist/src/keyboard/create_bindings.d.ts.map +1 -0
  158. package/dist/src/keyboard/index.d.ts +12 -0
  159. package/dist/src/keyboard/index.d.ts.map +1 -0
  160. package/dist/src/keyboard/keyboard_manager.d.ts +69 -0
  161. package/dist/src/keyboard/keyboard_manager.d.ts.map +1 -0
  162. package/dist/src/services/index.d.ts +5 -0
  163. package/dist/src/services/index.d.ts.map +1 -0
  164. package/dist/src/services/logger.d.ts +61 -0
  165. package/dist/src/services/logger.d.ts.map +1 -0
  166. package/dist/src/services/prompt.d.ts +37 -0
  167. package/dist/src/services/prompt.d.ts.map +1 -0
  168. package/dist/src/services/screen.d.ts +165 -0
  169. package/dist/src/services/screen.d.ts.map +1 -0
  170. package/dist/src/services/screen_manager.d.ts +124 -0
  171. package/dist/src/services/screen_manager.d.ts.map +1 -0
  172. package/dist/src/themes/dark.d.ts +7 -0
  173. package/dist/src/themes/dark.d.ts.map +1 -0
  174. package/dist/src/themes/high-contrast.d.ts +7 -0
  175. package/dist/src/themes/high-contrast.d.ts.map +1 -0
  176. package/dist/src/themes/index.d.ts +18 -0
  177. package/dist/src/themes/index.d.ts.map +1 -0
  178. package/dist/src/themes/light.d.ts +6 -0
  179. package/dist/src/themes/light.d.ts.map +1 -0
  180. package/dist/src/themes/utils.d.ts +22 -0
  181. package/dist/src/themes/utils.d.ts.map +1 -0
  182. package/dist/src/types/file.types.d.ts +40 -0
  183. package/dist/src/types/file.types.d.ts.map +1 -0
  184. package/dist/src/types/filter.types.d.ts +31 -0
  185. package/dist/src/types/filter.types.d.ts.map +1 -0
  186. package/dist/src/types/index.d.ts +81 -0
  187. package/dist/src/types/index.d.ts.map +1 -0
  188. package/dist/src/types/keyboard.types.d.ts +83 -0
  189. package/dist/src/types/keyboard.types.d.ts.map +1 -0
  190. package/dist/src/types/log.types.d.ts +32 -0
  191. package/dist/src/types/log.types.d.ts.map +1 -0
  192. package/dist/src/types/message.types.d.ts +91 -0
  193. package/dist/src/types/message.types.d.ts.map +1 -0
  194. package/dist/src/types/prompt.types.d.ts +89 -0
  195. package/dist/src/types/prompt.types.d.ts.map +1 -0
  196. package/dist/src/types/screen.types.d.ts +85 -0
  197. package/dist/src/types/screen.types.d.ts.map +1 -0
  198. package/dist/src/types/theme.types.d.ts +216 -0
  199. package/dist/src/types/theme.types.d.ts.map +1 -0
  200. package/dist/src/utils/colors/file-colors.d.ts +10 -0
  201. package/dist/src/utils/colors/file-colors.d.ts.map +1 -0
  202. package/dist/src/utils/colors/helpers.d.ts +29 -0
  203. package/dist/src/utils/colors/helpers.d.ts.map +1 -0
  204. package/dist/src/utils/colors/index.d.ts +13 -0
  205. package/dist/src/utils/colors/index.d.ts.map +1 -0
  206. package/dist/src/utils/colors/log-colors.d.ts +15 -0
  207. package/dist/src/utils/colors/log-colors.d.ts.map +1 -0
  208. package/dist/src/utils/colors/progress-colors.d.ts +25 -0
  209. package/dist/src/utils/colors/progress-colors.d.ts.map +1 -0
  210. package/dist/src/utils/colors/prompt-colors.d.ts +22 -0
  211. package/dist/src/utils/colors/prompt-colors.d.ts.map +1 -0
  212. package/dist/src/utils/colors/sidebar-colors.d.ts +50 -0
  213. package/dist/src/utils/colors/sidebar-colors.d.ts.map +1 -0
  214. package/dist/src/utils/colors/table-colors.d.ts +12 -0
  215. package/dist/src/utils/colors/table-colors.d.ts.map +1 -0
  216. package/dist/src/utils/filetype.d.ts +19 -0
  217. package/dist/src/utils/filetype.d.ts.map +1 -0
  218. package/dist/src/utils/format.d.ts +9 -0
  219. package/dist/src/utils/format.d.ts.map +1 -0
  220. package/dist/src/utils/index.d.ts +20 -0
  221. package/dist/src/utils/index.d.ts.map +1 -0
  222. package/dist/src/utils/stdout-printer.d.ts +10 -0
  223. package/dist/src/utils/stdout-printer.d.ts.map +1 -0
  224. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  225. package/dist/tsconfig.tsbuildinfo +1 -0
  226. package/dist/tsdown.config.d.mts +5 -0
  227. package/dist/tsdown.config.d.mts.map +1 -0
  228. package/dist/vitest.config.d.mts +3 -0
  229. package/dist/vitest.config.d.mts.map +1 -0
  230. package/lib/index.cjs +6349 -0
  231. package/lib/index.cjs.map +1 -0
  232. package/lib/index.d.cts +1720 -0
  233. package/lib/index.d.cts.map +1 -0
  234. package/lib/index.d.mts +1720 -0
  235. package/lib/index.d.mts.map +1 -0
  236. package/lib/index.mjs +6264 -0
  237. package/lib/index.mjs.map +1 -0
  238. package/lib/screen_manager_bridge-BpDgVu3e.cjs +3357 -0
  239. package/lib/screen_manager_bridge-BpDgVu3e.cjs.map +1 -0
  240. package/lib/screen_manager_bridge-CkV7637i.cjs +3 -0
  241. package/lib/screen_manager_bridge-DN2J6_k1.mjs +3 -0
  242. package/lib/screen_manager_bridge-Dfg4QUrl.mjs +3034 -0
  243. package/lib/screen_manager_bridge-Dfg4QUrl.mjs.map +1 -0
  244. package/package.json +43 -0
  245. package/project.json +60 -0
  246. package/src/__tests__/components/__snapshots__/filter_bar.spec.tsx.snap +2245 -0
  247. package/src/__tests__/components/__snapshots__/loading_message.spec.tsx.snap +1382 -0
  248. package/src/__tests__/components/__snapshots__/log_message.spec.tsx.snap +3169 -0
  249. package/src/__tests__/components/__snapshots__/progress_message.spec.tsx.snap +1743 -0
  250. package/src/__tests__/components/__snapshots__/prompt_renderer.spec.tsx.snap +3135 -0
  251. package/src/__tests__/components/__snapshots__/sidebar.spec.tsx.snap +2617 -0
  252. package/src/__tests__/components/filter_bar.spec.tsx +190 -0
  253. package/src/__tests__/components/loading_message.spec.tsx +110 -0
  254. package/src/__tests__/components/log_message.spec.tsx +166 -0
  255. package/src/__tests__/components/progress_message.spec.tsx +147 -0
  256. package/src/__tests__/components/prompt_renderer.spec.tsx +274 -0
  257. package/src/__tests__/components/sidebar.spec.tsx +305 -0
  258. package/src/__tests__/filter/filter_engine.spec.ts +325 -0
  259. package/src/__tests__/keyboard/keyboard_manager.spec.ts +557 -0
  260. package/src/__tests__/mocks/scm-mock.ts +5 -0
  261. package/src/__tests__/services/logger.spec.ts +630 -0
  262. package/src/__tests__/services/prompt.spec.ts +411 -0
  263. package/src/__tests__/services/screen.spec.ts +721 -0
  264. package/src/__tests__/setup.ts +43 -0
  265. package/src/__tests__/utils/factories.ts +354 -0
  266. package/src/__tests__/utils/filetype.spec.ts +195 -0
  267. package/src/__tests__/utils/format.spec.ts +178 -0
  268. package/src/__tests__/utils/render-utils.tsx +39 -0
  269. package/src/__tests__/utils/test-container.ts +48 -0
  270. package/src/components/file/file_log.tsx +241 -0
  271. package/src/components/file/index.ts +1 -0
  272. package/src/components/filter/filter_bar.tsx +79 -0
  273. package/src/components/filter/index.ts +1 -0
  274. package/src/components/help/help_overlay.tsx +100 -0
  275. package/src/components/help/index.ts +1 -0
  276. package/src/components/index.ts +15 -0
  277. package/src/components/log/index.ts +1 -0
  278. package/src/components/log/log_message.tsx +102 -0
  279. package/src/components/prompt/index.ts +2 -0
  280. package/src/components/prompt/prompt_renderer.tsx +346 -0
  281. package/src/components/screen/group_renderer.tsx +64 -0
  282. package/src/components/screen/index.ts +6 -0
  283. package/src/components/screen/loading_message.tsx +44 -0
  284. package/src/components/screen/message_renderer.tsx +108 -0
  285. package/src/components/screen/progress_message.tsx +60 -0
  286. package/src/components/screen/screen_bridge.tsx +149 -0
  287. package/src/components/screen/table_message.tsx +57 -0
  288. package/src/components/screen_manager_bridge.tsx +245 -0
  289. package/src/components/sidebar/index.ts +3 -0
  290. package/src/components/sidebar/sidebar.tsx +102 -0
  291. package/src/components/sidebar/sidebar_item.tsx +50 -0
  292. package/src/components/sidebar/sidebar_separator.tsx +13 -0
  293. package/src/context/index.ts +1 -0
  294. package/src/context/logger_context.tsx +109 -0
  295. package/src/factories/index.ts +1 -0
  296. package/src/factories/screen.factory.ts +22 -0
  297. package/src/filter/filter_engine.ts +113 -0
  298. package/src/filter/index.ts +1 -0
  299. package/src/hooks/index.ts +1 -0
  300. package/src/hooks/use_theme.ts +12 -0
  301. package/src/index.ts +64 -0
  302. package/src/keyboard/create_bindings.ts +457 -0
  303. package/src/keyboard/index.ts +2 -0
  304. package/src/keyboard/keyboard_manager.ts +233 -0
  305. package/src/overrides/console.logger.override.ts +61 -0
  306. package/src/overrides/index.ts +1 -0
  307. package/src/schemas/index.ts +3 -0
  308. package/src/schemas/logger-options.ts +13 -0
  309. package/src/schemas/prompt-options.ts +9 -0
  310. package/src/schemas/screen-options.ts +14 -0
  311. package/src/services/index.ts +4 -0
  312. package/src/services/logger.ts +369 -0
  313. package/src/services/prompt.ts +169 -0
  314. package/src/services/screen.ts +590 -0
  315. package/src/services/screen_manager.tsx +390 -0
  316. package/src/themes/dark.ts +173 -0
  317. package/src/themes/high-contrast.ts +178 -0
  318. package/src/themes/index.ts +4 -0
  319. package/src/themes/light.ts +172 -0
  320. package/src/themes/utils.ts +83 -0
  321. package/src/tokens/index.ts +3 -0
  322. package/src/tokens/logger.ts +10 -0
  323. package/src/tokens/prompt.ts +10 -0
  324. package/src/tokens/screen.ts +10 -0
  325. package/src/types/file.types.ts +60 -0
  326. package/src/types/filter.types.ts +51 -0
  327. package/src/types/index.ts +22 -0
  328. package/src/types/keyboard.types.ts +93 -0
  329. package/src/types/log.types.ts +61 -0
  330. package/src/types/message.types.ts +120 -0
  331. package/src/types/prompt.types.ts +106 -0
  332. package/src/types/screen.types.ts +124 -0
  333. package/src/types/theme.types.ts +252 -0
  334. package/src/utils/colors/file-colors.ts +9 -0
  335. package/src/utils/colors/helpers.ts +50 -0
  336. package/src/utils/colors/index.ts +20 -0
  337. package/src/utils/colors/log-colors.ts +56 -0
  338. package/src/utils/colors/progress-colors.ts +25 -0
  339. package/src/utils/colors/prompt-colors.ts +30 -0
  340. package/src/utils/colors/sidebar-colors.ts +52 -0
  341. package/src/utils/colors/table-colors.ts +11 -0
  342. package/src/utils/filetype.ts +64 -0
  343. package/src/utils/format.ts +52 -0
  344. package/src/utils/index.ts +11 -0
  345. package/src/utils/stdout-printer.ts +255 -0
  346. package/tsconfig.json +14 -0
  347. package/tsdown.config.mts +34 -0
  348. package/vitest.config.mts +10 -0
@@ -0,0 +1,557 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ import { formatKeyBinding, KeyboardManager } from '../../keyboard/keyboard_manager.ts'
4
+ import { createKeyboardContext } from '../utils/factories.ts'
5
+
6
+ import type { KeyBinding } from '../../types/index.ts'
7
+
8
+ describe('KeyboardManager', () => {
9
+ let manager: KeyboardManager
10
+
11
+ const createBinding = (overrides: Partial<KeyBinding> = {}): KeyBinding => ({
12
+ key: 'a',
13
+ handler: vi.fn(() => true),
14
+ description: 'Test binding',
15
+ category: 'general',
16
+ ...overrides,
17
+ })
18
+
19
+ beforeEach(() => {
20
+ manager = new KeyboardManager()
21
+ })
22
+
23
+ describe('construction', () => {
24
+ it('should create with empty bindings', () => {
25
+ const mgr = new KeyboardManager()
26
+ expect(mgr.getBindingsForHelp()).toHaveLength(0)
27
+ })
28
+
29
+ it('should create with initial bindings sorted by priority', () => {
30
+ const binding1 = createBinding({ key: 'a', priority: 1 })
31
+ const binding2 = createBinding({ key: 'b', priority: 10 })
32
+ const binding3 = createBinding({ key: 'c', priority: 5 })
33
+
34
+ const mgr = new KeyboardManager({
35
+ bindings: [binding1, binding2, binding3],
36
+ })
37
+
38
+ const bindings = mgr.getBindingsForHelp()
39
+ expect(bindings[0].key).toBe('b') // priority 10
40
+ expect(bindings[1].key).toBe('c') // priority 5
41
+ expect(bindings[2].key).toBe('a') // priority 1
42
+ })
43
+
44
+ it('should create with disabled keys', () => {
45
+ const binding = createBinding({ key: 'a' })
46
+ const mgr = new KeyboardManager({
47
+ bindings: [binding],
48
+ disabled: ['a'],
49
+ })
50
+
51
+ const context = createKeyboardContext()
52
+ const result = mgr.handleKey({ name: 'a' }, context)
53
+
54
+ expect(result).toBe(false)
55
+ expect(binding.handler).not.toHaveBeenCalled()
56
+ })
57
+ })
58
+
59
+ describe('addBindings', () => {
60
+ it('should add bindings and re-sort by priority', () => {
61
+ const existing = createBinding({ key: 'a', priority: 5 })
62
+ manager.addBindings([existing])
63
+
64
+ const newBinding = createBinding({ key: 'b', priority: 10 })
65
+ manager.addBindings([newBinding])
66
+
67
+ const bindings = manager.getBindingsForHelp()
68
+ expect(bindings[0].key).toBe('b') // Higher priority first
69
+ expect(bindings[1].key).toBe('a')
70
+ })
71
+ })
72
+
73
+ describe('removeBinding', () => {
74
+ it('should remove binding by key name', () => {
75
+ const binding = createBinding({ key: 'a' })
76
+ manager.addBindings([binding])
77
+
78
+ expect(manager.getBindingsForHelp()).toHaveLength(1)
79
+
80
+ manager.removeBinding('a')
81
+
82
+ expect(manager.getBindingsForHelp()).toHaveLength(0)
83
+ })
84
+
85
+ it('should remove binding with multiple keys (array)', () => {
86
+ const binding = createBinding({ key: ['a', 'b', 'c'] })
87
+ manager.addBindings([binding])
88
+
89
+ manager.removeBinding('b')
90
+
91
+ expect(manager.getBindingsForHelp()).toHaveLength(0)
92
+ })
93
+
94
+ it('should do nothing if key not found', () => {
95
+ const binding = createBinding({ key: 'a' })
96
+ manager.addBindings([binding])
97
+
98
+ manager.removeBinding('z')
99
+
100
+ expect(manager.getBindingsForHelp()).toHaveLength(1)
101
+ })
102
+ })
103
+
104
+ describe('disableKey / enableKey', () => {
105
+ it('should prevent disabled key from being matched', () => {
106
+ const binding = createBinding({ key: 'a' })
107
+ manager.addBindings([binding])
108
+
109
+ manager.disableKey('a')
110
+
111
+ const context = createKeyboardContext()
112
+ const result = manager.handleKey({ name: 'a' }, context)
113
+
114
+ expect(result).toBe(false)
115
+ expect(binding.handler).not.toHaveBeenCalled()
116
+ })
117
+
118
+ it('should allow re-enabled key to be matched again', () => {
119
+ const binding = createBinding({ key: 'a' })
120
+ manager.addBindings([binding])
121
+
122
+ manager.disableKey('a')
123
+ manager.enableKey('a')
124
+
125
+ const context = createKeyboardContext()
126
+ const result = manager.handleKey({ name: 'a' }, context)
127
+
128
+ expect(result).toBe(true)
129
+ expect(binding.handler).toHaveBeenCalled()
130
+ })
131
+ })
132
+
133
+ describe('handleKey', () => {
134
+ it('should match key by name', () => {
135
+ const binding = createBinding({ key: 'enter' })
136
+ manager.addBindings([binding])
137
+
138
+ const context = createKeyboardContext()
139
+ const result = manager.handleKey({ name: 'enter' }, context)
140
+
141
+ expect(result).toBe(true)
142
+ expect(binding.handler).toHaveBeenCalled()
143
+ })
144
+
145
+ it('should match key by sequence', () => {
146
+ const binding = createBinding({ key: '\x1b[A' }) // Up arrow sequence
147
+ manager.addBindings([binding])
148
+
149
+ const context = createKeyboardContext()
150
+ const result = manager.handleKey({ name: 'up', sequence: '\x1b[A' }, context)
151
+
152
+ expect(result).toBe(true)
153
+ expect(binding.handler).toHaveBeenCalled()
154
+ })
155
+
156
+ it('should respect ctrl modifier', () => {
157
+ const bindingWithCtrl = createBinding({ key: 'c', ctrl: true })
158
+ const bindingWithoutCtrl = createBinding({ key: 'c', ctrl: false })
159
+ manager.addBindings([bindingWithCtrl, bindingWithoutCtrl])
160
+
161
+ const context = createKeyboardContext()
162
+
163
+ // Should match binding with ctrl when ctrl is pressed
164
+ manager.handleKey({ name: 'c', ctrl: true }, context)
165
+ expect(bindingWithCtrl.handler).toHaveBeenCalled()
166
+ expect(bindingWithoutCtrl.handler).not.toHaveBeenCalled()
167
+ })
168
+
169
+ it('should respect meta modifier', () => {
170
+ const binding = createBinding({ key: 's', meta: true })
171
+ manager.addBindings([binding])
172
+
173
+ const context = createKeyboardContext()
174
+
175
+ // Without meta - should not match
176
+ let result = manager.handleKey({ name: 's', meta: false }, context)
177
+ expect(result).toBe(false)
178
+
179
+ // With meta - should match
180
+ result = manager.handleKey({ name: 's', meta: true }, context)
181
+ expect(result).toBe(true)
182
+ })
183
+
184
+ it('should respect shift modifier', () => {
185
+ const binding = createBinding({ key: 'tab', shift: true })
186
+ manager.addBindings([binding])
187
+
188
+ const context = createKeyboardContext()
189
+
190
+ // Without shift - should not match
191
+ let result = manager.handleKey({ name: 'tab', shift: false }, context)
192
+ expect(result).toBe(false)
193
+
194
+ // With shift - should match
195
+ result = manager.handleKey({ name: 'tab', shift: true }, context)
196
+ expect(result).toBe(true)
197
+ })
198
+
199
+ it('should return true when handler consumed event', () => {
200
+ const binding = createBinding({
201
+ key: 'a',
202
+ handler: vi.fn(() => true),
203
+ })
204
+ manager.addBindings([binding])
205
+
206
+ const context = createKeyboardContext()
207
+ const result = manager.handleKey({ name: 'a' }, context)
208
+
209
+ expect(result).toBe(true)
210
+ })
211
+
212
+ it('should return false when handler returns false', () => {
213
+ const binding = createBinding({
214
+ key: 'a',
215
+ handler: vi.fn(() => false),
216
+ })
217
+ manager.addBindings([binding])
218
+
219
+ const context = createKeyboardContext()
220
+ const result = manager.handleKey({ name: 'a' }, context)
221
+
222
+ expect(result).toBe(false)
223
+ })
224
+
225
+ it('should return false when no binding matches', () => {
226
+ const binding = createBinding({ key: 'a' })
227
+ manager.addBindings([binding])
228
+
229
+ const context = createKeyboardContext()
230
+ const result = manager.handleKey({ name: 'z' }, context)
231
+
232
+ expect(result).toBe(false)
233
+ })
234
+
235
+ it('should match binding with multiple keys', () => {
236
+ const binding = createBinding({ key: ['up', 'k'] })
237
+ manager.addBindings([binding])
238
+
239
+ const context = createKeyboardContext()
240
+
241
+ manager.handleKey({ name: 'up' }, context)
242
+ expect(binding.handler).toHaveBeenCalledTimes(1)
243
+
244
+ manager.handleKey({ name: 'k' }, context)
245
+ expect(binding.handler).toHaveBeenCalledTimes(2)
246
+ })
247
+ })
248
+
249
+ describe('condition matching', () => {
250
+ it('should match hasPrompt condition', () => {
251
+ const binding = createBinding({
252
+ key: 'enter',
253
+ when: { hasPrompt: true },
254
+ })
255
+ manager.addBindings([binding])
256
+
257
+ // Without prompt - should not match
258
+ let result = manager.handleKey({ name: 'enter' }, createKeyboardContext({ hasPrompt: false }))
259
+ expect(result).toBe(false)
260
+
261
+ // With prompt - should match
262
+ result = manager.handleKey({ name: 'enter' }, createKeyboardContext({ hasPrompt: true }))
263
+ expect(result).toBe(true)
264
+ })
265
+
266
+ it('should match inInputMode condition', () => {
267
+ const binding = createBinding({
268
+ key: 'backspace',
269
+ when: { inInputMode: true },
270
+ })
271
+ manager.addBindings([binding])
272
+
273
+ // Not in input mode - should not match
274
+ let result = manager.handleKey(
275
+ { name: 'backspace' },
276
+ createKeyboardContext({ inInputMode: false }),
277
+ )
278
+ expect(result).toBe(false)
279
+
280
+ // In input mode - should match
281
+ result = manager.handleKey(
282
+ { name: 'backspace' },
283
+ createKeyboardContext({ inInputMode: true }),
284
+ )
285
+ expect(result).toBe(true)
286
+ })
287
+
288
+ it('should match focusArea condition', () => {
289
+ const binding = createBinding({
290
+ key: 'enter',
291
+ when: { focusArea: 'sidebar' },
292
+ })
293
+ manager.addBindings([binding])
294
+
295
+ // Wrong focus area - should not match
296
+ let result = manager.handleKey(
297
+ { name: 'enter' },
298
+ createKeyboardContext({ focusArea: 'content' }),
299
+ )
300
+ expect(result).toBe(false)
301
+
302
+ // Correct focus area - should match
303
+ result = manager.handleKey(
304
+ { name: 'enter' },
305
+ createKeyboardContext({ focusArea: 'sidebar' }),
306
+ )
307
+ expect(result).toBe(true)
308
+ })
309
+
310
+ it('should match isFilterActive condition', () => {
311
+ const binding = createBinding({
312
+ key: 'escape',
313
+ when: { isFilterActive: true },
314
+ })
315
+ manager.addBindings([binding])
316
+
317
+ // Filter not active - should not match
318
+ let result = manager.handleKey(
319
+ { name: 'escape' },
320
+ createKeyboardContext({ isFilterActive: false }),
321
+ )
322
+ expect(result).toBe(false)
323
+
324
+ // Filter active - should match
325
+ result = manager.handleKey(
326
+ { name: 'escape' },
327
+ createKeyboardContext({ isFilterActive: true }),
328
+ )
329
+ expect(result).toBe(true)
330
+ })
331
+
332
+ it('should match isHelpVisible condition', () => {
333
+ const binding = createBinding({
334
+ key: 'escape',
335
+ when: { isHelpVisible: true },
336
+ })
337
+ manager.addBindings([binding])
338
+
339
+ // Help not visible - should not match
340
+ let result = manager.handleKey(
341
+ { name: 'escape' },
342
+ createKeyboardContext({ isHelpVisible: false }),
343
+ )
344
+ expect(result).toBe(false)
345
+
346
+ // Help visible - should match
347
+ result = manager.handleKey(
348
+ { name: 'escape' },
349
+ createKeyboardContext({ isHelpVisible: true }),
350
+ )
351
+ expect(result).toBe(true)
352
+ })
353
+
354
+ it('should match hasSidebar condition', () => {
355
+ const binding = createBinding({
356
+ key: 'tab',
357
+ when: { hasSidebar: true },
358
+ })
359
+ manager.addBindings([binding])
360
+
361
+ // No sidebar - should not match
362
+ let result = manager.handleKey({ name: 'tab' }, createKeyboardContext({ hasSidebar: false }))
363
+ expect(result).toBe(false)
364
+
365
+ // Has sidebar - should match
366
+ result = manager.handleKey({ name: 'tab' }, createKeyboardContext({ hasSidebar: true }))
367
+ expect(result).toBe(true)
368
+ })
369
+
370
+ it('should always match binding without condition', () => {
371
+ const binding = createBinding({ key: 'a' }) // No 'when' condition
372
+ manager.addBindings([binding])
373
+
374
+ const context = createKeyboardContext()
375
+ const result = manager.handleKey({ name: 'a' }, context)
376
+
377
+ expect(result).toBe(true)
378
+ })
379
+
380
+ it('should match multiple conditions (AND logic)', () => {
381
+ const binding = createBinding({
382
+ key: 'enter',
383
+ when: {
384
+ hasPrompt: true,
385
+ focusArea: 'content',
386
+ },
387
+ })
388
+ manager.addBindings([binding])
389
+
390
+ // Only hasPrompt true - should not match
391
+ let result = manager.handleKey(
392
+ { name: 'enter' },
393
+ createKeyboardContext({ hasPrompt: true, focusArea: 'sidebar' }),
394
+ )
395
+ expect(result).toBe(false)
396
+
397
+ // Both conditions true - should match
398
+ result = manager.handleKey(
399
+ { name: 'enter' },
400
+ createKeyboardContext({ hasPrompt: true, focusArea: 'content' }),
401
+ )
402
+ expect(result).toBe(true)
403
+ })
404
+ })
405
+
406
+ describe('getBindingsForHelp', () => {
407
+ it('should return only bindings with descriptions', () => {
408
+ const withDesc = createBinding({ key: 'a', description: 'Has description' })
409
+ const withoutDesc = createBinding({ key: 'b', description: '' })
410
+ const alsoWithDesc = createBinding({ key: 'c', description: 'Also has description' })
411
+
412
+ manager.addBindings([withDesc, withoutDesc, alsoWithDesc])
413
+
414
+ const helpBindings = manager.getBindingsForHelp()
415
+ expect(helpBindings).toHaveLength(2)
416
+ expect(helpBindings.map((b) => b.key)).toContain('a')
417
+ expect(helpBindings.map((b) => b.key)).toContain('c')
418
+ })
419
+ })
420
+
421
+ describe('getBindingsByCategory', () => {
422
+ it('should group bindings by category', () => {
423
+ const general = createBinding({ key: 'a', category: 'general', description: 'General' })
424
+ const nav1 = createBinding({ key: 'j', category: 'navigation', description: 'Down' })
425
+ const nav2 = createBinding({ key: 'k', category: 'navigation', description: 'Up' })
426
+ const screen = createBinding({ key: '1', category: 'screen', description: 'Screen 1' })
427
+
428
+ manager.addBindings([general, nav1, nav2, screen])
429
+
430
+ const grouped = manager.getBindingsByCategory()
431
+
432
+ expect(grouped.general).toHaveLength(1)
433
+ expect(grouped.navigation).toHaveLength(2)
434
+ expect(grouped.screen).toHaveLength(1)
435
+ expect(grouped.prompt).toHaveLength(0)
436
+ expect(grouped.filter).toHaveLength(0)
437
+ })
438
+
439
+ it('should exclude bindings without descriptions', () => {
440
+ const withDesc = createBinding({ key: 'a', category: 'general', description: 'Has desc' })
441
+ const withoutDesc = createBinding({ key: 'b', category: 'general', description: '' })
442
+
443
+ manager.addBindings([withDesc, withoutDesc])
444
+
445
+ const grouped = manager.getBindingsByCategory()
446
+
447
+ expect(grouped.general).toHaveLength(1)
448
+ })
449
+ })
450
+ })
451
+
452
+ describe('formatKeyBinding', () => {
453
+ it('should format single key', () => {
454
+ const binding: KeyBinding = {
455
+ key: 'a',
456
+ handler: () => true,
457
+ description: 'Test',
458
+ category: 'general',
459
+ }
460
+
461
+ expect(formatKeyBinding(binding)).toBe('a')
462
+ })
463
+
464
+ it('should format key with ctrl modifier', () => {
465
+ const binding: KeyBinding = {
466
+ key: 'c',
467
+ ctrl: true,
468
+ handler: () => true,
469
+ description: 'Test',
470
+ category: 'general',
471
+ }
472
+
473
+ expect(formatKeyBinding(binding)).toBe('Ctrl+c')
474
+ })
475
+
476
+ it('should format key with meta modifier', () => {
477
+ const binding: KeyBinding = {
478
+ key: 's',
479
+ meta: true,
480
+ handler: () => true,
481
+ description: 'Test',
482
+ category: 'general',
483
+ }
484
+
485
+ expect(formatKeyBinding(binding)).toBe('Cmd+s')
486
+ })
487
+
488
+ it('should format key with shift modifier', () => {
489
+ const binding: KeyBinding = {
490
+ key: 'tab',
491
+ shift: true,
492
+ handler: () => true,
493
+ description: 'Test',
494
+ category: 'general',
495
+ }
496
+
497
+ expect(formatKeyBinding(binding)).toBe('Shift+Tab')
498
+ })
499
+
500
+ it('should format key with multiple modifiers', () => {
501
+ const binding: KeyBinding = {
502
+ key: 's',
503
+ ctrl: true,
504
+ shift: true,
505
+ handler: () => true,
506
+ description: 'Test',
507
+ category: 'general',
508
+ }
509
+
510
+ expect(formatKeyBinding(binding)).toBe('Ctrl+Shift+s')
511
+ })
512
+
513
+ it('should format special keys with symbols', () => {
514
+ const testCases: Array<{ key: string; expected: string }> = [
515
+ { key: 'up', expected: '↑' },
516
+ { key: 'down', expected: '↓' },
517
+ { key: 'left', expected: '←' },
518
+ { key: 'right', expected: '→' },
519
+ { key: 'return', expected: 'Enter' },
520
+ { key: 'escape', expected: 'Esc' },
521
+ { key: 'space', expected: 'Space' },
522
+ { key: 'tab', expected: 'Tab' },
523
+ ]
524
+
525
+ for (const { key, expected } of testCases) {
526
+ const binding: KeyBinding = {
527
+ key,
528
+ handler: () => true,
529
+ description: 'Test',
530
+ category: 'general',
531
+ }
532
+ expect(formatKeyBinding(binding)).toBe(expected)
533
+ }
534
+ })
535
+
536
+ it('should format multiple keys with /', () => {
537
+ const binding: KeyBinding = {
538
+ key: ['up', 'k'],
539
+ handler: () => true,
540
+ description: 'Test',
541
+ category: 'general',
542
+ }
543
+
544
+ expect(formatKeyBinding(binding)).toBe('↑/k')
545
+ })
546
+
547
+ it('should format multiple special keys', () => {
548
+ const binding: KeyBinding = {
549
+ key: ['return', 'space'],
550
+ handler: () => true,
551
+ description: 'Test',
552
+ category: 'general',
553
+ }
554
+
555
+ expect(formatKeyBinding(binding)).toBe('Enter/Space')
556
+ })
557
+ })
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Mock for .scm (tree-sitter query) files from OpenTUI.
3
+ * These files can't be loaded in Node.js test environment.
4
+ */
5
+ export default ''