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
data/test/ed_test.rb ADDED
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This file tests that we can interact with 'ed' correctly for automated testing.
4
+ # We are NOT testing 'ed' itself - we assume 'ed' works correctly.
5
+ # We ARE testing that our test infrastructure can properly invoke 'ed'
6
+ # and handle its input/output/error streams for automated testing scenarios.
7
+
8
+ require 'minitest/autorun'
9
+ require 'pty'
10
+ require 'expect'
11
+ require 'tempfile'
12
+
13
+ class EdTest < Minitest::Test
14
+ def test_001_ed_basic_functionality_pty
15
+ # Test that ed can create and edit a file using PTY
16
+ temp_file = Tempfile.new(['test', '.txt'])
17
+ temp_file.close
18
+
19
+ PTY.spawn("ed #{temp_file.path}") do |read, write, pid|
20
+ begin
21
+ # Wait for ed to start
22
+ sleep 0.5
23
+
24
+ # Send ed commands
25
+ write.puts "a" # Enter append mode
26
+ write.puts "Hello world" # Add text
27
+ write.puts "This is a test" # Add more text
28
+ write.puts "." # Exit append mode
29
+ write.puts "w" # Write the file
30
+ write.puts "q" # Quit ed
31
+
32
+ # Wait for ed to exit
33
+ Process.wait(pid)
34
+
35
+ # Verify the file was written
36
+ content = File.read(temp_file.path)
37
+ assert_includes content, "Hello world"
38
+ assert_includes content, "This is a test"
39
+
40
+ ensure
41
+ Process.kill('TERM', pid) rescue nil
42
+ Process.wait(pid) rescue nil
43
+ end
44
+ end
45
+
46
+ temp_file.unlink
47
+ end
48
+
49
+ def test_002_ed_file_creation_pty
50
+ # Test that ed can create a new file using PTY
51
+ temp_file = Tempfile.new(['new', '.txt'])
52
+ file_path = temp_file.path # Save the path before closing
53
+ temp_file.close
54
+ temp_file.unlink # Remove the file so ed creates it
55
+
56
+ PTY.spawn("ed") do |read, write, pid|
57
+ begin
58
+ # Wait for ed to start
59
+ sleep 0.5
60
+
61
+ # Send ed commands
62
+ write.puts "a"
63
+ write.puts "New file content"
64
+ write.puts "Created by ed"
65
+ write.puts "."
66
+ write.puts "w #{file_path}"
67
+ write.puts "q"
68
+
69
+ # Wait for ed to exit
70
+ Process.wait(pid)
71
+
72
+ # Verify the file was created
73
+ assert File.exist?(file_path), "File should be created"
74
+
75
+ # Verify the file has content
76
+ content = File.read(file_path)
77
+ assert_includes content, "New file content"
78
+ assert_includes content, "Created by ed"
79
+
80
+ ensure
81
+ Process.kill('TERM', pid) rescue nil
82
+ Process.wait(pid) rescue nil
83
+ end
84
+ end
85
+
86
+ File.unlink(file_path) if File.exist?(file_path)
87
+ end
88
+
89
+ def test_003_ed_simulate_edit_file_usage_pty
90
+ # Test how edit_file would use ed with PTY
91
+ temp_file = Tempfile.new(['test', '.txt'])
92
+ temp_file.close
93
+
94
+ PTY.spawn("ed #{temp_file.path}") do |read, write, pid|
95
+ begin
96
+ # Wait for ed to start
97
+ sleep 0.5
98
+
99
+ # Send ed commands (simulating what edit_file would do)
100
+ write.puts "a"
101
+ write.puts "Content added by edit_file"
102
+ write.puts "This simulates user input"
103
+ write.puts "."
104
+ write.puts "w"
105
+ write.puts "q"
106
+
107
+ # Wait for ed to exit
108
+ Process.wait(pid)
109
+
110
+ # Verify the file was modified
111
+ content = File.read(temp_file.path)
112
+ assert_includes content, "Content added by edit_file"
113
+ assert_includes content, "This simulates user input"
114
+
115
+ ensure
116
+ Process.kill('TERM', pid) rescue nil
117
+ Process.wait(pid) rescue nil
118
+ end
119
+ end
120
+
121
+ temp_file.unlink
122
+ end
123
+
124
+ def test_004_ed_with_empty_file_pty
125
+ # Test ed behavior with an empty file using PTY
126
+ temp_file = Tempfile.new(['empty', '.txt'])
127
+ temp_file.close
128
+
129
+ PTY.spawn("ed #{temp_file.path}") do |read, write, pid|
130
+ begin
131
+ # Wait for ed to start
132
+ sleep 0.5
133
+
134
+ # Add content to empty file
135
+ write.puts "a"
136
+ write.puts "First line"
137
+ write.puts "Second line"
138
+ write.puts "."
139
+ write.puts "w"
140
+ write.puts "q"
141
+
142
+ # Wait for ed to exit
143
+ Process.wait(pid)
144
+
145
+ content = File.read(temp_file.path)
146
+ assert_includes content, "First line"
147
+ assert_includes content, "Second line"
148
+
149
+ ensure
150
+ Process.kill('TERM', pid) rescue nil
151
+ Process.wait(pid) rescue nil
152
+ end
153
+ end
154
+
155
+ temp_file.unlink
156
+ end
157
+
158
+ def test_005_ed_which_command
159
+ # Test that ed is available on the system
160
+ require 'open3'
161
+ stdout, stderr, status = Open3.capture3("which ed")
162
+
163
+ assert_equal 0, status.exitstatus, "ed should be available on the system"
164
+ assert_includes stdout, "ed", "which should find ed"
165
+ end
166
+
167
+ def test_006_ed_error_handling
168
+ # Test that ed handles errors gracefully
169
+ require 'open3'
170
+ stdout, stderr, status = Open3.capture3("ed /nonexistent/directory/file.txt", stdin_data: "q\n")
171
+
172
+ # ed should exit with an error
173
+ refute_equal 0, status.exitstatus, "ed should fail on non-existent directory"
174
+ end
175
+
176
+ def test_007_ed_stderr_behavior
177
+ # Test that ed puts error messages on stderr, not stdout
178
+ require 'open3'
179
+ temp_file = Tempfile.new(['stderr_test', '.txt'])
180
+ file_path = temp_file.path
181
+ temp_file.close
182
+ temp_file.unlink # Remove file so ed will give error
183
+
184
+ ed_commands = [
185
+ "a",
186
+ "Test content",
187
+ ".",
188
+ "w #{file_path}", # Specify the file path in the write command
189
+ "q"
190
+ ].join("\n")
191
+
192
+ stdout, stderr, status = Open3.capture3("ed", stdin_data: ed_commands)
193
+
194
+ # The error message should be on stderr (when we specify file on command line)
195
+ # But when we use 'w /path/to/file', there's no error
196
+ # stdout should contain the character count
197
+ refute_includes stdout, "No such file or directory", "Error should not be on stdout"
198
+
199
+ # The file should be created successfully
200
+ assert File.exist?(file_path), "File should be created"
201
+
202
+ File.unlink(file_path) if File.exist?(file_path)
203
+ end
204
+ end
@@ -0,0 +1,193 @@
1
+ # Automated equivalents to manual banner combination tests
2
+ #
3
+ # These tests verify sensible combinations of banner features,
4
+ # but in an automated way that's meaningful for me to run in Cursor.
5
+ #
6
+ # Instead of visual browser inspection, these tests:
7
+ # - Generate config files with the same syntax as manual combination tests
8
+ # - Create BannerSVG instances with the same parameters
9
+ # - Parse the generated HTML/JavaScript output
10
+ # - Verify that multiple features work together correctly
11
+ # - Check that gradients, colors, text positioning, etc. are all applied
12
+ #
13
+ # NOTE: These tests may not use the exact same config values as manual tests.
14
+ # Manual tests focus on realistic, complex combinations for visual inspection,
15
+ # while these automated tests focus on simple, isolated feature verification
16
+ # for reliable automated testing. Both test the same underlying features
17
+ # but with different complexity levels appropriate to their purpose.
18
+ #
19
+ # This allows me to verify that banner features work together properly
20
+ # while you use the manual tests for visual inspection in the browser.
21
+
22
+ require_relative 'lib/scriptorium/banner_svg'
23
+ require 'fileutils'
24
+
25
+ class CursorBannerCombinationTests
26
+ def initialize
27
+ @test_dir = "cursor-combination-test-temp"
28
+ setup_test_directory
29
+ end
30
+
31
+ def setup_test_directory
32
+ FileUtils.rm_rf(@test_dir) if Dir.exist?(@test_dir)
33
+ Dir.mkdir(@test_dir)
34
+ end
35
+
36
+ def cleanup
37
+ FileUtils.rm_rf(@test_dir) if Dir.exist?(@test_dir)
38
+ end
39
+
40
+ def run_all_tests
41
+ puts "Running automated banner combination tests..."
42
+ puts "=" * 50
43
+
44
+ test_count = 0
45
+ pass_count = 0
46
+
47
+ # Combination tests
48
+ test_count += 1
49
+ pass_count += 1 if test_red_gradient_bold_center
50
+ test_count += 1
51
+ pass_count += 1 if test_blue_gradient_large_italic
52
+ test_count += 1
53
+ pass_count += 1 if test_green_radial_small_right
54
+ test_count += 1
55
+ pass_count += 1 if test_yellow_gradient_blue_text
56
+ test_count += 1
57
+ pass_count += 1 if test_complex_combination
58
+
59
+ puts "=" * 50
60
+ puts "Results: #{pass_count}/#{test_count} tests passed"
61
+
62
+ cleanup
63
+ pass_count == test_count
64
+ end
65
+
66
+ def test_001_banner(config_content, title, subtitle, expectations)
67
+ # Write config file
68
+ File.write("#{@test_dir}/config.txt", config_content)
69
+
70
+ # Create banner and parse config
71
+ Dir.chdir(@test_dir) do
72
+ banner = BannerSVG.new(title, subtitle)
73
+ banner.parse_header_svg
74
+ js_output = banner.get_svg
75
+
76
+ # Check each expectation
77
+ expectations.each do |expectation|
78
+ case expectation[:type]
79
+ when :contains
80
+ unless js_output.include?(expectation[:value])
81
+ puts "❌ FAIL: Expected to contain '#{expectation[:value]}'"
82
+ return false
83
+ end
84
+ when :not_contains
85
+ if js_output.include?(expectation[:value])
86
+ puts "❌ FAIL: Expected NOT to contain '#{expectation[:value]}'"
87
+ return false
88
+ end
89
+ when :instance_variable
90
+ actual_value = banner.instance_variable_get(expectation[:variable])
91
+ expected_value = expectation[:value]
92
+ unless actual_value == expected_value
93
+ puts "❌ FAIL: #{expectation[:variable]} expected '#{expected_value}', got '#{actual_value}'"
94
+ return false
95
+ end
96
+ end
97
+ end
98
+
99
+ puts "✅ PASS: #{title}"
100
+ true
101
+ end
102
+ end
103
+
104
+ # Red gradient with bold text and center positioning
105
+ def test_002_red_gradient_bold_center
106
+ test_banner(
107
+ "back.linear red blue lr\ntitle.style bold\ntext.position center",
108
+ "Red Gradient Bold Center",
109
+ "Test",
110
+ [
111
+ { type: :contains, value: "linearGradient" },
112
+ { type: :contains, value: "stop-color:red" },
113
+ { type: :contains, value: "stop-color:blue" },
114
+ { type: :instance_variable, variable: :@title_weight, value: "bold" },
115
+ { type: :instance_variable, variable: :@text_anchor, value: "middle" }
116
+ ]
117
+ )
118
+ end
119
+
120
+ # Blue gradient with large text and italic style
121
+ def test_003_blue_gradient_large_italic
122
+ test_banner(
123
+ "back.linear blue green tb\ntitle.scale 1.2\ntitle.style italic",
124
+ "Blue Gradient Large Italic",
125
+ "Test",
126
+ [
127
+ { type: :contains, value: "linearGradient" },
128
+ { type: :contains, value: "stop-color:blue" },
129
+ { type: :contains, value: "stop-color:green" },
130
+ { type: :instance_variable, variable: :@title_scale, value: 1.2 },
131
+ { type: :instance_variable, variable: :@title_style, value: "italic" }
132
+ ]
133
+ )
134
+ end
135
+
136
+ # Green radial gradient with small text and right positioning
137
+ def test_004_green_radial_small_right
138
+ test_banner(
139
+ "back.radial green yellow\ntitle.scale 0.6\ntext.position right",
140
+ "Green Radial Small Right",
141
+ "Test",
142
+ [
143
+ { type: :contains, value: "radialGradient" },
144
+ { type: :contains, value: "stop-color:green" },
145
+ { type: :contains, value: "stop-color:yellow" },
146
+ { type: :instance_variable, variable: :@title_scale, value: 0.6 },
147
+ { type: :instance_variable, variable: :@text_anchor, value: "end" }
148
+ ]
149
+ )
150
+ end
151
+
152
+ # Yellow gradient with blue text
153
+ def test_005_yellow_gradient_blue_text
154
+ test_banner(
155
+ "back.linear yellow orange lr\ntext.color #0000ff",
156
+ "Yellow Gradient Blue Text",
157
+ "Test",
158
+ [
159
+ { type: :contains, value: "linearGradient" },
160
+ { type: :contains, value: "stop-color:yellow" },
161
+ { type: :contains, value: "stop-color:orange" },
162
+ { type: :instance_variable, variable: :@text_color, value: "#0000ff" }
163
+ ]
164
+ )
165
+ end
166
+
167
+ # Complex combination with multiple features
168
+ def test_006_complex_combination
169
+ test_banner(
170
+ "back.radial red blue\ntitle.scale 1.1\nsubtitle.scale 0.6\ntitle.style bold italic\ntext.color #00ff00\ntext.position center",
171
+ "Complex Combination",
172
+ "Test Subtitle",
173
+ [
174
+ { type: :contains, value: "radialGradient" },
175
+ { type: :contains, value: "stop-color:red" },
176
+ { type: :contains, value: "stop-color:blue" },
177
+ { type: :instance_variable, variable: :@title_scale, value: 1.1 },
178
+ { type: :instance_variable, variable: :@subtitle_scale, value: 0.6 },
179
+ { type: :instance_variable, variable: :@title_weight, value: "bold" },
180
+ { type: :instance_variable, variable: :@title_style, value: "italic" },
181
+ { type: :instance_variable, variable: :@text_color, value: "#00ff00" },
182
+ { type: :instance_variable, variable: :@text_anchor, value: "middle" }
183
+ ]
184
+ )
185
+ end
186
+ end
187
+
188
+ # Run the tests
189
+ if __FILE__ == $0
190
+ tests = CursorBannerCombinationTests.new
191
+ success = tests.run_all_tests
192
+ exit(success ? 0 : 1)
193
+ end