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/unit/view.rb ADDED
@@ -0,0 +1,431 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../../lib/scriptorium'
3
+ require_relative '../test_helpers'
4
+ require_relative '../../lib/scriptorium/view'
5
+
6
+ class TestScriptoriumView < Minitest::Test
7
+ include Scriptorium::Exceptions
8
+ include Scriptorium::Helpers
9
+ include TestHelpers
10
+
11
+ def setup
12
+ @test_dir = "test/view_test_files"
13
+ make_dir(@test_dir)
14
+ # Use a unique test repo path to avoid conflicts
15
+ @repo_path = "scriptorium-TEST"
16
+ FileUtils.rm_rf(@repo_path) if Dir.exist?(@repo_path)
17
+ @repo = Scriptorium::Repo.create(@repo_path, testmode: true)
18
+ @view = @repo.create_view("test_view", "Test View", "A test view", theme: "standard")
19
+ end
20
+
21
+ def teardown
22
+ FileUtils.rm_rf(@test_dir) if Dir.exist?(@test_dir)
23
+ FileUtils.rm_rf(@repo_path) if Dir.exist?(@repo_path)
24
+ Scriptorium::Repo.destroy if Scriptorium::Repo.testing
25
+ end
26
+
27
+ # Constructor validation tests
28
+ def test_001_initialize_with_valid_params
29
+ view = Scriptorium::View.new("valid_name", "Valid Title", "Subtitle", "standard")
30
+ assert_equal "valid_name", view.name
31
+ assert_equal "Valid Title", view.title
32
+ assert_equal "Subtitle", view.subtitle
33
+ assert_equal "standard", view.theme
34
+ end
35
+
36
+ def test_002_initialize_with_nil_name
37
+ assert_raises(CannotCreateViewNameNil) do
38
+ Scriptorium::View.new(nil, "Title", "Subtitle")
39
+ end
40
+ end
41
+
42
+ def test_003_initialize_with_empty_name
43
+ assert_raises(CannotCreateViewNameEmpty) do
44
+ Scriptorium::View.new("", "Title", "Subtitle")
45
+ end
46
+ end
47
+
48
+ def test_004_initialize_with_whitespace_name
49
+ assert_raises(CannotCreateViewNameEmpty) do
50
+ Scriptorium::View.new(" ", "Title", "Subtitle")
51
+ end
52
+ end
53
+
54
+ def test_005_initialize_with_invalid_name_format
55
+ assert_raises(CannotCreateViewNameInvalid) do
56
+ Scriptorium::View.new("invalid@name", "Title", "Subtitle")
57
+ end
58
+ end
59
+
60
+ def test_006_initialize_with_nil_title
61
+ assert_raises(CannotCreateViewTitleNil) do
62
+ Scriptorium::View.new("valid_name", nil, "Subtitle")
63
+ end
64
+ end
65
+
66
+ def test_007_initialize_with_empty_title
67
+ assert_raises(CannotCreateViewTitleEmpty) do
68
+ Scriptorium::View.new("valid_name", "", "Subtitle")
69
+ end
70
+ end
71
+
72
+ def test_008_initialize_with_whitespace_title
73
+ assert_raises(CannotCreateViewTitleEmpty) do
74
+ Scriptorium::View.new("valid_name", " ", "Subtitle")
75
+ end
76
+ end
77
+
78
+ # Theme handling tests
79
+ def test_009_theme_getter
80
+ assert_equal "standard", @view.theme
81
+ end
82
+
83
+ def test_010_theme_setter_with_valid_theme
84
+ @view.theme("standard")
85
+ assert_equal "standard", @view.theme
86
+ end
87
+
88
+ def test_011_theme_setter_with_nonexistent_theme
89
+ assert_raises(ThemeDoesntExist) do
90
+ @view.theme("nonexistent_theme")
91
+ end
92
+ end
93
+
94
+ # Widget validation tests
95
+ def test_012_build_widgets_with_valid_widget
96
+ # This test would need actual widget classes to be available
97
+ # For now, we'll test the validation logic
98
+ assert_raises(CannotBuildWidgetsArgNil) do
99
+ @view.build_widgets(nil)
100
+ end
101
+ end
102
+
103
+ def test_013_build_widgets_with_empty_arg
104
+ assert_raises(CannotBuildWidgetsArgEmpty) do
105
+ @view.build_widgets("")
106
+ end
107
+ end
108
+
109
+ def test_014_build_widgets_with_whitespace_arg
110
+ assert_raises(CannotBuildWidgetsArgEmpty) do
111
+ @view.build_widgets(" ")
112
+ end
113
+ end
114
+
115
+ def test_015_build_widgets_with_invalid_widget_name
116
+ assert_raises(CannotBuildWidgetNameInvalid) do
117
+ @view.build_widgets("invalid@widget")
118
+ end
119
+ end
120
+
121
+ # Layout file tests
122
+ def test_016_read_layout_with_missing_file
123
+ # Create a view without layout.txt by creating it through repo then deleting the layout file
124
+ view = @repo.create_view("no_layout", "No Layout", "No layout view", theme: "standard")
125
+ File.delete(view.dir/:config/"layout.txt") if File.exist?(view.dir/:config/"layout.txt")
126
+ assert_raises(LayoutFileMissing) do
127
+ view.read_layout
128
+ end
129
+ end
130
+
131
+ # Content tag tests
132
+ def test_017_content_tag
133
+ assert_equal "<!-- Section: header -->", @view.content_tag("header")
134
+ assert_equal "<!-- Section: footer -->", @view.content_tag("footer")
135
+ end
136
+
137
+ # Placeholder text tests
138
+ def test_018_placeholder_text_with_file_reference
139
+ # Create a test file
140
+ test_file = @view.dir/:config/:text/"test_content"
141
+ make_dir(File.dirname(test_file))
142
+ write_file(test_file, "Test content from file")
143
+
144
+ result = @view.placeholder_text("@test_content")
145
+ assert_equal "Test content from file\n", result
146
+ end
147
+
148
+ def test_019_placeholder_text_with_plain_text
149
+ result = @view.placeholder_text("Plain text content")
150
+ assert_equal "Plain text content", result
151
+ end
152
+
153
+ def test_020_placeholder_text_with_missing_file
154
+ result = @view.placeholder_text("@missing_file")
155
+ assert_match /Missing:.*missing_file/, result
156
+ end
157
+
158
+ # Section hash tests
159
+ def test_021_section_hash_default_behavior
160
+ hash = @view.section_hash("test_section")
161
+ assert hash.is_a?(Hash)
162
+
163
+ # Test default behavior for unknown keys
164
+ result = hash["unknown_key"].call
165
+ assert_match /Not defined for key: unknown_key/, result
166
+ end
167
+
168
+ def test_022_section_hash_text_component
169
+ hash = @view.section_hash("test_section")
170
+ result = hash["text"].call("Test argument")
171
+ assert_equal " <p>Test argument</p>\n", result
172
+ end
173
+
174
+ # Inspect test
175
+ def test_023_inspect
176
+ result = @view.inspect
177
+ assert_match /<View: test_view/, result
178
+ assert_match /Test View/, result
179
+ assert_match /theme: standard/, result
180
+ end
181
+
182
+ # HTML generation tests
183
+ def test_024_check_html_stubs
184
+ panes = @view.dir/:output/:panes
185
+ assert_file_exist?(panes/"header.html")
186
+ assert_file_exist?(panes/"footer.html")
187
+ assert_file_exist?(panes/"left.html")
188
+ assert_file_exist?(panes/"right.html")
189
+ assert_file_exist?(panes/"main.html")
190
+
191
+ assert_file_contains?(panes/"header.html", "<!-- HEADER CONTENT -->")
192
+ assert_file_contains?(panes/"footer.html", "<!-- FOOTER CONTENT -->")
193
+ assert_file_contains?(panes/"left.html", "<!-- LEFT CONTENT -->")
194
+ assert_file_contains?(panes/"right.html", "<!-- RIGHT CONTENT -->")
195
+ assert_file_contains?(panes/"main.html", "<!-- MAIN CONTENT -->")
196
+ end
197
+
198
+ # Layout parsing tests
199
+ def test_025_check_layout_parsing
200
+ file = @view.dir/:config/"layout.txt"
201
+ File.open(file, "w") do |f|
202
+ f.puts <<~EOS
203
+ main # Center pane
204
+ header
205
+ left 15%
206
+ right 20% # Right sidebar
207
+ footer
208
+ EOS
209
+ end
210
+ results = @view.read_layout.keys
211
+ expected = ["main", "header", "left", "right", "footer"].sort
212
+ assert results.sort == expected, "Error reading layout file (got #{results.inspect})"
213
+
214
+ File.open(file, "w") do |f|
215
+ f.puts <<~EOS
216
+ main # Center pane
217
+ header
218
+ banana
219
+ left 15%
220
+ right 20% # Right sidebar
221
+ footer
222
+ EOS
223
+ end
224
+ assert_raises(LayoutHasUnknownTag) { @view.read_layout }
225
+
226
+ File.open(file, "w") do |f|
227
+ f.puts <<~EOS
228
+ main # Center pane
229
+ header
230
+ main
231
+ left 15%
232
+ right 20% # Right sidebar
233
+ footer
234
+ EOS
235
+ end
236
+ assert_raises(LayoutHasDuplicateTags) { @view.read_layout }
237
+ end
238
+
239
+ def test_025_build_banner_svg_format
240
+ # Create svg.txt config file
241
+ svg_config = @view.dir/:config/"svg.txt"
242
+ make_dir(File.dirname(svg_config))
243
+ write_file!(svg_config, "back.color #f0f0f0",
244
+ "text.color #333333", "aspect 4.0")
245
+ result = @view.build_banner("svg")
246
+
247
+ # Should generate JavaScript with SVG content
248
+ assert_includes result, "<script>"
249
+ assert_includes result, "insert_svg_header"
250
+ assert_includes result, "svg_text"
251
+ end
252
+
253
+ def test_026_build_banner_image_format
254
+ # Create test image in view assets
255
+ image_path = @view.dir/:assets/"testbanner.jpg"
256
+ make_dir(File.dirname(image_path))
257
+ write_file(image_path, "fake image content")
258
+
259
+ result = @view.build_banner("testbanner.jpg")
260
+
261
+ # Should generate img tag with relative path
262
+ assert_includes result, "<img src='assets/testbanner.jpg'"
263
+ assert_includes result, "alt='Banner Image'"
264
+ assert_includes result, "style='width: 100%; height: auto;'"
265
+ end
266
+
267
+ def test_027_build_banner_image_missing_with_warning
268
+ result = @view.build_banner("missing.jpg")
269
+
270
+ # Should show warning message
271
+ assert_includes result, "<p>Banner image missing: missing.jpg</p>"
272
+ end
273
+
274
+ def test_028_build_navbar_with_default_file
275
+ # Create navbar.txt file
276
+ navbar_file = @view.dir/:config/"navbar.txt"
277
+ make_dir(File.dirname(navbar_file))
278
+ write_file!(navbar_file,
279
+ "=About",
280
+ " Vision & Mission mission",
281
+ " Board of Directors board",
282
+ "-Contact contact")
283
+ result = @view.build_nav(nil)
284
+
285
+ # Should generate Bootstrap navbar
286
+ assert_includes result, "navbar navbar-expand-lg"
287
+ assert_includes result, "dropdown-toggle"
288
+ assert_includes result, "About"
289
+ assert_includes result, "Vision &amp; Mission"
290
+ assert_includes result, "Contact"
291
+ assert_includes result, "load_main('pages/mission.html')"
292
+ assert_includes result, "load_main('pages/contact.html')"
293
+ end
294
+
295
+ def test_029_build_navbar_with_specified_file
296
+ # Create custom navbar file
297
+ navbar_file = @view.dir/:config/"custom-nav.txt"
298
+ make_dir(File.dirname(navbar_file))
299
+ write_file!(navbar_file,
300
+ "=Resources",
301
+ " Documentation docs",
302
+ " API Reference api",
303
+ "-Support support")
304
+ result = @view.build_nav("custom-nav.txt")
305
+
306
+ # Should generate Bootstrap navbar
307
+ assert_includes result, "Resources"
308
+ assert_includes result, "Documentation"
309
+ assert_includes result, "Support"
310
+ assert_includes result, "load_main('pages/docs.html')"
311
+ end
312
+
313
+ def test_030_build_navbar_with_missing_pages
314
+ # Create navbar.txt with references to non-existent pages
315
+ navbar_file = @view.dir/:config/"navbar.txt"
316
+ make_dir(File.dirname(navbar_file))
317
+ write_file!(navbar_file,
318
+ "-Home home",
319
+ "-Missing Page missing")
320
+ result = @view.build_nav(nil)
321
+
322
+ # Should still generate links but include warnings
323
+ assert_includes result, "load_main('pages/home.html')"
324
+ assert_includes result, "load_main('pages/missing.html')"
325
+ assert_includes result, "Warning: Page file 'missing.html' not found"
326
+ end
327
+
328
+ def test_031_build_navbar_parsing
329
+ # Test parsing of different line types
330
+ navbar_content = <<~EOS
331
+ =About
332
+ Vision & Mission mission
333
+ Board of Directors board
334
+ -Contact contact
335
+ =Resources
336
+ Documentation docs
337
+ -Support support
338
+ EOS
339
+
340
+ menu_items = @view.send(:parse_navbar_content, navbar_content)
341
+
342
+ # Should parse correctly
343
+ assert_equal 4, menu_items.length
344
+
345
+ # Check dropdown items
346
+ about_dropdown = menu_items.find { |item| item[:label] == "About" }
347
+ assert_equal :dropdown, about_dropdown[:type]
348
+ assert_equal 2, about_dropdown[:children].length
349
+
350
+ # Check regular items
351
+ contact_item = menu_items.find { |item| item[:title] == "Contact" }
352
+ assert_equal :item, contact_item[:type]
353
+ assert_equal "contact", contact_item[:filename]
354
+ end
355
+
356
+ # Pages directory copying tests
357
+ def test_032_generate_front_page_copies_pages_directory
358
+ # Create pages directory with test files
359
+ pages_dir = @view.dir/:pages
360
+ make_dir(pages_dir)
361
+ write_file(pages_dir/"about.html", "<html><title>About</title><body><h1>About</h1></body></html>")
362
+ write_file(pages_dir/"contact.html", "<html><title>Contact</title><body><h1>Contact</h1></body></html>")
363
+
364
+ # Generate front page
365
+ @view.generate_front_page
366
+
367
+ # Check that pages directory was copied to output
368
+ output_pages_dir = @view.dir/:output/:pages
369
+ assert Dir.exist?(output_pages_dir), "Expected pages directory to be copied to output"
370
+
371
+ # Check that page files were copied
372
+ assert File.exist?(output_pages_dir/"about.html"), "Expected about.html to be copied"
373
+ assert File.exist?(output_pages_dir/"contact.html"), "Expected contact.html to be copied"
374
+
375
+ # Check that content is preserved
376
+ about_content = read_file(output_pages_dir/"about.html")
377
+ assert_includes about_content, "<h1>About</h1>"
378
+
379
+ contact_content = read_file(output_pages_dir/"contact.html")
380
+ assert_includes contact_content, "<h1>Contact</h1>"
381
+ end
382
+
383
+ def test_033_generate_front_page_handles_missing_pages_directory
384
+ # Ensure pages directory doesn't exist
385
+ pages_dir = @view.dir/:pages
386
+ FileUtils.rm_rf(pages_dir) if Dir.exist?(pages_dir)
387
+
388
+ # Generate front page (should not fail)
389
+ @view.generate_front_page
390
+
391
+ # Check that output/index.html was still generated
392
+ index_file = @view.dir/:output/"index.html"
393
+ assert File.exist?(index_file), "Expected index.html to be generated even without pages directory"
394
+ end
395
+
396
+ def test_034_generate_front_page_copies_empty_pages_directory
397
+ # Create empty pages directory
398
+ pages_dir = @view.dir/:pages
399
+ make_dir(pages_dir)
400
+
401
+ # Generate front page
402
+ @view.generate_front_page
403
+
404
+ # Check that empty pages directory was created in output
405
+ output_pages_dir = @view.dir/:output/:pages
406
+ assert Dir.exist?(output_pages_dir), "Expected empty pages directory to be created in output"
407
+ end
408
+
409
+ def test_035_generate_front_page_preserves_page_file_permissions
410
+ # Create pages directory with test file
411
+ pages_dir = @view.dir/:pages
412
+ make_dir(pages_dir)
413
+ test_file = pages_dir/"test.html"
414
+ write_file(test_file, "<html><body>Test</body></html>")
415
+
416
+ # Set specific permissions
417
+ File.chmod(0644, test_file)
418
+ original_permissions = File.stat(test_file).mode & 0777
419
+
420
+ # Generate front page
421
+ @view.generate_front_page
422
+
423
+ # Check that file was copied
424
+ output_file = @view.dir/:output/:pages/"test.html"
425
+ assert File.exist?(output_file), "Expected test.html to be copied"
426
+
427
+ # Check that permissions are reasonable (may vary by system)
428
+ output_permissions = File.stat(output_file).mode & 0777
429
+ assert output_permissions > 0, "Expected reasonable file permissions"
430
+ end
431
+ end