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,369 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../../lib/scriptorium'
3
+ require_relative '../test_helpers'
4
+
5
+ class SocialTest < Minitest::Test
6
+ include Scriptorium::Exceptions
7
+ include Scriptorium::Helpers
8
+ include TestHelpers
9
+
10
+ def setup
11
+ @test_dir = "test/social_test_files"
12
+ make_dir(@test_dir)
13
+ @repo = Scriptorium::Repo.create("test/scriptorium-TEST", testmode: true)
14
+ @view = @repo.create_view("test", "Test View", "A test view")
15
+ end
16
+
17
+ def teardown
18
+ FileUtils.rm_rf(@test_dir) if Dir.exist?(@test_dir)
19
+ Scriptorium::Repo.destroy if Scriptorium::Repo.testing
20
+ end
21
+
22
+ def test_001_social_config_file_created
23
+ social_config_file = @view.dir/:config/"social.txt"
24
+ assert File.exist?(social_config_file), "Social config file should be created"
25
+
26
+ content = read_file(social_config_file)
27
+ assert_includes content, "enabled: true"
28
+ assert_includes content, "platforms: facebook, twitter"
29
+ end
30
+
31
+ def test_002_social_meta_tags_generated_when_enabled
32
+ # Enable social features
33
+ social_config = <<~EOS
34
+ enabled: true
35
+ platforms: facebook, twitter
36
+ EOS
37
+ write_file(@view.dir/:config/"social.txt", social_config)
38
+
39
+ # Generate meta tags
40
+ meta_tags = @view.generate_social_meta_tags
41
+
42
+ # Check for Open Graph tags
43
+ assert_includes meta_tags, 'property="og:title"'
44
+ assert_includes meta_tags, 'property="og:type"'
45
+ assert_includes meta_tags, 'property="og:url"'
46
+ assert_includes meta_tags, 'property="og:description"'
47
+ assert_includes meta_tags, 'property="og:site_name"'
48
+
49
+ # Check for Twitter Card tags
50
+ assert_includes meta_tags, 'name="twitter:card"'
51
+ assert_includes meta_tags, 'name="twitter:title"'
52
+ assert_includes meta_tags, 'name="twitter:description"'
53
+ assert_includes meta_tags, 'name="twitter:url"'
54
+ end
55
+
56
+ def test_003_social_meta_tags_not_generated_when_disabled
57
+ # Disable social features
58
+ social_config = <<~EOS
59
+ enabled: false
60
+ platforms: facebook, twitter
61
+ EOS
62
+ write_file(@view.dir/:config/"social.txt", social_config)
63
+
64
+ # Generate meta tags
65
+ meta_tags = @view.generate_social_meta_tags
66
+
67
+ # Should be empty when disabled
68
+ assert_empty meta_tags
69
+ end
70
+
71
+ def test_004_post_specific_meta_tags
72
+ # Enable social features
73
+ social_config = <<~EOS
74
+ enabled: true
75
+ platforms: facebook, twitter
76
+ EOS
77
+ write_file(@view.dir/:config/"social.txt", social_config)
78
+
79
+ # Create post data
80
+ post_data = {
81
+ :"post.id" => "1",
82
+ :"post.title" => "Test Post",
83
+ :"post.body" => "This is a test post body",
84
+ :"post.blurb" => "Test post blurb",
85
+ :"post.pubdate" => "2024-01-01 12:00:00",
86
+ :"post.slug" => "test-post.html"
87
+ }
88
+
89
+ # Generate meta tags for post
90
+ meta_tags = @view.generate_social_meta_tags(nil, post_data)
91
+
92
+ # Check for post-specific content
93
+ assert_includes meta_tags, 'content="Test Post"'
94
+ assert_includes meta_tags, 'content="article"'
95
+ assert_includes meta_tags, 'content="posts/test-post.html"'
96
+ assert_includes meta_tags, 'content="Test post blurb"'
97
+ assert_includes meta_tags, 'content="2024-01-01 12:00:00"'
98
+ end
99
+
100
+ def test_005_complete_post_html_generation
101
+ # Enable social features
102
+ social_config = <<~EOS
103
+ enabled: true
104
+ platforms: facebook, twitter
105
+ EOS
106
+ write_file(@view.dir/:config/"social.txt", social_config)
107
+
108
+ # Create post data
109
+ post_data = {
110
+ :"post.id" => "1",
111
+ :"post.title" => "Test Post",
112
+ :"post.body" => "<p>This is a test post body</p>",
113
+ :"post.pubdate" => "2024-01-01 12:00:00",
114
+ :"post.tags" => "test, example"
115
+ }
116
+
117
+ # Generate complete HTML
118
+ html = @view.generate_post_html(post_data)
119
+
120
+ # Check for complete HTML structure
121
+ assert_includes html, "<!DOCTYPE html>"
122
+ assert_includes html, "<html>"
123
+ assert_includes html, "<head>"
124
+ assert_includes html, "<body>"
125
+ assert_includes html, "Test Post"
126
+ assert_includes html, "This is a test post body"
127
+ assert_includes html, "2024-01-01 12:00:00"
128
+ assert_includes html, "test, example"
129
+
130
+ # Check for social meta tags
131
+ assert_includes html, 'property="og:title"'
132
+ assert_includes html, 'name="twitter:card"'
133
+ end
134
+
135
+ # Reddit Button Tests
136
+
137
+ def test_006_reddit_config_file_created
138
+ reddit_config_file = @view.dir/:config/"reddit.txt"
139
+ assert File.exist?(reddit_config_file), "Reddit config file should be created"
140
+
141
+ content = read_file(reddit_config_file)
142
+ assert_includes content, "button true"
143
+ assert_includes content, "subreddit"
144
+ assert_includes content, "hover_text"
145
+ end
146
+
147
+ def test_007_reddit_button_not_generated_when_reddit_not_enabled
148
+ # Disable Reddit in social config
149
+ social_config = <<~EOS
150
+ enabled: true
151
+ platforms: facebook, twitter
152
+ EOS
153
+ write_file(@view.dir/:config/"social.txt", social_config)
154
+
155
+ # Enable Reddit button in reddit config
156
+ reddit_config = <<~EOS
157
+ button true
158
+ subreddit
159
+ hover_text
160
+ EOS
161
+ write_file(@view.dir/:config/"reddit.txt", reddit_config)
162
+
163
+ # Generate Reddit button
164
+ button_html = @view.generate_reddit_button
165
+
166
+ # Should be empty when Reddit not enabled in social config
167
+ assert_empty button_html
168
+ end
169
+
170
+ def test_008_reddit_button_not_generated_when_button_disabled
171
+ # Enable Reddit in social config
172
+ social_config = <<~EOS
173
+ enabled: true
174
+ platforms: facebook, twitter, reddit
175
+ EOS
176
+ write_file(@view.dir/:config/"social.txt", social_config)
177
+
178
+ # Disable Reddit button in reddit config
179
+ reddit_config = <<~EOS
180
+ button false
181
+ subreddit
182
+ hover_text
183
+ EOS
184
+ write_file(@view.dir/:config/"reddit.txt", reddit_config)
185
+
186
+ # Generate Reddit button
187
+ button_html = @view.generate_reddit_button
188
+
189
+ # Should be empty when button disabled
190
+ assert_empty button_html
191
+ end
192
+
193
+ def test_009_reddit_button_generated_when_enabled
194
+ # Enable Reddit in social config
195
+ social_config = <<~EOS
196
+ enabled: true
197
+ platforms: facebook, twitter, reddit
198
+ EOS
199
+ write_file(@view.dir/:config/"social.txt", social_config)
200
+
201
+ # Enable Reddit button in reddit config
202
+ reddit_config = <<~EOS
203
+ button true
204
+ subreddit
205
+ hover_text
206
+ EOS
207
+ write_file(@view.dir/:config/"reddit.txt", reddit_config)
208
+
209
+ # Generate Reddit button
210
+ button_html = @view.generate_reddit_button
211
+
212
+ # Should contain Reddit button HTML
213
+ assert_includes button_html, 'href="https://reddit.com/submit'
214
+ assert_includes button_html, 'title="Share on Reddit"'
215
+ assert_includes button_html, 'src="assets/reddit-logo.png"'
216
+ assert_includes button_html, 'alt="Share on Reddit"'
217
+ end
218
+
219
+ def test_010_reddit_button_with_subreddit
220
+ # Enable Reddit in social config
221
+ social_config = <<~EOS
222
+ enabled: true
223
+ platforms: facebook, twitter, reddit
224
+ EOS
225
+ write_file(@view.dir/:config/"social.txt", social_config)
226
+
227
+ # Enable Reddit button with subreddit
228
+ reddit_config = <<~EOS
229
+ button true
230
+ subreddit RubyElixirEtc
231
+ hover_text
232
+ EOS
233
+ write_file(@view.dir/:config/"reddit.txt", reddit_config)
234
+
235
+ # Generate Reddit button
236
+ button_html = @view.generate_reddit_button
237
+
238
+ # Should contain subreddit-specific URL and hover text
239
+ assert_includes button_html, 'href="https://reddit.com/r/RubyElixirEtc/submit'
240
+ assert_includes button_html, 'title="Share on r/RubyElixirEtc"'
241
+ end
242
+
243
+ def test_011_reddit_button_with_custom_hover_text
244
+ # Enable Reddit in social config
245
+ social_config = <<~EOS
246
+ enabled: true
247
+ platforms: facebook, twitter, reddit
248
+ EOS
249
+ write_file(@view.dir/:config/"social.txt", social_config)
250
+
251
+ # Enable Reddit button with custom hover text
252
+ reddit_config = <<~EOS
253
+ button true
254
+ subreddit
255
+ hover_text Share this awesome post!
256
+ EOS
257
+ write_file(@view.dir/:config/"reddit.txt", reddit_config)
258
+
259
+ # Generate Reddit button
260
+ button_html = @view.generate_reddit_button
261
+
262
+ # Should contain custom hover text
263
+ assert_includes button_html, 'title="Share this awesome post!"'
264
+ end
265
+
266
+ def test_012_reddit_button_with_post_data
267
+ # Enable Reddit in social config
268
+ social_config = <<~EOS
269
+ enabled: true
270
+ platforms: facebook, twitter, reddit
271
+ EOS
272
+ write_file(@view.dir/:config/"social.txt", social_config)
273
+
274
+ # Enable Reddit button
275
+ reddit_config = <<~EOS
276
+ button true
277
+ subreddit
278
+ hover_text
279
+ EOS
280
+ write_file(@view.dir/:config/"reddit.txt", reddit_config)
281
+
282
+ # Create post data
283
+ post_data = {
284
+ :"post.id" => "123",
285
+ :"post.title" => "My Test Post",
286
+ :"post.slug" => "my-test-post"
287
+ }
288
+
289
+ # Generate Reddit button with post data
290
+ button_html = @view.generate_reddit_button(post_data)
291
+
292
+ # Should contain post-specific URL and title
293
+ assert_includes button_html, 'url=posts/my-test-post.html'
294
+ assert_includes button_html, 'title=My+Test+Post'
295
+ end
296
+
297
+ def test_013_reddit_button_integration_with_post_generation
298
+ # Enable Reddit in social config
299
+ social_config = <<~EOS
300
+ enabled: true
301
+ platforms: facebook, twitter, reddit
302
+ EOS
303
+ write_file(@view.dir/:config/"social.txt", social_config)
304
+
305
+ # Enable Reddit button
306
+ reddit_config = <<~EOS
307
+ button true
308
+ subreddit
309
+ hover_text
310
+ EOS
311
+ write_file(@view.dir/:config/"reddit.txt", reddit_config)
312
+
313
+ # Create and generate a post
314
+ draft_name = @repo.create_draft(title: "Reddit Test Post", tags: %w[test reddit])
315
+ body = "This is a test post for Reddit button integration."
316
+ text = File.read(draft_name)
317
+ text.sub!(/BEGIN HERE.../, body)
318
+ write_file(draft_name, text)
319
+ post_id = @repo.finish_draft(draft_name)
320
+ @repo.generate_post(post_id)
321
+
322
+ # Check that the generated post contains Reddit button
323
+ post_file = @repo.root/:views/:test/:output/:posts/"#{d4(post_id)}-reddit-test-post.html"
324
+ assert File.exist?(post_file), "Generated post should exist"
325
+
326
+ post_content = read_file(post_file)
327
+ assert_includes post_content, 'href="https://reddit.com/submit'
328
+ assert_includes post_content, 'src="assets/reddit-logo.png"'
329
+ end
330
+
331
+ def test_014_reddit_button_missing_config_file_handling
332
+ # Enable Reddit in social config
333
+ social_config = <<~EOS
334
+ enabled: true
335
+ platforms: facebook, twitter, reddit
336
+ EOS
337
+ write_file(@view.dir/:config/"social.txt", social_config)
338
+
339
+ # Remove reddit.txt config file
340
+ reddit_config_file = @view.dir/:config/"reddit.txt"
341
+ File.delete(reddit_config_file) if File.exist?(reddit_config_file)
342
+
343
+ # Generate Reddit button
344
+ button_html = @view.generate_reddit_button
345
+
346
+ # Should be empty when config file missing
347
+ assert_empty button_html
348
+ end
349
+
350
+ def test_015_reddit_button_missing_social_config_file_handling
351
+ # Remove social.txt config file
352
+ social_config_file = @view.dir/:config/"social.txt"
353
+ File.delete(social_config_file) if File.exist?(social_config_file)
354
+
355
+ # Enable Reddit button in reddit config
356
+ reddit_config = <<~EOS
357
+ button true
358
+ subreddit
359
+ hover_text
360
+ EOS
361
+ write_file(@view.dir/:config/"reddit.txt", reddit_config)
362
+
363
+ # Generate Reddit button
364
+ button_html = @view.generate_reddit_button
365
+
366
+ # Should be empty when social config file missing
367
+ assert_empty button_html
368
+ end
369
+ end
@@ -0,0 +1,213 @@
1
+ # Test file for symlink functionality
2
+ # Tests the clean URL symlink generation for posts
3
+
4
+ require 'minitest/autorun'
5
+ require_relative '../../lib/scriptorium'
6
+ require_relative '../test_helpers'
7
+ require 'fileutils'
8
+
9
+ class TestSymlinkFunctionality < Minitest::Test
10
+ include Scriptorium::Helpers
11
+ include TestHelpers
12
+
13
+ def setup
14
+ @test_dir = "test/symlink_test_files"
15
+ FileUtils.mkdir_p(@test_dir)
16
+ Scriptorium::Repo.testing = true
17
+
18
+ # Create test repository
19
+ @repo = Scriptorium::Repo.create("test/scriptorium-TEST", testmode: true)
20
+ @view = @repo.create_view("test_view", "Test View", "Test Subtitle")
21
+ end
22
+
23
+ def teardown
24
+ FileUtils.rm_rf(@test_dir) if File.exist?(@test_dir)
25
+ FileUtils.rm_rf("test/scriptorium-TEST") if File.exist?("test/scriptorium-TEST")
26
+ end
27
+
28
+ # ========================================
29
+ # clean_slugify function tests
30
+ # ========================================
31
+
32
+ def test_001_clean_slugify_basic
33
+ result = clean_slugify("My Test Post")
34
+ assert_equal "my-test-post", result
35
+ end
36
+
37
+ def test_002_clean_slugify_with_special_characters
38
+ result = clean_slugify("Post with & < > \" ' characters!")
39
+ assert_equal "post-with-characters", result
40
+ end
41
+
42
+ def test_003_clean_slugify_with_underscores_and_hyphens
43
+ result = clean_slugify("Post with_underscores-and-hyphens")
44
+ assert_equal "post-with-underscores-and-hyphens", result
45
+ end
46
+
47
+ def test_004_clean_slugify_with_multiple_spaces
48
+ result = clean_slugify("Post with multiple spaces")
49
+ assert_equal "post-with-multiple-spaces", result
50
+ end
51
+
52
+ def test_005_clean_slugify_with_leading_trailing_hyphens
53
+ result = clean_slugify("-Post with leading/trailing hyphens-")
54
+ assert_equal "post-with-leadingtrailing-hyphens", result
55
+ end
56
+
57
+ # ========================================
58
+ # Symlink generation tests
59
+ # ========================================
60
+
61
+ def test_006_symlink_creation_for_post
62
+ # Create a test post
63
+ post_title = "My First Test Post"
64
+ post_body = "This is the body of my test post."
65
+
66
+ # Create draft and finish it
67
+ draft_name = @repo.create_draft(title: post_title, body: post_body)
68
+ post_num = @repo.finish_draft(draft_name)
69
+
70
+ # Generate the post
71
+ @repo.generate_post(post_num)
72
+
73
+ # Check that both files exist
74
+ numbered_slug = slugify(post_num, post_title) + ".html"
75
+ clean_slug = clean_slugify(post_title) + ".html"
76
+
77
+ numbered_path = @view.dir/:output/:permalink/numbered_slug
78
+ clean_symlink_path = @view.dir/:output/:permalink/clean_slug
79
+
80
+ # Both files should exist
81
+ assert File.exist?(numbered_path), "Numbered post file should exist: #{numbered_path}"
82
+ assert File.exist?(clean_symlink_path), "Clean symlink should exist: #{clean_symlink_path}"
83
+
84
+ # Clean symlink should be a symlink
85
+ assert File.symlink?(clean_symlink_path), "Clean symlink should be a symlink"
86
+
87
+ # Symlink should point to the numbered file
88
+ symlink_target = File.readlink(clean_symlink_path)
89
+ expected_target = numbered_slug
90
+ assert_equal expected_target, symlink_target, "Symlink should point to numbered file"
91
+ end
92
+
93
+ def test_007_symlink_points_to_correct_file
94
+ # Create a test post with a specific title
95
+ post_title = "Another Test Post"
96
+ post_body = "This is another test post."
97
+
98
+ # Create draft and finish it
99
+ draft_name = @repo.create_draft(title: post_title, body: post_body)
100
+ post_num = @repo.finish_draft(draft_name)
101
+
102
+ # Generate the post
103
+ @repo.generate_post(post_num)
104
+
105
+ # Check symlink target
106
+ clean_slug = clean_slugify(post_title) + ".html"
107
+ clean_symlink_path = @view.dir/:output/:permalink/clean_slug
108
+
109
+ assert File.symlink?(clean_symlink_path), "Should be a symlink"
110
+
111
+ # Get the actual target
112
+ symlink_target = File.readlink(clean_symlink_path)
113
+
114
+ # Expected target should be the numbered slug
115
+ expected_slug = slugify(post_num, post_title) + ".html"
116
+ assert_equal expected_slug, symlink_target, "Symlink should point to correct numbered file"
117
+ end
118
+
119
+ def test_008_symlink_overwrites_existing
120
+ # Create a test post
121
+ post_title = "Duplicate Test Post"
122
+ post_body = "This is a test post."
123
+
124
+ # Create draft and finish it
125
+ draft_name = @repo.create_draft(title: post_title, body: post_body)
126
+ post_num = @repo.finish_draft(draft_name)
127
+
128
+ # Generate the post (should create symlink)
129
+ @repo.generate_post(post_num)
130
+
131
+ # Check that symlink exists
132
+ clean_slug = clean_slugify(post_title) + ".html"
133
+ clean_symlink_path = @view.dir/:output/:permalink/clean_slug
134
+
135
+ assert File.symlink?(clean_symlink_path), "Symlink should exist"
136
+
137
+ # Get the target
138
+ original_target = File.readlink(clean_symlink_path)
139
+
140
+ # Now create another post with the same clean slug (different numbered slug)
141
+ post_title2 = "Duplicate Test Post" # Same title, different post
142
+ post_body2 = "This is another test post with same title."
143
+
144
+ draft_name2 = @repo.create_draft(title: post_title2, body: post_body2)
145
+ post_num2 = @repo.finish_draft(draft_name2)
146
+
147
+ # Generate the second post (should overwrite symlink)
148
+ @repo.generate_post(post_num2)
149
+
150
+ # Check that symlink still exists and points to the new target
151
+ assert File.symlink?(clean_symlink_path), "Symlink should still exist"
152
+
153
+ new_target = File.readlink(clean_symlink_path)
154
+ expected_target = slugify(post_num2, post_title2) + ".html"
155
+
156
+ assert_equal expected_target, new_target, "Symlink should point to new target"
157
+ refute_equal original_target, new_target, "Symlink should have been updated"
158
+ end
159
+
160
+ def test_009_symlink_with_special_characters_in_title
161
+ # Test with a title that has special characters
162
+ post_title = "Post with Special Characters: & < > \" ' !"
163
+ post_body = "This post has special characters in the title."
164
+
165
+ # Create draft and finish it
166
+ draft_name = @repo.create_draft(title: post_title, body: post_body)
167
+ post_num = @repo.finish_draft(draft_name)
168
+
169
+ # Generate the post
170
+ @repo.generate_post(post_num)
171
+
172
+ # Check that symlink exists with cleaned slug
173
+ clean_slug = clean_slugify(post_title) + ".html"
174
+ clean_symlink_path = @view.dir/:output/:permalink/clean_slug
175
+
176
+ assert File.exist?(clean_symlink_path), "Symlink should exist for cleaned title"
177
+ assert File.symlink?(clean_symlink_path), "Should be a symlink"
178
+
179
+ # Check that the symlink points to the correct numbered file
180
+ symlink_target = File.readlink(clean_symlink_path)
181
+ expected_target = slugify(post_num, post_title) + ".html"
182
+ assert_equal expected_target, symlink_target, "Symlink should point to correct numbered file"
183
+ end
184
+
185
+ def test_010_symlink_deployment_ready
186
+ # Test that symlinks are created in the correct location for deployment
187
+ post_title = "Deployment Test Post"
188
+ post_body = "This post tests deployment readiness."
189
+
190
+ # Create draft and finish it
191
+ draft_name = @repo.create_draft(title: post_title, body: post_body)
192
+ post_num = @repo.finish_draft(draft_name)
193
+
194
+ # Generate the post
195
+ @repo.generate_post(post_num)
196
+
197
+ # Check that symlink is in the correct deployment location
198
+ clean_slug = clean_slugify(post_title) + ".html"
199
+ clean_symlink_path = @view.dir/:output/:permalink/clean_slug
200
+
201
+ # Verify the path structure
202
+ assert_equal @view.dir/:output/:permalink/clean_slug, clean_symlink_path
203
+ assert File.exist?(clean_symlink_path), "Symlink should exist in deployment location"
204
+
205
+ # Verify it's a symlink
206
+ assert File.symlink?(clean_symlink_path), "Should be a symlink"
207
+
208
+ # Verify the target exists and is accessible
209
+ symlink_target = File.readlink(clean_symlink_path)
210
+ target_path = @view.dir/:output/:permalink/symlink_target
211
+ assert File.exist?(target_path), "Symlink target should exist"
212
+ end
213
+ end