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,374 @@
1
+ # Automated equivalents to manual banner feature tests
2
+ #
3
+ # These tests verify the same scenarios as the manual inspection tests,
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 tests
8
+ # - Create BannerSVG instances with the same parameters
9
+ # - Parse the generated HTML/JavaScript output
10
+ # - Verify that the correct SVG attributes and values are present
11
+ # - Check that gradients, colors, text positioning, etc. are correct
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 the banner functionality programmatically
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 CursorBannerFeatureTests
26
+ def initialize
27
+ @test_dir = "cursor-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 feature tests..."
42
+ puts "=" * 50
43
+
44
+ test_count = 0
45
+ pass_count = 0
46
+
47
+ # Background color tests
48
+ test_count += 1
49
+ pass_count += 1 if test_red_background
50
+ test_count += 1
51
+ pass_count += 1 if test_blue_background
52
+ test_count += 1
53
+ pass_count += 1 if test_green_background
54
+
55
+ # Linear gradient tests
56
+ test_count += 1
57
+ pass_count += 1 if test_red_to_blue_gradient
58
+ test_count += 1
59
+ pass_count += 1 if test_green_to_yellow_gradient
60
+ test_count += 1
61
+ pass_count += 1 if test_top_to_bottom_gradient
62
+
63
+ # Radial gradient tests
64
+ test_count += 1
65
+ pass_count += 1 if test_radial_red_to_blue
66
+ test_count += 1
67
+ pass_count += 1 if test_radial_green_to_yellow
68
+
69
+ # Text size tests
70
+ test_count += 1
71
+ pass_count += 1 if test_small_text
72
+ test_count += 1
73
+ pass_count += 1 if test_large_text
74
+
75
+ # Text style tests
76
+ test_count += 1
77
+ pass_count += 1 if test_bold_text
78
+ test_count += 1
79
+ pass_count += 1 if test_italic_text
80
+ test_count += 1
81
+ pass_count += 1 if test_bold_italic_text
82
+
83
+ # Text color tests
84
+ test_count += 1
85
+ pass_count += 1 if test_blue_text
86
+ test_count += 1
87
+ pass_count += 1 if test_green_text
88
+
89
+ # Text positioning tests
90
+ test_count += 1
91
+ pass_count += 1 if test_left_position
92
+ test_count += 1
93
+ pass_count += 1 if test_center_position
94
+ test_count += 1
95
+ pass_count += 1 if test_right_position
96
+
97
+ puts "=" * 50
98
+ puts "Results: #{pass_count}/#{test_count} tests passed"
99
+
100
+ cleanup
101
+ pass_count == test_count
102
+ end
103
+
104
+ def test_001_banner(config_content, title, subtitle, expectations)
105
+ # Write config file
106
+ File.write("#{@test_dir}/config.txt", config_content)
107
+
108
+ # Create banner and parse config
109
+ Dir.chdir(@test_dir) do
110
+ banner = BannerSVG.new(title, subtitle)
111
+ banner.parse_header_svg
112
+ js_output = banner.get_svg
113
+
114
+ # Check each expectation
115
+ expectations.each do |expectation|
116
+ case expectation[:type]
117
+ when :contains
118
+ unless js_output.include?(expectation[:value])
119
+ puts "❌ FAIL: Expected to contain '#{expectation[:value]}'"
120
+ return false
121
+ end
122
+ when :not_contains
123
+ if js_output.include?(expectation[:value])
124
+ puts "❌ FAIL: Expected NOT to contain '#{expectation[:value]}'"
125
+ return false
126
+ end
127
+ when :instance_variable
128
+ actual_value = banner.instance_variable_get(expectation[:variable])
129
+ expected_value = expectation[:value]
130
+ unless actual_value == expected_value
131
+ puts "❌ FAIL: #{expectation[:variable]} expected '#{expected_value}', got '#{actual_value}'"
132
+ return false
133
+ end
134
+ end
135
+ end
136
+
137
+ puts "✅ PASS: #{title}"
138
+ true
139
+ end
140
+ end
141
+
142
+ # Background color tests
143
+ def test_002_red_background
144
+ test_banner(
145
+ "back.color #ff0000",
146
+ "Red Background",
147
+ "Test",
148
+ [
149
+ { type: :contains, value: "fill='#ff0000'" },
150
+ { type: :not_contains, value: "fill='#fff'" }
151
+ ]
152
+ )
153
+ end
154
+
155
+ def test_003_blue_background
156
+ test_banner(
157
+ "back.color #0000ff",
158
+ "Blue Background",
159
+ "Test",
160
+ [
161
+ { type: :contains, value: "fill='#0000ff'" },
162
+ { type: :not_contains, value: "fill='#fff'" }
163
+ ]
164
+ )
165
+ end
166
+
167
+ def test_004_green_background
168
+ test_banner(
169
+ "back.color #00ff00",
170
+ "Green Background",
171
+ "Test",
172
+ [
173
+ { type: :contains, value: "fill='#00ff00'" },
174
+ { type: :not_contains, value: "fill='#fff'" }
175
+ ]
176
+ )
177
+ end
178
+
179
+ # Linear gradient tests
180
+ def test_005_red_to_blue_gradient
181
+ test_banner(
182
+ "back.linear red blue lr",
183
+ "Red to Blue Gradient",
184
+ "Test",
185
+ [
186
+ { type: :contains, value: "linearGradient" },
187
+ { type: :contains, value: "stop-color:red" },
188
+ { type: :contains, value: "stop-color:blue" },
189
+ { type: :not_contains, value: "fill='#fff'" }
190
+ ]
191
+ )
192
+ end
193
+
194
+ def test_006_green_to_yellow_gradient
195
+ test_banner(
196
+ "back.linear green yellow lr",
197
+ "Green to Yellow Gradient",
198
+ "Test",
199
+ [
200
+ { type: :contains, value: "linearGradient" },
201
+ { type: :contains, value: "stop-color:green" },
202
+ { type: :contains, value: "stop-color:yellow" },
203
+ { type: :not_contains, value: "fill='#fff'" }
204
+ ]
205
+ )
206
+ end
207
+
208
+ def test_007_top_to_bottom_gradient
209
+ test_banner(
210
+ "back.linear blue red tb",
211
+ "Top to Bottom Gradient",
212
+ "Test",
213
+ [
214
+ { type: :contains, value: "linearGradient" },
215
+ { type: :contains, value: "stop-color:blue" },
216
+ { type: :contains, value: "stop-color:red" },
217
+ { type: :contains, value: "x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"" }
218
+ ]
219
+ )
220
+ end
221
+
222
+ # Radial gradient tests
223
+ def test_008_radial_red_to_blue
224
+ test_banner(
225
+ "back.radial red blue",
226
+ "Radial Red to Blue",
227
+ "Test",
228
+ [
229
+ { type: :contains, value: "radialGradient" },
230
+ { type: :contains, value: "stop-color:red" },
231
+ { type: :contains, value: "stop-color:blue" },
232
+ { type: :not_contains, value: "fill='#fff'" }
233
+ ]
234
+ )
235
+ end
236
+
237
+ def test_009_radial_green_to_yellow
238
+ test_banner(
239
+ "back.radial green yellow",
240
+ "Radial Green to Yellow",
241
+ "Test",
242
+ [
243
+ { type: :contains, value: "radialGradient" },
244
+ { type: :contains, value: "stop-color:green" },
245
+ { type: :contains, value: "stop-color:yellow" },
246
+ { type: :not_contains, value: "fill='#fff'" }
247
+ ]
248
+ )
249
+ end
250
+
251
+ # Text size tests
252
+ def test_010_small_text
253
+ test_banner(
254
+ "title.scale 0.5\nsubtitle.scale 0.3",
255
+ "Small Text",
256
+ "Test",
257
+ [
258
+ { type: :instance_variable, variable: :@title_scale, value: 0.5 },
259
+ { type: :instance_variable, variable: :@subtitle_scale, value: 0.3 }
260
+ ]
261
+ )
262
+ end
263
+
264
+ def test_011_large_text
265
+ test_banner(
266
+ "title.scale 1.2\nsubtitle.scale 0.8",
267
+ "Large Text",
268
+ "Test",
269
+ [
270
+ { type: :instance_variable, variable: :@title_scale, value: 1.2 },
271
+ { type: :instance_variable, variable: :@subtitle_scale, value: 0.8 }
272
+ ]
273
+ )
274
+ end
275
+
276
+ # Text style tests
277
+ def test_012_bold_text
278
+ test_banner(
279
+ "title.style bold",
280
+ "Bold Text",
281
+ "Test",
282
+ [
283
+ { type: :instance_variable, variable: :@title_weight, value: "bold" }
284
+ ]
285
+ )
286
+ end
287
+
288
+ def test_013_italic_text
289
+ test_banner(
290
+ "title.style italic",
291
+ "Italic Text",
292
+ "Test",
293
+ [
294
+ { type: :instance_variable, variable: :@title_style, value: "italic" }
295
+ ]
296
+ )
297
+ end
298
+
299
+ def test_014_bold_italic_text
300
+ test_banner(
301
+ "title.style bold italic",
302
+ "Bold Italic Text",
303
+ "Test",
304
+ [
305
+ { type: :instance_variable, variable: :@title_weight, value: "bold" },
306
+ { type: :instance_variable, variable: :@title_style, value: "italic" }
307
+ ]
308
+ )
309
+ end
310
+
311
+ # Text color tests
312
+ def test_015_blue_text
313
+ test_banner(
314
+ "text.color #0000ff",
315
+ "Blue Text",
316
+ "Test",
317
+ [
318
+ { type: :instance_variable, variable: :@text_color, value: "#0000ff" }
319
+ ]
320
+ )
321
+ end
322
+
323
+ def test_016_green_text
324
+ test_banner(
325
+ "text.color #00ff00",
326
+ "Green Text",
327
+ "Test",
328
+ [
329
+ { type: :instance_variable, variable: :@text_color, value: "#00ff00" }
330
+ ]
331
+ )
332
+ end
333
+
334
+ # Text positioning tests
335
+ def test_017_left_position
336
+ test_banner(
337
+ "text.position left",
338
+ "Left Position",
339
+ "Test",
340
+ [
341
+ { type: :instance_variable, variable: :@text_anchor, value: "start" }
342
+ ]
343
+ )
344
+ end
345
+
346
+ def test_018_center_position
347
+ test_banner(
348
+ "text.position center",
349
+ "Center Position",
350
+ "Test",
351
+ [
352
+ { type: :instance_variable, variable: :@text_anchor, value: "middle" }
353
+ ]
354
+ )
355
+ end
356
+
357
+ def test_019_right_position
358
+ test_banner(
359
+ "text.position right",
360
+ "Right Position",
361
+ "Test",
362
+ [
363
+ { type: :instance_variable, variable: :@text_anchor, value: "end" }
364
+ ]
365
+ )
366
+ end
367
+ end
368
+
369
+ # Run the tests
370
+ if __FILE__ == $0
371
+ tests = CursorBannerFeatureTests.new
372
+ success = tests.run_all_tests
373
+ exit(success ? 0 : 1)
374
+ end
@@ -0,0 +1,326 @@
1
+ # test/integration/integration_test.rb
2
+
3
+ require "minitest/autorun"
4
+ require "fileutils"
5
+ require_relative '../../lib/scriptorium'
6
+ require_relative "../test_helpers"
7
+
8
+ class IntegrationTest < Minitest::Test
9
+ include Scriptorium::Exceptions
10
+ include Scriptorium::Helpers
11
+ include TestHelpers
12
+
13
+ def setup
14
+ @tmpdir = Dir.mktmpdir
15
+ @old_dir = Dir.pwd
16
+ Dir.chdir(@tmpdir)
17
+ Dir.mkdir("test")
18
+ @repo = create_test_repo
19
+ @sample_view = @repo.lookup_view("sample")
20
+ end
21
+
22
+ def teardown
23
+ Dir.chdir(@old_dir)
24
+ FileUtils.remove_entry(@tmpdir)
25
+ end
26
+
27
+ def test_001_sample_view_generates_header_html
28
+ header_txt = @sample_view.dir/:config/"header.txt"
29
+ File.write(header_txt, "title")
30
+ sections = @sample_view.read_layout
31
+ @sample_view.build_header(sections)
32
+
33
+ output = @sample_view.dir/:output/:panes/"header.html"
34
+ assert File.exist?(output), "Expected header.html to exist"
35
+ content = File.read(output)
36
+ assert_includes content, "<h1>", "Expected header content to include <h1>"
37
+ end
38
+
39
+ def test_002_posts_generated_and_indexed_across_multiple_views
40
+ srand(42) # for random posts
41
+
42
+ create_3_views
43
+ n0 = @repo.all_posts.size
44
+
45
+ create_13_posts
46
+ n1 = @repo.all_posts.size
47
+ assert n1 == n0 + 13, "Expected 13 posts, found #{n1 - n0}"
48
+
49
+ alter_pubdates
50
+
51
+ num_posts_per_view(@repo, "blog1", 7)
52
+ num_posts_per_view(@repo, "blog2", 6)
53
+ num_posts_per_view(@repo, "blog3", 5)
54
+
55
+ try_blog1_index
56
+ end
57
+
58
+ def test_003_integration_index_contains_titles_and_dates
59
+ create_3_views
60
+ create_13_posts
61
+ alter_pubdates
62
+ @repo.generate_front_page("blog1")
63
+ posts_content = File.read(@repo.root/:views/"blog1"/:output/"index.html")
64
+ posts = @repo.all_posts("blog1")
65
+ posts.each do |post|
66
+ assert_includes posts_content, post.title
67
+ month, day, year = post.pubdate_month_day_year
68
+ assert_includes posts_content, month + " " + day
69
+ assert_includes posts_content, year
70
+ end
71
+ end
72
+
73
+ def try_blog1_index_with_post_checks # Helper
74
+ @repo.generate_front_page("blog1")
75
+
76
+ # 1. Verify index.html exists and has expected heading
77
+ index = @repo.root/:views/"blog1"/:output/"index.html"
78
+ assert File.exist?(index), "Expected index.html to exist"
79
+ html = File.read(index)
80
+ assert html.include?("<title>Blog 1</title>"), "Expected <title>Blog 1</title> in index"
81
+
82
+ # 2. Verify container files exist and are non-empty
83
+ %w[header main right footer].each do |section|
84
+ path = @repo.root/:views/"blog1"/:output/:panes/"#{section}.html"
85
+ assert File.exist?(path), "Expected #{section}.html to exist"
86
+ refute_empty File.read(path).strip, "Expected #{section}.html not to be blank"
87
+ end
88
+
89
+ # 3. Check that post_index.html includes correct post metadata
90
+ @repo.generate_post_index("blog1")
91
+ post_index = @repo.root/:views/"blog1"/:output/"post_index.html"
92
+ assert File.exist?(post_index), "Expected post_index.html to be generated"
93
+ content = File.read(post_index)
94
+
95
+ @repo.all_posts("blog1").each do |post|
96
+ next unless post.pubdate && post.title
97
+ month, day, year = post.pubdate_month_day_year
98
+ assert_includes content, month + " " + day + "</div>", "Expected pubdate #{month} #{day} to appear"
99
+ assert_includes content, year + "</div>", "Expected year #{year} to appear"
100
+ assert_includes content, post.title, "Expected title #{post.title} to appear"
101
+ end
102
+ end
103
+
104
+ def test_002_posts_generated_and_indexed_across_multiple_views
105
+ srand(42)
106
+ create_3_views
107
+ create_13_posts
108
+ alter_pubdates
109
+
110
+ num_posts_per_view(@repo, "blog1", 7)
111
+ num_posts_per_view(@repo, "blog2", 6)
112
+ num_posts_per_view(@repo, "blog3", 5)
113
+
114
+ try_blog1_index_with_post_checks
115
+ end
116
+
117
+ def test_005_generate_front_page_outputs_index_html
118
+ # Setup test view and layout
119
+ view = @repo.create_view("landing", "Landing Page", "Unit test front page")
120
+
121
+ layout_txt = <<~LAYOUT
122
+ header
123
+ footer
124
+ main
125
+ LAYOUT
126
+
127
+ layout_path = view.dir/:config/"layout.txt"
128
+ File.write(layout_path, layout_txt)
129
+
130
+ # Call the method under test
131
+ @repo.generate_front_page("landing")
132
+
133
+ # Assertions
134
+ index_file = view.dir/:output/"index.html"
135
+ assert File.exist?(index_file), "Expected index.html to be generated"
136
+
137
+ content = File.read(index_file)
138
+ targets = ["<!-- Section: header (output) -->",
139
+ "<!-- Section: main (output) -->",
140
+ "<!-- Section: footer (output) -->"]
141
+ assert_present(content, *targets)
142
+ assert_ordered(content, *targets)
143
+ end
144
+
145
+ def test_006_all_containers_are_present
146
+ # layout_file = @sample_view.dir/:config/"layout.txt"
147
+ layout = @sample_view.read_layout.keys
148
+ layout.each do |container|
149
+ output_file = @sample_view.dir/:output/:panes/"#{container}.html"
150
+ assert File.exist?(output_file), "Expected output file #{output_file} for container #{container}"
151
+ end
152
+ end
153
+
154
+ def test_007_missing_container_in_layout
155
+ layout_file = @sample_view.dir/:config/"layout.txt"
156
+ layout = read_commented_file(layout_file)
157
+
158
+ layout.each do |container|
159
+ output_file = @sample_view.dir/:output/:panes/"#{container}.html"
160
+ if File.exist?(output_file)
161
+ content = File.read(output_file)
162
+ refute content.include?("<!-- Missing #{container}.html -->"), "Missing #{container}.html should not appear"
163
+ end
164
+ end
165
+ end
166
+
167
+ def test_008_front_page_handles_missing_containers
168
+ @repo.generate_front_page("sample")
169
+ index_file = @repo.root/:views/"sample"/:output/"index.html"
170
+ assert File.exist?(index_file), "Expected index.html to be generated"
171
+
172
+ layout = @sample_view.read_layout.keys
173
+ layout.each do |container|
174
+ content = File.read(index_file)
175
+ if !File.exist?(@repo.root/:views/"sample"/:output/:panes/"#{container}.html")
176
+ assert_includes content, "<!-- Missing #{container}.html -->", "Expected placeholder for missing #{container}.html"
177
+ else
178
+ refute_includes content, "<!-- Missing #{container}.html -->", "Expected no placeholder for #{container}.html"
179
+ end
180
+ end
181
+ end
182
+
183
+ def test_009_create_view_and_generate_front_page_with_placeholders
184
+ view = @repo.create_view("test_view", "Test View", "A test view")
185
+
186
+ layout_txt = <<~LAYOUT
187
+ header
188
+ main
189
+ right
190
+ footer
191
+ LAYOUT
192
+ File.write(view.dir/:config/"layout.txt", layout_txt)
193
+
194
+ # Create text placeholders in the corresponding sections
195
+ # Skip main as a special case
196
+ header_txt = <<~HEADER
197
+ text "HEADER CONTENT PLACEHOLDER"
198
+ HEADER
199
+ File.write(view.dir/:config/"header.txt", header_txt)
200
+
201
+ right_txt = <<~RIGHT
202
+ text "RIGHT CONTENT PLACEHOLDER"
203
+ RIGHT
204
+ File.write(view.dir/:config/"right.txt", right_txt)
205
+
206
+ footer_txt = <<~FOOTER
207
+ text "FOOTER CONTENT PLACEHOLDER"
208
+ FOOTER
209
+ File.write(view.dir/:config/"footer.txt", footer_txt)
210
+
211
+ # Generate the front page
212
+ @repo.generate_front_page("test_view")
213
+
214
+ # Check that each section contains its placeholder replacement
215
+
216
+ # Verify header section
217
+ header_html = File.read(view.dir/:output/:panes/"header.html")
218
+ assert_includes header_html, "HEADER CONTENT PLACEHOLDER", "Expected header content to include placeholder replacement"
219
+
220
+ # Verify right section
221
+ right_html = File.read(view.dir/:output/:panes/"right.html")
222
+ assert_includes right_html, "RIGHT CONTENT PLACEHOLDER", "Expected right content to include placeholder replacement"
223
+
224
+ # Verify footer section
225
+ footer_html = File.read(view.dir/:output/:panes/"footer.html")
226
+ assert_includes footer_html, "FOOTER CONTENT PLACEHOLDER", "Expected footer content to include placeholder replacement"
227
+
228
+ # Verify that generated front page (index.html) contains all expected content
229
+ targets = ["HEADER CONTENT PLACEHOLDER",
230
+ "RIGHT CONTENT PLACEHOLDER",
231
+ "FOOTER CONTENT PLACEHOLDER"]
232
+ index_html = File.read(view.dir/:output/"index.html")
233
+ assert_present(index_html, *targets)
234
+ assert_ordered(index_html, *targets)
235
+ end
236
+
237
+ def test_010_content_of_nonempty_main_section
238
+ create_3_views
239
+ create_13_posts
240
+ alter_pubdates
241
+ @repo.generate_front_page("blog1")
242
+ index_file = @repo.root/:views/"blog1"/:output/"index.html"
243
+ index_html = File.read(index_file)
244
+ targets = ["<!-- Section: header (output) -->",
245
+ "<!-- Section: left (output) -->",
246
+ "<!-- Section: main (output) -->",
247
+ "<!-- Section: right (output) -->",
248
+ "<!-- Section: footer (output) -->"]
249
+ assert_present(index_html, *targets)
250
+ assert_ordered(index_html, *targets)
251
+ FileUtils.cp(index_file, "/tmp/testcms1.html")
252
+ post_div = %[class="index-entry"]
253
+ num_posts = index_html.scan(/#{Regexp.escape(post_div)}/).length
254
+ assert_equal 7, num_posts, "Expected 7 posts, found #{num_posts}"
255
+ end
256
+
257
+ def test_011_content_of_empty_main_section
258
+ create_3_views
259
+ @repo.generate_front_page("blog1")
260
+ index_file = @repo.root/:views/"blog1"/:output/"index.html"
261
+ index_html = File.read(index_file)
262
+ targets = ["<!-- Section: header (output) -->",
263
+ "<!-- Section: left (output) -->",
264
+ "<!-- Section: main (output) -->",
265
+ "<!-- Section: right (output) -->",
266
+ "<!-- Section: footer (output) -->"]
267
+ assert_present(index_html, *targets)
268
+ assert_ordered(index_html, *targets)
269
+ FileUtils.cp(index_file, "/tmp/testcms2.html")
270
+ post_div = %[<div class="index-entry">]
271
+ num_posts = index_html.scan(/#{Regexp.escape(post_div)}/).length
272
+ assert_equal 0, num_posts, "Expected 0 posts, found #{num_posts}"
273
+ assert_includes index_html, "No posts yet!", "Expected 'no posts yet' message"
274
+ end
275
+
276
+ def test_012_build_banner_with_image_found
277
+ testdir = File.expand_path("../../test", __dir__)
278
+ FileUtils.cp("#{testdir}/assets/testbanner.jpg", @sample_view.dir/:assets/"testbanner.jpg")
279
+ str = @sample_view.build_banner("testbanner.jpg")
280
+ expected = %[<img src='assets/testbanner.jpg' alt='Banner Image' style='width: 100%; height: auto;']
281
+ assert_includes str, expected
282
+ end
283
+
284
+ def test_013_build_banner_with_image_missing
285
+ str = @sample_view.build_banner("nosuchbanner.jpg")
286
+ expected = %[<p>Banner image missing: nosuchbanner.jpg</p>]
287
+ assert_includes str, expected
288
+ end
289
+
290
+ def test_014_generate_full_front_page
291
+ view = @repo.lookup_view("sample")
292
+ testdir = File.expand_path("../../test", __dir__)
293
+ FileUtils.cp("#{testdir}/assets/testbanner.jpg", @sample_view.dir/:assets/"testbanner.jpg")
294
+
295
+ # Set up the view with basic elements in config/header.txt and layout.txt
296
+ File.write(view.dir/:config/"header.txt", <<~EOS)
297
+ title
298
+ subtitle
299
+ nav topmenu.txt
300
+ banner testbanner.jpg
301
+ EOS
302
+
303
+ File.write(view.dir/:config/"layout.txt", <<~EOS)
304
+ header
305
+ left
306
+ main
307
+ right
308
+ footer
309
+ EOS
310
+
311
+ # Ensure the front page is generated
312
+ view.generate_front_page
313
+
314
+ # Path to the generated index.html
315
+ index_file = view.dir/:output/"index.html"
316
+
317
+ # Check that the file was created
318
+ assert File.exist?(index_file), "Expected index.html to exist"
319
+
320
+ # Optionally, you can check for some expected content
321
+ content = File.read(index_file)
322
+ assert_includes content, "<h1>", "Expected <h1> in header"
323
+ assert_includes content, "No posts yet!", "Expected 'No posts yet!' in main section"
324
+ end
325
+
326
+ end