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,669 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitest/autorun'
4
+ require_relative '../../lib/scriptorium'
5
+ require_relative '../test_helpers'
6
+
7
+ # Test widget classes defined at module level
8
+ class TestWidget < Scriptorium::Widget
9
+ def generate
10
+ "generated content"
11
+ end
12
+
13
+ def load_config
14
+ { test: "config" }
15
+ end
16
+ end
17
+
18
+ class TestWidget2 < Scriptorium::Widget
19
+ def generate
20
+ "generated content"
21
+ end
22
+
23
+ def load_config
24
+ { test: "config" }
25
+ end
26
+ end
27
+
28
+ class TestWidget3 < Scriptorium::Widget
29
+ def generate; "generated"; end
30
+ def load_config; {}; end
31
+ end
32
+
33
+ class TestWidget4 < Scriptorium::Widget
34
+ def generate; "generated"; end
35
+ def load_config; {}; end
36
+ end
37
+
38
+ class TestWidget5 < Scriptorium::Widget
39
+ def generate; "generated"; end
40
+ def load_config; {}; end
41
+ end
42
+
43
+ class TestWidget6 < Scriptorium::Widget
44
+ def generate; "generated"; end
45
+ def load_config; {}; end
46
+ end
47
+
48
+ class TestListWidget < Scriptorium::Widget::ListWidget
49
+ def generate
50
+ "generated"
51
+ end
52
+
53
+ def load_config
54
+ {}
55
+ end
56
+ end
57
+
58
+ class TestListWidget2 < Scriptorium::Widget::ListWidget
59
+ def generate
60
+ "generated"
61
+ end
62
+
63
+ def load_config
64
+ {}
65
+ end
66
+ end
67
+
68
+ class TestListWidget3 < Scriptorium::Widget::ListWidget
69
+ def generate
70
+ "generated"
71
+ end
72
+
73
+ def load_config
74
+ {}
75
+ end
76
+ end
77
+
78
+ class TestScriptoriumWidgets < Minitest::Test
79
+ include TestHelpers
80
+
81
+ def setup
82
+ @test_dir = "test/widget_test_files"
83
+ make_dir(@test_dir)
84
+ @repo = Scriptorium::Repo.create("test/scriptorium-TEST-widgets", testmode: true)
85
+ @view = @repo.create_view("test_view", "Test View", "Test subtitle")
86
+ end
87
+
88
+ def teardown
89
+ FileUtils.rm_rf(@test_dir) if Dir.exist?(@test_dir)
90
+ # Clean up the test repository directory
91
+ FileUtils.rm_rf("test/scriptorium-TEST") if Dir.exist?("test/scriptorium-TEST")
92
+ Scriptorium::Repo.destroy if Scriptorium::Repo.testing
93
+ end
94
+
95
+ # ========================================
96
+ # Base Widget Class Tests
97
+ # ========================================
98
+
99
+ def test_001_base_widget_initialization
100
+ widget = TestWidget.new(@repo, @view)
101
+
102
+ assert_equal @repo, widget.repo
103
+ assert_equal @view, widget.view
104
+ assert_equal({ test: "config" }, widget.config)
105
+ assert_equal "testwidget", widget.name
106
+ assert_equal "#{@view.dir}/widgets/testwidget", widget.path
107
+ end
108
+
109
+ def test_002_base_widget_abstract_methods
110
+ widget = TestWidget2.new(@repo, @view)
111
+
112
+ # Test that we can call the abstract methods on concrete implementation
113
+ assert_equal "generated content", widget.generate
114
+ assert_equal({ test: "config" }, widget.load_config)
115
+ end
116
+
117
+ def test_003_html_body_without_css
118
+ widget = TestWidget3.new(@repo, @view)
119
+ result = widget.html_body { "test content" }
120
+
121
+ assert_match(/<html>/, result)
122
+ assert_match(/<body>test content<\/body>/, result)
123
+ refute_match(/<head>/, result)
124
+ end
125
+
126
+ def test_004_html_body_with_css
127
+ widget = TestWidget4.new(@repo, @view)
128
+ result = widget.html_body("body { color: red; }") { "test content" }
129
+
130
+ assert_match(/<html>/, result)
131
+ assert_match(/<head><style>body \{ color: red; \}<\/style><\/head>/, result)
132
+ assert_match(/<body>test content<\/body>/, result)
133
+ end
134
+
135
+ def test_005_html_card
136
+ widget = TestWidget5.new(@repo, @view)
137
+ result = widget.html_card("Test Title", "test-tag", "test content")
138
+
139
+ assert_match(/<div class="card mb-3">/, result)
140
+ assert_match(/<h5 class="card-title">/, result)
141
+ assert_match(/<button.*data-bs-toggle="collapse".*data-bs-target="#test-tag">/, result)
142
+ assert_match(/<a href="javascript:void\(0\)".*onclick="javascript:load_main\('test-tag-main\.html'\)"/, result)
143
+ assert_match(/Test Title/, result)
144
+ assert_match(/<div class="collapse" id="test-tag">/, result)
145
+ assert_match(/test content/, result)
146
+ end
147
+
148
+ def test_006_html_container
149
+ widget = TestWidget6.new(@repo, @view)
150
+ result = widget.html_container("test content")
151
+
152
+ assert_match(/<div class="widget-container">/, result)
153
+ assert_match(/test content/, result)
154
+ end
155
+
156
+ # ========================================
157
+ # ListWidget Tests
158
+ # ========================================
159
+
160
+ def test_007_list_widget_initialization
161
+ # Create test list.txt file
162
+ list_file = "#{@view.dir}/widgets/testlistwidget/list.txt"
163
+ make_dir(File.dirname(list_file))
164
+ write_file!(list_file, "https://example.com, Example Site",
165
+ "https://test.com, Test Site")
166
+
167
+ widget = TestListWidget.new(@repo, @view)
168
+
169
+ assert_equal @repo, widget.repo
170
+ assert_equal @view, widget.view
171
+ assert_equal "testlistwidget", widget.name
172
+ assert_equal "#{@view.dir}/widgets/testlistwidget", widget.path
173
+ assert_equal list_file, widget.instance_variable_get(:@list)
174
+ assert_equal [
175
+ "https://example.com, Example Site",
176
+ "https://test.com, Test Site"
177
+ ], widget.instance_variable_get(:@data)
178
+ end
179
+
180
+ def test_008_list_widget_load_data_missing_file
181
+ # Should raise error when list.txt doesn't exist
182
+ assert_raises(CannotReadFileNotFound) do
183
+ TestListWidget2.new(@repo, @view)
184
+ end
185
+ end
186
+
187
+ def test_009_list_widget_load_data_empty_file
188
+ # Create test list.txt file
189
+ list_file = "#{@view.dir}/widgets/testlistwidget3/list.txt"
190
+ make_dir(File.dirname(list_file))
191
+ write_file(list_file, "")
192
+
193
+ widget = TestListWidget3.new(@repo, @view)
194
+ assert_equal [""], widget.instance_variable_get(:@data)
195
+ end
196
+
197
+ # ========================================
198
+ # Links Widget Tests
199
+ # ========================================
200
+
201
+ def test_010_links_widget_initialization
202
+ # Create test list.txt file
203
+ list_file = "#{@view.dir}/widgets/links/list.txt"
204
+ make_dir(File.dirname(list_file))
205
+ write_file!(list_file, "https://example.com, Example Site",
206
+ "https://test.com, Test Site")
207
+
208
+ widget = Scriptorium::Widget::Links.new(@repo, @view)
209
+
210
+ assert_equal @repo, widget.repo
211
+ assert_equal @view, widget.view
212
+ assert_equal "links", widget.name
213
+ assert_equal "#{@view.dir}/widgets/links", widget.path
214
+ assert_equal [
215
+ "https://example.com, Example Site",
216
+ "https://test.com, Test Site"
217
+ ], widget.instance_variable_get(:@lines)
218
+ end
219
+
220
+ def test_011_links_widget_get_list
221
+ # Create test list.txt file
222
+ list_file = "#{@view.dir}/widgets/links/list.txt"
223
+ make_dir(File.dirname(list_file))
224
+ write_file!(list_file, "https://example.com, Example Site",
225
+ "https://test.com, Test Site",
226
+ "https://space.com , Space News")
227
+
228
+ widget = Scriptorium::Widget::Links.new(@repo, @view)
229
+ result = widget.get_list
230
+
231
+ assert_equal [
232
+ ["https://example.com", "Example Site"],
233
+ ["https://test.com", "Test Site"],
234
+ ["https://space.com ", "Space News"]
235
+ ], result
236
+ end
237
+
238
+ def test_012_links_widget_link_item
239
+ # Create test list.txt file for initialization
240
+ list_file = "#{@view.dir}/widgets/links/list.txt"
241
+ make_dir(File.dirname(list_file))
242
+ write_file(list_file, "https://example.com, Example Site")
243
+
244
+ widget = Scriptorium::Widget::Links.new(@repo, @view)
245
+ result = widget.link_item("https://example.com", "Example Site")
246
+
247
+ assert_match(/<li class="list-group-item">/, result)
248
+ assert_match(/<a href="https:\/\/example\.com" target="_blank" style="text-decoration: none;">Example Site<\/a>/, result)
249
+ end
250
+
251
+ def test_013_links_widget_write_card
252
+ # Create test list.txt file
253
+ list_file = "#{@view.dir}/widgets/links/list.txt"
254
+ make_dir(File.dirname(list_file))
255
+ write_file!(list_file, "https://example.com, Example Site",
256
+ "https://test.com, Test Site")
257
+
258
+ widget = Scriptorium::Widget::Links.new(@repo, @view)
259
+ widget.write_card
260
+
261
+ # Check that card file was created
262
+ card_file = "#{@view.dir}/widgets/links/links-card.html"
263
+ assert_file_exist?(card_file)
264
+
265
+ # Check card content
266
+ content = read_file(card_file)
267
+ assert_match(/<div class="card mb-3">/, content)
268
+ assert_match(/<h5 class="card-title">/, content)
269
+ assert_match(/External links/, content)
270
+ assert_match(/<a href="https:\/\/example\.com" target="_blank"/, content)
271
+ assert_match(/Example Site/, content)
272
+ assert_match(/<a href="https:\/\/test\.com" target="_blank"/, content)
273
+ assert_match(/Test Site/, content)
274
+ end
275
+
276
+ def test_014_links_widget_generate
277
+ # Create test list.txt file
278
+ list_file = "#{@view.dir}/widgets/links/list.txt"
279
+ make_dir(File.dirname(list_file))
280
+ write_file(list_file, "https://example.com, Example Site")
281
+
282
+ widget = Scriptorium::Widget::Links.new(@repo, @view)
283
+ widget.generate
284
+
285
+ # Check that card file was created
286
+ card_file = "#{@view.dir}/widgets/links/links-card.html"
287
+ assert_file_exist?(card_file)
288
+
289
+ # Check card content
290
+ content = read_file(card_file)
291
+ assert_match(/External links/, content)
292
+ assert_match(/Example Site/, content)
293
+ end
294
+
295
+ def test_015_links_widget_card_method
296
+ # Create test list.txt file
297
+ list_file = "#{@view.dir}/widgets/links/list.txt"
298
+ make_dir(File.dirname(list_file))
299
+ write_file(list_file, "https://example.com, Example Site")
300
+
301
+ widget = Scriptorium::Widget::Links.new(@repo, @view)
302
+ widget.generate
303
+
304
+ # Test card method returns file content
305
+ card_content = widget.card
306
+ assert_match(/External links/, card_content)
307
+ assert_match(/Example Site/, card_content)
308
+ end
309
+
310
+ def test_016_links_widget_missing_list_file
311
+ # Should raise error when list.txt doesn't exist
312
+ assert_raises(CannotReadFileNotFound) do
313
+ Scriptorium::Widget::Links.new(@repo, @view)
314
+ end
315
+ end
316
+
317
+ # ========================================
318
+ # Widget Integration Tests
319
+ # ========================================
320
+
321
+ def test_017_build_widgets_with_links
322
+ # Create test list.txt file
323
+ list_file = "#{@view.dir}/widgets/links/list.txt"
324
+ make_dir(File.dirname(list_file))
325
+ write_file(list_file, "https://example.com, Example Site")
326
+
327
+ # Build widgets through view
328
+ result = @view.build_widgets("links")
329
+
330
+ # Check that HTML was generated
331
+ assert_match(/<div class="card mb-3">/, result)
332
+ assert_match(/External links/, result)
333
+ assert_match(/Example Site/, result)
334
+
335
+ # Check that card file was created
336
+ card_file = "#{@view.dir}/widgets/links/links-card.html"
337
+ assert_file_exist?(card_file)
338
+ end
339
+
340
+ def test_018_build_widgets_multiple_widgets
341
+ # Create test list.txt file for links widget
342
+ list_file = "#{@view.dir}/widgets/links/list.txt"
343
+ make_dir(File.dirname(list_file))
344
+ write_file(list_file, "https://example.com, Example Site")
345
+
346
+ # Build multiple widgets (just links for now, but tests the multiple widget handling)
347
+ result = @view.build_widgets("links links")
348
+
349
+ # Should contain widget HTML twice
350
+ assert_match(/External links/, result)
351
+ assert_match(/Example Site/, result)
352
+
353
+ # Count occurrences to verify it appears twice
354
+ assert_equal 2, result.scan(/External links/).length
355
+ end
356
+
357
+ def test_019_build_widgets_invalid_widget_name
358
+ assert_raises(NameError) do
359
+ @view.build_widgets("nonexistent")
360
+ end
361
+ end
362
+
363
+ def test_020_build_widgets_with_special_characters
364
+ assert_raises(CannotBuildWidgetNameInvalid) do
365
+ @view.build_widgets("invalid@widget")
366
+ end
367
+ end
368
+
369
+ # Pages widget tests
370
+ def test_021_pages_widget_initialization
371
+ # Create test list.txt file
372
+ list_file = "#{@view.dir}/widgets/pages/list.txt"
373
+ make_dir(File.dirname(list_file))
374
+ write_file!(list_file, "about", "contact", "faq")
375
+
376
+ # Create test HTML files
377
+ pages_dir = "#{@view.dir}/pages"
378
+ make_dir(pages_dir)
379
+ write_file("#{pages_dir}/about.html", "<html><title>About Us</title><body><h1>About Us</h1></body></html>")
380
+ write_file("#{pages_dir}/contact.html", "<html><title>Contact Information</title><body><h1>Contact</h1></body></html>")
381
+ write_file("#{pages_dir}/faq.html", "<html><title>FAQ</title><body><h1>FAQ</h1></body></html>")
382
+
383
+ widget = Scriptorium::Widget::Pages.new(@repo, @view)
384
+
385
+ assert_equal @repo, widget.repo
386
+ assert_equal @view, widget.view
387
+ assert_equal "pages", widget.name
388
+ assert_equal ["about", "contact", "faq"], widget.data
389
+ end
390
+
391
+ def test_022_pages_widget_extract_title_from_title_tag
392
+ # Create minimal list.txt to avoid initialization error
393
+ list_file = "#{@view.dir}/widgets/pages/list.txt"
394
+ make_dir(File.dirname(list_file))
395
+ write_file(list_file, "test")
396
+
397
+ widget = Scriptorium::Widget::Pages.new(@repo, @view)
398
+
399
+ html = "<html><head><title>My Page Title</title></head><body>Content</body></html>"
400
+ title = widget.send(:extract_title_from_html, html)
401
+
402
+ assert_equal "My Page Title", title
403
+ end
404
+
405
+ def test_023_pages_widget_extract_title_from_h1_tag
406
+ # Create minimal list.txt to avoid initialization error
407
+ list_file = "#{@view.dir}/widgets/pages/list.txt"
408
+ make_dir(File.dirname(list_file))
409
+ write_file(list_file, "test")
410
+
411
+ widget = Scriptorium::Widget::Pages.new(@repo, @view)
412
+
413
+ html = "<html><body><h1>My H1 Title</h1><p>Content</p></body></html>"
414
+ title = widget.send(:extract_title_from_html, html)
415
+
416
+ assert_equal "My H1 Title", title
417
+ end
418
+
419
+ def test_024_pages_widget_extract_title_fallback
420
+ # Create minimal list.txt to avoid initialization error
421
+ list_file = "#{@view.dir}/widgets/pages/list.txt"
422
+ make_dir(File.dirname(list_file))
423
+ write_file(list_file, "test")
424
+
425
+ widget = Scriptorium::Widget::Pages.new(@repo, @view)
426
+
427
+ html = "<html><body><p>No title here</p></body></html>"
428
+ title = widget.send(:extract_title_from_html, html)
429
+
430
+ assert_equal "Untitled", title
431
+ end
432
+
433
+ def test_025_pages_widget_generate
434
+ # Create test list.txt file
435
+ list_file = "#{@view.dir}/widgets/pages/list.txt"
436
+ make_dir(File.dirname(list_file))
437
+ write_file!(list_file, "about", "contact")
438
+
439
+ # Create test HTML files
440
+ pages_dir = "#{@view.dir}/pages"
441
+ make_dir(pages_dir)
442
+ write_file("#{pages_dir}/about.html", "<html><title>About Us</title><body></body></html>")
443
+ write_file("#{pages_dir}/contact.html", "<html><title>Contact</title><body></body></html>")
444
+
445
+ widget = Scriptorium::Widget::Pages.new(@repo, @view)
446
+ result = widget.generate
447
+
448
+ assert result
449
+
450
+ # Check that card file was created
451
+ card_file = "#{@view.dir}/widgets/pages/pages-card.html"
452
+ assert File.exist?(card_file)
453
+
454
+ # Check that card contains links to pages
455
+ card_content = read_file(card_file)
456
+ assert_includes card_content, 'onclick="load_main(\'pages/about.html\')"'
457
+ assert_includes card_content, 'onclick="load_main(\'pages/contact.html\')"'
458
+ assert_includes card_content, 'About Us'
459
+ assert_includes card_content, 'Contact'
460
+ end
461
+
462
+ def test_026_pages_widget_skip_missing_pages
463
+ # Create test list.txt file with a missing page
464
+ list_file = "#{@view.dir}/widgets/pages/list.txt"
465
+ make_dir(File.dirname(list_file))
466
+ write_file!(list_file, "about", "missing", "contact")
467
+
468
+ # Create only one HTML file
469
+ pages_dir = "#{@view.dir}/pages"
470
+ make_dir(pages_dir)
471
+ write_file("#{pages_dir}/about.html", "<html><title>About Us</title><body></body></html>")
472
+ write_file("#{pages_dir}/contact.html", "<html><title>Contact</title><body></body></html>")
473
+
474
+ widget = Scriptorium::Widget::Pages.new(@repo, @view)
475
+ result = widget.generate
476
+
477
+ assert result
478
+
479
+ # Check that card file was created and contains only existing pages
480
+ card_file = "#{@view.dir}/widgets/pages/pages-card.html"
481
+ card_content = read_file(card_file)
482
+ assert_includes card_content, 'onclick="load_main(\'pages/about.html\')"'
483
+ assert_includes card_content, 'onclick="load_main(\'pages/contact.html\')"'
484
+ refute_includes card_content, 'onclick="load_main(\'pages/missing.html\')"'
485
+ end
486
+
487
+ def test_029_featured_posts_widget_initialization
488
+ # Create test list.txt file
489
+ list_file = "#{@view.dir}/widgets/featuredposts/list.txt"
490
+ make_dir(File.dirname(list_file))
491
+ write_file!(list_file, "001 My Important Post", "002 Another Post")
492
+
493
+ widget = Scriptorium::Widget::FeaturedPosts.new(@repo, @view)
494
+ assert_equal "featuredposts", widget.name
495
+ assert_equal "Featured Posts", widget.widget_title
496
+ end
497
+
498
+ def test_030_featured_posts_widget_parse_line
499
+ # Create test list.txt file
500
+ list_file = "#{@view.dir}/widgets/featuredposts/list.txt"
501
+ make_dir(File.dirname(list_file))
502
+ write_file!(list_file, "001 My Important Post", "002 Another Post")
503
+
504
+ widget = Scriptorium::Widget::FeaturedPosts.new(@repo, @view)
505
+
506
+ # Test parsing with title
507
+ post_id, title = widget.parse_featured_line("001 My Important Post")
508
+ assert_equal "001", post_id
509
+ assert_equal "My Important Post", title
510
+
511
+ # Test parsing without title
512
+ post_id, title = widget.parse_featured_line("002")
513
+ assert_equal "002", post_id
514
+ assert_nil title
515
+
516
+ # Test parsing with multiple spaces
517
+ post_id, title = widget.parse_featured_line("003 Another Post Title")
518
+ assert_equal "003", post_id
519
+ assert_equal "Another Post Title", title
520
+ end
521
+
522
+ def test_031_featured_posts_widget_get_post_title
523
+ # Create test list.txt file
524
+ list_file = "#{@view.dir}/widgets/featuredposts/list.txt"
525
+ make_dir(File.dirname(list_file))
526
+ write_file!(list_file, "001 My Important Post", "002 Another Post")
527
+
528
+ widget = Scriptorium::Widget::FeaturedPosts.new(@repo, @view)
529
+
530
+ # Create a test post first
531
+ post = @repo.create_post(title: "Test Featured Post", views: ["sample"])
532
+
533
+ # Test getting title from existing post
534
+ title = widget.get_post_title(post.id)
535
+ assert_equal "Test Featured Post", title
536
+
537
+ # Test getting title from non-existent post
538
+ title = widget.get_post_title("999")
539
+ assert_equal "Error: Post 999 not found", title
540
+ end
541
+
542
+ def test_032_featured_posts_widget_generate
543
+ # Create test list.txt file
544
+ list_file = "#{@view.dir}/widgets/featuredposts/list.txt"
545
+ make_dir(File.dirname(list_file))
546
+ write_file!(list_file, "001 My Important Post", "002 Another Post")
547
+
548
+ # Create test posts
549
+ post1 = @repo.create_post(title: "My Important Post", views: ["sample"])
550
+ post2 = @repo.create_post(title: "Another Post", views: ["sample"])
551
+
552
+ widget = Scriptorium::Widget::FeaturedPosts.new(@repo, @view)
553
+ result = widget.generate
554
+
555
+ assert result
556
+
557
+ # Check that card file was created
558
+ card_file = "#{@view.dir}/widgets/featuredposts/featuredposts-card.html"
559
+ assert File.exist?(card_file)
560
+
561
+ # Check that card contains links to posts
562
+ card_content = read_file(card_file)
563
+ assert_includes card_content, 'onclick="load_main(\'posts/001.html\')"'
564
+ assert_includes card_content, 'onclick="load_main(\'posts/002.html\')"'
565
+ assert_includes card_content, 'My Important Post'
566
+ assert_includes card_content, 'Another Post'
567
+ end
568
+
569
+ def test_033_featured_posts_widget_handles_missing_posts
570
+ # Create test list.txt file with a missing post
571
+ list_file = "#{@view.dir}/widgets/featuredposts/list.txt"
572
+ make_dir(File.dirname(list_file))
573
+ write_file!(list_file, "001 My Important Post", "999 Missing Post")
574
+
575
+ # Create only one post
576
+ post1 = @repo.create_post(title: "My Important Post", views: ["sample"])
577
+
578
+ widget = Scriptorium::Widget::FeaturedPosts.new(@repo, @view)
579
+ result = widget.generate
580
+
581
+ assert result
582
+
583
+ # Check that card file was created and contains error for missing post
584
+ card_file = "#{@view.dir}/widgets/featuredposts/featuredposts-card.html"
585
+ card_content = read_file(card_file)
586
+ assert_includes card_content, 'onclick="load_main(\'posts/001.html\')"'
587
+ assert_includes card_content, 'My Important Post'
588
+ assert_includes card_content, 'Error: Post 999 not found'
589
+ end
590
+
591
+ # Pages widget with back links tests
592
+ def test_034_pages_widget_with_back_links
593
+ # Create test list.txt file
594
+ list_file = "#{@view.dir}/widgets/pages/list.txt"
595
+ make_dir(File.dirname(list_file))
596
+ write_file!(list_file, "about", "contact")
597
+
598
+ # Create test HTML files with back links
599
+ pages_dir = "#{@view.dir}/pages"
600
+ make_dir(pages_dir)
601
+ write_file("#{pages_dir}/about.html", "<html><title>About Us</title><body><h1>About Us</h1><p><a href=\"index.html\">← Back to Home</a></p></body></html>")
602
+ write_file("#{pages_dir}/contact.html", "<html><title>Contact</title><body><h1>Contact</h1><p><a href=\"index.html\">← Back to Home</a></p></body></html>")
603
+
604
+ widget = Scriptorium::Widget::Pages.new(@repo, @view)
605
+ result = widget.generate
606
+
607
+ assert result
608
+
609
+ # Check that card file was created
610
+ card_file = "#{@view.dir}/widgets/pages/pages-card.html"
611
+ assert File.exist?(card_file)
612
+
613
+ # Check that card contains links to pages
614
+ card_content = read_file(card_file)
615
+ assert_includes card_content, 'onclick="load_main(\'pages/about.html\')"'
616
+ assert_includes card_content, 'onclick="load_main(\'pages/contact.html\')"'
617
+ assert_includes card_content, 'About Us'
618
+ assert_includes card_content, 'Contact'
619
+ end
620
+
621
+ def test_035_pages_widget_back_links_functionality
622
+ # Create test list.txt file
623
+ list_file = "#{@view.dir}/widgets/pages/list.txt"
624
+ make_dir(File.dirname(list_file))
625
+ write_file!(list_file, "about")
626
+
627
+ # Create test HTML file with back link
628
+ pages_dir = "#{@view.dir}/pages"
629
+ make_dir(pages_dir)
630
+ about_content = "<html><title>About Us</title><body><h1>About Us</h1><p><a href=\"index.html\">← Back to Home</a></p></body></html>"
631
+ write_file("#{pages_dir}/about.html", about_content)
632
+
633
+ widget = Scriptorium::Widget::Pages.new(@repo, @view)
634
+
635
+ # Check that the page content includes the back link
636
+ page_content = read_file("#{pages_dir}/about.html")
637
+ assert_includes page_content, '<a href="index.html">← Back to Home</a>'
638
+
639
+ # Check that the back link points to index.html
640
+ assert_includes page_content, 'href="index.html"'
641
+ assert_includes page_content, '← Back to Home'
642
+ end
643
+
644
+ def test_036_pages_widget_handles_pages_without_back_links
645
+ # Create test list.txt file
646
+ list_file = "#{@view.dir}/widgets/pages/list.txt"
647
+ make_dir(File.dirname(list_file))
648
+ write_file!(list_file, "simple")
649
+
650
+ # Create test HTML file without back link
651
+ pages_dir = "#{@view.dir}/pages"
652
+ make_dir(pages_dir)
653
+ write_file("#{pages_dir}/simple.html", "<html><title>Simple</title><body><h1>Simple Page</h1></body></html>")
654
+
655
+ widget = Scriptorium::Widget::Pages.new(@repo, @view)
656
+ result = widget.generate
657
+
658
+ assert result
659
+
660
+ # Check that card file was created
661
+ card_file = "#{@view.dir}/widgets/pages/pages-card.html"
662
+ assert File.exist?(card_file)
663
+
664
+ # Check that card contains link to simple page
665
+ card_content = read_file(card_file)
666
+ assert_includes card_content, 'onclick="load_main(\'pages/simple.html\')"'
667
+ assert_includes card_content, 'Simple'
668
+ end
669
+ end