scriptorium 0.0.3 → 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 (292) 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 +170 -1
  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 +21 -40
  96. data/lib/skeleton.rb +8 -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 +359 -7
  291. data/lib/scriptorium/engine.rb +0 -22
  292. data/test/engine/unit.rb +0 -44
@@ -0,0 +1,637 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pty'
4
+ require 'expect'
5
+ require 'fileutils'
6
+ require 'minitest/autorun'
7
+ require_relative '../lib/scriptorium'
8
+
9
+ class TUIIntegrationTest < Minitest::Test
10
+ # Test repository path
11
+ TEST_REPO_PATH = "scriptorium-TEST"
12
+
13
+ def setup
14
+ cleanup_test_repo
15
+ # Don't create repo here - let the TUI create it interactively
16
+ # Disable DBC contracts in tests
17
+ ENV['DBC_DISABLED'] = 'true'
18
+ end
19
+
20
+ def teardown
21
+ cleanup_test_repo
22
+ end
23
+
24
+
25
+
26
+ def test_001_basic_tui_interaction
27
+ # Test TUI interaction - let TUI create repo interactively
28
+ ENV['NOREADLINE'] = '1'
29
+
30
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium') do |read, write, pid|
31
+ begin
32
+ run_basic_tui_interaction_test(read, write, pid)
33
+ ensure
34
+ Process.kill('TERM', pid) rescue nil
35
+ Process.wait(pid) rescue nil
36
+ end
37
+ end
38
+ ensure
39
+ ENV.delete('NOREADLINE')
40
+ end
41
+
42
+ def test_002_view_management
43
+ ENV['NOREADLINE'] = '1'
44
+
45
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium') do |read, write, pid|
46
+ begin
47
+ run_view_management_test(read, write, pid)
48
+ ensure
49
+ Process.kill('TERM', pid) rescue nil
50
+ Process.wait(pid) rescue nil
51
+ end
52
+ end
53
+ ensure
54
+ ENV.delete('NOREADLINE')
55
+ end
56
+
57
+ def test_003_command_abbreviations
58
+ ENV['NOREADLINE'] = '1'
59
+
60
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium') do |read, write, pid|
61
+ begin
62
+ run_command_abbreviations_test(read, write, pid)
63
+ ensure
64
+ Process.kill('TERM', pid) rescue nil
65
+ Process.wait(pid) rescue nil
66
+ end
67
+ end
68
+ ensure
69
+ ENV.delete('NOREADLINE')
70
+ end
71
+
72
+ def test_004_interactive_create_view
73
+ ENV['NOREADLINE'] = '1'
74
+
75
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium') do |read, write, pid|
76
+ begin
77
+ run_interactive_create_view_test(read, write, pid)
78
+ ensure
79
+ Process.kill('TERM', pid) rescue nil
80
+ Process.wait(pid) rescue nil
81
+ end
82
+ end
83
+ ensure
84
+ ENV.delete('NOREADLINE')
85
+ end
86
+
87
+ def test_005_unknown_commands
88
+ ENV['NOREADLINE'] = '1'
89
+
90
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium') do |read, write, pid|
91
+ begin
92
+ run_unknown_commands_test(read, write, pid)
93
+ ensure
94
+ Process.kill('TERM', pid) rescue nil
95
+ Process.wait(pid) rescue nil
96
+ end
97
+ end
98
+ ensure
99
+ ENV.delete('NOREADLINE')
100
+ end
101
+
102
+ def test_006_empty_input_handling
103
+ ENV['NOREADLINE'] = '1'
104
+
105
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium') do |read, write, pid|
106
+ begin
107
+ run_empty_input_handling_test(read, write, pid)
108
+ ensure
109
+ Process.kill('TERM', pid) rescue nil
110
+ Process.wait(pid) rescue nil
111
+ end
112
+ end
113
+ ensure
114
+ ENV.delete('NOREADLINE')
115
+ end
116
+
117
+ def test_007_exit_variations
118
+ ENV['NOREADLINE'] = '1'
119
+
120
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium') do |read, write, pid|
121
+ begin
122
+ run_exit_variations_test(read, write, pid)
123
+ ensure
124
+ Process.kill('TERM', pid) rescue nil
125
+ Process.wait(pid) rescue nil
126
+ end
127
+ end
128
+ ensure
129
+ ENV.delete('NOREADLINE')
130
+ end
131
+
132
+ def test_008_error_conditions
133
+ ENV['NOREADLINE'] = '1'
134
+
135
+ PTY.spawn({'NOREADLINE' => '1'}, 'ruby bin/scriptorium') do |read, write, pid|
136
+ begin
137
+ run_error_conditions_test(read, write, pid)
138
+ ensure
139
+ Process.kill('TERM', pid) rescue nil
140
+ Process.wait(pid) rescue nil
141
+ end
142
+ end
143
+ ensure
144
+ ENV.delete('NOREADLINE')
145
+ end
146
+
147
+ private
148
+
149
+ def send_and_expect(read, write, input, expected_pattern, description)
150
+ write.puts input
151
+ sleep 0.1 # Small delay to ensure input is processed
152
+ result = get_string(read, expected_pattern, description)
153
+ # Add explicit assertion for the expected pattern
154
+ if expected_pattern.is_a?(Regexp)
155
+ assert result.match?(expected_pattern), "#{description}: Expected pattern '#{expected_pattern}' not found in output"
156
+ else
157
+ assert result.include?(expected_pattern), "#{description}: Expected text '#{expected_pattern}' not found in output"
158
+ end
159
+ result
160
+ rescue Errno::EIO => e
161
+ # Handle case where TUI terminates immediately after output
162
+ if expected_pattern.to_s.include?("Goodbye!")
163
+ # If we're expecting Goodbye! and get an I/O error, that's probably OK
164
+ # The TUI terminated after outputting Goodbye!
165
+ return
166
+ else
167
+ raise e
168
+ end
169
+ end
170
+
171
+ def run_basic_tui_interaction_test(read, write, pid)
172
+ begin
173
+ # Wait for "No repository found" message
174
+ get_string(read, "No repository found.", "Should show 'No repository found'")
175
+
176
+ # Send 'y' to create new repository
177
+ send_and_expect(read, write, "y", "Created repository successfully.", "Should show repository created")
178
+
179
+ # Wait for editor setup
180
+ get_string(read, "No editor configured", "Should show editor setup")
181
+
182
+ # Wait for editor list
183
+ get_string(read, "Available editors", "Should show available editors")
184
+
185
+ # Wait for editor choice prompt
186
+ get_string(read, "Choose editor", "Should prompt for editor selection")
187
+ send_and_expect(read, write, "4", "Selected editor: ed", "Should show editor selection")
188
+
189
+ # Wait for setup completion
190
+ get_string(read, "Setup complete", "Should show setup completion")
191
+
192
+ # Wait for assistance question
193
+ get_string(read, "Do you want assistance in creating your first view", "Should ask about assistance")
194
+
195
+ # Send 'n' to skip assistance (simpler test)
196
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
197
+
198
+ # Send help command
199
+ send_and_expect(read, write, "h", "view", "Should show help")
200
+
201
+ # Wait for prompt again
202
+ get_string(read, /\[.*\] /, "Should show prompt after help")
203
+
204
+ # Send list views command
205
+ send_and_expect(read, write, "lsv", "sample", "Should show sample view")
206
+
207
+ # Wait for prompt
208
+ get_string(read, /\[.*\] /, "Should show prompt after lsv")
209
+
210
+ # Send view command to see current view
211
+ send_and_expect(read, write, "view", "Current view:", "Should show view info")
212
+
213
+ # Wait for prompt
214
+ get_string(read, /\[.*\] /, "Should show prompt after view")
215
+
216
+ # Send quit
217
+ send_and_expect(read, write, "q", "Goodbye!", "Should show goodbye")
218
+
219
+ ensure
220
+ Process.kill('TERM', pid) rescue nil
221
+ Process.wait(pid) rescue nil
222
+ end
223
+ end
224
+
225
+ def run_view_management_test(read, write, pid)
226
+ begin
227
+ # Wait for "No repository found" message
228
+ get_string(read, "No repository found.", "Should show 'No repository found'")
229
+
230
+ # Send 'y' to create new repository
231
+ send_and_expect(read, write, "y", "Created repository successfully.", "Should show repository created")
232
+
233
+ # Wait for editor setup
234
+ get_string(read, "No editor configured", "Should show editor setup")
235
+
236
+ # Wait for editor list
237
+ get_string(read, "Available editors", "Should show available editors")
238
+
239
+ # Wait for editor choice prompt
240
+ get_string(read, "Choose editor", "Should prompt for editor selection")
241
+ send_and_expect(read, write, "4", "Selected editor: ed", "Should show editor selection")
242
+
243
+ # Wait for setup completion
244
+ get_string(read, "Setup complete", "Should show setup completion")
245
+
246
+ # Wait for assistance question
247
+ get_string(read, "Do you want assistance in creating your first view", "Should ask about assistance")
248
+
249
+ # Send 'n' to skip assistance (simpler test)
250
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
251
+
252
+ # List views
253
+ send_and_expect(read, write, "lsv", "sample", "Should show sample view")
254
+
255
+ # Wait for prompt
256
+ get_string(read, /\[.*\] /, "Should show prompt after lsv")
257
+
258
+ # Create a new view first
259
+ write.puts "new view testview123 This is just a test..."
260
+ get_string(read, "Enter subtitle (optional):", "Should prompt for subtitle")
261
+ write.puts "" # Empty subtitle
262
+ get_string(read, "Created view 'testview123' with title", "Should create new view")
263
+
264
+ # Wait for the "Switched to view" message that comes after creation
265
+ get_string(read, "Switched to view 'testview123'", "Should switch to new view after creation")
266
+
267
+ # Wait for prompt
268
+ get_string(read, /\[.*\] /, "Should show prompt after creating view")
269
+
270
+ # Now change to the new view (should already be on it, so no switch message)
271
+ write.puts "cv testview123"
272
+ # No message expected since we're already on this view
273
+
274
+ # Wait for prompt
275
+ get_string(read, /\[.*\] /, "Should show prompt after switching view")
276
+
277
+ # Show current view
278
+ send_and_expect(read, write, "view", "Current view:", "Should show view info")
279
+
280
+ # Wait for prompt
281
+ get_string(read, /\[.*\] /, "Should show prompt after view command")
282
+
283
+ # Create new view (this should fail because view already exists)
284
+ write.puts "new view testview123 This is just a test..."
285
+ get_string(read, "Enter subtitle (optional):", "Should prompt for subtitle")
286
+ write.puts "" # Empty subtitle
287
+ get_string(read, "View 'testview123' already exists", "Should show view already exists error")
288
+
289
+ # Wait for prompt
290
+ get_string(read, /\[.*\] /, "Should show prompt after failed view creation")
291
+
292
+ # Change to new view (already on this view, so no switch message)
293
+ write.puts "cv testview123"
294
+ # No message expected since we're already on this view
295
+
296
+ # Wait for prompt
297
+ get_string(read, /\[.*\] /, "Should show prompt after cv command")
298
+
299
+ # Try to change to non-existent view
300
+ send_and_expect(read, write, "cv nonexistent", "View 'nonexistent' not found", "Should show error for non-existent view")
301
+
302
+ # Wait for prompt
303
+ get_string(read, /\[.*\] /, "Should show prompt after error")
304
+
305
+ # Quit
306
+ send_and_expect(read, write, "q", "Goodbye!", "Should show goodbye")
307
+
308
+ ensure
309
+ Process.kill('TERM', pid) rescue nil
310
+ Process.wait(pid) rescue nil
311
+ end
312
+ end
313
+
314
+ def run_command_abbreviations_test(read, write, pid)
315
+ begin
316
+ # Wait for "No repository found" message
317
+ get_string(read, "No repository found.", "Should show 'No repository found'")
318
+
319
+ # Send 'y' to create new repository
320
+ send_and_expect(read, write, "y", "Created repository successfully.", "Should show repository created")
321
+
322
+ # Wait for editor setup
323
+ get_string(read, "No editor configured", "Should show editor setup")
324
+
325
+ # Wait for editor list
326
+ get_string(read, "Available editors", "Should show available editors")
327
+
328
+ # Wait for editor choice prompt
329
+ get_string(read, "Choose editor", "Should prompt for editor selection")
330
+ send_and_expect(read, write, "4", "Selected editor: ed", "Should show editor selection")
331
+
332
+ # Wait for setup completion
333
+ get_string(read, "Setup complete", "Should show setup completion")
334
+
335
+ # Wait for assistance question
336
+ get_string(read, "Do you want assistance in creating your first view", "Should ask about assistance")
337
+
338
+ # Send 'n' to skip assistance (simpler test)
339
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
340
+
341
+ # Send help command
342
+ send_and_expect(read, write, "h", "view", "Should show help")
343
+
344
+ # Wait for prompt
345
+ get_string(read, /\[.*\] /, "Should show prompt after help")
346
+
347
+ # Send version command
348
+ send_and_expect(read, write, "v", "Scriptorium", "Should show version")
349
+
350
+ # Wait for prompt
351
+ get_string(read, /\[.*\] /, "Should show prompt after version")
352
+
353
+ # Send list views command
354
+ send_and_expect(read, write, "lsv", "sample", "Should show sample view")
355
+
356
+ # Wait for prompt
357
+ get_string(read, /\[.*\] /, "Should show prompt after lsv")
358
+
359
+ # Quit
360
+ send_and_expect(read, write, "q", "Goodbye!", "Should show goodbye")
361
+
362
+ ensure
363
+ Process.kill('TERM', pid) rescue nil
364
+ Process.wait(pid) rescue nil
365
+ end
366
+ end
367
+
368
+ def run_interactive_create_view_test(read, write, pid)
369
+ begin
370
+ # Wait for "No repository found" message
371
+ get_string(read, "No repository found.", "Should show 'No repository found'")
372
+
373
+ # Send 'y' to create new repository
374
+ send_and_expect(read, write, "y", "Created repository successfully.", "Should show repository created")
375
+
376
+ # Wait for editor setup
377
+ get_string(read, "No editor configured", "Should show editor setup")
378
+
379
+ # Wait for editor list
380
+ get_string(read, "Available editors", "Should show available editors")
381
+
382
+ # Wait for editor choice prompt
383
+ get_string(read, "Choose editor", "Should prompt for editor selection")
384
+ send_and_expect(read, write, "4", "Selected editor: ed", "Should show editor selection")
385
+
386
+ # Wait for setup completion
387
+ get_string(read, "Setup complete", "Should show setup completion")
388
+
389
+ # Wait for assistance question
390
+ get_string(read, "Do you want assistance in creating your first view", "Should ask about assistance")
391
+
392
+ # Send 'n' to skip assistance (simpler test)
393
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
394
+
395
+ # Start interactive create view
396
+ send_and_expect(read, write, "new view", "Enter view name:", "Should prompt for view name")
397
+
398
+ # Enter view name
399
+ send_and_expect(read, write, "interactiveview", "Enter view title:", "Should prompt for view title")
400
+
401
+ # Enter view title
402
+ send_and_expect(read, write, "Interactive View", "Enter subtitle (optional):", "Should prompt for subtitle")
403
+
404
+ # Enter subtitle
405
+ send_and_expect(read, write, "Interactive Subtitle", "Created view 'interactiveview'", "Should create view")
406
+
407
+ # Wait for prompt
408
+ get_string(read, /\[.*\] /, "Should show prompt after creating view")
409
+
410
+ # List views to verify
411
+ send_and_expect(read, write, "lsv", "interactiveview", "Should show new view in list")
412
+
413
+ # Wait for prompt
414
+ get_string(read, /\[.*\] /, "Should show prompt after lsv")
415
+
416
+ # Quit
417
+ send_and_expect(read, write, "q", "Goodbye!", "Should show goodbye")
418
+
419
+ ensure
420
+ Process.kill('TERM', pid) rescue nil
421
+ Process.wait(pid) rescue nil
422
+ end
423
+ end
424
+
425
+ def run_unknown_commands_test(read, write, pid)
426
+ begin
427
+ # Wait for "No repository found" message
428
+ get_string(read, "No repository found.", "Should show 'No repository found'")
429
+
430
+ # Send 'y' to create new repository
431
+ send_and_expect(read, write, "y", "Created repository successfully.", "Should show repository created")
432
+
433
+ # Wait for editor setup
434
+ get_string(read, "No editor configured", "Should show editor setup")
435
+
436
+ # Wait for editor list
437
+ get_string(read, "Available editors", "Should show available editors")
438
+
439
+ # Wait for editor choice prompt
440
+ get_string(read, "Choose editor", "Should prompt for editor selection")
441
+ send_and_expect(read, write, "4", "Selected editor: ed", "Should show editor selection")
442
+
443
+ # Wait for setup completion
444
+ get_string(read, "Setup complete", "Should show setup completion")
445
+
446
+ # Wait for assistance question
447
+ get_string(read, "Do you want assistance in creating your first view", "Should ask about assistance")
448
+
449
+ # Send 'n' to skip assistance (simpler test)
450
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
451
+
452
+ # Send unknown command
453
+ send_and_expect(read, write, "unknowncommand", "Unknown command: unknowncommand", "Should show unknown command error")
454
+
455
+ # Wait for prompt
456
+ get_string(read, /\[.*\] /, "Should show prompt after error")
457
+
458
+ # Send another unknown command
459
+ send_and_expect(read, write, "xyz123", "Unknown command: xyz123", "Should show unknown command error")
460
+
461
+ # Wait for prompt
462
+ get_string(read, /\[.*\] /, "Should show prompt after second error")
463
+
464
+ # Quit
465
+ send_and_expect(read, write, "q", "Goodbye!", "Should show goodbye")
466
+
467
+ ensure
468
+ Process.kill('TERM', pid) rescue nil
469
+ Process.wait(pid) rescue nil
470
+ end
471
+ end
472
+
473
+ def run_empty_input_handling_test(read, write, pid)
474
+ begin
475
+ # Wait for "No repository found" message
476
+ get_string(read, "No repository found.", "Should show 'No repository found'")
477
+
478
+ # Send 'y' to create new repository
479
+ send_and_expect(read, write, "y", "Created repository successfully.", "Should show repository created")
480
+
481
+ # Wait for editor setup
482
+ get_string(read, "No editor configured", "Should show editor setup")
483
+
484
+ # Wait for editor list
485
+ get_string(read, "Available editors", "Should show available editors")
486
+
487
+ # Wait for editor choice prompt
488
+ get_string(read, "Choose editor", "Should prompt for editor selection")
489
+ send_and_expect(read, write, "4", "Selected editor: ed", "Should show editor selection")
490
+
491
+ # Wait for setup completion
492
+ get_string(read, "Setup complete", "Should show setup completion")
493
+
494
+ # Wait for assistance question
495
+ get_string(read, "Do you want assistance in creating your first view", "Should ask about assistance")
496
+
497
+ # Send 'n' to skip assistance (simpler test)
498
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
499
+
500
+ # Send empty line
501
+ write.puts ""
502
+
503
+ # Send whitespace-only line
504
+ write.puts " "
505
+
506
+ # Send help command to verify we're still working
507
+ send_and_expect(read, write, "h", "view", "Should show help after empty input")
508
+
509
+ # Wait for prompt
510
+ get_string(read, /\[.*\] /, "Should show prompt after help")
511
+
512
+ # Quit
513
+ send_and_expect(read, write, "q", "Goodbye!", "Should show goodbye")
514
+
515
+ ensure
516
+ Process.kill('TERM', pid) rescue nil
517
+ Process.wait(pid) rescue nil
518
+ end
519
+ end
520
+
521
+ def run_exit_variations_test(read, write, pid)
522
+ begin
523
+ # Wait for "No repository found" message
524
+ get_string(read, "No repository found.", "Should show 'No repository found'")
525
+
526
+ # Send 'y' to create new repository
527
+ send_and_expect(read, write, "y", "Created repository successfully.", "Should show repository created")
528
+
529
+ # Wait for editor setup
530
+ get_string(read, "No editor configured", "Should show editor setup")
531
+
532
+ # Wait for editor list
533
+ get_string(read, "Available editors", "Should show available editors")
534
+
535
+ # Wait for editor choice prompt
536
+ get_string(read, "Choose editor", "Should prompt for editor selection")
537
+ send_and_expect(read, write, "4", "Selected editor: ed", "Should show editor selection")
538
+
539
+ # Wait for setup completion
540
+ get_string(read, "Setup complete", "Should show setup completion")
541
+
542
+ # Wait for assistance question
543
+ get_string(read, "Do you want assistance in creating your first view", "Should ask about assistance")
544
+
545
+ # Send 'n' to skip assistance (simpler test)
546
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
547
+
548
+ # Send quit command
549
+ send_and_expect(read, write, "quit", "Goodbye!", "Should show goodbye")
550
+
551
+ ensure
552
+ Process.kill('TERM', pid) rescue nil
553
+ Process.wait(pid) rescue nil
554
+ end
555
+ end
556
+
557
+ def run_error_conditions_test(read, write, pid)
558
+ begin
559
+ # Wait for "No repository found" message
560
+ get_string(read, "No repository found.", "Should show 'No repository found'")
561
+
562
+ # Send 'y' to create new repository
563
+ send_and_expect(read, write, "y", "Created repository successfully.", "Should show repository created")
564
+
565
+ # Wait for editor setup
566
+ get_string(read, "No editor configured", "Should show editor setup")
567
+
568
+ # Wait for editor list
569
+ get_string(read, "Available editors", "Should show available editors")
570
+
571
+ # Wait for editor choice prompt
572
+ get_string(read, "Choose editor", "Should prompt for editor selection")
573
+ send_and_expect(read, write, "4", "Selected editor: ed", "Should show editor selection")
574
+
575
+ # Wait for setup completion
576
+ get_string(read, "Setup complete", "Should show setup completion")
577
+
578
+ # Wait for assistance question
579
+ get_string(read, "Do you want assistance in creating your first view", "Should ask about assistance")
580
+
581
+ # Send 'n' to skip assistance (simpler test)
582
+ send_and_expect(read, write, "n", "[sample]", "Should show main prompt")
583
+
584
+ # Start interactive create view
585
+ send_and_expect(read, write, "new view", "Enter view name:", "Should prompt for view name")
586
+
587
+ # Enter invalid view name
588
+ send_and_expect(read, write, "invalid/name", "Enter view title:", "Should prompt for view title")
589
+
590
+ # Enter title
591
+ send_and_expect(read, write, "Invalid Title", "Enter subtitle (optional):", "Should prompt for subtitle")
592
+
593
+ # Enter subtitle
594
+ send_and_expect(read, write, "Invalid Subtitle", "Cannot create view: invalid name", "Should show error for invalid name")
595
+
596
+ # Wait for prompt
597
+ get_string(read, /\[.*\] /, "Should show prompt after error")
598
+
599
+ # Quit
600
+ send_and_expect(read, write, "q", "Goodbye!", "Should show goodbye")
601
+
602
+ ensure
603
+ Process.kill('TERM', pid) rescue nil
604
+ Process.wait(pid) rescue nil
605
+ end
606
+ end
607
+
608
+ def get_string(read, pattern, message, timeout = 5)
609
+ # Convenience method to get a string matching a pattern with timeout
610
+ # Set VERBOSE=1 to see detailed output, defaults to silent
611
+ begin
612
+ output = read.expect(pattern, timeout)
613
+ if output
614
+ puts "Found: #{output[0].strip}" if ENV['VERBOSE']
615
+ return output[0] # Return the matched string
616
+ else
617
+ puts "Failed to find pattern: #{pattern}"
618
+ # Read what we actually got
619
+ available = read.read_nonblock(1000) rescue ""
620
+ puts "Available output: #{available.inspect}"
621
+ flunk "#{message}: Pattern '#{pattern}' not found within #{timeout} seconds"
622
+ end
623
+ rescue IO::EAGAINWaitReadable
624
+ puts "No output available within timeout"
625
+ flunk "#{message}: No output received within #{timeout} seconds"
626
+ rescue => e
627
+ puts "Error in get_string: #{e.message}"
628
+ flunk "#{message}: Error - #{e.message}"
629
+ end
630
+ end
631
+
632
+ def cleanup_test_repo
633
+ # Clean up test repositories
634
+ FileUtils.rm_rf(TEST_REPO_PATH) if Dir.exist?(TEST_REPO_PATH)
635
+ FileUtils.rm_rf("scriptorium-TEST") if Dir.exist?("scriptorium-TEST")
636
+ end
637
+ end