ratatui_ruby 0.7.1 → 0.7.3

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 (311) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +12 -4
  7. data/CHANGELOG.md +49 -0
  8. data/README.md +7 -7
  9. data/Rakefile +1 -1
  10. data/doc/{application_architecture.md → concepts/application_architecture.md} +30 -0
  11. data/doc/{application_testing.md → concepts/application_testing.md} +4 -2
  12. data/doc/{event_handling.md → concepts/event_handling.md} +1 -1
  13. data/doc/contributors/auditing/parity.md +233 -0
  14. data/doc/contributors/developing_examples.md +10 -10
  15. data/doc/contributors/upstream_requests/tab_rects.md +173 -0
  16. data/doc/contributors/upstream_requests/title_rects.md +132 -0
  17. data/doc/contributors/v1.0.0_blockers.md +54 -747
  18. data/doc/{quickstart.md → getting_started/quickstart.md} +26 -26
  19. data/doc/{why.md → getting_started/why.md} +1 -1
  20. data/doc/index.md +23 -9
  21. data/doc/{terminal_limitations.md → troubleshooting/terminal_limitations.md} +33 -0
  22. data/doc/troubleshooting/tui_output.md +76 -0
  23. data/examples/app_all_events/README.md +1 -0
  24. data/examples/app_all_events/app.rb +2 -0
  25. data/examples/app_all_events/model/app_model.rb +2 -0
  26. data/examples/app_all_events/model/event_color_cycle.rb +2 -0
  27. data/examples/app_all_events/model/event_entry.rb +2 -0
  28. data/examples/app_all_events/model/msg.rb +2 -0
  29. data/examples/app_all_events/model/timestamp.rb +2 -0
  30. data/examples/app_all_events/update.rb +2 -0
  31. data/examples/app_all_events/view/app_view.rb +2 -0
  32. data/examples/app_all_events/view/controls_view.rb +2 -0
  33. data/examples/app_all_events/view/counts_view.rb +2 -0
  34. data/examples/app_all_events/view/live_view.rb +2 -0
  35. data/examples/app_all_events/view/log_view.rb +2 -0
  36. data/examples/app_all_events/view.rb +2 -0
  37. data/examples/app_color_picker/README.md +2 -0
  38. data/examples/app_color_picker/app.rb +2 -0
  39. data/examples/app_color_picker/clipboard.rb +2 -0
  40. data/examples/app_color_picker/color.rb +2 -0
  41. data/examples/app_color_picker/controls.rb +2 -0
  42. data/examples/app_color_picker/copy_dialog.rb +2 -0
  43. data/examples/app_color_picker/export_pane.rb +2 -0
  44. data/examples/app_color_picker/harmony.rb +2 -0
  45. data/examples/app_color_picker/input.rb +2 -0
  46. data/examples/app_color_picker/main_container.rb +2 -0
  47. data/examples/app_color_picker/palette.rb +2 -0
  48. data/examples/app_login_form/README.md +3 -0
  49. data/examples/app_login_form/app.rb +2 -0
  50. data/examples/app_stateful_interaction/README.md +2 -0
  51. data/examples/app_stateful_interaction/app.rb +2 -0
  52. data/examples/timeout_demo.rb +2 -0
  53. data/examples/verify_quickstart_dsl/README.md +2 -2
  54. data/examples/verify_quickstart_dsl/app.rb +2 -0
  55. data/examples/verify_quickstart_layout/README.md +2 -2
  56. data/examples/verify_quickstart_layout/app.rb +2 -0
  57. data/examples/verify_quickstart_lifecycle/README.md +2 -2
  58. data/examples/verify_quickstart_lifecycle/app.rb +2 -0
  59. data/examples/verify_readme_usage/app.rb +2 -0
  60. data/examples/{widget_barchart_demo → widget_barchart}/README.md +5 -3
  61. data/examples/{widget_barchart_demo → widget_barchart}/app.rb +7 -5
  62. data/examples/{widget_block_demo → widget_block}/README.md +5 -3
  63. data/examples/{widget_block_demo → widget_block}/app.rb +6 -4
  64. data/examples/{widget_box_demo → widget_box}/README.md +7 -4
  65. data/examples/{widget_box_demo → widget_box}/app.rb +7 -5
  66. data/examples/{widget_calendar_demo → widget_calendar}/README.md +6 -3
  67. data/examples/{widget_calendar_demo → widget_calendar}/app.rb +6 -4
  68. data/examples/{widget_canvas_demo → widget_canvas}/README.md +2 -2
  69. data/examples/{widget_canvas_demo → widget_canvas}/app.rb +6 -4
  70. data/examples/{widget_cell_demo → widget_cell}/README.md +6 -3
  71. data/examples/{widget_cell_demo → widget_cell}/app.rb +7 -5
  72. data/examples/{widget_center_demo → widget_center}/README.md +2 -2
  73. data/examples/{widget_center_demo → widget_center}/app.rb +6 -4
  74. data/examples/{widget_chart_demo → widget_chart}/README.md +7 -4
  75. data/examples/{widget_chart_demo → widget_chart}/app.rb +7 -5
  76. data/examples/{widget_gauge_demo → widget_gauge}/README.md +6 -3
  77. data/examples/{widget_gauge_demo → widget_gauge}/app.rb +7 -5
  78. data/examples/widget_layout_split/README.md +5 -2
  79. data/examples/widget_layout_split/app.rb +3 -1
  80. data/examples/{widget_line_gauge_demo → widget_line_gauge}/README.md +6 -3
  81. data/examples/{widget_line_gauge_demo → widget_line_gauge}/app.rb +7 -5
  82. data/examples/{widget_list_demo → widget_list}/README.md +7 -4
  83. data/examples/{widget_list_demo → widget_list}/app.rb +7 -5
  84. data/examples/{widget_map_demo → widget_map}/README.md +7 -4
  85. data/examples/{widget_map_demo → widget_map}/app.rb +4 -2
  86. data/examples/{widget_overlay_demo → widget_overlay}/README.md +6 -3
  87. data/examples/{widget_overlay_demo → widget_overlay}/app.rb +5 -3
  88. data/examples/{widget_popup_demo → widget_popup}/README.md +7 -4
  89. data/examples/{widget_popup_demo → widget_popup}/app.rb +6 -4
  90. data/examples/{widget_ratatui_logo_demo → widget_ratatui_logo}/README.md +6 -3
  91. data/examples/{widget_ratatui_logo_demo → widget_ratatui_logo}/app.rb +8 -6
  92. data/examples/{widget_ratatui_mascot_demo → widget_ratatui_mascot}/README.md +6 -3
  93. data/examples/{widget_ratatui_mascot_demo → widget_ratatui_mascot}/app.rb +6 -4
  94. data/examples/widget_rect/README.md +5 -2
  95. data/examples/widget_rect/app.rb +2 -0
  96. data/examples/widget_render/README.md +4 -1
  97. data/examples/widget_render/app.rb +2 -0
  98. data/examples/widget_rich_text/README.md +4 -1
  99. data/examples/widget_rich_text/app.rb +2 -0
  100. data/examples/widget_scroll_text/README.md +4 -1
  101. data/examples/widget_scroll_text/app.rb +3 -1
  102. data/examples/{widget_scrollbar_demo → widget_scrollbar}/README.md +7 -4
  103. data/examples/{widget_scrollbar_demo → widget_scrollbar}/app.rb +6 -4
  104. data/examples/{widget_sparkline_demo → widget_sparkline}/README.md +6 -3
  105. data/examples/{widget_sparkline_demo → widget_sparkline}/app.rb +7 -5
  106. data/examples/widget_style_colors/README.md +4 -1
  107. data/examples/widget_style_colors/app.rb +2 -0
  108. data/examples/{widget_table_demo → widget_table}/README.md +7 -4
  109. data/examples/{widget_table_demo → widget_table}/app.rb +4 -2
  110. data/examples/{widget_tabs_demo → widget_tabs}/README.md +6 -3
  111. data/examples/{widget_tabs_demo → widget_tabs}/app.rb +7 -5
  112. data/examples/widget_text_width/README.md +5 -2
  113. data/examples/widget_text_width/app.rb +2 -0
  114. data/exe/.gitkeep +0 -0
  115. data/ext/ratatui_ruby/Cargo.lock +1 -1
  116. data/ext/ratatui_ruby/Cargo.toml +1 -1
  117. data/ext/ratatui_ruby/extconf.rb +2 -0
  118. data/ext/ratatui_ruby/src/lib.rs +2 -2
  119. data/ext/ratatui_ruby/src/rendering.rs +9 -0
  120. data/ext/ratatui_ruby/src/style.rs +22 -2
  121. data/ext/ratatui_ruby/src/text.rs +26 -0
  122. data/ext/ratatui_ruby/src/widgets/barchart.rs +8 -6
  123. data/ext/ratatui_ruby/src/widgets/chart.rs +31 -4
  124. data/ext/ratatui_ruby/src/widgets/table.rs +13 -5
  125. data/ext/ratatui_ruby/src/widgets/tabs.rs +49 -9
  126. data/lib/ratatui_ruby/buffer/cell.rb +2 -0
  127. data/lib/ratatui_ruby/buffer.rb +2 -0
  128. data/lib/ratatui_ruby/cell.rb +2 -0
  129. data/lib/ratatui_ruby/event/focus_gained.rb +2 -0
  130. data/lib/ratatui_ruby/event/focus_lost.rb +2 -0
  131. data/lib/ratatui_ruby/event/key/character.rb +2 -0
  132. data/lib/ratatui_ruby/event/key/media.rb +2 -0
  133. data/lib/ratatui_ruby/event/key/modifier.rb +2 -0
  134. data/lib/ratatui_ruby/event/key/navigation.rb +2 -0
  135. data/lib/ratatui_ruby/event/key/system.rb +2 -0
  136. data/lib/ratatui_ruby/event/key.rb +2 -0
  137. data/lib/ratatui_ruby/event/mouse.rb +2 -0
  138. data/lib/ratatui_ruby/event/none.rb +2 -0
  139. data/lib/ratatui_ruby/event/paste.rb +2 -0
  140. data/lib/ratatui_ruby/event/resize.rb +2 -0
  141. data/lib/ratatui_ruby/event.rb +2 -0
  142. data/lib/ratatui_ruby/frame.rb +2 -0
  143. data/lib/ratatui_ruby/layout/constraint.rb +2 -0
  144. data/lib/ratatui_ruby/layout/layout.rb +2 -0
  145. data/lib/ratatui_ruby/layout/rect.rb +2 -0
  146. data/lib/ratatui_ruby/layout.rb +2 -0
  147. data/lib/ratatui_ruby/list_state.rb +2 -0
  148. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -0
  149. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +2 -0
  150. data/lib/ratatui_ruby/schema/bar_chart.rb +4 -2
  151. data/lib/ratatui_ruby/schema/block.rb +4 -2
  152. data/lib/ratatui_ruby/schema/calendar.rb +4 -2
  153. data/lib/ratatui_ruby/schema/canvas.rb +2 -0
  154. data/lib/ratatui_ruby/schema/center.rb +2 -0
  155. data/lib/ratatui_ruby/schema/chart.rb +4 -2
  156. data/lib/ratatui_ruby/schema/clear.rb +2 -0
  157. data/lib/ratatui_ruby/schema/constraint.rb +2 -0
  158. data/lib/ratatui_ruby/schema/cursor.rb +2 -0
  159. data/lib/ratatui_ruby/schema/draw.rb +2 -0
  160. data/lib/ratatui_ruby/schema/gauge.rb +4 -2
  161. data/lib/ratatui_ruby/schema/layout.rb +2 -0
  162. data/lib/ratatui_ruby/schema/line_gauge.rb +4 -2
  163. data/lib/ratatui_ruby/schema/list.rb +3 -1
  164. data/lib/ratatui_ruby/schema/list_item.rb +2 -0
  165. data/lib/ratatui_ruby/schema/overlay.rb +2 -0
  166. data/lib/ratatui_ruby/schema/paragraph.rb +2 -0
  167. data/lib/ratatui_ruby/schema/ratatui_logo.rb +4 -2
  168. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +4 -2
  169. data/lib/ratatui_ruby/schema/rect.rb +2 -0
  170. data/lib/ratatui_ruby/schema/row.rb +2 -0
  171. data/lib/ratatui_ruby/schema/scrollbar.rb +4 -2
  172. data/lib/ratatui_ruby/schema/shape/label.rb +2 -0
  173. data/lib/ratatui_ruby/schema/sparkline.rb +4 -2
  174. data/lib/ratatui_ruby/schema/style.rb +2 -0
  175. data/lib/ratatui_ruby/schema/table.rb +2 -0
  176. data/lib/ratatui_ruby/schema/tabs.rb +4 -2
  177. data/lib/ratatui_ruby/schema/text.rb +2 -0
  178. data/lib/ratatui_ruby/scrollbar_state.rb +2 -0
  179. data/lib/ratatui_ruby/style/style.rb +3 -0
  180. data/lib/ratatui_ruby/style.rb +2 -0
  181. data/lib/ratatui_ruby/table_state.rb +2 -0
  182. data/lib/ratatui_ruby/test_helper/event_injection.rb +2 -0
  183. data/lib/ratatui_ruby/test_helper/snapshot.rb +62 -21
  184. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
  185. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
  186. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
  187. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
  188. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
  189. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
  190. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
  191. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
  192. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
  193. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
  194. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
  195. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
  196. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
  197. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
  198. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
  199. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
  200. data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
  201. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
  202. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
  203. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
  204. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
  205. data/lib/ratatui_ruby/test_helper/style_assertions.rb +2 -0
  206. data/lib/ratatui_ruby/test_helper/terminal.rb +5 -0
  207. data/lib/ratatui_ruby/test_helper/test_doubles.rb +2 -0
  208. data/lib/ratatui_ruby/test_helper.rb +6 -4
  209. data/lib/ratatui_ruby/tui/buffer_factories.rb +2 -0
  210. data/lib/ratatui_ruby/tui/canvas_factories.rb +2 -0
  211. data/lib/ratatui_ruby/tui/core.rb +2 -0
  212. data/lib/ratatui_ruby/tui/layout_factories.rb +2 -0
  213. data/lib/ratatui_ruby/tui/state_factories.rb +2 -0
  214. data/lib/ratatui_ruby/tui/style_factories.rb +2 -0
  215. data/lib/ratatui_ruby/tui/text_factories.rb +2 -0
  216. data/lib/ratatui_ruby/tui/widget_factories.rb +2 -0
  217. data/lib/ratatui_ruby/tui.rb +2 -0
  218. data/lib/ratatui_ruby/version.rb +3 -1
  219. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
  220. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
  221. data/lib/ratatui_ruby/widgets/bar_chart.rb +7 -4
  222. data/lib/ratatui_ruby/widgets/block.rb +46 -2
  223. data/lib/ratatui_ruby/widgets/calendar.rb +4 -2
  224. data/lib/ratatui_ruby/widgets/canvas.rb +2 -0
  225. data/lib/ratatui_ruby/widgets/cell.rb +2 -0
  226. data/lib/ratatui_ruby/widgets/center.rb +2 -0
  227. data/lib/ratatui_ruby/widgets/chart.rb +13 -6
  228. data/lib/ratatui_ruby/widgets/clear.rb +2 -0
  229. data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
  230. data/lib/ratatui_ruby/widgets/gauge.rb +4 -2
  231. data/lib/ratatui_ruby/widgets/line_gauge.rb +4 -2
  232. data/lib/ratatui_ruby/widgets/list.rb +3 -1
  233. data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
  234. data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
  235. data/lib/ratatui_ruby/widgets/paragraph.rb +2 -0
  236. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +4 -2
  237. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +4 -2
  238. data/lib/ratatui_ruby/widgets/row.rb +2 -0
  239. data/lib/ratatui_ruby/widgets/scrollbar.rb +4 -2
  240. data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
  241. data/lib/ratatui_ruby/widgets/sparkline.rb +7 -4
  242. data/lib/ratatui_ruby/widgets/table.rb +2 -0
  243. data/lib/ratatui_ruby/widgets/tabs.rb +12 -8
  244. data/lib/ratatui_ruby/widgets.rb +2 -0
  245. data/lib/ratatui_ruby.rb +130 -9
  246. data/tasks/autodoc/examples.rb +2 -0
  247. data/tasks/autodoc/member.rb +2 -0
  248. data/tasks/autodoc/name.rb +2 -0
  249. data/tasks/autodoc.rake +2 -0
  250. data/tasks/bump/cargo_lockfile.rb +2 -0
  251. data/tasks/bump/changelog.rb +2 -0
  252. data/tasks/bump/header.rb +2 -0
  253. data/tasks/bump/history.rb +2 -0
  254. data/tasks/bump/links.rb +2 -0
  255. data/tasks/bump/manifest.rb +2 -0
  256. data/tasks/bump/ruby_gem.rb +2 -0
  257. data/tasks/bump/sem_ver.rb +2 -0
  258. data/tasks/bump/unreleased_section.rb +2 -0
  259. data/tasks/bump.rake +2 -0
  260. data/tasks/doc.rake +268 -0
  261. data/tasks/extension.rake +2 -0
  262. data/tasks/lint.rake +115 -0
  263. data/tasks/rdoc_config.rb +18 -4
  264. data/tasks/sourcehut.rake +2 -0
  265. data/tasks/terminal_preview/app_screenshot.rb +2 -0
  266. data/tasks/terminal_preview/crash_report.rb +2 -0
  267. data/tasks/terminal_preview/example_app.rb +2 -0
  268. data/tasks/terminal_preview/launcher_script.rb +2 -0
  269. data/tasks/terminal_preview/preview_collection.rb +2 -0
  270. data/tasks/terminal_preview/preview_timing.rb +2 -0
  271. data/tasks/terminal_preview/safety_confirmation.rb +2 -0
  272. data/tasks/terminal_preview/saved_screenshot.rb +2 -0
  273. data/tasks/terminal_preview/system_appearance.rb +2 -0
  274. data/tasks/terminal_preview/terminal_window.rb +2 -0
  275. data/tasks/terminal_preview/window_id.rb +2 -0
  276. data/tasks/terminal_preview.rake +2 -0
  277. data/tasks/test.rake +2 -0
  278. data/tasks/website/index_page.rb +2 -0
  279. data/tasks/website/version.rb +12 -2
  280. data/tasks/website/version_menu.rb +2 -0
  281. data/tasks/website/versioned_documentation.rb +2 -0
  282. data/tasks/website/website.rb +2 -0
  283. data/tasks/website.rake +2 -0
  284. metadata +97 -75
  285. data/doc/contributors/architectural_overhaul/chat_conversations.md +0 -4952
  286. data/doc/contributors/architectural_overhaul/implementation_plan.md +0 -60
  287. data/doc/contributors/architectural_overhaul/task.md +0 -37
  288. /data/doc/{async.md → concepts/async.md} +0 -0
  289. /data/doc/{interactive_design.md → concepts/interactive_design.md} +0 -0
  290. /data/doc/images/{widget_barchart_demo.png → widget_barchart.png} +0 -0
  291. /data/doc/images/{widget_block_demo.png → widget_block.png} +0 -0
  292. /data/doc/images/{widget_box_demo.png → widget_box.png} +0 -0
  293. /data/doc/images/{widget_calendar_demo.png → widget_calendar.png} +0 -0
  294. /data/doc/images/{widget_canvas_demo.png → widget_canvas.png} +0 -0
  295. /data/doc/images/{widget_cell_demo.png → widget_cell.png} +0 -0
  296. /data/doc/images/{widget_center_demo.png → widget_center.png} +0 -0
  297. /data/doc/images/{widget_chart_demo.png → widget_chart.png} +0 -0
  298. /data/doc/images/{widget_gauge_demo.png → widget_gauge.png} +0 -0
  299. /data/doc/images/{widget_line_gauge_demo.png → widget_line_gauge.png} +0 -0
  300. /data/doc/images/{widget_list_demo.png → widget_list.png} +0 -0
  301. /data/doc/images/{widget_map_demo.png → widget_map.png} +0 -0
  302. /data/doc/images/{widget_overlay_demo.png → widget_overlay.png} +0 -0
  303. /data/doc/images/{widget_popup_demo.png → widget_popup.png} +0 -0
  304. /data/doc/images/{widget_ratatui_logo_demo.png → widget_ratatui_logo.png} +0 -0
  305. /data/doc/images/{widget_ratatui_mascot_demo.png → widget_ratatui_mascot.png} +0 -0
  306. /data/doc/images/{widget_scrollbar_demo.png → widget_scrollbar.png} +0 -0
  307. /data/doc/images/{widget_sparkline_demo.png → widget_sparkline.png} +0 -0
  308. /data/doc/images/{widget_table_demo.png → widget_table.png} +0 -0
  309. /data/doc/images/{widget_tabs_demo.png → widget_tabs.png} +0 -0
  310. /data/doc/{v0.7.0_migration.md → migration/v0_7_0.md} +0 -0
  311. /data/doc/{debugging.md → troubleshooting/debugging.md} +0 -0
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  module TestHelper
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "fileutils"
7
9
 
