agents-inc 0.151.0 → 0.152.1

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 (254) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +25 -25
  3. package/dist/{chunk-YA4QQKKE.js → chunk-5GUOIJSG.js} +3 -7
  4. package/dist/{chunk-YA4QQKKE.js.map → chunk-5GUOIJSG.js.map} +1 -1
  5. package/dist/{chunk-44LLPS2N.js → chunk-5UWKOOIS.js} +6 -26
  6. package/dist/chunk-5UWKOOIS.js.map +1 -0
  7. package/dist/{chunk-O5ASLZ2K.js → chunk-76ADG7LQ.js} +1 -5
  8. package/dist/{chunk-O5ASLZ2K.js.map → chunk-76ADG7LQ.js.map} +1 -1
  9. package/dist/{chunk-WK4J2CHH.js → chunk-BNSRA6HL.js} +13 -18
  10. package/dist/{chunk-WK4J2CHH.js.map → chunk-BNSRA6HL.js.map} +1 -1
  11. package/dist/{chunk-FYPUUYSG.js → chunk-DTSG5XZB.js} +3 -7
  12. package/dist/{chunk-FYPUUYSG.js.map → chunk-DTSG5XZB.js.map} +1 -1
  13. package/dist/{chunk-NGLQ5W7U.js → chunk-E5X6RJ4E.js} +5 -15
  14. package/dist/chunk-E5X6RJ4E.js.map +1 -0
  15. package/dist/{chunk-TVHRMRD3.js → chunk-EEPELKOM.js} +4008 -4084
  16. package/dist/chunk-EEPELKOM.js.map +1 -0
  17. package/dist/{chunk-NIKUMXE7.js → chunk-F7T5AFFN.js} +1 -5
  18. package/dist/{chunk-NIKUMXE7.js.map → chunk-F7T5AFFN.js.map} +1 -1
  19. package/dist/{chunk-LAXAQ3PZ.js → chunk-FVDKAVOL.js} +10 -14
  20. package/dist/{chunk-LAXAQ3PZ.js.map → chunk-FVDKAVOL.js.map} +1 -1
  21. package/dist/{chunk-BNE4HHIH.js → chunk-GRVKW3CC.js} +2 -6
  22. package/dist/{chunk-BNE4HHIH.js.map → chunk-GRVKW3CC.js.map} +1 -1
  23. package/dist/{chunk-3XFUQ6OL.js → chunk-IHUY4M22.js} +4 -8
  24. package/dist/{chunk-3XFUQ6OL.js.map → chunk-IHUY4M22.js.map} +1 -1
  25. package/dist/{chunk-OZZGXBCN.js → chunk-IUQBOCSU.js} +1 -5
  26. package/dist/{chunk-OZZGXBCN.js.map → chunk-IUQBOCSU.js.map} +1 -1
  27. package/dist/{chunk-LYYXX2W5.js → chunk-J27W6TXS.js} +4 -6
  28. package/dist/{chunk-LYYXX2W5.js.map → chunk-J27W6TXS.js.map} +1 -1
  29. package/dist/chunk-JFLVEFFP.js +16 -0
  30. package/dist/chunk-JFLVEFFP.js.map +1 -0
  31. package/dist/{chunk-PV2HNW4I.js → chunk-KFB5CRXZ.js} +7 -12
  32. package/dist/{chunk-PV2HNW4I.js.map → chunk-KFB5CRXZ.js.map} +1 -1
  33. package/dist/{chunk-SYUOXSPU.js → chunk-KR65TPKC.js} +168 -14
  34. package/dist/chunk-KR65TPKC.js.map +1 -0
  35. package/dist/{chunk-KOPEM3Q7.js → chunk-KXYVJGJP.js} +2 -6
  36. package/dist/{chunk-KOPEM3Q7.js.map → chunk-KXYVJGJP.js.map} +1 -1
  37. package/dist/{chunk-TYJLR32W.js → chunk-L7SZXUBJ.js} +9 -14
  38. package/dist/{chunk-TYJLR32W.js.map → chunk-L7SZXUBJ.js.map} +1 -1
  39. package/dist/{chunk-NHCGC6DG.js → chunk-LASI35KT.js} +4 -9
  40. package/dist/{chunk-NHCGC6DG.js.map → chunk-LASI35KT.js.map} +1 -1
  41. package/dist/{chunk-VG2TVBFN.js → chunk-LDP5ASDM.js} +2 -6
  42. package/dist/{chunk-VG2TVBFN.js.map → chunk-LDP5ASDM.js.map} +1 -1
  43. package/dist/{chunk-5V2O6C3T.js → chunk-LZ52XEPR.js} +4 -8
  44. package/dist/{chunk-5V2O6C3T.js.map → chunk-LZ52XEPR.js.map} +1 -1
  45. package/dist/{chunk-WFWCZEMS.js → chunk-M7HO7NBK.js} +4 -8
  46. package/dist/{chunk-WFWCZEMS.js.map → chunk-M7HO7NBK.js.map} +1 -1
  47. package/dist/{chunk-XA57SX4T.js → chunk-MJ2KHG24.js} +4 -8
  48. package/dist/{chunk-XA57SX4T.js.map → chunk-MJ2KHG24.js.map} +1 -1
  49. package/dist/{chunk-DAG2ULNF.js → chunk-MM7CTTAV.js} +2 -6
  50. package/dist/chunk-MM7CTTAV.js.map +1 -0
  51. package/dist/{chunk-UFURYGD3.js → chunk-MQ7FLIYS.js} +2 -6
  52. package/dist/{chunk-UFURYGD3.js.map → chunk-MQ7FLIYS.js.map} +1 -1
  53. package/dist/{chunk-SI5FOARI.js → chunk-MVR3SYMN.js} +6 -8
  54. package/dist/chunk-MVR3SYMN.js.map +1 -0
  55. package/dist/{chunk-UTFN6UJS.js → chunk-MWG4VU64.js} +12 -17
  56. package/dist/{chunk-UTFN6UJS.js.map → chunk-MWG4VU64.js.map} +1 -1
  57. package/dist/{chunk-ES3NJZON.js → chunk-NR3OZGL4.js} +1 -5
  58. package/dist/{chunk-ES3NJZON.js.map → chunk-NR3OZGL4.js.map} +1 -1
  59. package/dist/{chunk-GYG5SE2D.js → chunk-ODNDXQH2.js} +2 -6
  60. package/dist/{chunk-GYG5SE2D.js.map → chunk-ODNDXQH2.js.map} +1 -1
  61. package/dist/{chunk-2RSNKBXC.js → chunk-P44SXVH3.js} +6 -10
  62. package/dist/{chunk-2RSNKBXC.js.map → chunk-P44SXVH3.js.map} +1 -1
  63. package/dist/{chunk-3LIU2ZEF.js → chunk-PMM2MYMA.js} +3 -16
  64. package/dist/{chunk-3LIU2ZEF.js.map → chunk-PMM2MYMA.js.map} +1 -1
  65. package/dist/{chunk-QTFQEYDA.js → chunk-PZIZNMHK.js} +24 -36
  66. package/dist/chunk-PZIZNMHK.js.map +1 -0
  67. package/dist/{chunk-C55DN7GE.js → chunk-Q2KG7UV3.js} +3 -5
  68. package/dist/chunk-Q2KG7UV3.js.map +1 -0
  69. package/dist/{chunk-QQLRX4KI.js → chunk-Q45ZU6HD.js} +2 -6
  70. package/dist/{chunk-QQLRX4KI.js.map → chunk-Q45ZU6HD.js.map} +1 -1
  71. package/dist/{chunk-6RBQBOBS.js → chunk-QRDTLVXG.js} +9 -14
  72. package/dist/{chunk-6RBQBOBS.js.map → chunk-QRDTLVXG.js.map} +1 -1
  73. package/dist/{chunk-YBVA7GPW.js → chunk-QZZJFYUQ.js} +3 -7
  74. package/dist/{chunk-YBVA7GPW.js.map → chunk-QZZJFYUQ.js.map} +1 -1
  75. package/dist/{chunk-OJIESW5B.js → chunk-R5NOI2LP.js} +13 -17
  76. package/dist/{chunk-OJIESW5B.js.map → chunk-R5NOI2LP.js.map} +1 -1
  77. package/dist/{chunk-DUVWGO6E.js → chunk-RNJWAKMO.js} +3 -7
  78. package/dist/{chunk-DUVWGO6E.js.map → chunk-RNJWAKMO.js.map} +1 -1
  79. package/dist/{chunk-4R6PGHQQ.js → chunk-SBHBBZKF.js} +7 -23
  80. package/dist/chunk-SBHBBZKF.js.map +1 -0
  81. package/dist/{chunk-NM3EDGB5.js → chunk-SKQWRUPX.js} +10 -14
  82. package/dist/{chunk-NM3EDGB5.js.map → chunk-SKQWRUPX.js.map} +1 -1
  83. package/dist/{chunk-Y4CXDA3I.js → chunk-STMHDOZD.js} +4 -21
  84. package/dist/chunk-STMHDOZD.js.map +1 -0
  85. package/dist/{chunk-SHHOMVWC.js → chunk-SX5H6TSC.js} +7 -11
  86. package/dist/chunk-SX5H6TSC.js.map +1 -0
  87. package/dist/{chunk-RBHYLCUB.js → chunk-TJJPWUMA.js} +2 -6
  88. package/dist/{chunk-RBHYLCUB.js.map → chunk-TJJPWUMA.js.map} +1 -1
  89. package/dist/{chunk-IOJR5TOY.js → chunk-TSIU7DME.js} +1 -5
  90. package/dist/{chunk-IOJR5TOY.js.map → chunk-TSIU7DME.js.map} +1 -1
  91. package/dist/{chunk-EVXG3X6G.js → chunk-TUPYW2IQ.js} +3 -7
  92. package/dist/{chunk-EVXG3X6G.js.map → chunk-TUPYW2IQ.js.map} +1 -1
  93. package/dist/{chunk-YU2VGX5R.js → chunk-UBEDZ4FD.js} +1 -8
  94. package/dist/{chunk-YU2VGX5R.js.map → chunk-UBEDZ4FD.js.map} +1 -1
  95. package/dist/{chunk-VEP23B5W.js → chunk-VASLW636.js} +1 -5
  96. package/dist/{chunk-VEP23B5W.js.map → chunk-VASLW636.js.map} +1 -1
  97. package/dist/{chunk-PG4G4IIK.js → chunk-VY6MNOXL.js} +2 -6
  98. package/dist/{chunk-PG4G4IIK.js.map → chunk-VY6MNOXL.js.map} +1 -1
  99. package/dist/{chunk-IHO75PMZ.js → chunk-WBCPLH66.js} +15 -13
  100. package/dist/chunk-WBCPLH66.js.map +1 -0
  101. package/dist/{chunk-DG2NGZ7S.js → chunk-WKTHWQKM.js} +2 -7
  102. package/dist/{chunk-DG2NGZ7S.js.map → chunk-WKTHWQKM.js.map} +1 -1
  103. package/dist/{chunk-EZ7STXUV.js → chunk-XHUODHZ4.js} +19 -25
  104. package/dist/{chunk-EZ7STXUV.js.map → chunk-XHUODHZ4.js.map} +1 -1
  105. package/dist/{chunk-KIR7VYQD.js → chunk-XVAQG2CV.js} +1 -5
  106. package/dist/{chunk-KIR7VYQD.js.map → chunk-XVAQG2CV.js.map} +1 -1
  107. package/dist/{chunk-P3PN5SVI.js → chunk-ZL7PFNHN.js} +2 -6
  108. package/dist/{chunk-P3PN5SVI.js.map → chunk-ZL7PFNHN.js.map} +1 -1
  109. package/dist/{chunk-3YGUHWLR.js → chunk-ZPPHXXAP.js} +9 -15
  110. package/dist/{chunk-3YGUHWLR.js.map → chunk-ZPPHXXAP.js.map} +1 -1
  111. package/dist/commands/build/marketplace.js +10 -15
  112. package/dist/commands/build/marketplace.js.map +1 -1
  113. package/dist/commands/build/plugins.js +9 -14
  114. package/dist/commands/build/plugins.js.map +1 -1
  115. package/dist/commands/compile.js +14 -19
  116. package/dist/commands/compile.js.map +1 -1
  117. package/dist/commands/doctor.js +13 -18
  118. package/dist/commands/doctor.js.map +1 -1
  119. package/dist/commands/edit.js +51 -55
  120. package/dist/commands/edit.js.map +1 -1
  121. package/dist/commands/eject.js +13 -18
  122. package/dist/commands/eject.js.map +1 -1
  123. package/dist/commands/import/skill.js +9 -14
  124. package/dist/commands/import/skill.js.map +1 -1
  125. package/dist/commands/init.js +50 -51
  126. package/dist/commands/list.js +14 -16
  127. package/dist/commands/list.js.map +1 -1
  128. package/dist/commands/new/agent.js +12 -16
  129. package/dist/commands/new/agent.js.map +1 -1
  130. package/dist/commands/new/marketplace.js +15 -20
  131. package/dist/commands/new/marketplace.js.map +1 -1
  132. package/dist/commands/new/skill.js +11 -13
  133. package/dist/commands/search.js +14 -19
  134. package/dist/commands/search.js.map +1 -1
  135. package/dist/commands/uninstall.js +17 -21
  136. package/dist/commands/uninstall.js.map +1 -1
  137. package/dist/commands/update.js +17 -21
  138. package/dist/commands/update.js.map +1 -1
  139. package/dist/commands/validate.js +10 -16
  140. package/dist/commands/validate.js.map +1 -1
  141. package/dist/components/common/confirm.js +1 -2
  142. package/dist/components/common/prompt-confirm.js +2 -2
  143. package/dist/components/common/select-list.js +3 -4
  144. package/dist/components/common/spinner.js +1 -2
  145. package/dist/components/wizard/category-grid.js +8 -9
  146. package/dist/components/wizard/checkbox-grid.js +6 -7
  147. package/dist/components/wizard/domain-selection.js +14 -15
  148. package/dist/components/wizard/run-wizard-session.js +40 -40
  149. package/dist/components/wizard/scroll-affordance.js +2 -3
  150. package/dist/components/wizard/search-modal.js +6 -7
  151. package/dist/components/wizard/selection-card.js +2 -3
  152. package/dist/components/wizard/skill-agent-summary.js +8 -9
  153. package/dist/components/wizard/source-grid.js +13 -14
  154. package/dist/components/wizard/stack-selection.js +12 -13
  155. package/dist/components/wizard/step-agents.js +14 -15
  156. package/dist/components/wizard/step-build.js +14 -15
  157. package/dist/components/wizard/step-confirm.js +13 -14
  158. package/dist/components/wizard/step-settings.js +11 -12
  159. package/dist/components/wizard/step-sources.js +20 -21
  160. package/dist/components/wizard/step-stack.js +13 -14
  161. package/dist/components/wizard/summary-panel.js +12 -13
  162. package/dist/components/wizard/toast.js +2 -3
  163. package/dist/components/wizard/wizard-layout.js +18 -19
  164. package/dist/components/wizard/wizard-tabs.js +8 -9
  165. package/dist/components/wizard/wizard.js +38 -39
  166. package/dist/config-exports.js +1 -7
  167. package/dist/config-exports.js.map +1 -1
  168. package/dist/copy-local-skills-6EMVN7RB.js +14 -0
  169. package/dist/hooks/init.js +50 -54
  170. package/dist/hooks/init.js.map +1 -1
  171. package/dist/index.js +0 -4
  172. package/dist/index.js.map +1 -1
  173. package/dist/load-agent-defs-BWOUVEAR.js +15 -0
  174. package/dist/{loader-E2ERJYVA.js → loader-5JP4S7CJ.js} +4 -5
  175. package/dist/recompile-project-agents-6DL45TDI.js +19 -0
  176. package/dist/{source-loader-5UNRU7EZ.js → source-loader-MZYCPL2S.js} +7 -8
  177. package/dist/stores/wizard-store.js +7 -8
  178. package/package.json +15 -14
  179. package/dist/chunk-44LLPS2N.js.map +0 -1
  180. package/dist/chunk-4R6PGHQQ.js.map +0 -1
  181. package/dist/chunk-7QIHKQL5.js +0 -181
  182. package/dist/chunk-7QIHKQL5.js.map +0 -1
  183. package/dist/chunk-C55DN7GE.js.map +0 -1
  184. package/dist/chunk-DAG2ULNF.js.map +0 -1
  185. package/dist/chunk-DIBCRIMR.js +0 -36
  186. package/dist/chunk-DIBCRIMR.js.map +0 -1
  187. package/dist/chunk-EKWAUOQB.js +0 -96
  188. package/dist/chunk-EKWAUOQB.js.map +0 -1
  189. package/dist/chunk-I3XKEGHM.js +0 -16383
  190. package/dist/chunk-I3XKEGHM.js.map +0 -1
  191. package/dist/chunk-IHO75PMZ.js.map +0 -1
  192. package/dist/chunk-K3OU6TGX.js +0 -77
  193. package/dist/chunk-K3OU6TGX.js.map +0 -1
  194. package/dist/chunk-KSZ6PCY4.js +0 -43
  195. package/dist/chunk-KSZ6PCY4.js.map +0 -1
  196. package/dist/chunk-NGLQ5W7U.js.map +0 -1
  197. package/dist/chunk-P56WJ5FA.js +0 -50
  198. package/dist/chunk-P56WJ5FA.js.map +0 -1
  199. package/dist/chunk-QTFQEYDA.js.map +0 -1
  200. package/dist/chunk-SHHOMVWC.js.map +0 -1
  201. package/dist/chunk-SI5FOARI.js.map +0 -1
  202. package/dist/chunk-SYUOXSPU.js.map +0 -1
  203. package/dist/chunk-THKIOI32.js +0 -939
  204. package/dist/chunk-THKIOI32.js.map +0 -1
  205. package/dist/chunk-TVHRMRD3.js.map +0 -1
  206. package/dist/chunk-V7QOSGQA.js +0 -298
  207. package/dist/chunk-V7QOSGQA.js.map +0 -1
  208. package/dist/chunk-Y4CXDA3I.js.map +0 -1
  209. package/dist/components/common/confirm.test.js +0 -203
  210. package/dist/components/common/confirm.test.js.map +0 -1
  211. package/dist/components/wizard/category-grid.test.js +0 -1253
  212. package/dist/components/wizard/category-grid.test.js.map +0 -1
  213. package/dist/components/wizard/checkbox-grid.test.js +0 -264
  214. package/dist/components/wizard/checkbox-grid.test.js.map +0 -1
  215. package/dist/components/wizard/search-modal.test.js +0 -212
  216. package/dist/components/wizard/search-modal.test.js.map +0 -1
  217. package/dist/components/wizard/source-grid.test.js +0 -1094
  218. package/dist/components/wizard/source-grid.test.js.map +0 -1
  219. package/dist/components/wizard/step-agents.test.js +0 -204
  220. package/dist/components/wizard/step-agents.test.js.map +0 -1
  221. package/dist/components/wizard/step-build.test.js +0 -552
  222. package/dist/components/wizard/step-build.test.js.map +0 -1
  223. package/dist/components/wizard/step-confirm.test.js +0 -744
  224. package/dist/components/wizard/step-confirm.test.js.map +0 -1
  225. package/dist/components/wizard/step-settings.test.js +0 -244
  226. package/dist/components/wizard/step-settings.test.js.map +0 -1
  227. package/dist/components/wizard/step-sources.test.js +0 -155
  228. package/dist/components/wizard/step-sources.test.js.map +0 -1
  229. package/dist/components/wizard/step-stack.test.js +0 -262
  230. package/dist/components/wizard/step-stack.test.js.map +0 -1
  231. package/dist/components/wizard/summary-panel.test.js +0 -317
  232. package/dist/components/wizard/summary-panel.test.js.map +0 -1
  233. package/dist/components/wizard/wizard-layout.test.js +0 -200
  234. package/dist/components/wizard/wizard-layout.test.js.map +0 -1
  235. package/dist/components/wizard/wizard-tabs.test.js +0 -290
  236. package/dist/components/wizard/wizard-tabs.test.js.map +0 -1
  237. package/dist/copy-local-skills-UPA2GUZV.js +0 -15
  238. package/dist/dist-VNJNNJK7.js +0 -550
  239. package/dist/dist-VNJNNJK7.js.map +0 -1
  240. package/dist/load-agent-defs-SB4XAOVB.js +0 -16
  241. package/dist/magic-string.es-5TV73NJD.js +0 -1316
  242. package/dist/magic-string.es-5TV73NJD.js.map +0 -1
  243. package/dist/recompile-project-agents-U4ZY2GDX.js +0 -20
  244. package/dist/source-manager-3O54SIIB.js +0 -18
  245. package/dist/source-manager-3O54SIIB.js.map +0 -1
  246. package/dist/stores/d227-same-scope-tombstone-duplicate.test.js +0 -118
  247. package/dist/stores/d227-same-scope-tombstone-duplicate.test.js.map +0 -1
  248. package/dist/stores/wizard-store.test.js +0 -3053
  249. package/dist/stores/wizard-store.test.js.map +0 -1
  250. /package/dist/{copy-local-skills-UPA2GUZV.js.map → copy-local-skills-6EMVN7RB.js.map} +0 -0
  251. /package/dist/{load-agent-defs-SB4XAOVB.js.map → load-agent-defs-BWOUVEAR.js.map} +0 -0
  252. /package/dist/{loader-E2ERJYVA.js.map → loader-5JP4S7CJ.js.map} +0 -0
  253. /package/dist/{recompile-project-agents-U4ZY2GDX.js.map → recompile-project-agents-6DL45TDI.js.map} +0 -0
  254. /package/dist/{source-loader-5UNRU7EZ.js.map → source-loader-MZYCPL2S.js.map} +0 -0
