termcast 1.3.32 → 1.3.34

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 (327) hide show
  1. package/dist/action-utils.d.ts.map +1 -1
  2. package/dist/action-utils.js +8 -0
  3. package/dist/action-utils.js.map +1 -1
  4. package/dist/apis/cache.d.ts +1 -2
  5. package/dist/apis/cache.d.ts.map +1 -1
  6. package/dist/apis/cache.js +138 -54
  7. package/dist/apis/cache.js.map +1 -1
  8. package/dist/apis/clipboard.d.ts.map +1 -1
  9. package/dist/apis/clipboard.js +4 -0
  10. package/dist/apis/clipboard.js.map +1 -1
  11. package/dist/apis/oauth.d.ts.map +1 -1
  12. package/dist/apis/oauth.js +31 -4
  13. package/dist/apis/oauth.js.map +1 -1
  14. package/dist/build.d.ts +0 -1
  15. package/dist/build.d.ts.map +1 -1
  16. package/dist/build.js +30 -51
  17. package/dist/build.js.map +1 -1
  18. package/dist/cli.js +31 -14
  19. package/dist/cli.js.map +1 -1
  20. package/dist/compile.d.ts.map +1 -1
  21. package/dist/compile.js +5 -1
  22. package/dist/compile.js.map +1 -1
  23. package/dist/components/actions.d.ts +14 -0
  24. package/dist/components/actions.d.ts.map +1 -1
  25. package/dist/components/actions.js +151 -59
  26. package/dist/components/actions.js.map +1 -1
  27. package/dist/components/alert.d.ts.map +1 -1
  28. package/dist/components/alert.js +6 -5
  29. package/dist/components/alert.js.map +1 -1
  30. package/dist/components/animation-tick.d.ts +1 -1
  31. package/dist/components/animation-tick.js +1 -1
  32. package/dist/components/animation-tick.js.map +1 -1
  33. package/dist/components/detail.d.ts +5 -31
  34. package/dist/components/detail.d.ts.map +1 -1
  35. package/dist/components/detail.js +36 -52
  36. package/dist/components/detail.js.map +1 -1
  37. package/dist/components/dropdown.d.ts +1 -1
  38. package/dist/components/dropdown.d.ts.map +1 -1
  39. package/dist/components/dropdown.js +50 -22
  40. package/dist/components/dropdown.js.map +1 -1
  41. package/dist/components/footer.d.ts.map +1 -1
  42. package/dist/components/footer.js +19 -18
  43. package/dist/components/footer.js.map +1 -1
  44. package/dist/components/form/checkbox.d.ts.map +1 -1
  45. package/dist/components/form/checkbox.js +12 -11
  46. package/dist/components/form/checkbox.js.map +1 -1
  47. package/dist/components/form/date-picker.d.ts.map +1 -1
  48. package/dist/components/form/date-picker.js +7 -22
  49. package/dist/components/form/date-picker.js.map +1 -1
  50. package/dist/components/form/description.d.ts +1 -1
  51. package/dist/components/form/description.d.ts.map +1 -1
  52. package/dist/components/form/description.js +6 -5
  53. package/dist/components/form/description.js.map +1 -1
  54. package/dist/components/form/dropdown.d.ts.map +1 -1
  55. package/dist/components/form/dropdown.js +53 -50
  56. package/dist/components/form/dropdown.js.map +1 -1
  57. package/dist/components/form/file-autocomplete.d.ts.map +1 -1
  58. package/dist/components/form/file-autocomplete.js +5 -4
  59. package/dist/components/form/file-autocomplete.js.map +1 -1
  60. package/dist/components/form/file-picker.d.ts.map +1 -1
  61. package/dist/components/form/file-picker.js +23 -22
  62. package/dist/components/form/file-picker.js.map +1 -1
  63. package/dist/components/form/form-end.d.ts.map +1 -1
  64. package/dist/components/form/form-end.js +6 -4
  65. package/dist/components/form/form-end.js.map +1 -1
  66. package/dist/components/form/form-field-wrapper.d.ts +15 -0
  67. package/dist/components/form/form-field-wrapper.d.ts.map +1 -0
  68. package/dist/components/form/form-field-wrapper.js +29 -0
  69. package/dist/components/form/form-field-wrapper.js.map +1 -0
  70. package/dist/components/form/index.d.ts.map +1 -1
  71. package/dist/components/form/index.js +31 -30
  72. package/dist/components/form/index.js.map +1 -1
  73. package/dist/components/form/password-field.d.ts.map +1 -1
  74. package/dist/components/form/password-field.js +7 -6
  75. package/dist/components/form/password-field.js.map +1 -1
  76. package/dist/components/form/separator.d.ts.map +1 -1
  77. package/dist/components/form/separator.js +3 -2
  78. package/dist/components/form/separator.js.map +1 -1
  79. package/dist/components/form/tagpicker.d.ts.map +1 -1
  80. package/dist/components/form/tagpicker.js +2 -1
  81. package/dist/components/form/tagpicker.js.map +1 -1
  82. package/dist/components/form/text-area.d.ts.map +1 -1
  83. package/dist/components/form/text-area.js +7 -6
  84. package/dist/components/form/text-area.js.map +1 -1
  85. package/dist/components/form/text-field.d.ts.map +1 -1
  86. package/dist/components/form/text-field.js +7 -6
  87. package/dist/components/form/text-field.js.map +1 -1
  88. package/dist/components/form/use-form-navigation.d.ts.map +1 -1
  89. package/dist/components/form/use-form-navigation.js +4 -4
  90. package/dist/components/form/use-form-navigation.js.map +1 -1
  91. package/dist/components/form/with-left-border.d.ts +15 -0
  92. package/dist/components/form/with-left-border.d.ts.map +1 -1
  93. package/dist/components/form/with-left-border.js +21 -9
  94. package/dist/components/form/with-left-border.js.map +1 -1
  95. package/dist/components/icon.d.ts +14 -0
  96. package/dist/components/icon.d.ts.map +1 -1
  97. package/dist/components/icon.js +60 -0
  98. package/dist/components/icon.js.map +1 -1
  99. package/dist/components/image.d.ts +47 -2
  100. package/dist/components/image.d.ts.map +1 -1
  101. package/dist/components/image.js +46 -7
  102. package/dist/components/image.js.map +1 -1
  103. package/dist/components/list.d.ts +5 -0
  104. package/dist/components/list.d.ts.map +1 -1
  105. package/dist/components/list.js +188 -132
  106. package/dist/components/list.js.map +1 -1
  107. package/dist/components/loading-bar.d.ts.map +1 -1
  108. package/dist/components/loading-bar.js +4 -3
  109. package/dist/components/loading-bar.js.map +1 -1
  110. package/dist/components/metadata.d.ts +70 -0
  111. package/dist/components/metadata.d.ts.map +1 -0
  112. package/dist/components/metadata.js +82 -0
  113. package/dist/components/metadata.js.map +1 -0
  114. package/dist/components/theme-picker.d.ts.map +1 -1
  115. package/dist/components/theme-picker.js +3 -2
  116. package/dist/components/theme-picker.js.map +1 -1
  117. package/dist/descendants-v2.d.ts +60 -0
  118. package/dist/descendants-v2.d.ts.map +1 -0
  119. package/dist/descendants-v2.js +144 -0
  120. package/dist/descendants-v2.js.map +1 -0
  121. package/dist/examples/actions-context.d.ts +2 -0
  122. package/dist/examples/actions-context.d.ts.map +1 -0
  123. package/dist/examples/actions-context.js +33 -0
  124. package/dist/examples/actions-context.js.map +1 -0
  125. package/dist/examples/form-basic.d.ts.map +1 -1
  126. package/dist/examples/form-basic.js +1 -1
  127. package/dist/examples/form-basic.js.map +1 -1
  128. package/dist/examples/form-dropdown.js +1 -1
  129. package/dist/examples/form-dropdown.js.map +1 -1
  130. package/dist/examples/internal/custom-action-renderables.d.ts +70 -0
  131. package/dist/examples/internal/custom-action-renderables.d.ts.map +1 -0
  132. package/dist/examples/internal/custom-action-renderables.js +163 -0
  133. package/dist/examples/internal/custom-action-renderables.js.map +1 -0
  134. package/dist/examples/internal/custom-dropdown.d.ts +99 -0
  135. package/dist/examples/internal/custom-dropdown.d.ts.map +1 -0
  136. package/dist/examples/internal/custom-dropdown.js +270 -0
  137. package/dist/examples/internal/custom-dropdown.js.map +1 -0
  138. package/dist/examples/internal/custom-renderable-form.d.ts +43 -0
  139. package/dist/examples/internal/custom-renderable-form.d.ts.map +1 -0
  140. package/dist/examples/internal/custom-renderable-form.js +284 -0
  141. package/dist/examples/internal/custom-renderable-form.js.map +1 -0
  142. package/dist/examples/internal/custom-renderable-list-default-search.d.ts +2 -0
  143. package/dist/examples/internal/custom-renderable-list-default-search.d.ts.map +1 -0
  144. package/dist/examples/internal/custom-renderable-list-default-search.js +16 -0
  145. package/dist/examples/internal/custom-renderable-list-default-search.js.map +1 -0
  146. package/dist/examples/internal/custom-renderable-list-v2-default-search.d.ts +2 -0
  147. package/dist/examples/internal/custom-renderable-list-v2-default-search.d.ts.map +1 -0
  148. package/dist/examples/internal/custom-renderable-list-v2-default-search.js +24 -0
  149. package/dist/examples/internal/custom-renderable-list-v2-default-search.js.map +1 -0
  150. package/dist/examples/internal/custom-renderable-list-v2.d.ts +189 -0
  151. package/dist/examples/internal/custom-renderable-list-v2.d.ts.map +1 -0
  152. package/dist/examples/internal/custom-renderable-list-v2.js +708 -0
  153. package/dist/examples/internal/custom-renderable-list-v2.js.map +1 -0
  154. package/dist/examples/internal/custom-renderable-list.d.ts +72 -0
  155. package/dist/examples/internal/custom-renderable-list.d.ts.map +1 -0
  156. package/dist/examples/internal/custom-renderable-list.js +544 -0
  157. package/dist/examples/internal/custom-renderable-list.js.map +1 -0
  158. package/dist/examples/internal/rhf-custom-ref.js +5 -4
  159. package/dist/examples/internal/rhf-custom-ref.js.map +1 -1
  160. package/dist/examples/internal/scrollbox-with-descendants.js +4 -2
  161. package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
  162. package/dist/examples/list-controlled-search.d.ts +2 -0
  163. package/dist/examples/list-controlled-search.d.ts.map +1 -0
  164. package/dist/examples/list-controlled-search.js +12 -0
  165. package/dist/examples/list-controlled-search.js.map +1 -0
  166. package/dist/examples/list-detail-metadata.js +1 -1
  167. package/dist/examples/list-detail-metadata.js.map +1 -1
  168. package/dist/examples/simple-image-mask.d.ts +8 -0
  169. package/dist/examples/simple-image-mask.d.ts.map +1 -0
  170. package/dist/examples/simple-image-mask.js +12 -0
  171. package/dist/examples/simple-image-mask.js.map +1 -0
  172. package/dist/examples/toast-variations.js +1 -1
  173. package/dist/examples/toast-variations.js.map +1 -1
  174. package/dist/extensions/dev.d.ts.map +1 -1
  175. package/dist/extensions/dev.js +3 -2
  176. package/dist/extensions/dev.js.map +1 -1
  177. package/dist/extensions/react-refresh-init.d.ts.map +1 -1
  178. package/dist/extensions/react-refresh-init.js +4 -3
  179. package/dist/extensions/react-refresh-init.js.map +1 -1
  180. package/dist/index.d.ts +3 -2
  181. package/dist/index.d.ts.map +1 -1
  182. package/dist/index.js +1 -1
  183. package/dist/index.js.map +1 -1
  184. package/dist/internal/date-picker-widget.d.ts.map +1 -1
  185. package/dist/internal/date-picker-widget.js +2 -1
  186. package/dist/internal/date-picker-widget.js.map +1 -1
  187. package/dist/internal/dialog.d.ts +6 -0
  188. package/dist/internal/dialog.d.ts.map +1 -1
  189. package/dist/internal/dialog.js +59 -18
  190. package/dist/internal/dialog.js.map +1 -1
  191. package/dist/internal/navigation.d.ts.map +1 -1
  192. package/dist/internal/navigation.js +8 -1
  193. package/dist/internal/navigation.js.map +1 -1
  194. package/dist/internal/offscreen.d.ts +3 -0
  195. package/dist/internal/offscreen.d.ts.map +1 -1
  196. package/dist/internal/offscreen.js +5 -0
  197. package/dist/internal/offscreen.js.map +1 -1
  198. package/dist/internal/providers.d.ts.map +1 -1
  199. package/dist/internal/providers.js +20 -3
  200. package/dist/internal/providers.js.map +1 -1
  201. package/dist/internal/scrollbox.d.ts.map +1 -1
  202. package/dist/internal/scrollbox.js +3 -2
  203. package/dist/internal/scrollbox.js.map +1 -1
  204. package/dist/logger.d.ts.map +1 -1
  205. package/dist/logger.js +4 -0
  206. package/dist/logger.js.map +1 -1
  207. package/dist/preload.js +5 -17
  208. package/dist/preload.js.map +1 -1
  209. package/dist/state.d.ts +4 -0
  210. package/dist/state.d.ts.map +1 -1
  211. package/dist/state.js +4 -0
  212. package/dist/state.js.map +1 -1
  213. package/dist/test-border-overlay.d.ts +2 -0
  214. package/dist/test-border-overlay.d.ts.map +1 -0
  215. package/dist/test-border-overlay.js +7 -0
  216. package/dist/test-border-overlay.js.map +1 -0
  217. package/dist/test-layout-2.d.ts +2 -0
  218. package/dist/test-layout-2.d.ts.map +1 -0
  219. package/dist/test-layout-2.js +5 -0
  220. package/dist/test-layout-2.js.map +1 -0
  221. package/dist/test-layout.d.ts +2 -0
  222. package/dist/test-layout.d.ts.map +1 -0
  223. package/dist/test-layout.js +7 -0
  224. package/dist/test-layout.js.map +1 -0
  225. package/dist/theme.d.ts +1 -2
  226. package/dist/theme.d.ts.map +1 -1
  227. package/dist/theme.js +5 -9
  228. package/dist/theme.js.map +1 -1
  229. package/dist/utils/run-command.d.ts +1 -1
  230. package/dist/utils/run-command.d.ts.map +1 -1
  231. package/dist/utils/run-command.js +27 -7
  232. package/dist/utils/run-command.js.map +1 -1
  233. package/dist/utils.d.ts +1 -0
  234. package/dist/utils.d.ts.map +1 -1
  235. package/dist/utils.js +44 -23
  236. package/dist/utils.js.map +1 -1
  237. package/dist/watcher.d.ts.map +1 -1
  238. package/dist/watcher.js +24 -4
  239. package/dist/watcher.js.map +1 -1
  240. package/package.json +14 -12
  241. package/src/action-utils.tsx +10 -0
  242. package/src/apis/cache.test.ts +35 -3
  243. package/src/apis/cache.tsx +184 -59
  244. package/src/apis/clipboard.tsx +5 -0
  245. package/src/apis/oauth.tsx +33 -4
  246. package/src/build.tsx +35 -58
  247. package/src/cli.tsx +156 -134
  248. package/src/compile.tsx +6 -3
  249. package/src/compile.vitest.tsx +33 -15
  250. package/src/components/actions.tsx +230 -99
  251. package/src/components/alert.tsx +11 -10
  252. package/src/components/animation-tick.tsx +1 -1
  253. package/src/components/detail.tsx +56 -151
  254. package/src/components/dropdown.tsx +70 -36
  255. package/src/components/footer.tsx +58 -33
  256. package/src/components/form/checkbox.tsx +30 -32
  257. package/src/components/form/date-picker.tsx +27 -47
  258. package/src/components/form/description.tsx +19 -18
  259. package/src/components/form/dropdown.tsx +95 -103
  260. package/src/components/form/file-autocomplete.tsx +9 -8
  261. package/src/components/form/file-picker.tsx +46 -46
  262. package/src/components/form/form-end.tsx +6 -4
  263. package/src/components/form/index.tsx +38 -48
  264. package/src/components/form/password-field.tsx +25 -27
  265. package/src/components/form/separator.tsx +3 -2
  266. package/src/components/form/tagpicker.tsx +2 -1
  267. package/src/components/form/text-area.tsx +25 -30
  268. package/src/components/form/text-field.tsx +25 -27
  269. package/src/components/form/use-form-navigation.tsx +4 -5
  270. package/src/components/form/with-left-border.tsx +48 -10
  271. package/src/components/icon.tsx +69 -0
  272. package/src/components/image.tsx +60 -7
  273. package/src/components/list.tsx +270 -202
  274. package/src/components/loading-bar.tsx +4 -3
  275. package/src/components/metadata.tsx +217 -0
  276. package/src/components/theme-picker.tsx +3 -2
  277. package/src/examples/actions-context.tsx +63 -0
  278. package/src/examples/actions-context.vitest.tsx +110 -0
  279. package/src/examples/actions-dialog-layout.vitest.tsx +2 -1
  280. package/src/examples/file-autocomplete.vitest.tsx +15 -15
  281. package/src/examples/form-basic.tsx +12 -0
  282. package/src/examples/form-basic.vitest.tsx +74 -74
  283. package/src/examples/form-dropdown.tsx +8 -0
  284. package/src/examples/form-dropdown.vitest.tsx +364 -421
  285. package/src/examples/form-tagpicker.vitest.tsx +56 -54
  286. package/src/examples/github.vitest.tsx +252 -0
  287. package/src/examples/internal/rhf-custom-ref.tsx +16 -15
  288. package/src/examples/internal/scrollbox-with-descendants.tsx +4 -2
  289. package/src/examples/internal/simple-dialog.tsx +1 -1
  290. package/src/examples/internal/simple-scrollbox.vitest.tsx +14 -9
  291. package/src/examples/list-controlled-search.tsx +28 -0
  292. package/src/examples/list-controlled-search.vitest.tsx +49 -0
  293. package/src/examples/list-detail-metadata.tsx +8 -5
  294. package/src/examples/list-detail-metadata.vitest.tsx +22 -22
  295. package/src/examples/list-dropdown-default.vitest.tsx +12 -12
  296. package/src/examples/list-scrollbox.vitest.tsx +52 -38
  297. package/src/examples/list-with-detail.vitest.tsx +45 -41
  298. package/src/examples/list-with-dropdown.vitest.tsx +5 -5
  299. package/src/examples/list-with-sections.vitest.tsx +65 -12
  300. package/src/examples/list-with-toast.vitest.tsx +4 -4
  301. package/src/examples/simple-file-picker.vitest.tsx +12 -12
  302. package/src/examples/simple-grid.vitest.tsx +53 -53
  303. package/src/examples/simple-image-mask.tsx +58 -0
  304. package/src/examples/simple-navigation.vitest.tsx +19 -19
  305. package/src/examples/store.vitest.tsx +1 -1
  306. package/src/examples/swift-extension.vitest.tsx +4 -2
  307. package/src/examples/synonyms.vitest.tsx +31 -9
  308. package/src/examples/toast-action.vitest.tsx +8 -8
  309. package/src/examples/toast-variations.tsx +1 -1
  310. package/src/examples/toast-variations.vitest.tsx +69 -134
  311. package/src/extensions/dev.tsx +3 -2
  312. package/src/extensions/dev.vitest.tsx +65 -28
  313. package/src/extensions/react-refresh-init.tsx +4 -3
  314. package/src/index.tsx +3 -1
  315. package/src/internal/date-picker-widget.tsx +2 -1
  316. package/src/internal/dialog.tsx +100 -28
  317. package/src/internal/navigation.tsx +8 -1
  318. package/src/internal/offscreen.tsx +10 -0
  319. package/src/internal/providers.tsx +34 -8
  320. package/src/internal/scrollbox.tsx +4 -2
  321. package/src/logger.tsx +4 -0
  322. package/src/preload.tsx +5 -17
  323. package/src/state.tsx +12 -0
  324. package/src/theme.tsx +6 -9
  325. package/src/utils/run-command.tsx +32 -8
  326. package/src/utils.tsx +58 -23
  327. package/src/watcher.tsx +26 -6