@@ -13,9 +15,10 @@ module RatatuiRuby
13
15
  # Verifying every character of a TUI screen by hand is tedious. Snapshots let you
14
16
  # capture the screen once and compare against it in future runs.
15
17
  #
16
- # This mixin provides <tt>assert_snapshot</tt> for plain text and
17
- # <tt>assert_rich_snapshot</tt> for styled ANSI output. Both auto-create
18
- # snapshot files on first run.
18
+ # This mixin provides <tt>assert_plain_snapshot</tt> for plain text,
19
+ # <tt>assert_rich_snapshot</tt> for styled ANSI output, and
20
+ # <tt>assert_snapshots</tt> (plural) for both. All auto-create snapshot
21
+ # files on first run.
19
22
  #
20
23
  # Use it to verify complex layouts, styles, and interactions without manual assertions.
21
24
  #
@@ -60,38 +63,46 @@ module RatatuiRuby
60
63
  #
61
64
  # Mask dynamic content (timestamps, IDs) with a normalization block:
62
65
  #
63
- # assert_snapshot("dashboard") do |lines|
66
+ # assert_snapshots("dashboard") do |lines|
64
67
  # lines.map { |l| l.gsub(/\d{4}-\d{2}-\d{2}/, "YYYY-MM-DD") }
65
68
  # end
