ratatui_ruby 1.4.0-x86_64-linux

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 (292) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +15 -0
  3. data/LICENSES/AGPL-3.0-or-later.txt +661 -0
  4. data/LICENSES/CC-BY-SA-4.0.txt +427 -0
  5. data/LICENSES/CC0-1.0.txt +121 -0
  6. data/LICENSES/LGPL-3.0-or-later.txt +304 -0
  7. data/LICENSES/MIT-0.txt +16 -0
  8. data/LICENSES/MIT.txt +21 -0
  9. data/REUSE.toml +42 -0
  10. data/exe/.gitkeep +0 -0
  11. data/ext/ratatui_ruby/.cargo/config.toml +13 -0
  12. data/ext/ratatui_ruby/.gitignore +4 -0
  13. data/ext/ratatui_ruby/Cargo.lock +1737 -0
  14. data/ext/ratatui_ruby/Cargo.toml +24 -0
  15. data/ext/ratatui_ruby/clippy.toml +7 -0
  16. data/ext/ratatui_ruby/extconf.rb +21 -0
  17. data/ext/ratatui_ruby/src/color.rs +82 -0
  18. data/ext/ratatui_ruby/src/errors.rs +28 -0
  19. data/ext/ratatui_ruby/src/events.rs +700 -0
  20. data/ext/ratatui_ruby/src/frame.rs +241 -0
  21. data/ext/ratatui_ruby/src/lib.rs +343 -0
  22. data/ext/ratatui_ruby/src/lib_header.rs +11 -0
  23. data/ext/ratatui_ruby/src/rendering.rs +158 -0
  24. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  25. data/ext/ratatui_ruby/src/style.rs +469 -0
  26. data/ext/ratatui_ruby/src/terminal/capabilities.rs +46 -0
  27. data/ext/ratatui_ruby/src/terminal/init.rs +233 -0
  28. data/ext/ratatui_ruby/src/terminal/mod.rs +42 -0
  29. data/ext/ratatui_ruby/src/terminal/mutations.rs +158 -0
  30. data/ext/ratatui_ruby/src/terminal/queries.rs +231 -0
  31. data/ext/ratatui_ruby/src/terminal/query.rs +400 -0
  32. data/ext/ratatui_ruby/src/terminal/storage.rs +109 -0
  33. data/ext/ratatui_ruby/src/terminal/wrapper.rs +16 -0
  34. data/ext/ratatui_ruby/src/text.rs +225 -0
  35. data/ext/ratatui_ruby/src/widgets/barchart.rs +169 -0
  36. data/ext/ratatui_ruby/src/widgets/block.rs +41 -0
  37. data/ext/ratatui_ruby/src/widgets/calendar.rs +84 -0
  38. data/ext/ratatui_ruby/src/widgets/canvas.rs +183 -0
  39. data/ext/ratatui_ruby/src/widgets/center.rs +79 -0
  40. data/ext/ratatui_ruby/src/widgets/chart.rs +222 -0
  41. data/ext/ratatui_ruby/src/widgets/clear.rs +39 -0
  42. data/ext/ratatui_ruby/src/widgets/cursor.rs +32 -0
  43. data/ext/ratatui_ruby/src/widgets/gauge.rs +65 -0
  44. data/ext/ratatui_ruby/src/widgets/layout.rs +379 -0
  45. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +100 -0
  46. data/ext/ratatui_ruby/src/widgets/list.rs +378 -0
  47. data/ext/ratatui_ruby/src/widgets/list_state.rs +173 -0
  48. data/ext/ratatui_ruby/src/widgets/mod.rs +26 -0
  49. data/ext/ratatui_ruby/src/widgets/overlay.rs +24 -0
  50. data/ext/ratatui_ruby/src/widgets/paragraph.rs +87 -0
  51. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
  52. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +55 -0
  53. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +214 -0
  54. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  55. data/ext/ratatui_ruby/src/widgets/sparkline.rs +127 -0
  56. data/ext/ratatui_ruby/src/widgets/table.rs +415 -0
  57. data/ext/ratatui_ruby/src/widgets/table_state.rs +203 -0
  58. data/ext/ratatui_ruby/src/widgets/tabs.rs +194 -0
  59. data/lib/ratatui_ruby/backend/window_size.rb +50 -0
  60. data/lib/ratatui_ruby/backend.rb +59 -0
  61. data/lib/ratatui_ruby/buffer/cell.rb +212 -0
  62. data/lib/ratatui_ruby/buffer.rb +149 -0
  63. data/lib/ratatui_ruby/cell.rb +208 -0
  64. data/lib/ratatui_ruby/debug.rb +215 -0
  65. data/lib/ratatui_ruby/draw.rb +63 -0
  66. data/lib/ratatui_ruby/event/focus_gained.rb +125 -0
  67. data/lib/ratatui_ruby/event/focus_lost.rb +127 -0
  68. data/lib/ratatui_ruby/event/key/character.rb +53 -0
  69. data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
  70. data/lib/ratatui_ruby/event/key/media.rb +46 -0
  71. data/lib/ratatui_ruby/event/key/modifier.rb +107 -0
  72. data/lib/ratatui_ruby/event/key/navigation.rb +72 -0
  73. data/lib/ratatui_ruby/event/key/system.rb +47 -0
  74. data/lib/ratatui_ruby/event/key.rb +479 -0
  75. data/lib/ratatui_ruby/event/mouse.rb +291 -0
  76. data/lib/ratatui_ruby/event/none.rb +53 -0
  77. data/lib/ratatui_ruby/event/paste.rb +130 -0
  78. data/lib/ratatui_ruby/event/resize.rb +221 -0
  79. data/lib/ratatui_ruby/event/sync.rb +52 -0
  80. data/lib/ratatui_ruby/event.rb +163 -0
  81. data/lib/ratatui_ruby/frame.rb +257 -0
  82. data/lib/ratatui_ruby/labs/a11y.rb +182 -0
  83. data/lib/ratatui_ruby/labs/frame_a11y_capture.rb +50 -0
  84. data/lib/ratatui_ruby/labs.rb +47 -0
  85. data/lib/ratatui_ruby/layout/alignment.rb +91 -0
  86. data/lib/ratatui_ruby/layout/constraint.rb +337 -0
  87. data/lib/ratatui_ruby/layout/layout.rb +258 -0
  88. data/lib/ratatui_ruby/layout/position.rb +81 -0
  89. data/lib/ratatui_ruby/layout/rect.rb +733 -0
  90. data/lib/ratatui_ruby/layout/size.rb +62 -0
  91. data/lib/ratatui_ruby/layout.rb +29 -0
  92. data/lib/ratatui_ruby/list_state.rb +201 -0
  93. data/lib/ratatui_ruby/output_guard.rb +171 -0
  94. data/lib/ratatui_ruby/ratatui_ruby.so +0 -0
  95. data/lib/ratatui_ruby/scrollbar_state.rb +122 -0
  96. data/lib/ratatui_ruby/style/color.rb +149 -0
  97. data/lib/ratatui_ruby/style/style.rb +147 -0
  98. data/lib/ratatui_ruby/style.rb +19 -0
  99. data/lib/ratatui_ruby/symbols.rb +435 -0
  100. data/lib/ratatui_ruby/synthetic_events.rb +106 -0
  101. data/lib/ratatui_ruby/table_state.rb +251 -0
  102. data/lib/ratatui_ruby/terminal/capabilities.rb +316 -0
  103. data/lib/ratatui_ruby/terminal/viewport.rb +80 -0
  104. data/lib/ratatui_ruby/terminal.rb +66 -0
  105. data/lib/ratatui_ruby/terminal_lifecycle.rb +303 -0
  106. data/lib/ratatui_ruby/terminal_lifecycle.rb.bak +197 -0
  107. data/lib/ratatui_ruby/test_helper/event_injection.rb +241 -0
  108. data/lib/ratatui_ruby/test_helper/global_state.rb +111 -0
  109. data/lib/ratatui_ruby/test_helper/snapshot.rb +568 -0
  110. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
  111. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
  112. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
  113. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
  114. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
  115. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
  116. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
  117. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
  118. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
  119. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
  120. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
  121. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
  122. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
  123. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
  124. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
  125. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
  126. data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
  127. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
  128. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
  129. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
  130. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
  131. data/lib/ratatui_ruby/test_helper/style_assertions.rb +449 -0
  132. data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
  133. data/lib/ratatui_ruby/test_helper/terminal.rb +187 -0
  134. data/lib/ratatui_ruby/test_helper/test_doubles.rb +86 -0
  135. data/lib/ratatui_ruby/test_helper.rb +115 -0
  136. data/lib/ratatui_ruby/text/line.rb +245 -0
  137. data/lib/ratatui_ruby/text/span.rb +158 -0
  138. data/lib/ratatui_ruby/text.rb +99 -0
  139. data/lib/ratatui_ruby/tui/buffer_factories.rb +22 -0
  140. data/lib/ratatui_ruby/tui/canvas_factories.rb +149 -0
  141. data/lib/ratatui_ruby/tui/core.rb +67 -0
  142. data/lib/ratatui_ruby/tui/layout_factories.rb +153 -0
  143. data/lib/ratatui_ruby/tui/state_factories.rb +77 -0
  144. data/lib/ratatui_ruby/tui/style_factories.rb +22 -0
  145. data/lib/ratatui_ruby/tui/text_factories.rb +86 -0
  146. data/lib/ratatui_ruby/tui/widget_factories.rb +272 -0
  147. data/lib/ratatui_ruby/tui.rb +106 -0
  148. data/lib/ratatui_ruby/version.rb +12 -0
  149. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +51 -0
  150. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +29 -0
  151. data/lib/ratatui_ruby/widgets/bar_chart.rb +308 -0
  152. data/lib/ratatui_ruby/widgets/block.rb +266 -0
  153. data/lib/ratatui_ruby/widgets/calendar.rb +88 -0
  154. data/lib/ratatui_ruby/widgets/canvas.rb +297 -0
  155. data/lib/ratatui_ruby/widgets/cell.rb +59 -0
  156. data/lib/ratatui_ruby/widgets/center.rb +71 -0
  157. data/lib/ratatui_ruby/widgets/chart.rb +172 -0
  158. data/lib/ratatui_ruby/widgets/clear.rb +66 -0
  159. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  160. data/lib/ratatui_ruby/widgets/cursor.rb +54 -0
  161. data/lib/ratatui_ruby/widgets/gauge.rb +146 -0
  162. data/lib/ratatui_ruby/widgets/line_gauge.rb +158 -0
  163. data/lib/ratatui_ruby/widgets/list.rb +252 -0
  164. data/lib/ratatui_ruby/widgets/list_item.rb +55 -0
  165. data/lib/ratatui_ruby/widgets/overlay.rb +55 -0
  166. data/lib/ratatui_ruby/widgets/paragraph.rb +113 -0
  167. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +35 -0
  168. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +40 -0
  169. data/lib/ratatui_ruby/widgets/row.rb +123 -0
  170. data/lib/ratatui_ruby/widgets/scrollbar.rb +147 -0
  171. data/lib/ratatui_ruby/widgets/shape/label.rb +80 -0
  172. data/lib/ratatui_ruby/widgets/sparkline.rb +153 -0
  173. data/lib/ratatui_ruby/widgets/table.rb +213 -0
  174. data/lib/ratatui_ruby/widgets/tabs.rb +91 -0
  175. data/lib/ratatui_ruby/widgets.rb +43 -0
  176. data/lib/ratatui_ruby.rb +555 -0
  177. data/sig/examples/app_all_events/app.rbs +11 -0
  178. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  179. data/sig/examples/app_all_events/model/event_entry.rbs +23 -0
  180. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  181. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  182. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  183. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  184. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  185. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  186. data/sig/examples/app_all_events/view.rbs +14 -0
  187. data/sig/examples/app_cli_rich_moments/app.rbs +12 -0
  188. data/sig/examples/app_color_picker/app.rbs +17 -0
  189. data/sig/examples/app_external_editor/app.rbs +12 -0
  190. data/sig/examples/app_login_form/app.rbs +11 -0
  191. data/sig/examples/app_stateful_interaction/app.rbs +39 -0
  192. data/sig/examples/verify_quickstart_dsl/app.rbs +17 -0
  193. data/sig/examples/verify_quickstart_lifecycle/app.rbs +17 -0
  194. data/sig/examples/verify_readme_usage/app.rbs +17 -0
  195. data/sig/examples/widget_block_demo/app.rbs +38 -0
  196. data/sig/examples/widget_box_demo/app.rbs +17 -0
  197. data/sig/examples/widget_calendar_demo/app.rbs +17 -0
  198. data/sig/examples/widget_cell_demo/app.rbs +17 -0
  199. data/sig/examples/widget_chart_demo/app.rbs +17 -0
  200. data/sig/examples/widget_gauge_demo/app.rbs +17 -0
  201. data/sig/examples/widget_layout_split/app.rbs +16 -0
  202. data/sig/examples/widget_line_gauge_demo/app.rbs +17 -0
  203. data/sig/examples/widget_list_demo/app.rbs +17 -0
  204. data/sig/examples/widget_map_demo/app.rbs +17 -0
  205. data/sig/examples/widget_popup_demo/app.rbs +17 -0
  206. data/sig/examples/widget_ratatui_logo_demo/app.rbs +17 -0
  207. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +17 -0
  208. data/sig/examples/widget_rect/app.rbs +18 -0
  209. data/sig/examples/widget_render/app.rbs +16 -0
  210. data/sig/examples/widget_rich_text/app.rbs +17 -0
  211. data/sig/examples/widget_scroll_text/app.rbs +17 -0
  212. data/sig/examples/widget_scrollbar_demo/app.rbs +17 -0
  213. data/sig/examples/widget_sparkline_demo/app.rbs +16 -0
  214. data/sig/examples/widget_style_colors/app.rbs +20 -0
  215. data/sig/examples/widget_table_demo/app.rbs +17 -0
  216. data/sig/examples/widget_text_width/app.rbs +16 -0
  217. data/sig/generated/event_key_predicates.rbs +1348 -0
  218. data/sig/manifest.yaml +5 -0
  219. data/sig/patches/data.rbs +26 -0
  220. data/sig/patches/debugger__.rbs +8 -0
  221. data/sig/ratatui_ruby/backend/window_size.rbs +17 -0
  222. data/sig/ratatui_ruby/backend.rbs +12 -0
  223. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  224. data/sig/ratatui_ruby/buffer.rbs +18 -0
  225. data/sig/ratatui_ruby/cell.rbs +44 -0
  226. data/sig/ratatui_ruby/clear.rbs +18 -0
  227. data/sig/ratatui_ruby/constraint.rbs +26 -0
  228. data/sig/ratatui_ruby/debug.rbs +45 -0
  229. data/sig/ratatui_ruby/draw.rbs +30 -0
  230. data/sig/ratatui_ruby/event.rbs +249 -0
  231. data/sig/ratatui_ruby/frame.rbs +23 -0
  232. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  233. data/sig/ratatui_ruby/labs.rbs +90 -0
  234. data/sig/ratatui_ruby/layout/alignment.rbs +26 -0
  235. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  236. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  237. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  238. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  239. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  240. data/sig/ratatui_ruby/list_state.rbs +23 -0
  241. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  242. data/sig/ratatui_ruby/ratatui_ruby.rbs +113 -0
  243. data/sig/ratatui_ruby/rect.rbs +17 -0
  244. data/sig/ratatui_ruby/scrollbar_state.rbs +24 -0
  245. data/sig/ratatui_ruby/session.rbs +93 -0
  246. data/sig/ratatui_ruby/style/color.rbs +22 -0
  247. data/sig/ratatui_ruby/style/style.rbs +29 -0
  248. data/sig/ratatui_ruby/symbols.rbs +141 -0
  249. data/sig/ratatui_ruby/synthetic_events.rbs +24 -0
  250. data/sig/ratatui_ruby/table_state.rbs +27 -0
  251. data/sig/ratatui_ruby/terminal/capabilities.rbs +38 -0
  252. data/sig/ratatui_ruby/terminal/viewport.rbs +33 -0
  253. data/sig/ratatui_ruby/terminal_lifecycle.rbs +39 -0
  254. data/sig/ratatui_ruby/test_helper/event_injection.rbs +22 -0
  255. data/sig/ratatui_ruby/test_helper/snapshot.rbs +37 -0
  256. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +77 -0
  257. data/sig/ratatui_ruby/test_helper/terminal.rbs +20 -0
  258. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +32 -0
  259. data/sig/ratatui_ruby/test_helper.rbs +18 -0
  260. data/sig/ratatui_ruby/text/line.rbs +27 -0
  261. data/sig/ratatui_ruby/text/span.rbs +23 -0
  262. data/sig/ratatui_ruby/text.rbs +12 -0
  263. data/sig/ratatui_ruby/tui/buffer_factories.rbs +16 -0
  264. data/sig/ratatui_ruby/tui/canvas_factories.rbs +38 -0
  265. data/sig/ratatui_ruby/tui/core.rbs +23 -0
  266. data/sig/ratatui_ruby/tui/layout_factories.rbs +39 -0
  267. data/sig/ratatui_ruby/tui/state_factories.rbs +23 -0
  268. data/sig/ratatui_ruby/tui/style_factories.rbs +18 -0
  269. data/sig/ratatui_ruby/tui/text_factories.rbs +23 -0
  270. data/sig/ratatui_ruby/tui/widget_factories.rbs +138 -0
  271. data/sig/ratatui_ruby/tui.rbs +25 -0
  272. data/sig/ratatui_ruby/version.rbs +12 -0
  273. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  274. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  275. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  276. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  277. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  278. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  279. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  280. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  281. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  282. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  283. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  284. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  285. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  286. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  287. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  288. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  289. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  290. data/sig/ratatui_ruby/widgets.rbs +16 -0
  291. data/vendor/goodcop/base.yml +1047 -0
  292. metadata +729 -0