@@ -43,9 +43,9 @@ test('pressing enter triggers primary action on toast', async () => {
43
43
  Other Item
44
44
 
45
45
 
46
- ✓ File Deleted document.pdf was moved to trash Undo ctrl t
47
46
 
48
47
 
48
+ ✓ File Deleted document.pdf was moved to trash Undo ctrl t
49
49
 
50
50
 
51
51
 
@@ -76,9 +76,9 @@ test('pressing enter triggers primary action on toast', async () => {
76
76
  Other Item
77
77
 
78
78
 
79
- ✓ Undone File restored
80
79
 
81
80
 
81
+ ✓ Undone File restored
82
82
 
83
83
 
84
84
 
@@ -122,9 +122,9 @@ test('pressing escape hides the toast', async () => {
122
122
  Other Item
123
123
 
124
124
 
125
- ↵ show toast ↑↓ navigate ^k actions
126
125
 
127
126
 
127
+ ↵ show toast ↑↓ navigate ^k actions
128
128
 
129
129
 
130
130
 
@@ -195,7 +195,7 @@ test('form toast: pressing enter triggers primary action (navigation)', async ()
195
195
  "
196
196
 
197
197
 
198
-
198
+
199
199
 
200
200
  ◆ Name
201
201
  │ John
@@ -203,7 +203,7 @@ test('form toast: pressing enter triggers primary action (navigation)', async ()
203
203
 
204
204
 
205
205
 
206
- ✓ Form Submitted Hello, John! View Details ctrl t
206
+ ✓ Form Submitted Hello, John! View Details ctrl t
207
207
 
208
208
 
209
209
 
@@ -233,12 +233,12 @@ test('form toast: pressing enter triggers primary action (navigation)', async ()
233
233
  ›Welcome, John! Form submission successful
234
234
 
235
235
 
236
- ↑↓ navigate ^k actions
237
236
 
238
237
 
239
238
 
240
239
 
241
240
 
241
+ ↑↓ navigate ^k actions
242
242
 
243
243
 
244
244
 
@@ -358,7 +358,7 @@ test('delayed toast action: primaryAction set after toast shown works with enter
358
358
 
359
359
 
360
360
 
361
- ✓ Done Hello, Test! Open ctrl t
361
+ ✓ Done Hello, Test! Open ctrl t
362
362
 
363
363
 
364
364
 
@@ -390,7 +390,7 @@ test('delayed toast action: primaryAction set after toast shown works with enter
390
390
 
391
391
 
392
392
 
393
- ✓ Opened! Action triggered successfully
393
+ ✓ Opened! Action triggered successfully
394
394
 
395
395
 
396
396
 
@@ -35,7 +35,7 @@ const toastVariations: ToastVariation[] = [
35
35
  options: {
36
36
  title: 'Error',
37
37
  message:
38
- 'This is a very long error message that should wrap to multiple lines when displayed in the toast component.',
38
+ 'This is a very long error message that should wrap to multiple lines when displayed in the toast component. This is a very long error message that should wrap to multiple lines when displayed in the toast component.',
39
39
  style: Toast.Style.Failure,
40
40
  },
41
41
  },
@@ -1,6 +1,15 @@
1
1
  import { test, expect, afterEach, beforeEach } from 'vitest'
2
2
  import { launchTerminal, Session } from 'tuistory/src'
3
3
 
4
+ // Normalize terminal output to avoid flaky tests:
5
+ // 1. Collapse multiple consecutive blank lines into exactly 2 blank lines
6
+ // 2. Trim trailing whitespace
7
+ function normalizeOutput(text: string): string {
8
+ return text
9
+ .replace(/\n(\s*\n){2,}/g, '\n\n\n') // Collapse 3+ consecutive newlines to 2 blank lines
10
+ .replace(/\n+\s*$/, '\n') // Trim trailing blank lines
11
+ }
12
+
4
13
  let session: Session
5
14
 
6
15
  beforeEach(async () => {
@@ -17,11 +26,11 @@ afterEach(() => {
17
26
  })
18
27
 
19
28
  test('toast variations display correctly', async () => {
20
- // Simple Success
29
+ // Simple Success - wait for toast to appear after list items (with any number of blank lines)
21
30
  const simpleSuccess = await session.text({
22
- waitFor: (text) => text.includes('Simple Success') && text.includes('Success'),
31
+ waitFor: (text) => text.includes('Simple Success') && /Error with Retry[\s\n]+✓ Success/.test(text),
23
32
  })
24
- expect(simpleSuccess).toMatchInlineSnapshot(`
33
+ expect(normalizeOutput(simpleSuccess)).toMatchInlineSnapshot(`
25
34
  "
26
35
 
27
36
 
@@ -41,24 +50,16 @@ test('toast variations display correctly', async () => {
41
50
  Error with Retry
42
51
 
43
52
 
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
- ✓ Success
54
-
53
+ ✓ Success
55
54
  "
56
55
  `)
57
56
 
58
57
  // Simple Failure
59
58
  await session.press('down')
60
- const simpleFailure = await session.text()
61
- expect(simpleFailure).toMatchInlineSnapshot(`
59
+ const simpleFailure = await session.text({
60
+ waitFor: (text) => text.includes('›Simple Failure') && /Error with Retry[\s\n]+✗ Error/.test(text),
61
+ })
62
+ expect(normalizeOutput(simpleFailure)).toMatchInlineSnapshot(`
62
63
  "
63
64
 
64
65
 
@@ -78,24 +79,16 @@ test('toast variations display correctly', async () => {
78
79
  Error with Retry
79
80
 
80
81
 
81
- ✗ Error
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
82
+ ✗ Error
92
83
  "
93
84
  `)
94
85
 
95
86
  // With Short Message
96
87
  await session.press('down')
97
- const shortMessage = await session.text()
98
- expect(shortMessage).toMatchInlineSnapshot(`
88
+ const shortMessage = await session.text({
89
+ waitFor: (text) => text.includes('›With Short Message') && /Error with Retry[\s\n]+✓ Copied/.test(text),
90
+ })
91
+ expect(normalizeOutput(shortMessage)).toMatchInlineSnapshot(`
99
92
  "
100
93
 
101
94
 
@@ -115,24 +108,16 @@ test('toast variations display correctly', async () => {
115
108
  Error with Retry
116
109
 
117
110
 
118
- ✓ Copied Text copied to clipboard
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
111
+ ✓ Copied Text copied to clipboard
129
112
  "
130
113
  `)
131
114
 
132
115
  // With Long Message
133
116
  await session.press('down')
134
- const longMessage = await session.text()
135
- expect(longMessage).toMatchInlineSnapshot(`
117
+ const longMessage = await session.text({
118
+ waitFor: (text) => text.includes('›With Long Message') && /Error with Retry[\s\n]+✗ Error/.test(text),
119
+ })
120
+ expect(normalizeOutput(longMessage)).toMatchInlineSnapshot(`
136
121
  "
137
122
 
138
123
 
@@ -152,24 +137,17 @@ test('toast variations display correctly', async () => {
152
137
  Error with Retry
153
138
 
154
139
 
155
- ✗ Error This is a very long error message that should wrap to multiple
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
140
+ ✗ Error This is a very long error message that should wrap to multiple line
166
141
  "
167
142
  `)
168
143
 
169
144
  // With Super Long Message
170
145
  await session.press('down')
171
- const superLongMessage = await session.text()
172
- expect(superLongMessage).toMatchInlineSnapshot(`
146
+ const superLongMessage = await session.text({
147
+ waitFor: (text) =>
148
+ text.includes('›With Super Long Message') && /Error with Retry[\s\n]+✗ Warning/.test(text),
149
+ })
150
+ expect(normalizeOutput(superLongMessage)).toMatchInlineSnapshot(`
173
151
  "
174
152
 
175
153
 
@@ -180,8 +158,8 @@ test('toast variations display correctly', async () => {
180
158
  Simple Success
181
159
  Simple Failure
182
160
  With Short Message
183
- With Long Message
184
- With Super Long Message
161
+ With Long Message
162
+ With Super Long Message
185
163
  With Primary Action
186
164
  With Both Actions
187
165
  Long Title with Actions
@@ -189,24 +167,17 @@ test('toast variations display correctly', async () => {
189
167
  Error with Retry
190
168
 
191
169
 
192
- Error This is a very long error message that should wrap to multiple
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
170
+ Warning Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do e
203
171
  "
204
172
  `)
205
173
 
206
174
  // With Primary Action
207
175
  await session.press('down')
208
- const primaryAction = await session.text()
209
- expect(primaryAction).toMatchInlineSnapshot(`
176
+ const primaryAction = await session.text({
177
+ waitFor: (text) =>
178
+ text.includes('›With Primary Action') && /Error with Retry[\s\n]+✓ File Deleted/.test(text),
179
+ })
180
+ expect(normalizeOutput(primaryAction)).toMatchInlineSnapshot(`
210
181
  "
211
182
 
212
183
 
@@ -226,24 +197,17 @@ test('toast variations display correctly', async () => {
226
197
  Error with Retry
227
198
 
228
199
 
229
- ✓ File Deleted document.pdf was moved to trash Undo ctrl t
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
200
+ ✓ File Deleted document.pdf was moved to trash Undo ctrl t
240
201
  "
241
202
  `)
242
203
 
243
204
  // With Both Actions
244
205
  await session.press('down')
245
- const bothActions = await session.text()
246
- expect(bothActions).toMatchInlineSnapshot(`
206
+ const bothActions = await session.text({
207
+ waitFor: (text) =>
208
+ text.includes('›With Both Actions') && /Error with Retry[\s\n]+✓ Update Available/.test(text),
209
+ })
210
+ expect(normalizeOutput(bothActions)).toMatchInlineSnapshot(`
247
211
  "
248
212
 
249
213
 
@@ -263,24 +227,17 @@ test('toast variations display correctly', async () => {
263
227
  Error with Retry
264
228
 
265
229
 
266
- ✓ Update Available Version 2.0 is ready to i Install ctrl t Later ctrl g
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
230
+ ✓ Update Available Version 2.0 is ready to install Install ctrl t Later ct
277
231
  "
278
232
  `)
279
233
 
280
234
  // Long Title with Actions
281
235
  await session.press('down')
282
- const longTitleActions = await session.text()
283
- expect(longTitleActions).toMatchInlineSnapshot(`
236
+ const longTitleActions = await session.text({
237
+ waitFor: (text) =>
238
+ text.includes('›Long Title with Actions') && /Error with Retry[\s\n]+✓ Operation Completed/.test(text),
239
+ })
240
+ expect(normalizeOutput(longTitleActions)).toMatchInlineSnapshot(`
284
241
  "
285
242
 
286
243
 
@@ -300,24 +257,19 @@ test('toast variations display correctly', async () => {
300
257
  Error with Retry
301
258
 
302
259
 
303
- ✓ Operation Completed Successfully Al View Results ctrl t Dismiss ctrl g
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
260
+ ✓ Operation Completed Successfully All files have been processed View Resu
314
261
  "
315
262
  `)
316
263
 
317
- // Animated Loading
264
+ // Animated Loading - normalize spinner character since it's animated
318
265
  await session.press('down')
319
- const animatedLoading = await session.text()
320
- expect(animatedLoading).toMatchInlineSnapshot(`
266
+ const animatedLoading = await session.text({
267
+ waitFor: (text) =>
268
+ text.includes('›Animated Loading') && /Error with Retry[\s\n]+[⣾⣽⣻⢿⡿⣟⣯⣷] Processing/.test(text),
269
+ })
270
+ // Normalize the spinner character (⣾⣽⣻⢿⡿⣟⣯⣷) to a fixed one for stable snapshot
271
+ const normalizedAnimatedLoading = animatedLoading.replace(/[⣾⣽⣻⢿⡿⣟⣯⣷]/g, '◌')
272
+ expect(normalizeOutput(normalizedAnimatedLoading)).toMatchInlineSnapshot(`
321
273
  "
322
274
 
323
275
 
@@ -337,24 +289,17 @@ test('toast variations display correctly', async () => {
337
289
  Error with Retry
338
290
 
339
291
 
340
- Processing Please wait while we process your request...
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
-
292
+ Processing Please wait while we process your request...
351
293
  "
352
294
  `)
353
295
 
354
296
  // Error with Retry
355
297
  await session.press('down')
356
- const errorRetry = await session.text()
357
- expect(errorRetry).toMatchInlineSnapshot(`
298
+ const errorRetry = await session.text({
299
+ waitFor: (text) =>
300
+ text.includes('›Error with Retry') && /Error with Retry[\s\n]+✗ Connection Failed/.test(text),
301
+ })
302
+ expect(normalizeOutput(errorRetry)).toMatchInlineSnapshot(`
358
303
  "
359
304
 
360
305
 
@@ -374,17 +319,7 @@ test('toast variations display correctly', async () => {
374
319
  ›Error with Retry
375
320
 
376
321
 
377
- ✗ Connection Failed Unable to connect to the server. Please Retry ctrl t
378
-
379
-
380
-
381
-
382
-
383
-
384
-
385
-
386
-
387
-
322
+ ✗ Connection Failed Unable to connect to the server. Please check your inte
388
323
  "
389
324
  `)
390
325
  }, 30000)
@@ -14,7 +14,7 @@ import { useNavigation } from 'termcast/src/internal/navigation'
14
14
  import { TermcastProvider } from 'termcast/src/internal/providers'
15
15
  import { showToast, Toast } from 'termcast/src/apis/toast'
16
16
  import { Icon } from 'termcast'
17
- import { Theme } from 'termcast/src/theme'
17
+ import { useTheme } from 'termcast/src/theme'
18
18
  import { logger } from '../logger'
19
19
  import { getCommandsWithFiles, CommandWithFile, RaycastPackageJson } from '../package-json'
20
20
  import { buildExtensionCommands } from '../build'
@@ -26,9 +26,10 @@ import {
26
26
  } from '../utils/run-command'
27
27
 
28
28
  function CommandLoadError({ error }: { error: Error }): any {
29
+ const theme = useTheme()
29
30
  return (
30
31
  <box padding={2}>
31
- <text fg={Theme.error} wrapMode='none'>{error.stack}</text>
32
+ <text fg={theme.error} wrapMode='none'>{error.stack}</text>
32
33
  </box>
33
34
  )
34
35
  }
@@ -1,11 +1,33 @@
1
1
  import { test, expect, afterEach, beforeEach } from 'vitest'
2
2
  import { launchTerminal, Session } from 'tuistory/src'
3
+ import fs from 'node:fs'
3
4
  import path from 'node:path'
4
5
 
5
6
  let session: Session
6
7
 
7
8
  const fixtureDir = path.resolve(__dirname, '../../fixtures/simple-extension')
8
9
 
10
+ async function waitForTextWithDebug({
11
+ session,
12
+ waitFor,
13
+ timeout,
14
+ }: {
15
+ session: Session
16
+ waitFor: (text: string) => boolean
17
+ timeout: number
18
+ }): Promise<string> {
19
+ try {
20
+ return await session.text({ waitFor, timeout })
21
+ } catch (error) {
22
+ const logPath = path.resolve(process.cwd(), 'app.log')
23
+ const appLog = fs.existsSync(logPath)
24
+ ? fs.readFileSync(logPath, 'utf-8').slice(-8000)
25
+ : 'app.log not found'
26
+ const message = error instanceof Error ? error.message : String(error)
27
+ throw new Error(`${message}\n\napp.log tail:\n${appLog}`)
28
+ }
29
+ }
30
+
9
31
  beforeEach(async () => {
10
32
  session = await launchTerminal({
11
33
  command: 'bun',
@@ -20,7 +42,8 @@ afterEach(() => {
20
42
  })
21
43
 
22
44
  test('dev command shows extension commands list', async () => {
23
- await session.text({
45
+ await waitForTextWithDebug({
46
+ session,
24
47
  waitFor: (text) => /Simple Test Extension/i.test(text),
25
48
  timeout: 10000,
26
49
  })
@@ -38,17 +61,18 @@ test('dev command shows extension commands list', async () => {
38
61
  ›List Items Displays a simple list with some ite view ▀
39
62
  Search Items Search and filter through a list o view
40
63
  Google Oauth view
41
- usePromise Demo Shows how to use the usePromise view
64
+ usePromise Demo Shows how to use the usePromise view
65
+ Show State Shows the current application state view ▼
42
66
 
43
67
 
44
68
  ↵ run command ↑↓ navigate ^k actions
45
-
46
69
  "
47
70
  `)
48
71
  }, 30000)
49
72
 
50
73
  test('selecting command with arguments shows arguments form', async () => {
51
- await session.text({
74
+ await waitForTextWithDebug({
75
+ session,
52
76
  waitFor: (text) => /Simple Test Extension/i.test(text),
53
77
  timeout: 10000,
54
78
  })
@@ -69,15 +93,15 @@ test('selecting command with arguments shows arguments form', async () => {
69
93
 
70
94
  > Search commands...
71
95
 
72
- usePromise Demo Shows how to use the usePromise view ▲
96
+ Google Oauth view ▲
97
+ usePromise Demo Shows how to use the usePromise view
73
98
  Show State Shows the current application state view
74
- ›With Arguments Demonstrates command arguments ( view
75
- Quick Action Copies current timestamp to cli no-view
99
+ ›With Arguments Demonstrates command arguments ( view
100
+ Quick Action Copies current timestamp to cli no-view
76
101
  Throw Error Command that throws an error at roo view ▼
77
102
 
78
103
 
79
104
  ↵ run command ↑↓ navigate ^k actions
80
-
81
105
  "
82
106
  `)
83
107
 
@@ -85,7 +109,8 @@ test('selecting command with arguments shows arguments form', async () => {
85
109
  await session.press('enter')
86
110
  await session.press('enter')
87
111
 
88
- await session.text({
112
+ await waitForTextWithDebug({
113
+ session,
89
114
  waitFor: (text) => /Search query/i.test(text),
90
115
  timeout: 10000,
91
116
  })
@@ -113,7 +138,8 @@ test('selecting command with arguments shows arguments form', async () => {
113
138
  }, 30000)
114
139
 
115
140
  test('can fill arguments and run command', async () => {
116
- await session.text({
141
+ await waitForTextWithDebug({
142
+ session,
117
143
  waitFor: (text) => /Simple Test Extension/i.test(text),
118
144
  timeout: 10000,
119
145
  })
@@ -127,7 +153,8 @@ test('can fill arguments and run command', async () => {
127
153
  await session.press('enter')
128
154
  await session.press('enter')
129
155
 
130
- await session.text({
156
+ await waitForTextWithDebug({
157
+ session,
131
158
  waitFor: (text) => /Search query/i.test(text),
132
159
  timeout: 10000,
133
160
  })
@@ -156,11 +183,13 @@ test('can fill arguments and run command', async () => {
156
183
  "
157
184
  `)
158
185
 
159
- // Submit the form with Alt+Enter (opens action panel), then Enter (selects submit)
160
- await session.press(['alt', 'enter'])
186
+ // Submit via action panel (ctrl+k then enter)
187
+ await session.press(['ctrl', 'k'])
188
+ await session.waitIdle()
161
189
  await session.press('enter')
162
190
 
163
- await session.text({
191
+ await waitForTextWithDebug({
192
+ session,
164
193
  waitFor: (text) => /Received Arguments/i.test(text),
165
194
  timeout: 10000,
166
195
  })
@@ -181,14 +210,15 @@ test('can fill arguments and run command', async () => {
181
210
 
182
211
 
183
212
 
184
- ✓ Copied to Clipboard (empty)
185
213
 
214
+ ↵ copy value ↑↓ navigate ^k actions
186
215
  "
187
216
  `)
188
217
  }, 30000)
189
218
 
190
219
  test('can run simple view command without arguments', async () => {
191
- await session.text({
220
+ await waitForTextWithDebug({
221
+ session,
192
222
  waitFor: (text) => /Simple Test Extension/i.test(text),
193
223
  timeout: 10000,
194
224
  })
@@ -197,7 +227,8 @@ test('can run simple view command without arguments', async () => {
197
227
  await session.press('enter')
198
228
  await session.press('enter')
199
229
 
200
- await session.text({
230
+ await waitForTextWithDebug({
231
+ session,
201
232
  waitFor: (text) => /First Item/i.test(text),
202
233
  timeout: 10000,
203
234
  })
@@ -213,13 +244,13 @@ test('can run simple view command without arguments', async () => {
213
244
 
214
245
  Items ▲
215
246
  ›▲ First Item This is the first item █
216
- ▲ Second Item This is the second item
247
+ ▲ Second Item This is the second item
217
248
  ▲ Third Item This is the third item
218
- ▲ Fourth Item This is the fourth item
219
-
249
+ ▲ Fourth Item This is the fourth item
250
+ ▲ Fifth Item This is the fifth item ▼
220
251
 
221
- ✓ Copied to Clipboard First Item
222
252
 
253
+ ✓ Copied to Clipboard First Item
223
254
  "
224
255
  `)
225
256
  }, 30000)
@@ -246,7 +277,8 @@ test('hot reload updates TUI when source file changes', async () => {
246
277
 
247
278
  try {
248
279
  // Wait for the extension to load
249
- await hotReloadSession.text({
280
+ await waitForTextWithDebug({
281
+ session: hotReloadSession,
250
282
  waitFor: (text) => /Hot Reload Test/i.test(text) && /Detail View/i.test(text),
251
283
  timeout: 5000,
252
284
  })
@@ -258,7 +290,8 @@ test('hot reload updates TUI when source file changes', async () => {
258
290
  await hotReloadSession.waitIdle()
259
291
 
260
292
  // Wait for the detail view to show
261
- await hotReloadSession.text({
293
+ await waitForTextWithDebug({
294
+ session: hotReloadSession,
262
295
  waitFor: (text) => /MARKER_VALUE/i.test(text),
263
296
  timeout: 10000,
264
297
  })
@@ -310,7 +343,8 @@ test('hot reload with navigation - preserves navigation and updates content', as
310
343
 
311
344
  try {
312
345
  // Wait for the extension to load
313
- await hotReloadSession.text({
346
+ await waitForTextWithDebug({
347
+ session: hotReloadSession,
314
348
  waitFor: (text) => /Hot Reload Test/i.test(text) && /List With Navigation/i.test(text),
315
349
  timeout: 10000,
316
350
  })
@@ -319,13 +353,15 @@ test('hot reload with navigation - preserves navigation and updates content', as
319
353
  await hotReloadSession.press('down')
320
354
  await hotReloadSession.waitIdle()
321
355
 
322
- // Run the command (enter opens action panel, enter again runs)
323
- await hotReloadSession.press('enter')
356
+ // Run selected command via actions panel for deterministic behavior
357
+ await hotReloadSession.press(['ctrl', 'k'])
358
+ await hotReloadSession.waitIdle()
324
359
  await hotReloadSession.press('enter')
325
360
  await hotReloadSession.waitIdle()
326
361
 
327
362
  // Wait for the list to show OR the detail view (enter might auto-execute first action)
328
- await hotReloadSession.text({
363
+ await waitForTextWithDebug({
364
+ session: hotReloadSession,
329
365
  waitFor: (text) => /Item One/i.test(text),
330
366
  timeout: 10000,
331
367
  })
@@ -339,7 +375,8 @@ test('hot reload with navigation - preserves navigation and updates content', as
339
375
  await hotReloadSession.waitIdle()
340
376
 
341
377
  // Wait for the detail view with the marker
342
- await hotReloadSession.text({
378
+ await waitForTextWithDebug({
379
+ session: hotReloadSession,
343
380
  waitFor: (text) => /NAV_MARKER_VALUE/i.test(text) && /Item One Details/i.test(text),
344
381
  timeout: 10000,
345
382
  })
@@ -41,9 +41,10 @@ function initializeReactRefresh() {
41
41
 
42
42
  // Only load react-refresh in non-production
43
43
  if (process.env.NODE_ENV !== 'production') {
44
- // Dynamic import to avoid bundling in production
45
- RefreshRuntime = require('react-refresh/runtime')
46
- initializeReactRefresh()
44
+ void import('react-refresh/runtime').then((runtime) => {
45
+ RefreshRuntime = runtime
46
+ initializeReactRefresh()
47
+ })
47
48
  }
48
49
 
49
50
  // Get captured renderer internals