66
69
  #
67
70
  module Snapshot
68
71
  ##
69
- # Asserts that the current screen content matches a stored snapshot.
72
+ # Asserts that the current screen content matches a stored plain text snapshot.
70
73
  #
71
- # This method simplifies snapshot testing by automatically resolving the snapshot path
72
- # relative to the test file calling this method. It assumes a "snapshots" directory
73
- # exists in the same directory as the test file.
74
+ # Plain text snapshots capture layout but miss styling bugs: wrong colors, missing bold,
75
+ # invisible text on a matching background. *Prefer <tt>assert_snapshots</tt>* (plural) to catch
76
+ # styling regressions.
74
77
  #
75
- # # In test/test_login.rb
76
- # assert_snapshot("login_screen")
77
- # # Look for: test/snapshots/login_screen.txt
78
+ # Plain text snapshots are human-readable when viewed in any editor or diff tool. They
79
+ # pair well with rich snapshots for documentation. Use <tt>assert_snapshots</tt> to generate both.
80
+ #
81
+ # assert_plain_snapshot("login_screen")
82
+ # # Compares against: test/snapshots/login_screen.txt
78
83
  #
79
84
  # # With normalization block
80
- # assert_snapshot("clock") do |actual|
85
+ # assert_plain_snapshot("clock") do |actual|
81
86
  # actual.map { |l| l.gsub(/\d{2}:\d{2}/, "XX:XX") }
