scriptorium 0.0.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. checksums.yaml +4 -4
  2. data/README.lt3 +324 -0
  3. data/README.md +3155 -1
  4. data/assets/.DS_Store +0 -0
  5. data/assets/README.md +44 -0
  6. data/assets/back-icon.png +0 -0
  7. data/assets/icons/facebook.svg +1 -0
  8. data/assets/icons/github.svg +1 -0
  9. data/assets/icons/instagram.svg +1 -0
  10. data/assets/icons/reddit.svg +1 -0
  11. data/assets/icons/ui/.DS_Store +0 -0
  12. data/assets/icons/ui/back.png +0 -0
  13. data/assets/icons/ui/copy.png +0 -0
  14. data/assets/icons/ui/down.png +0 -0
  15. data/assets/icons/ui/end.png +0 -0
  16. data/assets/icons/ui/exit.png +0 -0
  17. data/assets/icons/ui/foo +10 -0
  18. data/assets/icons/ui/home.png +0 -0
  19. data/assets/icons/ui/left.png +0 -0
  20. data/assets/icons/ui/next.png +0 -0
  21. data/assets/icons/ui/right.png +0 -0
  22. data/assets/icons/ui/start.png +0 -0
  23. data/assets/icons/ui/up.png +0 -0
  24. data/assets/icons/x.svg +1 -0
  25. data/assets/icons/youtube.svg +1 -0
  26. data/assets/samples/placeholder.svg +9 -0
  27. data/assets/themes/standard/favicon.svg +6 -0
  28. data/bin/scriptorium +1511 -0
  29. data/doc/README.txt +6 -0
  30. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +95 -0
  31. data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +34 -0
  32. data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +50 -0
  33. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +31 -0
  34. data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +73 -0
  35. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +64 -0
  36. data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +70 -0
  37. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
  38. data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +110 -0
  39. data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +73 -0
  40. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
  41. data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +124 -0
  42. data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +46 -0
  43. data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +97 -0
  44. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
  45. data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +211 -0
  46. data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +141 -0
  47. data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +211 -0
  48. data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +134 -0
  49. data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +41 -0
  50. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +45 -0
  51. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +30 -0
  52. data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +49 -0
  53. data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +46 -0
  54. data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +41 -0
  55. data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +41 -0
  56. data/doc/anti-amnesia/20250807-213025.md +116 -0
  57. data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +256 -0
  58. data/doc/banner_svg_config.md +114 -0
  59. data/doc/contrib.lt3 +8 -0
  60. data/doc/dependencies.md +281 -0
  61. data/doc/hacker.lt3 +5 -0
  62. data/doc/reddit_credentials_template.json +8 -0
  63. data/doc/reddit_integration.md +207 -0
  64. data/doc/user.lt3 +38 -0
  65. data/doc/user_guide_section_1.md +137 -0
  66. data/doc/user_guide_section_10.md +515 -0
  67. data/doc/user_guide_section_11.md +708 -0
  68. data/doc/user_guide_section_2.md +233 -0
  69. data/doc/user_guide_section_3.md +5 -0
  70. data/doc/user_guide_section_4.md +221 -0
  71. data/doc/user_guide_section_5.md +243 -0
  72. data/doc/user_guide_section_6.md +147 -0
  73. data/doc/user_guide_section_7.md +311 -0
  74. data/doc/user_guide_section_8.md +224 -0
  75. data/doc/user_guide_section_9.md +375 -0
  76. data/doc/userdoc-toc.txt +88 -0
  77. data/lib/rouge/lexers/livetext.rb +74 -0
  78. data/lib/scriptorium/api.rb +640 -0
  79. data/lib/scriptorium/banner_svg.rb +742 -0
  80. data/lib/scriptorium/contract.rb +33 -0
  81. data/lib/scriptorium/exceptions.rb +174 -0
  82. data/lib/scriptorium/helpers.rb +475 -0
  83. data/lib/scriptorium/post.rb +195 -0
  84. data/lib/scriptorium/reddit.rb +83 -0
  85. data/lib/scriptorium/repo.rb +624 -0
  86. data/lib/scriptorium/standard_files.rb +515 -0
  87. data/lib/scriptorium/syntax_highlighter.rb +234 -0
  88. data/lib/scriptorium/theme.rb +179 -0
  89. data/lib/scriptorium/version.rb +2 -2
  90. data/lib/scriptorium/view.rb +976 -0
  91. data/lib/scriptorium/widgets/featured_posts.rb +149 -0
  92. data/lib/scriptorium/widgets/links.rb +112 -0
  93. data/lib/scriptorium/widgets/pages.rb +133 -0
  94. data/lib/scriptorium/widgets/widget.rb +133 -0
  95. data/lib/scriptorium.rb +22 -9
  96. data/lib/skeleton.rb +11 -2
  97. data/scriptorium.gemspec +15 -4
  98. data/test/README.md +69 -0
  99. data/test/all +43 -0
  100. data/test/api_demo.rb +99 -0
  101. data/test/assets/imagenotfound.jpg +0 -0
  102. data/test/assets/images/.DS_Store +0 -0
  103. data/test/assets/images/README.md +27 -0
  104. data/test/assets/images/odd_aspect.png +0 -0
  105. data/test/assets/images/perfect.png +0 -0
  106. data/test/assets/images/small.png +0 -0
  107. data/test/assets/images/tall.png +0 -0
  108. data/test/assets/images/very_tall.png +0 -0
  109. data/test/assets/images/very_wide.png +0 -0
  110. data/test/assets/images/wide.png +0 -0
  111. data/test/assets/testbanner.jpg +0 -0
  112. data/test/banner_svg/simple_helpers.rb +13 -0
  113. data/test/banner_svg/unit.rb +768 -0
  114. data/test/ed_test.rb +204 -0
  115. data/test/integration/cursor_banner_combinations.rb +193 -0
  116. data/test/integration/cursor_banner_features.rb +374 -0
  117. data/test/integration/integration_test.rb +326 -0
  118. data/test/livetext_plugin_test.rb +229 -0
  119. data/test/manual/asset_mgmt.rb +67 -0
  120. data/test/manual/banner-tests/config.txt +3 -0
  121. data/test/manual/banner-tests/index.html +45 -0
  122. data/test/manual/banner-tests/test01.html +58 -0
  123. data/test/manual/banner-tests/test02.html +58 -0
  124. data/test/manual/banner-tests/test03.html +58 -0
  125. data/test/manual/banner-tests/test04.html +65 -0
  126. data/test/manual/banner-tests/test05.html +65 -0
  127. data/test/manual/banner-tests/test06.html +65 -0
  128. data/test/manual/banner-tests/test07.html +65 -0
  129. data/test/manual/banner-tests/test08.html +59 -0
  130. data/test/manual/banner-tests/test09.html +59 -0
  131. data/test/manual/banner-tests/test10.html +59 -0
  132. data/test/manual/banner-tests/test11.html +59 -0
  133. data/test/manual/banner-tests/test12.html +59 -0
  134. data/test/manual/banner-tests/test13.html +59 -0
  135. data/test/manual/banner-tests/test14.html +59 -0
  136. data/test/manual/banner-tests/test15.html +58 -0
  137. data/test/manual/banner-tests/test16.html +58 -0
  138. data/test/manual/banner-tests/test17.html +58 -0
  139. data/test/manual/banner-tests/test18.html +68 -0
  140. data/test/manual/banner-tests/test19.html +68 -0
  141. data/test/manual/banner-tests/test20.html +68 -0
  142. data/test/manual/banner-tests/test21.html +68 -0
  143. data/test/manual/banner-tests/test22.html +68 -0
  144. data/test/manual/banner-tests/test23.html +68 -0
  145. data/test/manual/banner-tests/test24.html +68 -0
  146. data/test/manual/banner-tests/test25.html +67 -0
  147. data/test/manual/banner_environment.rb +192 -0
  148. data/test/manual/deploy_symlink_demo.rb +142 -0
  149. data/test/manual/environment.rb +67 -0
  150. data/test/manual/make_banner.rb +153 -0
  151. data/test/manual/sample_banner_config.txt +12 -0
  152. data/test/manual/symlink_demo.rb +117 -0
  153. data/test/manual/test1.rb +47 -0
  154. data/test/manual/test2.rb +12 -0
  155. data/test/manual/test3.rb +38 -0
  156. data/test/manual/test4.rb +40 -0
  157. data/test/manual/test5.rb +24 -0
  158. data/test/manual/test6.rb +73 -0
  159. data/test/manual/test_banner_combinations.rb +120 -0
  160. data/test/manual/test_banner_features.rb +306 -0
  161. data/test/manual/test_banner_from_file.rb +150 -0
  162. data/test/manual/test_banner_in_header.rb +35 -0
  163. data/test/manual/test_code_highlighting.rb +68 -0
  164. data/test/manual/test_complex_header.rb +74 -0
  165. data/test/manual/test_empty_header.rb +32 -0
  166. data/test/manual/test_radial_custom.rb +58 -0
  167. data/test/manual/test_radial_large_radius.rb +52 -0
  168. data/test/manual/test_svg_debug.rb +47 -0
  169. data/test/manual/test_syntax_highlighting.rb +147 -0
  170. data/test/pages-demo/config/currentview.txt +1 -0
  171. data/test/pages-demo/views/demo/config/bootstrap_css.txt +5 -0
  172. data/test/pages-demo/views/demo/config/bootstrap_js.txt +4 -0
  173. data/test/pages-demo/views/demo/config/common.js +57 -0
  174. data/test/pages-demo/views/demo/config/footer.txt +1 -0
  175. data/test/pages-demo/views/demo/config/global-head.txt +8 -0
  176. data/test/pages-demo/views/demo/config/header.txt +1 -0
  177. data/test/pages-demo/views/demo/config/layout.txt +1 -0
  178. data/test/pages-demo/views/demo/config/left.txt +1 -0
  179. data/test/pages-demo/views/demo/config/main.txt +1 -0
  180. data/test/pages-demo/views/demo/config/right.txt +1 -0
  181. data/test/pages-demo/views/demo/config.txt +3 -0
  182. data/test/pages-demo/views/demo/output/panes/footer.html +1 -0
  183. data/test/pages-demo/views/demo/output/panes/header.html +1 -0
  184. data/test/pages-demo/views/demo/output/panes/left.html +1 -0
  185. data/test/pages-demo/views/demo/output/panes/main.html +1 -0
  186. data/test/pages-demo/views/demo/output/panes/right.html +1 -0
  187. data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
  188. data/test/rubytext/rubytext_demo_test.rb +42 -0
  189. data/test/rubytext/rubytext_testing_guide.md +277 -0
  190. data/test/run_automated_tests.rb +45 -0
  191. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +5 -0
  192. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +4 -0
  193. data/test/scriptorium-TEST-1754622690-146/config/common.js +57 -0
  194. data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +1 -0
  195. data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +9 -0
  196. data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +1 -0
  197. data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +4 -0
  198. data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +3 -0
  199. data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +8 -0
  200. data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +6 -0
  201. data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +1 -0
  202. data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +1 -0
  203. data/test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 +12 -0
  204. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt +2 -0
  205. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt +4 -0
  206. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt +3 -0
  207. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt +5 -0
  208. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt +3 -0
  209. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +1 -0
  210. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/layout.txt +5 -0
  211. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +1 -0
  212. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +14 -0
  213. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +13 -0
  214. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +1 -0
  215. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +5 -0
  216. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +4 -0
  217. data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +57 -0
  218. data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +5 -0
  219. data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +2 -0
  220. data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +9 -0
  221. data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +4 -0
  222. data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +5 -0
  223. data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +3 -0
  224. data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +5 -0
  225. data/test/scriptorium-TEST-1754622690-146/views/sample/config/reddit.txt +10 -0
  226. data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +3 -0
  227. data/test/scriptorium-TEST-1754622690-146/views/sample/config/social.txt +7 -0
  228. data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +7 -0
  229. data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +3 -0
  230. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +3 -0
  231. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +3 -0
  232. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +3 -0
  233. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +3 -0
  234. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +3 -0
  235. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +1 -0
  236. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +1 -0
  237. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +1 -0
  238. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +1 -0
  239. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +1 -0
  240. data/test/staging/.DS_Store +0 -0
  241. data/test/syntax_highlighting_test.lt3 +124 -0
  242. data/test/test_helpers.rb +230 -0
  243. data/test/tui_editor_integration_test.rb +296 -0
  244. data/test/tui_integration_test.rb +637 -0
  245. data/test/unit/api.rb +1056 -0
  246. data/test/unit/asset_management.rb +245 -0
  247. data/test/unit/clipboard_test.rb +60 -0
  248. data/test/unit/contract_test.rb +91 -0
  249. data/test/unit/core.rb +857 -0
  250. data/test/unit/deploy_test.rb +187 -0
  251. data/test/unit/gem_asset_management.rb +189 -0
  252. data/test/unit/livetext_basic.rb +69 -0
  253. data/test/unit/livetext_compatibility.rb +89 -0
  254. data/test/unit/post.rb +244 -0
  255. data/test/unit/read_commented_file_test.rb +276 -0
  256. data/test/unit/reddit_test.rb +235 -0
  257. data/test/unit/repo.rb +548 -0
  258. data/test/unit/social_test.rb +369 -0
  259. data/test/unit/symlink_test.rb +213 -0
  260. data/test/unit/view.rb +431 -0
  261. data/test/unit/widgets.rb +669 -0
  262. data/test/wizard_test.rb +123 -0
  263. data/ui/README.md +67 -0
  264. data/ui/common/lib/ui_common.rb +8 -0
  265. data/ui/rubytext/README.md +191 -0
  266. data/ui/rubytext/bin/scriptorium-rubytext +402 -0
  267. data/ui/rubytext/lib/rubytext_ui.rb +300 -0
  268. data/ui/tui/bin/scriptorium +1420 -0
  269. data/ui/tui/test/tui_test.rb +23 -0
  270. data/ui/web/app/app.rb +1378 -0
  271. data/ui/web/app/error_helpers.rb +150 -0
  272. data/ui/web/app/views/advanced_config.erb +190 -0
  273. data/ui/web/app/views/asset_management.erb +589 -0
  274. data/ui/web/app/views/banner_config.erb +200 -0
  275. data/ui/web/app/views/configure_view.erb +401 -0
  276. data/ui/web/app/views/dashboard.erb +162 -0
  277. data/ui/web/app/views/deploy_config.erb +146 -0
  278. data/ui/web/app/views/edit_pages.erb +195 -0
  279. data/ui/web/app/views/edit_post.erb +54 -0
  280. data/ui/web/app/views/error_page.erb +29 -0
  281. data/ui/web/app/views/header_config.erb +155 -0
  282. data/ui/web/app/views/layout_config.erb +147 -0
  283. data/ui/web/app/views/navbar_config.erb +411 -0
  284. data/ui/web/app/views/view_dashboard.erb +138 -0
  285. data/ui/web/bin/scriptorium-web +153 -0
  286. data/ui/web/test/web_basic_test.rb +38 -0
  287. data/ui/web/test_navbar.txt +7 -0
  288. data/ui/web/tmp/web_server.log +5 -0
  289. data/ui/web/tmp/web_server.pid +1 -0
  290. metadata +360 -5