@@ -1,1253 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- ARROW_DOWN,
4
- ARROW_LEFT,
5
- ARROW_RIGHT,
6
- ARROW_UP,
7
- INPUT_DELAY_MS,
8
- RENDER_DELAY_MS,
9
- TAB,
10
- delay
11
- } from "../../chunk-KSZ6PCY4.js";
12
- import {
13
- render
14
- } from "../../chunk-EKWAUOQB.js";
15
- import {
16
- CATEGORY_GRID_MATRIX
17
- } from "../../chunk-THKIOI32.js";
18
- import "../../chunk-K3OU6TGX.js";
19
- import "../../chunk-V7QOSGQA.js";
20
- import {
21
- afterEach,
22
- beforeEach,
23
- describe,
24
- globalExpect,
25
- it,
26
- vi
27
- } from "../../chunk-I3XKEGHM.js";
28
- import {
29
- CategoryGrid
30
- } from "../../chunk-6RBQBOBS.js";
31
- import "../../chunk-LYYXX2W5.js";
32
- import "../../chunk-DAG2ULNF.js";
33
- import "../../chunk-VG2TVBFN.js";
34
- import "../../chunk-IHO75PMZ.js";
35
- import {
36
- BUILT_IN_MATRIX,
37
- initializeMatrix
38
- } from "../../chunk-DG2NGZ7S.js";
39
- import "../../chunk-VEP23B5W.js";
40
- import "../../chunk-O5ASLZ2K.js";
41
- import {
42
- init_esm_shims
43
- } from "../../chunk-P56WJ5FA.js";
44
-
45
- // src/cli/components/wizard/category-grid.test.tsx
46
- init_esm_shims();
47
- import { jsx } from "react/jsx-runtime";
48
- var createOption = (id, overrides = {}) => ({
49
- id,
50
- state: { status: "normal" },
51
- selected: false,
52
- ...overrides
53
- });
54
- var createCategory = (id, displayName, options, overrides = {}) => ({
55
- id,
56
- displayName,
57
- required: false,
58
- exclusive: true,
59
- options,
60
- ...overrides
61
- });
62
- var defaultCategories = [
63
- createCategory(
64
- "web-framework",
65
- "Framework",
66
- [
67
- createOption("web-framework-react", {
68
- state: { status: "recommended", reason: "Popular choice" }
69
- }),
70
- createOption("web-framework-vue-composition-api"),
71
- createOption("web-framework-angular-standalone"),
72
- createOption("web-framework-solidjs")
73
- ],
74
- { required: true }
75
- ),
76
- createCategory(
77
- "web-styling",
78
- "Styling",
79
- [
80
- createOption("web-styling-scss-modules", { selected: true }),
81
- createOption("web-styling-tailwind", {
82
- state: { status: "recommended", reason: "Modern utility-first CSS" }
83
- }),
84
- createOption("web-styling-cva"),
85
- createOption("web-meta-framework-nuxt")
86
- ],
87
- { required: true }
88
- ),
89
- createCategory("web-client-state", "Client State", [
90
- createOption("web-state-zustand", {
91
- state: { status: "recommended", reason: "Simple and performant" }
92
- }),
93
- createOption("web-state-jotai"),
94
- createOption("web-state-redux-toolkit", {
95
- state: { status: "discouraged", reason: "Complex for most apps" }
96
- }),
97
- createOption("web-state-mobx")
98
- ]),
99
- createCategory("web-server-state", "Server State", [
100
- createOption("web-server-state-react-query", { selected: true }),
101
- createOption("web-data-fetching-swr"),
102
- createOption("web-data-fetching-graphql-apollo")
103
- ]),
104
- createCategory("api-analytics", "Analytics", [createOption("api-analytics-posthog-analytics")])
105
- ];
106
- var categoriesWithFramework = [
107
- createCategory(
108
- "web-framework",
109
- "Framework",
110
- [
111
- createOption("web-framework-react", {
112
- state: { status: "recommended", reason: "Popular choice" },
113
- selected: true
114
- // Framework selected
115
- }),
116
- createOption("web-framework-vue-composition-api"),
117
- createOption("web-framework-angular-standalone"),
118
- createOption("web-framework-solidjs")
119
- ],
120
- { required: true }
121
- ),
122
- createCategory(
123
- "web-styling",
124
- "Styling",
125
- [
126
- createOption("web-styling-scss-modules"),
127
- createOption("web-styling-tailwind", {
128
- state: { status: "recommended", reason: "Modern utility-first CSS" }
129
- }),
130
- createOption("web-styling-cva"),
131
- createOption("web-meta-framework-nuxt")
132
- ],
133
- { required: true }
134
- ),
135
- createCategory("web-client-state", "Client State", [
136
- createOption("web-state-zustand", {
137
- state: { status: "recommended", reason: "Simple and performant" }
138
- }),
139
- createOption("web-state-jotai"),
140
- createOption("web-state-redux-toolkit", {
141
- state: { status: "discouraged", reason: "Complex for most apps" }
142
- }),
143
- createOption("web-state-mobx")
144
- ])
145
- ];
146
- var manyCategories = [
147
- createCategory("web-framework", "Category 0", [
148
- createOption("web-framework-react"),
149
- createOption("web-framework-vue-composition-api"),
150
- createOption("web-framework-angular-standalone")
151
- ]),
152
- createCategory("web-styling", "Category 1", [
153
- createOption("web-styling-tailwind"),
154
- createOption("web-styling-scss-modules"),
155
- createOption("web-styling-cva")
156
- ]),
157
- createCategory("web-client-state", "Category 2", [
158
- createOption("web-state-zustand"),
159
- createOption("web-state-jotai"),
160
- createOption("web-state-mobx")
161
- ]),
162
- createCategory("web-server-state", "Category 3", [
163
- createOption("web-server-state-react-query"),
164
- createOption("web-data-fetching-swr"),
165
- createOption("web-data-fetching-graphql-apollo")
166
- ]),
167
- createCategory("web-forms", "Category 4", [
168
- createOption("web-forms-react-hook-form"),
169
- createOption("web-forms-vee-validate"),
170
- createOption("web-forms-zod-validation")
171
- ]),
172
- createCategory("web-testing", "Category 5", [
173
- createOption("web-testing-vitest"),
174
- createOption("web-testing-playwright-e2e"),
175
- createOption("web-testing-cypress-e2e")
176
- ]),
177
- createCategory("web-mocking", "Category 6", [
178
- createOption("web-mocks-msw"),
179
- createOption("web-testing-react-testing-library"),
180
- createOption("web-testing-vue-test-utils")
181
- ]),
182
- createCategory("web-i18n", "Category 7", [
183
- createOption("web-i18n-next-intl"),
184
- createOption("web-i18n-react-intl"),
185
- createOption("web-i18n-vue-i18n")
186
- ])
187
- ];
188
- var navCategories = [
189
- createCategory("web-framework", "Category 0", [createOption("web-framework-react")]),
190
- createCategory("web-styling", "Category 1", [createOption("web-styling-tailwind")]),
191
- createCategory("web-client-state", "Category 2", [createOption("web-state-zustand")]),
192
- createCategory("web-server-state", "Category 3", [createOption("web-server-state-react-query")]),
193
- createCategory("web-forms", "Category 4", [createOption("web-forms-react-hook-form")]),
194
- createCategory("web-testing", "Category 5", [createOption("web-testing-vitest")])
195
- ];
196
- var defaultProps = {
197
- categories: defaultCategories,
198
- defaultFocusedRow: 0,
199
- defaultFocusedCol: 0,
200
- showLabels: false,
201
- onToggle: vi.fn(),
202
- onFocusChange: vi.fn(),
203
- onToggleLabels: vi.fn()
204
- };
205
- var renderGrid = (props = {}) => {
206
- return render(/* @__PURE__ */ jsx(CategoryGrid, { ...defaultProps, ...props }));
207
- };
208
- describe("CategoryGrid component", () => {
209
- let cleanup;
210
- beforeEach(() => {
211
- initializeMatrix(CATEGORY_GRID_MATRIX);
212
- });
213
- afterEach(() => {
214
- cleanup?.();
215
- cleanup = void 0;
216
- initializeMatrix(BUILT_IN_MATRIX);
217
- });
218
- describe("rendering", () => {
219
- it("should render all categories as sections", () => {
220
- const { lastFrame, unmount } = renderGrid();
221
- cleanup = unmount;
222
- const output = lastFrame();
223
- globalExpect(output).toContain("Framework");
224
- globalExpect(output).toContain("Styling");
225
- globalExpect(output).toContain("Client State");
226
- globalExpect(output).toContain("Server State");
227
- globalExpect(output).toContain("Analytics");
228
- });
229
- it("should render all options in each category", () => {
230
- const { lastFrame, unmount } = renderGrid();
231
- cleanup = unmount;
232
- const output = lastFrame();
233
- globalExpect(output).toContain("React");
234
- globalExpect(output).toContain("Vue");
235
- globalExpect(output).toContain("Angular");
236
- globalExpect(output).toContain("SolidJS");
237
- globalExpect(output).toContain("SCSS Modules");
238
- globalExpect(output).toContain("Tailwind");
239
- });
240
- it("should show required indicator (*) for required categories", () => {
241
- const { lastFrame, unmount } = renderGrid();
242
- cleanup = unmount;
243
- const output = lastFrame();
244
- globalExpect(output).toContain("*");
245
- });
246
- it("should NOT show (optional) for non-required categories", () => {
247
- const { lastFrame, unmount } = renderGrid();
248
- cleanup = unmount;
249
- const output = lastFrame();
250
- globalExpect(output).not.toContain("(optional)");
251
- });
252
- it("should handle empty categories array", () => {
253
- const { lastFrame, unmount } = renderGrid({ categories: [] });
254
- cleanup = unmount;
255
- const output = lastFrame();
256
- globalExpect(output).toContain("No categories to display");
257
- });
258
- it("should render section underlines", () => {
259
- const { lastFrame, unmount } = renderGrid();
260
- cleanup = unmount;
261
- const output = lastFrame();
262
- globalExpect(output).toContain("\u2500");
263
- });
264
- });
265
- describe("visual states", () => {
266
- it("should show selected options with display name", () => {
267
- const { lastFrame, unmount } = renderGrid();
268
- cleanup = unmount;
269
- const output = lastFrame();
270
- globalExpect(output).toContain("SCSS Modules");
271
- globalExpect(output).toContain("React Query");
272
- });
273
- it("should show unselected options with display name", () => {
274
- const { lastFrame, unmount } = renderGrid();
275
- cleanup = unmount;
276
- const output = lastFrame();
277
- globalExpect(output).toContain("Vue");
278
- globalExpect(output).toContain("Angular");
279
- });
280
- it("should NOT show star indicator for recommended options (uses background instead)", () => {
281
- const { lastFrame, unmount } = renderGrid();
282
- cleanup = unmount;
283
- const output = lastFrame();
284
- globalExpect(output).not.toContain("\u2B50");
285
- });
286
- it("should NOT show warning indicator for discouraged options (uses color instead)", () => {
287
- const { lastFrame, unmount } = renderGrid();
288
- cleanup = unmount;
289
- const output = lastFrame();
290
- globalExpect(output).not.toContain("\u26A0");
291
- });
292
- it("should show discouraged options with warning styling", () => {
293
- const categories = [
294
- createCategory("web-testing", "Test", [
295
- createOption("web-forms-react-hook-form"),
296
- createOption("web-forms-vee-validate", {
297
- state: { status: "discouraged", reason: "Not recommended" }
298
- })
299
- ])
300
- ];
301
- const { lastFrame, unmount } = renderGrid({ categories });
302
- cleanup = unmount;
303
- const output = lastFrame();
304
- globalExpect(output).toContain("Vee Validate");
305
- });
306
- it("should render selected skills with display name", () => {
307
- const categories = [
308
- createCategory("web-forms", "Forms", [
309
- createOption("web-forms-react-hook-form", { selected: true })
310
- ])
311
- ];
312
- const { lastFrame, unmount } = renderGrid({ categories });
313
- cleanup = unmount;
314
- const output = lastFrame();
315
- globalExpect(output).toContain("React Hook Form");
316
- });
317
- it("should render unselected skills with display name", () => {
318
- const categories = [
319
- createCategory("web-forms", "Forms", [
320
- createOption("web-forms-react-hook-form", {
321
- state: { status: "normal" },
322
- selected: false
323
- })
324
- ])
325
- ];
326
- const { lastFrame, unmount } = renderGrid({ categories });
327
- cleanup = unmount;
328
- const output = lastFrame();
329
- globalExpect(output).toContain("React Hook Form");
330
- });
331
- it("should render discouraged skills with display name", () => {
332
- const categories = [
333
- createCategory("web-forms", "Forms", [
334
- createOption("web-forms-react-hook-form", {
335
- state: { status: "discouraged", reason: "Not recommended" }
336
- })
337
- ])
338
- ];
339
- const { lastFrame, unmount } = renderGrid({ categories });
340
- cleanup = unmount;
341
- const output = lastFrame();
342
- globalExpect(output).toContain("React Hook Form");
343
- });
344
- it("should render discouraged+selected skills with display name", () => {
345
- const categories = [
346
- createCategory("web-forms", "Forms", [
347
- createOption("web-forms-react-hook-form", {
348
- state: { status: "discouraged", reason: "Not recommended" },
349
- selected: true
350
- }),
351
- createOption("web-forms-vee-validate", {
352
- state: { status: "discouraged", reason: "Not recommended" },
353
- selected: false
354
- })
355
- ])
356
- ];
357
- const { lastFrame, unmount } = renderGrid({ categories });
358
- cleanup = unmount;
359
- const output = lastFrame();
360
- globalExpect(output).toContain("React Hook Form");
361
- globalExpect(output).toContain("Vee Validate");
362
- });
363
- it("should render discouraged skills with display name from store", () => {
364
- const categories = [
365
- createCategory("web-forms", "Forms", [
366
- createOption("web-forms-react-hook-form", {
367
- state: { status: "discouraged", reason: "Not recommended" }
368
- })
369
- ])
370
- ];
371
- const { lastFrame, unmount } = renderGrid({ categories });
372
- cleanup = unmount;
373
- const output = lastFrame();
374
- globalExpect(output).toContain("React Hook Form");
375
- });
376
- it("should render both selected and unselected skills with display names", () => {
377
- const categories = [
378
- createCategory("web-forms", "Forms", [
379
- createOption("web-forms-react-hook-form", { selected: true }),
380
- createOption("web-forms-vee-validate", { selected: false })
381
- ])
382
- ];
383
- const { lastFrame, unmount } = renderGrid({ categories });
384
- cleanup = unmount;
385
- const output = lastFrame();
386
- globalExpect(output).toContain("React Hook Form");
387
- globalExpect(output).toContain("Vee Validate");
388
- });
389
- it("should render skill display name regardless of selection state", () => {
390
- const categories1 = [
391
- createCategory("web-forms", "Forms", [
392
- createOption("web-forms-react-hook-form", { selected: false })
393
- ])
394
- ];
395
- const { lastFrame: frame1, unmount: unmount1 } = renderGrid({ categories: categories1 });
396
- const output1 = frame1();
397
- unmount1();
398
- const categories2 = [
399
- createCategory("web-forms", "Forms", [
400
- createOption("web-forms-react-hook-form", { selected: true })
401
- ])
402
- ];
403
- const { lastFrame: frame2, unmount: unmount2 } = renderGrid({ categories: categories2 });
404
- cleanup = unmount2;
405
- const output2 = frame2();
406
- globalExpect(output1).toContain("React Hook Form");
407
- globalExpect(output2).toContain("React Hook Form");
408
- });
409
- });
410
- describe("locked sections", () => {
411
- it("should show all categories including locked ones", () => {
412
- const { lastFrame, unmount } = renderGrid();
413
- cleanup = unmount;
414
- const output = lastFrame();
415
- globalExpect(output).toContain("Framework");
416
- globalExpect(output).toContain("Styling");
417
- globalExpect(output).toContain("Client State");
418
- });
419
- it("should unlock sections when framework is selected", () => {
420
- const { lastFrame, unmount } = renderGrid({
421
- categories: categoriesWithFramework
422
- });
423
- cleanup = unmount;
424
- const output = lastFrame();
425
- globalExpect(output).toContain("Framework");
426
- globalExpect(output).toContain("Styling");
427
- globalExpect(output).toContain("Client State");
428
- });
429
- it("should not lock any sections when no framework category exists", () => {
430
- const categoriesNoFramework = [
431
- createCategory("web-styling", "Styling", [
432
- createOption("web-styling-scss-modules"),
433
- createOption("web-styling-tailwind")
434
- ]),
435
- createCategory("web-client-state", "State", [createOption("web-state-zustand")])
436
- ];
437
- const { lastFrame, unmount } = renderGrid({
438
- categories: categoriesNoFramework
439
- });
440
- cleanup = unmount;
441
- const output = lastFrame();
442
- globalExpect(output).toContain("Styling");
443
- globalExpect(output).toContain("State");
444
- });
445
- });
446
- describe("initial focus dispatch", () => {
447
- it("dispatches onFocusedSkillChange for the initially focused cell on mount without navigation", async () => {
448
- const onFocusedSkillChange = vi.fn();
449
- const { unmount } = renderGrid({
450
- categories: categoriesWithFramework,
451
- defaultFocusedRow: 0,
452
- defaultFocusedCol: 0,
453
- onFocusedSkillChange
454
- });
455
- cleanup = unmount;
456
- await delay(RENDER_DELAY_MS);
457
- globalExpect(onFocusedSkillChange).toHaveBeenCalledWith("web-framework-react");
458
- });
459
- });
460
- describe("focus indicator", () => {
461
- it("should render focused option with display name", () => {
462
- const { lastFrame, unmount } = renderGrid({
463
- defaultFocusedRow: 0,
464
- defaultFocusedCol: 0
465
- });
466
- cleanup = unmount;
467
- const output = lastFrame();
468
- globalExpect(output).toContain("React");
469
- });
470
- it("should render correctly when focusedRow changes", () => {
471
- const { lastFrame: frame1, unmount: unmount1 } = renderGrid({
472
- categories: categoriesWithFramework,
473
- defaultFocusedRow: 0,
474
- defaultFocusedCol: 0
475
- });
476
- const output1 = frame1();
477
- unmount1();
478
- const { lastFrame: frame2, unmount: unmount2 } = renderGrid({
479
- categories: categoriesWithFramework,
480
- defaultFocusedRow: 1,
481
- defaultFocusedCol: 0
482
- });
483
- cleanup = unmount2;
484
- const output2 = frame2();
485
- globalExpect(output1).toContain("Framework");
486
- globalExpect(output2).toContain("Styling");
487
- });
488
- it("should highlight focused category name", () => {
489
- const { lastFrame, unmount } = renderGrid({
490
- categories: categoriesWithFramework,
491
- defaultFocusedRow: 1,
492
- defaultFocusedCol: 0
493
- });
494
- cleanup = unmount;
495
- const output = lastFrame();
496
- globalExpect(output).toContain("Styling");
497
- });
498
- });
499
- describe("keyboard navigation - arrow keys", () => {
500
- it("should call onFocusChange when pressing left arrow", async () => {
501
- const onFocusChange = vi.fn();
502
- const { stdin, unmount } = renderGrid({
503
- defaultFocusedRow: 0,
504
- defaultFocusedCol: 1,
505
- onFocusChange
506
- });
507
- cleanup = unmount;
508
- await delay(RENDER_DELAY_MS * 2);
509
- await stdin.write(ARROW_LEFT);
510
- await delay(INPUT_DELAY_MS);
511
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 0);
512
- });
513
- it("should call onFocusChange when pressing right arrow", async () => {
514
- const onFocusChange = vi.fn();
515
- const { stdin, unmount } = renderGrid({
516
- defaultFocusedRow: 0,
517
- defaultFocusedCol: 0,
518
- onFocusChange
519
- });
520
- cleanup = unmount;
521
- await delay(RENDER_DELAY_MS);
522
- await stdin.write(ARROW_RIGHT);
523
- await delay(INPUT_DELAY_MS);
524
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 1);
525
- });
526
- it("should call onFocusChange when pressing up arrow (wraps to last row)", async () => {
527
- const onFocusChange = vi.fn();
528
- const { stdin, unmount } = renderGrid({
529
- defaultFocusedRow: 0,
530
- defaultFocusedCol: 0,
531
- onFocusChange
532
- });
533
- cleanup = unmount;
534
- await delay(RENDER_DELAY_MS);
535
- await stdin.write(ARROW_UP);
536
- await delay(INPUT_DELAY_MS);
537
- globalExpect(onFocusChange).toHaveBeenCalledWith(4, 0);
538
- });
539
- it("should navigate between sections when unlocked", async () => {
540
- const onFocusChange = vi.fn();
541
- const { stdin, unmount } = renderGrid({
542
- categories: categoriesWithFramework,
543
- defaultFocusedRow: 0,
544
- defaultFocusedCol: 0,
545
- onFocusChange
546
- });
547
- cleanup = unmount;
548
- await delay(RENDER_DELAY_MS);
549
- await stdin.write(ARROW_DOWN);
550
- await delay(INPUT_DELAY_MS);
551
- globalExpect(onFocusChange).toHaveBeenCalledWith(1, 0);
552
- });
553
- it("should wrap horizontally when pressing left at first column", async () => {
554
- const onFocusChange = vi.fn();
555
- const { stdin, unmount } = renderGrid({
556
- defaultFocusedRow: 0,
557
- defaultFocusedCol: 0,
558
- onFocusChange
559
- });
560
- cleanup = unmount;
561
- await delay(RENDER_DELAY_MS);
562
- await stdin.write(ARROW_LEFT);
563
- await delay(INPUT_DELAY_MS);
564
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 3);
565
- });
566
- it("should wrap horizontally when pressing right at last column", async () => {
567
- const onFocusChange = vi.fn();
568
- const { stdin, unmount } = renderGrid({
569
- defaultFocusedRow: 0,
570
- defaultFocusedCol: 3,
571
- // Last option in framework
572
- onFocusChange
573
- });
574
- cleanup = unmount;
575
- await delay(RENDER_DELAY_MS);
576
- await stdin.write(ARROW_RIGHT);
577
- await delay(INPUT_DELAY_MS);
578
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 0);
579
- });
580
- it("should wrap vertically when all sections are unlocked", async () => {
581
- const onFocusChange = vi.fn();
582
- const { stdin, unmount } = renderGrid({
583
- categories: categoriesWithFramework,
584
- defaultFocusedRow: 0,
585
- defaultFocusedCol: 0,
586
- onFocusChange
587
- });
588
- cleanup = unmount;
589
- await delay(RENDER_DELAY_MS);
590
- await stdin.write(ARROW_UP);
591
- await delay(INPUT_DELAY_MS);
592
- globalExpect(onFocusChange).toHaveBeenCalledWith(2, 0);
593
- });
594
- });
595
- describe("keyboard navigation - vim keys", () => {
596
- it("should move left with h key", async () => {
597
- const onFocusChange = vi.fn();
598
- const { stdin, unmount } = renderGrid({
599
- defaultFocusedRow: 0,
600
- defaultFocusedCol: 1,
601
- onFocusChange
602
- });
603
- cleanup = unmount;
604
- await delay(RENDER_DELAY_MS);
605
- await stdin.write("h");
606
- await delay(INPUT_DELAY_MS);
607
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 0);
608
- });
609
- it("should move right with l key", async () => {
610
- const onFocusChange = vi.fn();
611
- const { stdin, unmount } = renderGrid({
612
- defaultFocusedRow: 0,
613
- defaultFocusedCol: 0,
614
- onFocusChange
615
- });
616
- cleanup = unmount;
617
- await delay(RENDER_DELAY_MS);
618
- await stdin.write("l");
619
- await delay(INPUT_DELAY_MS);
620
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 1);
621
- });
622
- it("should move up with k key", async () => {
623
- const onFocusChange = vi.fn();
624
- const { stdin, unmount } = renderGrid({
625
- categories: categoriesWithFramework,
626
- defaultFocusedRow: 1,
627
- defaultFocusedCol: 0,
628
- onFocusChange
629
- });
630
- cleanup = unmount;
631
- await delay(RENDER_DELAY_MS);
632
- await stdin.write("k");
633
- await delay(INPUT_DELAY_MS);
634
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 0);
635
- });
636
- it("should move down with j key", async () => {
637
- const onFocusChange = vi.fn();
638
- const { stdin, unmount } = renderGrid({
639
- categories: categoriesWithFramework,
640
- defaultFocusedRow: 0,
641
- defaultFocusedCol: 0,
642
- onFocusChange
643
- });
644
- cleanup = unmount;
645
- await delay(RENDER_DELAY_MS);
646
- await stdin.write("j");
647
- await delay(INPUT_DELAY_MS);
648
- globalExpect(onFocusChange).toHaveBeenCalledWith(1, 0);
649
- });
650
- });
651
- describe("selection toggle", () => {
652
- it("should call onToggle when pressing space on a normal option", async () => {
653
- const onToggle = vi.fn();
654
- const { stdin, unmount } = renderGrid({
655
- defaultFocusedRow: 0,
656
- defaultFocusedCol: 1,
657
- // vue (normal state)
658
- onToggle
659
- });
660
- cleanup = unmount;
661
- await delay(RENDER_DELAY_MS);
662
- await stdin.write(" ");
663
- await delay(INPUT_DELAY_MS);
664
- globalExpect(onToggle).toHaveBeenCalledWith("web-framework", "web-framework-vue-composition-api");
665
- });
666
- it("should call onToggle when pressing space on a selected option", async () => {
667
- const onToggle = vi.fn();
668
- const categories = [
669
- createCategory(
670
- "web-framework",
671
- "Framework",
672
- [
673
- createOption("web-framework-react", { selected: true }),
674
- createOption("web-framework-vue-composition-api")
675
- ],
676
- { required: true }
677
- )
678
- ];
679
- const { stdin, unmount } = renderGrid({
680
- categories,
681
- defaultFocusedRow: 0,
682
- defaultFocusedCol: 0,
683
- // react (selected)
684
- onToggle
685
- });
686
- cleanup = unmount;
687
- await delay(RENDER_DELAY_MS);
688
- await stdin.write(" ");
689
- await delay(INPUT_DELAY_MS);
690
- globalExpect(onToggle).toHaveBeenCalledWith("web-framework", "web-framework-react");
691
- });
692
- it("should call onToggle for discouraged options (still selectable)", async () => {
693
- const onToggle = vi.fn();
694
- const categories = [
695
- createCategory("web-testing", "Test", [
696
- createOption("web-forms-react-hook-form", {
697
- state: { status: "discouraged", reason: "Not recommended" }
698
- }),
699
- createOption("web-forms-vee-validate", {
700
- state: { status: "discouraged", reason: "Not recommended" }
701
- })
702
- ])
703
- ];
704
- const { stdin, unmount } = renderGrid({
705
- categories,
706
- defaultFocusedRow: 0,
707
- defaultFocusedCol: 0,
708
- onToggle
709
- });
710
- cleanup = unmount;
711
- await delay(RENDER_DELAY_MS);
712
- await stdin.write(" ");
713
- await delay(INPUT_DELAY_MS);
714
- globalExpect(onToggle).toHaveBeenCalled();
715
- });
716
- it("should keep focus on initial row when no sections are locked", async () => {
717
- const onFocusChange = vi.fn();
718
- const { unmount } = renderGrid({
719
- defaultFocusedRow: 1,
720
- defaultFocusedCol: 0,
721
- onFocusChange
722
- });
723
- cleanup = unmount;
724
- await delay(RENDER_DELAY_MS);
725
- globalExpect(onFocusChange).not.toHaveBeenCalled();
726
- });
727
- });
728
- describe("discouraged options navigation", () => {
729
- it("should navigate to discouraged options when navigating right", async () => {
730
- const onFocusChange = vi.fn();
731
- const categories = [
732
- createCategory("web-testing", "Test", [
733
- createOption("web-forms-react-hook-form"),
734
- createOption("web-forms-vee-validate", {
735
- state: { status: "discouraged", reason: "Not recommended" }
736
- }),
737
- createOption("web-forms-zod-validation")
738
- ])
739
- ];
740
- const { stdin, unmount } = renderGrid({
741
- categories,
742
- defaultFocusedRow: 0,
743
- defaultFocusedCol: 0,
744
- onFocusChange
745
- });
746
- cleanup = unmount;
747
- await delay(RENDER_DELAY_MS);
748
- await stdin.write(ARROW_RIGHT);
749
- await delay(INPUT_DELAY_MS);
750
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 1);
751
- });
752
- it("should navigate to discouraged options when navigating left", async () => {
753
- const onFocusChange = vi.fn();
754
- const categories = [
755
- createCategory("web-testing", "Test", [
756
- createOption("web-forms-react-hook-form"),
757
- createOption("web-forms-vee-validate", {
758
- state: { status: "discouraged", reason: "Not recommended" }
759
- }),
760
- createOption("web-forms-zod-validation")
761
- ])
762
- ];
763
- const { stdin, unmount } = renderGrid({
764
- categories,
765
- defaultFocusedRow: 0,
766
- defaultFocusedCol: 2,
767
- // Start at opt3 (index 2)
768
- onFocusChange
769
- });
770
- cleanup = unmount;
771
- await delay(RENDER_DELAY_MS);
772
- await stdin.write(ARROW_LEFT);
773
- await delay(INPUT_DELAY_MS);
774
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 1);
775
- });
776
- it("should navigate between all-discouraged options in a row", async () => {
777
- const onFocusChange = vi.fn();
778
- const categories = [
779
- createCategory("web-testing", "Test", [
780
- createOption("web-forms-react-hook-form", {
781
- state: { status: "discouraged", reason: "Not recommended" }
782
- }),
783
- createOption("web-forms-vee-validate", {
784
- state: { status: "discouraged", reason: "Not recommended" }
785
- })
786
- ])
787
- ];
788
- const { stdin, unmount } = renderGrid({
789
- categories,
790
- defaultFocusedRow: 0,
791
- defaultFocusedCol: 0,
792
- onFocusChange
793
- });
794
- cleanup = unmount;
795
- await delay(RENDER_DELAY_MS);
796
- await stdin.write(ARROW_RIGHT);
797
- await delay(INPUT_DELAY_MS);
798
- globalExpect(onFocusChange).toHaveBeenCalledWith(0, 1);
799
- });
800
- });
801
- describe("exclusive categories", () => {
802
- it("should render exclusive category correctly", () => {
803
- const { lastFrame, unmount } = renderGrid();
804
- cleanup = unmount;
805
- const output = lastFrame();
806
- globalExpect(output).toContain("Framework");
807
- });
808
- });
809
- describe("tab navigation", () => {
810
- it("should jump to next section when pressing Tab", async () => {
811
- const onFocusChange = vi.fn();
812
- const { stdin, unmount } = renderGrid({
813
- categories: categoriesWithFramework,
814
- defaultFocusedRow: 0,
815
- defaultFocusedCol: 0,
816
- onFocusChange
817
- });
818
- cleanup = unmount;
819
- await delay(RENDER_DELAY_MS);
820
- await stdin.write(TAB);
821
- await delay(INPUT_DELAY_MS);
822
- globalExpect(onFocusChange).toHaveBeenCalledWith(1, 0);
823
- });
824
- it("should jump to next section on Tab", async () => {
825
- const onFocusChange = vi.fn();
826
- const { stdin, unmount } = renderGrid({
827
- defaultFocusedRow: 0,
828
- defaultFocusedCol: 0,
829
- onFocusChange
830
- });
831
- cleanup = unmount;
832
- await delay(RENDER_DELAY_MS);
833
- await stdin.write(TAB);
834
- await delay(INPUT_DELAY_MS);
835
- globalExpect(onFocusChange).toHaveBeenCalledWith(1, 0);
836
- });
837
- });
838
- describe("compatibility labels toggle", () => {
839
- it("should call onToggleLabels when pressing d key", async () => {
840
- const onToggleLabels = vi.fn();
841
- const { stdin, unmount } = renderGrid({
842
- onToggleLabels
843
- });
844
- cleanup = unmount;
845
- await delay(RENDER_DELAY_MS);
846
- await stdin.write("d");
847
- await delay(INPUT_DELAY_MS);
848
- globalExpect(onToggleLabels).toHaveBeenCalled();
849
- });
850
- it("should show compatibility labels when showLabels is true", () => {
851
- const { lastFrame, unmount } = renderGrid({ showLabels: true });
852
- cleanup = unmount;
853
- const output = lastFrame();
854
- globalExpect(output).toContain("(recommended)");
855
- });
856
- it("should not show label for selected skills when showLabels is true", () => {
857
- const { lastFrame, unmount } = renderGrid({ showLabels: true });
858
- cleanup = unmount;
859
- const output = lastFrame();
860
- globalExpect(output).not.toContain("(selected)");
861
- });
862
- it("should show Discouraged label when showLabels is true and skill is focused", () => {
863
- const { lastFrame, unmount } = renderGrid({
864
- categories: categoriesWithFramework,
865
- showLabels: true,
866
- defaultFocusedRow: 2,
867
- defaultFocusedCol: 2
868
- });
869
- cleanup = unmount;
870
- const output = lastFrame();
871
- globalExpect(output).toContain("(discouraged)");
872
- });
873
- it("should hide compatibility labels when showLabels is false", () => {
874
- const { lastFrame, unmount } = renderGrid({ showLabels: false });
875
- cleanup = unmount;
876
- const output = lastFrame();
877
- globalExpect(output).not.toContain("(recommended)");
878
- globalExpect(output).not.toContain("(selected)");
879
- globalExpect(output).not.toContain("(discouraged)");
880
- });
881
- it("should show discouraged label for focused discouraged option when showLabels is true", () => {
882
- const categories = [
883
- createCategory("web-testing", "Test", [
884
- createOption("web-forms-vee-validate", {
885
- state: { status: "discouraged", reason: "Not recommended" }
886
- }),
887
- createOption("web-forms-react-hook-form")
888
- ])
889
- ];
890
- const { lastFrame, unmount } = renderGrid({ categories, showLabels: true });
891
- cleanup = unmount;
892
- const output = lastFrame();
893
- globalExpect(output).toContain("(discouraged)");
894
- });
895
- });
896
- describe("option ordering", () => {
897
- it("should preserve original order regardless of state", () => {
898
- const categories = [
899
- createCategory("web-client-state", "State", [
900
- createOption("web-state-jotai"),
901
- createOption("web-state-zustand", {
902
- state: { status: "recommended", reason: "Recommended choice" }
903
- }),
904
- createOption("web-state-redux-toolkit", {
905
- state: { status: "discouraged", reason: "Not recommended" }
906
- }),
907
- createOption("web-state-mobx")
908
- ])
909
- ];
910
- const { lastFrame, unmount } = renderGrid({ categories });
911
- cleanup = unmount;
912
- const output = lastFrame();
913
- const jotaiIdx = output.indexOf("Jotai");
914
- const zustandIdx = output.indexOf("Zustand");
915
- const reduxIdx = output.indexOf("Redux");
916
- const mobxIdx = output.indexOf("MobX");
917
- globalExpect(jotaiIdx).toBeLessThan(zustandIdx);
918
- globalExpect(zustandIdx).toBeLessThan(reduxIdx);
919
- globalExpect(reduxIdx).toBeLessThan(mobxIdx);
920
- });
921
- it("should not change order when a skill is selected", () => {
922
- const categoriesBefore = [
923
- createCategory("web-client-state", "State", [
924
- createOption("web-state-jotai"),
925
- createOption("web-state-zustand"),
926
- createOption("web-state-redux-toolkit")
927
- ])
928
- ];
929
- const categoriesAfter = [
930
- createCategory("web-client-state", "State", [
931
- createOption("web-state-jotai"),
932
- createOption("web-state-zustand", { selected: true }),
933
- createOption("web-state-redux-toolkit")
934
- ])
935
- ];
936
- const { lastFrame: frameBefore, unmount: unmountBefore } = renderGrid({
937
- categories: categoriesBefore
938
- });
939
- const outputBefore = frameBefore();
940
- unmountBefore();
941
- const { lastFrame: frameAfter, unmount: unmountAfter } = renderGrid({
942
- categories: categoriesAfter
943
- });
944
- cleanup = unmountAfter;
945
- const outputAfter = frameAfter();
946
- globalExpect(outputBefore.indexOf("Jotai")).toBeLessThan(outputBefore.indexOf("Zustand"));
947
- globalExpect(outputBefore.indexOf("Zustand")).toBeLessThan(outputBefore.indexOf("Redux"));
948
- globalExpect(outputAfter.indexOf("Jotai")).toBeLessThan(outputAfter.indexOf("Zustand"));
949
- globalExpect(outputAfter.indexOf("Zustand")).toBeLessThan(outputAfter.indexOf("Redux"));
950
- });
951
- it("should not change order when a skill state changes from normal to discouraged", () => {
952
- const categoriesBefore = [
953
- createCategory("web-client-state", "State", [
954
- createOption("web-state-jotai"),
955
- createOption("web-state-zustand"),
956
- createOption("web-state-redux-toolkit")
957
- ])
958
- ];
959
- const categoriesAfter = [
960
- createCategory("web-client-state", "State", [
961
- createOption("web-state-jotai"),
962
- createOption("web-state-zustand", {
963
- state: { status: "discouraged", reason: "Not recommended" }
964
- }),
965
- createOption("web-state-redux-toolkit")
966
- ])
967
- ];
968
- const { lastFrame: frameBefore, unmount: unmountBefore } = renderGrid({
969
- categories: categoriesBefore
970
- });
971
- const outputBefore = frameBefore();
972
- unmountBefore();
973
- const { lastFrame: frameAfter, unmount: unmountAfter } = renderGrid({
974
- categories: categoriesAfter
975
- });
976
- cleanup = unmountAfter;
977
- const outputAfter = frameAfter();
978
- globalExpect(outputBefore.indexOf("Jotai")).toBeLessThan(outputBefore.indexOf("Zustand"));
979
- globalExpect(outputBefore.indexOf("Zustand")).toBeLessThan(outputBefore.indexOf("Redux"));
980
- globalExpect(outputAfter.indexOf("Jotai")).toBeLessThan(outputAfter.indexOf("Zustand"));
981
- globalExpect(outputAfter.indexOf("Zustand")).toBeLessThan(outputAfter.indexOf("Redux"));
982
- });
983
- });
984
- describe("edge cases", () => {
985
- it("should handle single category", () => {
986
- const categories = [
987
- createCategory("web-forms", "Single Category", [createOption("web-forms-react-hook-form")])
988
- ];
989
- const { lastFrame, unmount } = renderGrid({ categories });
990
- cleanup = unmount;
991
- const output = lastFrame();
992
- globalExpect(output).toContain("Single Category");
993
- globalExpect(output).toContain("React Hook Form");
994
- });
995
- it("should handle single option in category", () => {
996
- const categories = [
997
- createCategory("web-forms", "Single", [createOption("web-forms-react-hook-form")])
998
- ];
999
- const { lastFrame, unmount } = renderGrid({ categories });
1000
- cleanup = unmount;
1001
- const output = lastFrame();
1002
- globalExpect(output).toContain("React Hook Form");
1003
- });
1004
- it("should handle category with many options (flows naturally)", () => {
1005
- const manySkillIds = [
1006
- "web-framework-react",
1007
- "web-framework-vue-composition-api",
1008
- "web-framework-angular-standalone",
1009
- "web-framework-solidjs",
1010
- "web-meta-framework-nuxt",
1011
- "web-meta-framework-remix",
1012
- "web-meta-framework-nextjs",
1013
- "web-styling-tailwind",
1014
- "web-styling-scss-modules"
1015
- ];
1016
- const options = manySkillIds.map((id) => createOption(id));
1017
- const categories = [createCategory("web-mocking", "Many Options", options)];
1018
- const { lastFrame, unmount } = renderGrid({ categories });
1019
- cleanup = unmount;
1020
- const output = lastFrame();
1021
- globalExpect(output).toContain("Many Options");
1022
- globalExpect(output).toContain("React");
1023
- globalExpect(output).toContain("SCSS Modules");
1024
- });
1025
- it("should handle long display names", () => {
1026
- const categories = [
1027
- createCategory("web-i18n", "Long Labels", [
1028
- createOption("web-i18n-next-intl"),
1029
- createOption("web-i18n-react-intl")
1030
- ])
1031
- ];
1032
- const { lastFrame, unmount } = renderGrid({ categories });
1033
- cleanup = unmount;
1034
- const output = lastFrame();
1035
- globalExpect(output).toContain("Next Intl");
1036
- });
1037
- it("should handle categories with different option counts", () => {
1038
- const categories = [
1039
- createCategory("web-framework", "Category 1", [
1040
- createOption("web-forms-react-hook-form"),
1041
- createOption("web-forms-vee-validate")
1042
- ]),
1043
- createCategory("web-styling", "Category 2", [createOption("web-forms-zod-validation")]),
1044
- createCategory("web-client-state", "Category 3", [
1045
- createOption("web-testing-vitest"),
1046
- createOption("web-testing-playwright-e2e"),
1047
- createOption("web-testing-react-testing-library")
1048
- ])
1049
- ];
1050
- const { lastFrame, unmount } = renderGrid({ categories });
1051
- cleanup = unmount;
1052
- const output = lastFrame();
1053
- globalExpect(output).toContain("Category 1");
1054
- globalExpect(output).toContain("Category 2");
1055
- globalExpect(output).toContain("Category 3");
1056
- });
1057
- });
1058
- describe("column adjustment", () => {
1059
- it("should adjust focusedCol when changing to row with fewer options", async () => {
1060
- const onFocusChange = vi.fn();
1061
- const categories = [
1062
- createCategory(
1063
- "web-framework",
1064
- "Framework",
1065
- [
1066
- createOption("web-forms-react-hook-form", { selected: true }),
1067
- // Framework selected
1068
- createOption("web-forms-vee-validate"),
1069
- createOption("web-forms-zod-validation")
1070
- ],
1071
- { required: true }
1072
- ),
1073
- createCategory("web-styling", "Category 2", [createOption("web-testing-vitest")])
1074
- ];
1075
- const { stdin, unmount } = renderGrid({
1076
- categories,
1077
- defaultFocusedRow: 0,
1078
- defaultFocusedCol: 2,
1079
- // Last option in first row
1080
- onFocusChange
1081
- });
1082
- cleanup = unmount;
1083
- await delay(RENDER_DELAY_MS);
1084
- await stdin.write(ARROW_DOWN);
1085
- await delay(INPUT_DELAY_MS);
1086
- globalExpect(onFocusChange).toHaveBeenCalledWith(1, 0);
1087
- });
1088
- });
1089
- describe("installed skills", () => {
1090
- it("should render installed skill with display name (no checkmark icon)", () => {
1091
- const categories = [
1092
- createCategory("web-forms", "Forms", [
1093
- createOption("web-forms-react-hook-form", { installed: true })
1094
- ])
1095
- ];
1096
- const { lastFrame, unmount } = renderGrid({ categories });
1097
- cleanup = unmount;
1098
- const output = lastFrame();
1099
- globalExpect(output).not.toContain("\u2713");
1100
- globalExpect(output).toContain("React Hook Form");
1101
- });
1102
- it("should render installed and selected skill with display name only", () => {
1103
- const categories = [
1104
- createCategory("web-forms", "Forms", [
1105
- createOption("web-forms-react-hook-form", {
1106
- installed: true,
1107
- selected: true
1108
- })
1109
- ])
1110
- ];
1111
- const { lastFrame, unmount } = renderGrid({ categories });
1112
- cleanup = unmount;
1113
- const output = lastFrame();
1114
- globalExpect(output).not.toContain("\u2713");
1115
- globalExpect(output).toContain("React Hook Form");
1116
- });
1117
- it("should render local installed skill without L badge or checkmark", () => {
1118
- const categories = [
1119
- createCategory("web-forms", "Forms", [
1120
- createOption("web-forms-react-hook-form", { local: true, installed: true })
1121
- ])
1122
- ];
1123
- const { lastFrame, unmount } = renderGrid({ categories });
1124
- cleanup = unmount;
1125
- const output = lastFrame();
1126
- globalExpect(output).not.toContain("\u2713");
1127
- globalExpect(output).toContain("React Hook Form");
1128
- });
1129
- });
1130
- describe("natural flow (no virtual scroll)", () => {
1131
- it("should render all categories without scroll indicators", () => {
1132
- const { lastFrame, unmount } = renderGrid();
1133
- cleanup = unmount;
1134
- const output = lastFrame();
1135
- globalExpect(output).toContain("Framework");
1136
- globalExpect(output).toContain("Styling");
1137
- globalExpect(output).toContain("Client State");
1138
- globalExpect(output).toContain("Server State");
1139
- globalExpect(output).toContain("Analytics");
1140
- globalExpect(output).not.toContain("more categories");
1141
- });
1142
- it("should render all categories even with many entries", () => {
1143
- const { lastFrame, unmount } = renderGrid({
1144
- categories: manyCategories
1145
- });
1146
- cleanup = unmount;
1147
- const output = lastFrame();
1148
- for (const category of manyCategories) {
1149
- globalExpect(output).toContain(category.displayName);
1150
- }
1151
- globalExpect(output).not.toContain("more categories");
1152
- });
1153
- it("should keep focused category visible when navigating down", async () => {
1154
- const onFocusChange = vi.fn();
1155
- const { stdin, lastFrame, unmount } = renderGrid({
1156
- categories: navCategories,
1157
- defaultFocusedRow: 0,
1158
- onFocusChange
1159
- });
1160
- cleanup = unmount;
1161
- await delay(RENDER_DELAY_MS);
1162
- await stdin.write(ARROW_DOWN);
1163
- await delay(INPUT_DELAY_MS);
1164
- await stdin.write(ARROW_DOWN);
1165
- await delay(INPUT_DELAY_MS);
1166
- await stdin.write(ARROW_DOWN);
1167
- await delay(INPUT_DELAY_MS);
1168
- const output = lastFrame();
1169
- globalExpect(output).toContain("Category 3");
1170
- });
1171
- });
1172
- describe("selection counter", () => {
1173
- it("should show 'X of 1' for exclusive categories with no selection", () => {
1174
- const categories = [
1175
- createCategory(
1176
- "web-framework",
1177
- "Framework",
1178
- [createOption("web-framework-react"), createOption("web-framework-vue-composition-api")],
1179
- { exclusive: true }
1180
- )
1181
- ];
1182
- const { lastFrame, unmount } = renderGrid({ categories });
1183
- cleanup = unmount;
1184
- const output = lastFrame();
1185
- globalExpect(output).toContain("(0 of 1)");
1186
- });
1187
- it("should show '1 of 1' for exclusive categories with one selection", () => {
1188
- const categories = [
1189
- createCategory(
1190
- "web-framework",
1191
- "Framework",
1192
- [
1193
- createOption("web-framework-react", { selected: true }),
1194
- createOption("web-framework-vue-composition-api")
1195
- ],
1196
- { exclusive: true }
1197
- )
1198
- ];
1199
- const { lastFrame, unmount } = renderGrid({ categories });
1200
- cleanup = unmount;
1201
- const output = lastFrame();
1202
- globalExpect(output).toContain("(1 of 1)");
1203
- });
1204
- it("should not show counter for non-exclusive categories", () => {
1205
- const categories = [
1206
- createCategory(
1207
- "web-testing",
1208
- "Testing",
1209
- [
1210
- createOption("web-testing-vitest", { selected: true }),
1211
- createOption("web-testing-playwright-e2e", { selected: true }),
1212
- createOption("web-testing-cypress-e2e")
1213
- ],
1214
- { exclusive: false }
1215
- )
1216
- ];
1217
- const { lastFrame, unmount } = renderGrid({ categories });
1218
- cleanup = unmount;
1219
- const output = lastFrame();
1220
- globalExpect(output).not.toContain("selected)");
1221
- globalExpect(output).not.toContain("of 1)");
1222
- });
1223
- it("should only show counter for exclusive categories in mixed layout", () => {
1224
- const categories = [
1225
- createCategory(
1226
- "web-framework",
1227
- "Framework",
1228
- [
1229
- createOption("web-framework-react", { selected: true }),
1230
- createOption("web-framework-vue-composition-api")
1231
- ],
1232
- { exclusive: true }
1233
- ),
1234
- createCategory(
1235
- "web-testing",
1236
- "Testing",
1237
- [
1238
- createOption("web-testing-vitest", { selected: true }),
1239
- createOption("web-testing-playwright-e2e", { selected: true }),
1240
- createOption("web-testing-cypress-e2e")
1241
- ],
1242
- { exclusive: false }
1243
- )
1244
- ];
1245
- const { lastFrame, unmount } = renderGrid({ categories });
1246
- cleanup = unmount;
1247
- const output = lastFrame();
1248
- globalExpect(output).toContain("(1 of 1)");
1249
- globalExpect(output).not.toContain("selected)");
1250
- });
1251
- });
1252
- });
1253
- //# sourceMappingURL=category-grid.test.js.map