82
87
  # end
83
88
  #
84
89
  # [name] String name of the snapshot (without extension).
85
90
  # [msg] String optional failure message.
86
- def assert_snapshot(name, msg = nil, &)
91
+ def assert_plain_snapshot(name, msg = nil, snapshot_dir: nil, &)
87
92
  # Get the path of the test file calling this method
88
- caller_path = caller_locations(1, 1).first.path
89
- snapshot_dir = File.join(File.dirname(caller_path), "snapshots")
93
+ snapshot_dir ||= File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
90
94
  snapshot_path = File.join(snapshot_dir, "#{name}.txt")
91
95
 
92
96
  assert_screen_matches(snapshot_path, msg, &)
93
97
  end
94
98
 
99
+ ##
100
+ # @deprecated Use {#assert_plain_snapshot} instead.
101
+ def assert_snapshot(name, msg = nil, &)
102
+ warn "assert_snapshot is deprecated; use assert_plain_snapshot instead", uplevel: 1
103
+ assert_plain_snapshot(name, msg, &)
104
+ end
105
+
95
106
  ##
96
107
  # Asserts that the current screen content matches the expected content.
97
108
  #
@@ -184,12 +195,17 @@ module RatatuiRuby
184
195
  end
185
196
 
186
197
  ##
187
- # Asserts that the current screen content (including colors!) matches a stored ANSI snapshot.
198
+ # Asserts that the current screen content (including colors and styles) matches a stored ANSI snapshot.
199
+ #
200
+ # TUIs communicate meaning through colors and styles. Rich snapshots capture everything:
201
+ # wrong colors, missing bold, invisible text on a matching background. *Prefer <tt>assert_snapshots</tt>*
202
+ # (plural) to also generate human-readable plain text files for documentation.
188
203
  #