@@ -0,0 +1,12 @@
1
+  ┌──┐
2
+  ▄│HB│
3
+  ▄▀ └──┘
4
+  ▄▀▀ 
5
+  ▄▀ 
6
+  ▄▀▀ 
7
+  ▄▄▀ 
8
+  ▄▀ 
9
+  ▄▄▀ 
10
+  ▄▀ 
11
+  ▄▄▀ 
12
+ ▄▀ 
@@ -0,0 +1,12 @@
1
+ ┌──┐
2
+ ▄│HB│
3
+ ▄▀ └──┘
4
+ ▄▀▀
5
+ ▄▀
6
+ ▄▀▀
7
+ ▄▄▀
8
+ ▄▀
9
+ ▄▄▀
10
+ ▄▀
11
+ ▄▄▀
12
+ ▄▀
@@ -0,0 +1,12 @@
1
+  ••
2
+  •• 
3
+  ••• 
4
+  ••• 
5
+  •• 
6
+  ••• 
7
+  ••• 
8
+  •• 
9
+  ••• 
10
+  ••• ┌──┐ 
11
+  •• │DS│ 
12
+ •• └──┘ 
@@ -0,0 +1,12 @@
1
+ ••
2
+ ••
3
+ •••
4
+ •••
5
+ ••
6
+ •••
7
+ •••
8
+ ••
9
+ •••
10
+ ••• ┌──┐
11
+ •• │DS│
12
+ •• └──┘
@@ -0,0 +1,12 @@
1
+  ••
2
+  •• 
3
+  ••• 
4
+  ••• 
5
+ ┌──┐ •• 
6
+ │DS│ ••• 
7
+ └──┘ ••• 
8
+  •• 
9
+  ••• 
10
+  ••• 
11
+  •• 
12
+ •• 
@@ -0,0 +1,12 @@
1
+ ••
2
+ ••
3
+ •••
4
+ •••
5
+ ┌──┐ ••
6
+ │DS│ •••
7
+ └──┘ •••
8
+ ••
9
+ •••
10
+ •••
11
+ ••
12
+ ••
@@ -0,0 +1,12 @@
1
+  ••
2
+  •• 
3
+  ••• 
4
+  ••• 
5
+  •• ┌──┐
6
+  ••• │DS│
7
+  ••• └──┘
8
+  •• 
9
+  ••• 
10
+  ••• 
11
+  •• 
12
+ •• 
@@ -0,0 +1,12 @@
1
+ ••
2
+ ••
3
+ •••
4
+ •••
5
+ •• ┌──┐
6
+ ••• │DS│
7
+ ••• └──┘
8
+ ••
9
+ •••
10
+ •••
11
+ ••
12
+ ••
@@ -0,0 +1,12 @@
1
+  ┌──┐ ••
2
+  │DS│ •• 
3
+  └──┘ ••• 
4
+  ••• 
5
+  •• 
6
+  ••• 
7
+  ••• 
8
+  •• 
9
+  ••• 
10
+  ••• 
11
+  •• 
12
+ •• 
@@ -0,0 +1,12 @@
1
+ ┌──┐ ••
2
+ │DS│ ••
3
+ └──┘ •••
4
+ •••
5
+ ••
6
+ •••
7
+ •••
8
+ ••
9
+ •••
10
+ •••
11
+ ••
12
+ ••
@@ -0,0 +1 @@
1
+ Snapshot Content
@@ -0,0 +1,10 @@
1
+  ┌───┐
2
+  ••│DS1│
3
+  •••• └───┘
4
+  ••••• 
5
+  •••• 
6
+  •••• 
7
+  ••••• 
8
+  •••• 
9
+  •••• 
10
+ ••• StyledTime
@@ -0,0 +1,10 @@
1
+ ┌───┐
2
+ ••│DS1│
3
+ •••• └───┘
4
+ •••••
5
+ ••••
6
+ ••••
7
+ •••••
8
+ ••••
9
+ ••••
10
+ ••• StyledTime
@@ -0,0 +1,10 @@
1
+  ┌────────┐
2
+  │StyledDS│
3
+  •└────────┘
4
+  ••••• 
5
+  •••• 
6
+  •••• 
7
+  ••••• 
8
+  •••• 
9
+  •••• 
10
+ ••• 
@@ -0,0 +1,10 @@
1
+ ┌────────┐
2
+ │StyledDS│
3
+ •└────────┘
4
+ •••••
5
+ ••••
6
+ ••••
7
+ •••••
8
+ ••••
9
+ ••••
10
+ •••
@@ -0,0 +1,449 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
+ #++
7
+
8
+ module RatatuiRuby
9
+ module TestHelper
10
+ ##
11
+ # Assertions for verifying cell-level styling in terminal UIs.
12
+ #
13
+ # TUI styling is invisible to plain text comparisons. Colors, bold, italic,
14
+ # and other modifiers define the visual hierarchy. Without style assertions,
15
+ # you cannot verify that your highlight is actually highlighted.
16
+ #
17
+ # This mixin provides assertions to check foreground, background, and modifiers
18
+ # at specific coordinates or across entire regions.
19
+ #
20
+ # Use it to verify selection highlights, error colors, or themed areas.
21
+ #
22
+ # === Examples
23
+ #
24
+ #--
25
+ # SPDX-SnippetBegin
26
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
27
+ # SPDX-License-Identifier: MIT-0
28
+ #++
29
+ # # Single cell
30
+ # assert_cell_style(0, 0, fg: :red, modifiers: [:bold])
31
+ #
32
+ # # Foreground color at coordinate
33
+ # assert_color(:green, x: 5, y: 2)
34
+ #
35
+ # # Entire header region
36
+ # assert_area_style({ x: 0, y: 0, w: 80, h: 1 }, bg: :blue)
37
+ #
38
+ #--
39
+ # SPDX-SnippetEnd
40
+ #++
41
+ module StyleAssertions
42
+ ##
43
+ # Asserts that a cell has the expected style attributes.
44
+ #
45
+ # === Example
46
+ #
47
+ #--
48
+ # SPDX-SnippetBegin
49
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
50
+ # SPDX-License-Identifier: MIT-0
51
+ #++
52
+ # assert_cell_style(0, 0, char: "H", fg: :red)
53
+ #
54
+ #--
55
+ # SPDX-SnippetEnd
56
+ #++
57
+ # [x] Integer x-coordinate.
58
+ # [y] Integer y-coordinate.
59
+ # [expected_attributes] Hash of attribute names to expected values.
60
+ def assert_cell_style(x, y, **expected_attributes)
61
+ cell = get_cell(x, y)
62
+ expected_attributes.each do |key, value|
63
+ actual_value = cell.public_send(key)
64
+ if value.nil?
65
+ assert_nil actual_value, "Expected cell at (#{x}, #{y}) to have #{key}=nil, but got #{actual_value.inspect}"
66
+ else
67
+ assert_equal value, actual_value, "Expected cell at (#{x}, #{y}) to have #{key}=#{value.inspect}, but got #{actual_value.inspect}"
68
+ end
69
+ end
70
+ end
71
+
72
+ ##
73
+ # Asserts foreground or background color at a coordinate.
74
+ #
75
+ # Accepts symbols (<tt>:red</tt>), indexed colors (integers), or hex strings.
76
+ #
77
+ # === Examples
78
+ #
79
+ #--
80
+ # SPDX-SnippetBegin
81
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
82
+ # SPDX-License-Identifier: MIT-0
83
+ #++
84
+ # assert_color(:red, x: 10, y: 5)
85
+ # assert_color(5, x: 10, y: 5, layer: :bg)
86
+ # assert_color("#ff00ff", x: 10, y: 5)
87
+ #
88
+ #--
89
+ # SPDX-SnippetEnd
90
+ #++
91
+ # [expected] Symbol, Integer, or String (hex).
92
+ # [x] Integer x-coordinate.
93
+ # [y] Integer y-coordinate.
94
+ # [layer] <tt>:fg</tt> (default) or <tt>:bg</tt>.
95
+ def assert_color(expected, x:, y:, layer: :fg)
96
+ cell = get_cell(x, y)
97
+ actual = cell.public_send(layer)
98
+
99
+ # Normalize expected integer to symbol if needed (RatatuiRuby returns :indexed_N)
100
+ expected_normalized = if expected.is_a?(Integer)
101
+ :"indexed_#{expected}"
102
+ else
103
+ expected
104
+ end
105
+
106
+ assert_equal expected_normalized, actual,
107
+ "Expected #{layer} at (#{x}, #{y}) to be #{expected.inspect}, but got #{actual.inspect}"
108
+ end
109
+
110
+ ##
111
+ # Asserts that all cells in an area have the expected style.
112
+ #
113
+ # === Examples
114
+ #
115
+ #--
116
+ # SPDX-SnippetBegin
117
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
118
+ # SPDX-License-Identifier: MIT-0
119
+ #++
120
+ # header = RatatuiRuby::Layout::Rect.new(x: 0, y: 0, width: 80, height: 1)
121
+ # assert_area_style(header, bg: :blue, modifiers: [:bold])
122
+ #
123
+ # assert_area_style({ x: 0, y: 0, w: 10, h: 1 }, fg: :red)
124
+ #
125
+ #--
126
+ # SPDX-SnippetEnd
127
+ #++
128
+ # [area] Rect-like object or Hash with x, y, width/w, height/h.
129
+ # [attributes] Style attributes to verify.
130
+ def assert_area_style(area, **attributes)
131
+ if area.is_a?(Hash)
132
+ x = area[:x] || 0
133
+ y = area[:y] || 0
134
+ w = area[:width] || area[:w] || 0
135
+ h = area[:height] || area[:h] || 0
136
+ else
137
+ x = area.x
138
+ y = area.y
139
+ w = area.width
140
+ h = area.height
141
+ end
142
+
143
+ (y...(y + h)).each do |row|
144
+ (x...(x + w)).each do |col|
145
+ assert_cell_style(col, row, **attributes)
146
+ end
147
+ end
148
+ end
149
+
150
+ ##
151
+ # Asserts the foreground color at a coordinate.
152
+ #
153
+ # Convenience alias for <tt>assert_color(expected, x:, y:, layer: :fg)</tt>.
154
+ #
155
+ # === Example
156
+ #
157
+ #--
158
+ # SPDX-SnippetBegin
159
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
160
+ # SPDX-License-Identifier: MIT-0
161
+ #++
162
+ # assert_fg_color(:yellow, 0, 2)
163
+ #
164
+ #--
165
+ # SPDX-SnippetEnd
166
+ #++
167
+ # [expected] Symbol, Integer, or String (hex).
168
+ # [x] Integer x-coordinate.
169
+ # [y] Integer y-coordinate.
170
+ def assert_fg_color(expected, x, y)
171
+ assert_color(expected, x:, y:, layer: :fg)
172
+ end
173
+ alias assert_fg assert_fg_color
174
+
175
+ ##
176
+ # Asserts the background color at a coordinate.
177
+ #
178
+ # Convenience alias for <tt>assert_color(expected, x:, y:, layer: :bg)</tt>.
179
+ #
180
+ # === Example
181
+ #
182
+ #--
183
+ # SPDX-SnippetBegin
184
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
185
+ # SPDX-License-Identifier: MIT-0
186
+ #++
187
+ # assert_bg_color(:blue, 0, 2)
188
+ #
189
+ #--
190
+ # SPDX-SnippetEnd
191
+ #++
192
+ # [expected] Symbol, Integer, or String (hex).
193
+ # [x] Integer x-coordinate.
194
+ # [y] Integer y-coordinate.
195
+ def assert_bg_color(expected, x, y)
196
+ assert_color(expected, x:, y:, layer: :bg)
197
+ end
198
+ alias assert_bg assert_bg_color
199
+
200
+ ##
201
+ # Asserts that a cell has the bold modifier.
202
+ #
203
+ # === Example
204
+ #
205
+ #--
206
+ # SPDX-SnippetBegin
207
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
208
+ # SPDX-License-Identifier: MIT-0
209
+ #++
210
+ # assert_bold(0, 2)
211
+ #
212
+ #--
213
+ # SPDX-SnippetEnd
214
+ #++
215
+ # [x] Integer x-coordinate.
216
+ # [y] Integer y-coordinate.
217
+ def assert_bold(x, y)
218
+ cell = get_cell(x, y)
219
+ modifiers = (cell.modifiers || []).map(&:to_sym)
220
+ assert modifiers.include?(:bold),
221
+ "Expected cell at (#{x}, #{y}) to be bold, but modifiers were #{modifiers.inspect}"
222
+ end
223
+
224
+ ##
225
+ # Asserts that a cell has the italic modifier.
226
+ #
227
+ # === Example
228
+ #
229
+ #--
230
+ # SPDX-SnippetBegin
231
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
232
+ # SPDX-License-Identifier: MIT-0
233
+ #++
234
+ # assert_italic(0, 2)
235
+ #
236
+ #--
237
+ # SPDX-SnippetEnd
238
+ #++
239
+ # [x] Integer x-coordinate.
240
+ # [y] Integer y-coordinate.
241
+ def assert_italic(x, y)
242
+ cell = get_cell(x, y)
243
+ modifiers = (cell.modifiers || []).map(&:to_sym)
244
+ assert modifiers.include?(:italic),
245
+ "Expected cell at (#{x}, #{y}) to be italic, but modifiers were #{modifiers.inspect}"
246
+ end
247
+
248
+ ##
249
+ # Asserts that a cell has the underlined modifier.
250
+ #
251
+ # === Example
252
+ #
253
+ #--
254
+ # SPDX-SnippetBegin
255
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
256
+ # SPDX-License-Identifier: MIT-0
257
+ #++
258
+ # assert_underlined(0, 2)
259
+ #
260
+ #--
261
+ # SPDX-SnippetEnd
262
+ #++
263
+ # [x] Integer x-coordinate.
264
+ # [y] Integer y-coordinate.
265
+ def assert_underlined(x, y)
266
+ cell = get_cell(x, y)
267
+ modifiers = (cell.modifiers || []).map(&:to_sym)
268
+ assert modifiers.include?(:underlined),
269
+ "Expected cell at (#{x}, #{y}) to be underlined, but modifiers were #{modifiers.inspect}"
270
+ end
271
+ alias assert_underline assert_underlined
272
+
273
+ ##
274
+ # Asserts that a cell has the dim modifier.
275
+ #
276
+ # === Example
277
+ #
278
+ #--
279
+ # SPDX-SnippetBegin
280
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
281
+ # SPDX-License-Identifier: MIT-0
282
+ #++
283
+ # assert_dim(0, 2)
284
+ #
285
+ #--
286
+ # SPDX-SnippetEnd
287
+ #++
288
+ # [x] Integer x-coordinate.
289
+ # [y] Integer y-coordinate.
290
+ def assert_dim(x, y)
291
+ cell = get_cell(x, y)
292
+ modifiers = (cell.modifiers || []).map(&:to_sym)
293
+ assert modifiers.include?(:dim),
294
+ "Expected cell at (#{x}, #{y}) to be dim, but modifiers were #{modifiers.inspect}"
295
+ end
296
+
297
+ ##
298
+ # Asserts that a cell has the reversed (inverse video) modifier.
299
+ #
300
+ # === Example
301
+ #
302
+ #--
303
+ # SPDX-SnippetBegin
304
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
305
+ # SPDX-License-Identifier: MIT-0
306
+ #++
307
+ # assert_reversed(0, 2)
308
+ #
309
+ #--
310
+ # SPDX-SnippetEnd
311
+ #++
312
+ # [x] Integer x-coordinate.
313
+ # [y] Integer y-coordinate.
314
+ def assert_reversed(x, y)
315
+ cell = get_cell(x, y)
316
+ modifiers = (cell.modifiers || []).map(&:to_sym)
317
+ assert modifiers.include?(:reversed),
318
+ "Expected cell at (#{x}, #{y}) to be reversed, but modifiers were #{modifiers.inspect}"
319
+ end
320
+ alias assert_inverse assert_reversed
321
+ alias assert_inverse_video assert_reversed
322
+
323
+ ##
324
+ # Asserts that a cell has the crossed_out (strikethrough) modifier.
325
+ #
326
+ # === Example
327
+ #
328
+ #--
329
+ # SPDX-SnippetBegin
330
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
331
+ # SPDX-License-Identifier: MIT-0
332
+ #++
333
+ # assert_crossed_out(0, 2)
334
+ #
335
+ #--
336
+ # SPDX-SnippetEnd
337
+ #++
338
+ # [x] Integer x-coordinate.
339
+ # [y] Integer y-coordinate.
340
+ def assert_crossed_out(x, y)
341
+ cell = get_cell(x, y)
342
+ modifiers = (cell.modifiers || []).map(&:to_sym)
343
+ assert modifiers.include?(:crossed_out),
344
+ "Expected cell at (#{x}, #{y}) to be crossed_out, but modifiers were #{modifiers.inspect}"
345
+ end
346
+ alias assert_strikethrough assert_crossed_out
347
+ alias assert_strike assert_crossed_out
348
+
349
+ ##
350
+ # Asserts that a cell has the hidden modifier.
351
+ #
352
+ # [x] Integer x-coordinate.
353
+ # [y] Integer y-coordinate.
354
+ def assert_hidden(x, y)
355
+ cell = get_cell(x, y)
356
+ modifiers = (cell.modifiers || []).map(&:to_sym)
357
+ assert modifiers.include?(:hidden),
358
+ "Expected cell at (#{x}, #{y}) to be hidden, but modifiers were #{modifiers.inspect}"
359
+ end
360
+
361
+ ##
362
+ # Asserts that a cell has the slow_blink modifier.
363
+ #
364
+ # [x] Integer x-coordinate.
365
+ # [y] Integer y-coordinate.
366
+ def assert_slow_blink(x, y)
367
+ cell = get_cell(x, y)
368
+ modifiers = (cell.modifiers || []).map(&:to_sym)
369
+ assert modifiers.include?(:slow_blink),
370
+ "Expected cell at (#{x}, #{y}) to have slow_blink, but modifiers were #{modifiers.inspect}"
371
+ end
372
+ alias assert_blink assert_slow_blink
373
+
374
+ ##
375
+ # Asserts that a cell has the rapid_blink modifier.
376
+ #
377
+ # [x] Integer x-coordinate.
378
+ # [y] Integer y-coordinate.
379
+ def assert_rapid_blink(x, y)
380
+ cell = get_cell(x, y)
381
+ modifiers = (cell.modifiers || []).map(&:to_sym)
382
+ assert modifiers.include?(:rapid_blink),
383
+ "Expected cell at (#{x}, #{y}) to have rapid_blink, but modifiers were #{modifiers.inspect}"
384
+ end
385
+ # Color-specific assertion helpers.
386
+ #
387
+ # Manually specifying <tt>:red</tt> or <tt>:blue</tt> in every <tt>assert_color</tt> call is repetitive.
388
+ # It hides the intent of the test behind boilerplate arguments.
389
+ #
390
+ # These meta-programmed helpers provide a punchy, intent-focused API. Use them to
391
+ # verify colors with minimal ceremony.
392
+ #
393
+ # === Standard Foreground Color Aliases
394
+ #
395
+ # - <tt>assert_black(x, y)</tt>
396
+ # - <tt>assert_red(x, y)</tt>
397
+ # - <tt>assert_green(x, y)</tt>
398
+ # - <tt>assert_yellow(x, y)</tt>
399
+ # - <tt>assert_blue(x, y)</tt>
400
+ # - <tt>assert_magenta(x, y)</tt>
401
+ # - <tt>assert_cyan(x, y)</tt>
402
+ # - <tt>assert_gray(x, y)</tt>
403
+ # - <tt>assert_dark_gray(x, y)</tt>
404
+ # - <tt>assert_light_red(x, y)</tt>
405
+ # - <tt>assert_light_green(x, y)</tt>
406
+ # - <tt>assert_light_yellow(x, y)</tt>
407
+ # - <tt>assert_light_blue(x, y)</tt>
408
+ # - <tt>assert_light_magenta(x, y)</tt>
409
+ # - <tt>assert_light_cyan(x, y)</tt>
410
+ # - <tt>assert_white(x, y)</tt>
411
+ #
412
+ # === Standard Background Color Aliases
413
+ #
414
+ # - <tt>assert_bg_black(x, y)</tt>
415
+ # - <tt>assert_bg_red(x, y)</tt>
416
+ # - ...and so on for all standard colors.
417
+ [
418
+ :black,
419
+ :red,
420
+ :green,
421
+ :yellow,
422
+ :blue,
423
+ :magenta,
424
+ :cyan,
425
+ :gray,
426
+ :dark_gray,
427
+ :light_red,
428
+ :light_green,
429
+ :light_yellow,
430
+ :light_blue,
431
+ :light_magenta,
432
+ :light_cyan,
433
+ :white,
434
+ ].each do |color|
435
+ # :method: assert_#{color}
436
+ # Asserts the foreground color at (x, y) is <tt>:#{color}</tt>.
437
+ define_method(:"assert_#{color}") do |x, y|
438
+ assert_fg_color(color, x, y)
439
+ end
440
+
441
+ # :method: assert_bg_#{color}
442
+ # Asserts the background color at (x, y) is <tt>:#{color}</tt>.
443
+ define_method(:"assert_bg_#{color}") do |x, y|
444
+ assert_bg_color(color, x, y)
445
+ end
446
+ end
447
+ end
448
+ end
449
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
+ #++
7
+
8
+ require "timeout"
9
+
10
+ module RatatuiRuby
11
+ module TestHelper
12
+ ##
13
+ # Portable subprocess timeout helper.
14
+ module SubprocessTimeout
15
+ private def popen_with_timeout(env, cmd, timeout: 2)
16
+ output = +""
17
+ IO.popen(env, cmd, err: [:child, :out]) do |io|
18
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
19
+ loop do
20
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
21
+ break if remaining <= 0
22
+ break unless io.wait_readable(remaining)
23
+
24
+ chunk = io.read_nonblock(4096, exception: false)
25
+ break if chunk.nil? || chunk == :wait_readable
26
+
27
+ output << chunk
28
+ end
29
+ Process.kill("KILL", io.pid) rescue nil # rubocop:disable Style/RescueModifier
30
+ end
31
+ output.strip
32
+ end
33
+ end
34
+ end
35
+ end