@@ -0,0 +1,277 @@
1
+ # RubyText Testing Guide
2
+
3
+ ## Overview
4
+
5
+ Testing curses-based applications like RubyText is challenging because they're inherently interactive and terminal-dependent. This guide outlines a comprehensive testing strategy that balances automation with practical reality.
6
+
7
+ ## Testing Challenges
8
+
9
+ ### **1. Terminal Dependencies**
10
+ - Different terminals behave differently
11
+ - Terminal capabilities vary (colors, Unicode, etc.)
12
+ - Screen sizes and resolutions differ
13
+
14
+ ### **2. Interactive Nature**
15
+ - User input is time-sensitive
16
+ - Screen state changes dynamically
17
+ - Hard to capture and verify visual output
18
+
19
+ ### **3. Platform Differences**
20
+ - Unix/Linux vs macOS vs Windows
21
+ - Different curses implementations
22
+ - Terminal emulator variations
23
+
24
+ ## Testing Strategy
25
+
26
+ ### **1. Component Testing (Recommended)**
27
+
28
+ Test individual components in isolation:
29
+
30
+ ```ruby
31
+ # Test menu logic without curses
32
+ def test_menu_logic
33
+ menu = RubyText::Menu.new(["Option 1", "Option 2"])
34
+ assert_equal 2, menu.options.length
35
+ assert_equal "Option 1", menu.options[0]
36
+ end
37
+
38
+ # Test screen buffer logic
39
+ def test_screen_buffer
40
+ screen = RubyText::Screen.new(80, 24)
41
+ screen.puts("Hello")
42
+ assert_includes screen.buffer, "Hello"
43
+ end
44
+ ```
45
+
46
+ ### **2. PTY-Based Integration Testing**
47
+
48
+ Use PTY for realistic terminal interaction:
49
+
50
+ ```ruby
51
+ def test_basic_interaction
52
+ PTY.spawn('ruby -e "require \"rubytext\"; RubyText.start { puts \"Hello\"; gets }"') do |read, write, pid|
53
+ begin
54
+ read.expect(/Hello/, 5)
55
+ write.puts "q"
56
+ Process.wait(pid)
57
+ assert_equal 0, $?.exitstatus
58
+ ensure
59
+ Process.kill('TERM', pid) rescue nil
60
+ end
61
+ end
62
+ end
63
+ ```
64
+
65
+ ### **3. Regression Testing with Screenshots**
66
+
67
+ Capture expected screen states:
68
+
69
+ ```ruby
70
+ def test_screen_regression
71
+ # Capture current screen
72
+ current_screen = capture_screen do
73
+ run_rubytext_app_with_input(["help", "quit"])
74
+ end
75
+
76
+ # Compare with expected
77
+ expected_screen = File.read("test/fixtures/expected_help_screen.txt")
78
+ assert_equal expected_screen, current_screen
79
+ end
80
+ ```
81
+
82
+ ### **4. Automated Demo Testing**
83
+
84
+ Automate your existing demo/slideshow:
85
+
86
+ ```ruby
87
+ def test_demo_automation
88
+ PTY.spawn('ruby demo.rb --automated') do |read, write, pid|
89
+ begin
90
+ # Navigate through slides
91
+ read.expect(/Slide 1/, 5)
92
+ write.puts "n" # next
93
+ read.expect(/Slide 2/, 5)
94
+ write.puts "q" # quit
95
+
96
+ Process.wait(pid)
97
+ assert_equal 0, $?.exitstatus
98
+ ensure
99
+ Process.kill('TERM', pid) rescue nil
100
+ end
101
+ end
102
+ end
103
+ ```
104
+
105
+ ## Test Categories
106
+
107
+ ### **Core Functionality**
108
+ - [ ] Menu creation and navigation
109
+ - [ ] Screen rendering and layout
110
+ - [ ] Input handling (keyboard, mouse)
111
+ - [ ] Color and style support
112
+ - [ ] Window management
113
+
114
+ ### **User Interface**
115
+ - [ ] Menu interactions
116
+ - [ ] Form input
117
+ - [ ] Dialog boxes
118
+ - [ ] Progress indicators
119
+ - [ ] Error messages
120
+
121
+ ### **Performance**
122
+ - [ ] Large content rendering
123
+ - [ ] Screen refresh speed
124
+ - [ ] Memory usage
125
+ - [ ] Input responsiveness
126
+
127
+ ### **Error Handling**
128
+ - [ ] Invalid input
129
+ - [ ] Terminal errors
130
+ - [ ] Resource limits
131
+ - [ ] Graceful degradation
132
+
133
+ ### **Platform Compatibility**
134
+ - [ ] Different terminals
135
+ - [ ] Screen sizes
136
+ - [ ] Color support
137
+ - [ ] Unicode support
138
+
139
+ ## Implementation Examples
140
+
141
+ ### **1. Menu Testing**
142
+
143
+ ```ruby
144
+ class MenuTest < Minitest::Test
145
+ def test_menu_creation
146
+ menu = RubyText::Menu.new(["Option 1", "Option 2"])
147
+ assert_equal 2, menu.options.length
148
+ end
149
+
150
+ def test_menu_interaction
151
+ PTY.spawn('ruby -e "require \"rubytext\"; RubyText.start { menu = RubyText::Menu.new([\"A\", \"B\"]); puts menu.show; gets }"') do |read, write, pid|
152
+ begin
153
+ read.expect(/A/, 5)
154
+ write.puts "\r" # Select first option
155
+ result = read.expect(/A/, 5)
156
+ assert result
157
+ ensure
158
+ Process.kill('TERM', pid) rescue nil
159
+ end
160
+ end
161
+ end
162
+ end
163
+ ```
164
+
165
+ ### **2. Screen Testing**
166
+
167
+ ```ruby
168
+ class ScreenTest < Minitest::Test
169
+ def test_screen_rendering
170
+ PTY.spawn('ruby -e "require \"rubytext\"; RubyText.start { puts \"Test\"; gets }"') do |read, write, pid|
171
+ begin
172
+ result = read.expect(/Test/, 5)
173
+ assert result, "Screen should display content"
174
+ ensure
175
+ Process.kill('TERM', pid) rescue nil
176
+ end
177
+ end
178
+ end
179
+ end
180
+ ```
181
+
182
+ ### **3. Input Testing**
183
+
184
+ ```ruby
185
+ class InputTest < Minitest::Test
186
+ def test_keyboard_input
187
+ PTY.spawn('ruby -e "require \"rubytext\"; RubyText.start { key = RubyText.getch; puts \"Key: #{key}\"; gets }"') do |read, write, pid|
188
+ begin
189
+ write.puts "a"
190
+ result = read.expect(/Key: a/, 5)
191
+ assert result, "Should detect key press"
192
+ ensure
193
+ Process.kill('TERM', pid) rescue nil
194
+ end
195
+ end
196
+ end
197
+ end
198
+ ```
199
+
200
+ ## Testing Tools
201
+
202
+ ### **1. PTY (Pseudo-Terminal)**
203
+ - Realistic terminal interaction
204
+ - Captures input/output
205
+ - Good for integration testing
206
+
207
+ ### **2. Screen Capture**
208
+ - Capture screen states
209
+ - Compare with expected output
210
+ - Good for regression testing
211
+
212
+ ### **3. Mock Terminals**
213
+ - Test logic without curses
214
+ - Faster execution
215
+ - Good for unit testing
216
+
217
+ ### **4. Automated Demos**
218
+ - Test real user workflows
219
+ - Verify end-to-end functionality
220
+ - Good for acceptance testing
221
+
222
+ ## Best Practices
223
+
224
+ ### **1. Test Structure**
225
+ - Separate unit tests from integration tests
226
+ - Use descriptive test names
227
+ - Group related tests together
228
+
229
+ ### **2. Error Handling**
230
+ - Test error conditions
231
+ - Verify graceful degradation
232
+ - Check error messages
233
+
234
+ ### **3. Performance**
235
+ - Test with realistic data sizes
236
+ - Monitor memory usage
237
+ - Check response times
238
+
239
+ ### **4. Platform Coverage**
240
+ - Test on multiple platforms
241
+ - Use CI/CD for automated testing
242
+ - Document platform-specific issues
243
+
244
+ ## Manual Testing Checklist
245
+
246
+ Since automated testing can't cover everything, maintain a manual testing checklist:
247
+
248
+ ### **Visual Verification**
249
+ - [ ] Colors display correctly
250
+ - [ ] Text alignment is proper
251
+ - [ ] Screen layout is clean
252
+ - [ ] Unicode characters render
253
+
254
+ ### **Interaction Testing**
255
+ - [ ] Menu navigation works
256
+ - [ ] Keyboard shortcuts function
257
+ - [ ] Mouse input responds
258
+ - [ ] Error messages are clear
259
+
260
+ ### **Performance Testing**
261
+ - [ ] Large content renders quickly
262
+ - [ ] Screen updates are smooth
263
+ - [ ] Input is responsive
264
+ - [ ] Memory usage is reasonable
265
+
266
+ ## Conclusion
267
+
268
+ Testing RubyText requires a multi-faceted approach:
269
+
270
+ 1. **Unit test the core logic** (menus, screens, input handling)
271
+ 2. **Integration test with PTY** (real terminal interaction)
272
+ 3. **Regression test with screenshots** (visual verification)
273
+ 4. **Manual test the complex interactions** (user experience)
274
+
275
+ The key is to **automate what you can** and **accept that some testing will always be manual**. Focus on testing the critical paths and user workflows, and use your existing demo/slideshow as a foundation for automated testing.
276
+
277
+ Remember: **Perfect automated testing of curses applications is impossible, but good testing is definitely achievable!**
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Set up environment
4
+ ENV['PATH'] = "#{ENV['HOME']}/.rbenv/shims:#{ENV['PATH']}"
5
+
6
+ puts "\n" + "="*60
7
+ puts "Running all automated tests..."
8
+ puts "="*60
9
+
10
+ # Run unit tests
11
+ puts "\n--- Running unit tests ---"
12
+ system("ruby test/all")
13
+
14
+ # Run manual tests in automated mode
15
+ puts "\n--- Running manual tests in automated mode ---"
16
+
17
+ manual_tests = [
18
+ "test/manual/test1.rb",
19
+ "test/manual/test2.rb",
20
+ "test/manual/test3.rb blog1",
21
+ "test/manual/test4.rb",
22
+ "test/manual/test5.rb",
23
+ "test/manual/test_banner_combinations.rb",
24
+ "test/manual/test_banner_features.rb",
25
+ "test/manual/test_complex_header.rb",
26
+ "test/manual/test_empty_header.rb",
27
+ "test/manual/test_banner_in_header.rb",
28
+ "test/manual/test_radial_custom.rb",
29
+ "test/manual/test_radial_large_radius.rb",
30
+ "test/manual/test_svg_debug.rb"
31
+ ]
32
+
33
+ manual_tests.each do |test|
34
+ puts "\nRunning: #{test}"
35
+ result = system("ruby #{test} --automated")
36
+ if result
37
+ puts "✓ PASSED"
38
+ else
39
+ puts "✗ FAILED"
40
+ end
41
+ end
42
+
43
+ puts "\n" + "="*60
44
+ puts "All automated tests completed!"
45
+ puts "="*60
@@ -0,0 +1,5 @@
1
+ href https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css
2
+ rel stylesheet
3
+ # integrity has a problem - compute instead?
4
+ # integrity sha384-KyZXEJ04F5o1v7V5b7ZMjGhGjxA8yQmBfvZwzI1r+0gEv+9KnQIMJxWwzD0u8nZ7
5
+ # crossorigin anonymous
@@ -0,0 +1,4 @@
1
+ src https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js
2
+ # integrity has a problem - compute instead?
3
+ # integrity sha384-pzjw8f+ua7Kw1TIq0+v5+GZkR6P/6v03cI0myXcJU22Hc5p5BY5/X93HmaJXjm4C
4
+ crossorigin anonymous
@@ -0,0 +1,57 @@
1
+ // Handle the back button (or JavaScript history.go(-1))
2
+ window.onpopstate = function(event) {
3
+ console.log('onpopstate event:', event); // Log the event object
4
+ if (event.state && event.state.slug) {
5
+ console.log('Navigating to slug:', event.state.slug); // Log the slug
6
+ load_main(event.state.slug); // Load the post for the previous history state
7
+ }
8
+ };
9
+
10
+ // Initialize with the front page when navigating via the back button or similar
11
+ window.onload = function() {
12
+ // Check if the initial state exists, if not, set it
13
+ if (!history.state) {
14
+ // Don't try to load post_index.html if there are no posts
15
+ // The "No posts yet!" message is already in the main container
16
+ history.replaceState({ slug: "index.html" }, "", "index.html");
17
+ }
18
+ };
19
+
20
+ // Load the main content and other page containers (header, footer, left, right)
21
+ function load_main(slug) {
22
+ // Get all container elements (header, footer, left, right, and main)
23
+ const contentDiv = document.getElementById("main");
24
+ const headerDiv = document.querySelector("header");
25
+ const footerDiv = document.querySelector("footer");
26
+ const leftDiv = document.querySelector(".left");
27
+ const rightDiv = document.querySelector(".right");
28
+ console.log('Loading main with slug:', slug); // Log the slug
29
+
30
+ fetch(slug)
31
+ .then(response => {
32
+ if (response.ok) {
33
+ console.log('Response is ok');
34
+ return response.text();
35
+ } else {
36
+ console.error('Failed to load:', response.status); // Log the failed response
37
+ }
38
+ })
39
+ .then(content => {
40
+ console.log('Loaded content into div'); // Log successful content insertion
41
+
42
+ // Now, reload the content into the respective containers:
43
+ // Main section
44
+ contentDiv.innerHTML = content;
45
+
46
+ // Re-insert header, footer, left, and right (if necessary)
47
+ // If you want the static layout to be kept, you can preserve these parts
48
+ // with additional logic or predefined structure (here it's assumed
49
+ // that header/footer/left/right are already statically included).
50
+
51
+ // You can also replace the other parts (left, right, header, footer) if needed.
52
+ history.pushState({slug: slug}, "", slug); // Update browser history
53
+ })
54
+ .catch(error => {
55
+ console.log("Error loading content:", error); // Log any errors during fetch
56
+ });
57
+ }
@@ -0,0 +1,9 @@
1
+ # This global file supplies the default values for all views.
2
+ # title is omitted - filled in at generation
3
+ charset UTF-8
4
+ desc A blog powered by Scriptorium. This is default text intended to be changed by the user.
5
+ viewport width=device-width initial-scale=1.0
6
+ robots index follow
7
+ javascript # See common.js
8
+ bootstrap # See bootstrap.txt
9
+ social # See social.txt for configuration
@@ -0,0 +1,4 @@
1
+ # Generated at repo creation for macOS
2
+ def open_file(file_path)
3
+ system("open", file_path)
4
+ end
@@ -0,0 +1,3 @@
1
+ links
2
+ pages
3
+ featuredposts
@@ -0,0 +1,8 @@
1
+ post.id 0001
2
+ post.created 2025-08-07-22-11-30
3
+ post.published no
4
+ post.deployed no
5
+ post.title Test Post
6
+ post.blurb ADD BLURB HERE
7
+ post.views sample
8
+ post.tags
@@ -0,0 +1,6 @@
1
+ .title Test Post
2
+ .blurb ADD BLURB HERE
3
+ .views sample
4
+ .tags
5
+
6
+ Test body
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
@@ -0,0 +1,12 @@
1
+ . Initial file created by StandardFiles#post_template(num)
2
+
3
+ .id %{num}
4
+ .created %{created}
5
+
6
+ .title %{title}
7
+ .blurb %{blurb}
8
+
9
+ .views %{views}
10
+ .tags %{tags}
11
+
12
+ BEGIN HERE...
@@ -0,0 +1,2 @@
1
+ # <!-- Section: footer -->
2
+ # Contents of footer
@@ -0,0 +1,4 @@
1
+ # <!-- Section: header -->
2
+ # Contents of header
3
+ # (may include banner, title, navbar, ...)
4
+ title
@@ -0,0 +1,3 @@
1
+ # <!-- Section: left -->
2
+ # Contents of left sidebar
3
+ # (may be widgets or whatever)
@@ -0,0 +1,5 @@
1
+ # <!-- Section: main -->
2
+ # Contents of center pane
3
+ # This may be empty, as it is "usually" populated
4
+ # by Javascript (list of recent posts, content from
5
+ # a widget, or whatever)
@@ -0,0 +1,3 @@
1
+ # <!-- Section: right -->
2
+ # Contents of right sidebar
3
+ # (may be widgets or whatever)
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
@@ -0,0 +1,5 @@
1
+ header # Top (banner? title? navbar? etc.)
2
+ left 15% # Left sidebar, 15% width
3
+ main # Main (center) container - posts/etc.
4
+ right 15% # Right sidebar, 15% width
5
+ footer # Footer (copyright? mail? social media? etc.)
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
@@ -0,0 +1,14 @@
1
+ <div class="index-entry" style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px;">
2
+ <!-- Left Side: Date (right aligned) -->
3
+ <div style="text-align: right; font-size: 0.7em; flex-basis: 10%%; padding-top: 3px;">
4
+ <div>%{post.pubdate.month} %{post.pubdate.day}</div>
5
+ <div>%{post.pubdate.year}</div>
6
+ </div>
7
+ <!-- Right Side: Title and Blurb (left aligned) -->
8
+ <div style="font-size: 1.2em; margin-left: 10px; flex-grow: 1; padding-top: 0;">
9
+ <div><a href="javascript:void(0)"
10
+ style="text-decoration: none;"
11
+ onclick="load_main('posts/%{post.slug}')">%{post.title}</a></div>
12
+ <div style="font-size: 0.9em;">%{post.blurb}</div>
13
+ </div>
14
+ </div>
@@ -0,0 +1,13 @@
1
+ <!-- theme: standard -->
2
+
3
+ <div align='right'><a style="text-decoration: none" href="javascript:history.go(-1)">
4
+ <img src="assets/back-icon.png" width=24 height=24 alt="Go back"></img></a>
5
+ </div>
6
+ <div style="display: flex; justify-content: space-between; align-items: baseline;">
7
+ <span style="text-align: left; font-size: 1.5em;">%{post.title}</span>
8
+ <span style="text-align: right; font-size: 0.9em;">%{reddit_button}%{post.pubdate}</span>
9
+ </div>
10
+ <hr>
11
+ %{post.body}
12
+ <hr>
13
+ <div style="text-align: right; font-size: 0.8em;">%{post.tags}</div>
@@ -0,0 +1 @@
1
+ Empty file generated at 2025-08-07 22:11:30 -0500
@@ -0,0 +1,5 @@
1
+ href https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css
2
+ rel stylesheet
3
+ # integrity has a problem - compute instead?
4
+ # integrity sha384-KyZXEJ04F5o1v7V5b7ZMjGhGjxA8yQmBfvZwzI1r+0gEv+9KnQIMJxWwzD0u8nZ7
5
+ # crossorigin anonymous
@@ -0,0 +1,4 @@
1
+ src https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js
2
+ # integrity has a problem - compute instead?
3
+ # integrity sha384-pzjw8f+ua7Kw1TIq0+v5+GZkR6P/6v03cI0myXcJU22Hc5p5BY5/X93HmaJXjm4C
4
+ crossorigin anonymous
@@ -0,0 +1,57 @@
1
+ // Handle the back button (or JavaScript history.go(-1))
2
+ window.onpopstate = function(event) {
3
+ console.log('onpopstate event:', event); // Log the event object
4
+ if (event.state && event.state.slug) {
5
+ console.log('Navigating to slug:', event.state.slug); // Log the slug
6
+ load_main(event.state.slug); // Load the post for the previous history state
7
+ }
8
+ };
9
+
10
+ // Initialize with the front page when navigating via the back button or similar
11
+ window.onload = function() {
12
+ // Check if the initial state exists, if not, set it
13
+ if (!history.state) {
14
+ // Don't try to load post_index.html if there are no posts
15
+ // The "No posts yet!" message is already in the main container
16
+ history.replaceState({ slug: "index.html" }, "", "index.html");
17
+ }
18
+ };
19
+
20
+ // Load the main content and other page containers (header, footer, left, right)
21
+ function load_main(slug) {
22
+ // Get all container elements (header, footer, left, right, and main)
23
+ const contentDiv = document.getElementById("main");
24
+ const headerDiv = document.querySelector("header");
25
+ const footerDiv = document.querySelector("footer");
26
+ const leftDiv = document.querySelector(".left");
27
+ const rightDiv = document.querySelector(".right");
28
+ console.log('Loading main with slug:', slug); // Log the slug
29
+
30
+ fetch(slug)
31
+ .then(response => {
32
+ if (response.ok) {
33
+ console.log('Response is ok');
34
+ return response.text();
35
+ } else {
36
+ console.error('Failed to load:', response.status); // Log the failed response
37
+ }
38
+ })
39
+ .then(content => {
40
+ console.log('Loaded content into div'); // Log successful content insertion
41
+
42
+ // Now, reload the content into the respective containers:
43
+ // Main section
44
+ contentDiv.innerHTML = content;
45
+
46
+ // Re-insert header, footer, left, and right (if necessary)
47
+ // If you want the static layout to be kept, you can preserve these parts
48
+ // with additional logic or predefined structure (here it's assumed
49
+ // that header/footer/left/right are already statically included).
50
+
51
+ // You can also replace the other parts (left, right, header, footer) if needed.
52
+ history.pushState({slug: slug}, "", slug); // Update browser history
53
+ })
54
+ .catch(error => {
55
+ console.log("Error loading content:", error); // Log any errors during fetch
56
+ });
57
+ }
@@ -0,0 +1,5 @@
1
+ user root
2
+ server example.com
3
+ docroot /var/www/html
4
+ path sample
5
+ proto https
@@ -0,0 +1,2 @@
1
+ # <!-- Section: footer -->
2
+ # Contents of footer
@@ -0,0 +1,9 @@
1
+ # This view-specific file supplies the default values for this view.
2
+ # title is omitted - filled in at generation
3
+ charset UTF-8
4
+ desc A blog powered by Scriptorium. This is default text intended to be changed by the user.
5
+ viewport width=device-width initial-scale=1.0
6
+ robots index follow
7
+ javascript # See common.js
8
+ bootstrap # See bootstrap.txt
9
+ social # See social.txt for configuration
@@ -0,0 +1,4 @@
1
+ # <!-- Section: header -->
2
+ # Contents of header
3
+ # (may include banner, title, navbar, ...)
4
+ title
@@ -0,0 +1,5 @@
1
+ header # Top (banner? title? navbar? etc.)
2
+ left 15% # Left sidebar, 15% width
3
+ main # Main (center) container - posts/etc.
4
+ right 15% # Right sidebar, 15% width
5
+ footer # Footer (copyright? mail? social media? etc.)
@@ -0,0 +1,3 @@
1
+ # <!-- Section: left -->
2
+ # Contents of left sidebar
3
+ # (may be widgets or whatever)
@@ -0,0 +1,5 @@
1
+ # <!-- Section: main -->
2
+ # Contents of center pane
3
+ # This may be empty, as it is "usually" populated
4
+ # by Javascript (list of recent posts, content from
5
+ # a widget, or whatever)
@@ -0,0 +1,10 @@
1
+ # Reddit sharing button configuration
2
+ # Set to true to show Reddit share button on posts
3
+ button true
4
+
5
+ # Optional: specify a subreddit for direct posting
6
+ # Leave empty or omit to let users choose subreddit
7
+ subreddit
8
+
9
+ # Optional: custom hover text (defaults to "Share on Reddit" or "Share on [subreddit]")
10
+ hover_text
@@ -0,0 +1,3 @@
1
+ # <!-- Section: right -->
2
+ # Contents of right sidebar
3
+ # (may be widgets or whatever)