189
- # Generates/Compares against a file with <tt>.ansi</tt> extension.
190
- # You can <tt>cat</tt> this file to see exactly what the screen looked like.
204
+ # The <tt>.ansi</tt> snapshot files contain ANSI escape codes. You can <tt>cat</tt> them in a terminal
205
+ # to see exactly what the screen looked like.
191
206
  #
192
207
  # assert_rich_snapshot("login_screen")
208
+ # # Compares against: test/snapshots/login_screen.ansi
193
209
  #
194
210
  # # With normalization
195
211
  # assert_rich_snapshot("log_view") do |lines|
@@ -198,9 +214,8 @@ module RatatuiRuby
198
214
  #
199
215
  # [name] String snapshot name.
200
216
  # [msg] String optional failure message.
201
- def assert_rich_snapshot(name, msg = nil)
202
- caller_path = caller_locations(1, 1).first.path
203
- snapshot_dir = File.join(File.dirname(caller_path), "snapshots")
217
+ def assert_rich_snapshot(name, msg = nil, snapshot_dir: nil)
218
+ snapshot_dir ||= File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
204
219
  snapshot_path = File.join(snapshot_dir, "#{name}.ansi")
205
220
 
206
221
  actual_content = _render_buffer_with_ansi
@@ -252,6 +267,32 @@ module RatatuiRuby
252
267
  end
253
268
  end
254
269
 
270
+ ##
271
+ # Asserts both plain text and rich (ANSI-styled) snapshots match.
272
+ #
273
+ # This is the recommended snapshot assertion. It calls both <tt>assert_plain_snapshot</tt> and
274
+ # <tt>assert_rich_snapshot</tt> with the same name, generating <tt>.txt</tt> and <tt>.ansi</tt> files.
275
+ #
276
+ # Rich snapshots catch styling bugs that plain text misses. Plain text snapshots are
277
+ # human-readable in any editor or diff tool, making them valuable for documentation and
278
+ # code review. Together, they provide comprehensive coverage and discoverability.
279
+ #
280
+ # assert_snapshots("login_screen")
281
+ # # Creates/compares: snapshots/login_screen.txt AND snapshots/login_screen.ansi
282
+ #
283
+ # # With normalization (masks dynamic content like timestamps)
284
+ # assert_snapshots("dashboard") do |lines|
285
+ # lines.map { |l| l.gsub(/\d{2}:\d{2}:\d{2}/, "HH:MM:SS") }
286
+ # end
287
+ #
288
+ # [name] String snapshot name (without extension).
289
+ # [msg] String optional failure message.
290
+ def assert_snapshots(name, msg = nil, &)
291
+ snapshot_dir = File.join(File.dirname(caller_locations(1, 1).first.path), "snapshots")
292
+ assert_plain_snapshot(name, msg, snapshot_dir:, &)
293
+ assert_rich_snapshot(name, msg, snapshot_dir:, &)
294
+ end
295
+
255
296
  ##
256
297
  # Returns the current buffer content as an ANSI-encoded string.
257
298
  #
@@ -0,0 +1,24 @@
1
+ ┌Aligned Chart─────────────────────────────────────────────────────────────────┐
2
+ │10│Value ┌──────┐│
3
+ │ │ │TestDS││
4
+ │ │ ••└──────┘│
5
+ │ │ •••• │
6
+ │ │ •••• │
7
+ │ │ •••• │
8
+ │ │ •••• │
9
+ │ │ •••• │
10
+ │ │ •••• │
11
+ │ │ ••• │
12
+ │ 5│ •••• │
13
+ │ │ •••• │
14
+ │ │ •••• │
15
+ │ │ •••• │
16
+ │ │ •••• │
17
+ │ │ •••• │
18
+ │ │ •••• │
19
+ │ │ •••• │
20
+ │ │ •••• │
21
+ │ 0│•• Time│
22
+ │ └───────────────────────────────────────────────────────────────────────────│
23
+ │ 0 5 10│
24
+ └──────────────────────────────────────────────────────────────────────────────┘
@@ -0,0 +1,24 @@
1
+ ┌Aligned Chart─────────────────────────────────────────────────────────────────┐
2
+ │10│Value ┌──────┐│
3
+ │ │ │TestDS││
4
+ │ │ ••└──────┘│
5
+ │ │ •••• │
6
+ │ │ •••• │
7
+ │ │ •••• │
8
+ │ │ •••• │
9
+ │ │ •••• │
10
+ │ │ •••• │
11
+ │ │ ••• │
12
+ │ 5│ •••• │
13
+ │ │ •••• │
14
+ │ │ •••• │
15
+ │ │ •••• │
16
+ │ │ •••• │
17
+ │ │ •••• │
18
+ │ │ •••• │
19
+ │ │ •••• │
20
+ │ │ •••• │
21
+ │ 0│•• Time│
22
+ │ └───────────────────────────────────────────────────────────────────────────│
23
+ │ 0 5 10│
24
+ └──────────────────────────────────────────────────────────────────────────────┘
@@ -0,0 +1,5 @@
1
+ █████ 
2
+ █████ 
3
+ █████ 
4
+ ██5██ 
5
+ Group 
@@ -0,0 +1,5 @@
1
+ █████
2
+ █████
3
+ █████
4
+ ██5██
5
+ Group
@@ -0,0 +1,24 @@
1
+ ┌Test Chart────────────────────────────────────────────────────────────────────┐
2
+ │10│Value ┌──────┐│
3
+ │ │ │TestDS││
4
+ │ │ ••└──────┘│
5
+ │ │ •••• │
6
+ │ │ •••• │
7
+ │ │ •••• │
8
+ │ │ •••• │
9
+ │ │ •••• │
10
+ │ │ •••• │
11
+ │ │ ••• │
12
+ │ │ •••• │
13
+ │ │ •••• │
14
+ │ │ •••• │
15
+ │ │ •••• │
16
+ │ │ •••• │
17
+ │ │ •••• │
18
+ │ │ •••• │
19
+ │ │ •••• │
20
+ │ │ •••• │
21
+ │0 │•• Time│
22
+ │ └───────────────────────────────────────────────────────────────────────────│
23
+ │ 0 10│
24
+ └──────────────────────────────────────────────────────────────────────────────┘
@@ -0,0 +1,24 @@
1
+ ┌Test Chart────────────────────────────────────────────────────────────────────┐
2
+ │10│Value ┌──────┐│
3
+ │ │ │TestDS││
4
+ │ │ ••└──────┘│
5
+ │ │ •••• │
6
+ │ │ •••• │
7
+ │ │ •••• │
8
+ │ │ •••• │
9
+ │ │ •••• │
10
+ │ │ •••• │
11
+ │ │ ••• │
12
+ │ │ •••• │
13
+ │ │ •••• │
14
+ │ │ •••• │
15
+ │ │ •••• │
16
+ │ │ •••• │
17
+ │ │ •••• │
18
+ │ │ •••• │
19
+ │ │ •••• │
20
+ │ │ •••• │
21
+ │0 │•• Time│
22
+ │ └───────────────────────────────────────────────────────────────────────────│
23
+ │ 0 10│
24
+ └──────────────────────────────────────────────────────────────────────────────┘
@@ -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
+ •••
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  module TestHelper
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require "timeout"
7
9
  require "minitest/mock"
@@ -46,6 +48,9 @@ module RatatuiRuby
46
48
  # # render and test your app
47
49
  # end
48
50
  def with_test_terminal(width = 80, height = 24, **opts)
51
+ # Defensive cleanup: reset any stale session state from previous test failures
52
+ RatatuiRuby.instance_variable_set(:@tui_session_active, false)
53
+
49
54
  RatatuiRuby.init_test_terminal(width, height)
50
55
  # Flush any lingering events from previous tests
51
56
  while (event = RatatuiRuby.poll_event) && !event.none?; end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  module TestHelper
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
4
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
7
+
3
8
  require "fileutils"
4
9
  require_relative "test_helper/terminal"
5
10
  require_relative "test_helper/snapshot"
@@ -7,9 +12,6 @@ require_relative "test_helper/event_injection"
7
12
  require_relative "test_helper/style_assertions"
8
13
  require_relative "test_helper/test_doubles"
9
14
 
10
- # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
11
- # SPDX-License-Identifier: AGPL-3.0-or-later
12
-
13
15
  module RatatuiRuby
14
16
  ##
15
17
  # Helpers for testing RatatuiRuby applications.
@@ -39,7 +41,7 @@ module RatatuiRuby
39
41
  # def test_initial_render
40
42
  # with_test_terminal(80, 24) do
41
43
  # MyApp.new.run_once
42
- # assert_snapshot("initial")
44
+ # assert_snapshots("initial")
43
45
  # end
44
46
  # end
45
47
  #
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  class TUI
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  class TUI
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  class TUI
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  class TUI
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  class TUI
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  class TUI
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  class TUI
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  module RatatuiRuby
7
9
  class TUI
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ #--
3
4
  # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
5
  # SPDX-License-Identifier: AGPL-3.0-or-later
6
+ #++
5
7
 
6
8
  require_relative "tui/core"
7
9
  require_relative "tui/layout_factories"