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.
- checksums.yaml +4 -4
- data/README.lt3 +324 -0
- data/README.md +3155 -1
- data/assets/.DS_Store +0 -0
- data/assets/README.md +44 -0
- data/assets/back-icon.png +0 -0
- data/assets/icons/facebook.svg +1 -0
- data/assets/icons/github.svg +1 -0
- data/assets/icons/instagram.svg +1 -0
- data/assets/icons/reddit.svg +1 -0
- data/assets/icons/ui/.DS_Store +0 -0
- data/assets/icons/ui/back.png +0 -0
- data/assets/icons/ui/copy.png +0 -0
- data/assets/icons/ui/down.png +0 -0
- data/assets/icons/ui/end.png +0 -0
- data/assets/icons/ui/exit.png +0 -0
- data/assets/icons/ui/foo +10 -0
- data/assets/icons/ui/home.png +0 -0
- data/assets/icons/ui/left.png +0 -0
- data/assets/icons/ui/next.png +0 -0
- data/assets/icons/ui/right.png +0 -0
- data/assets/icons/ui/start.png +0 -0
- data/assets/icons/ui/up.png +0 -0
- data/assets/icons/x.svg +1 -0
- data/assets/icons/youtube.svg +1 -0
- data/assets/samples/placeholder.svg +9 -0
- data/assets/themes/standard/favicon.svg +6 -0
- data/bin/scriptorium +1511 -0
- data/doc/README.txt +6 -0
- data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +95 -0
- data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +34 -0
- data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +50 -0
- data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +31 -0
- data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +73 -0
- data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +64 -0
- data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +70 -0
- data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
- data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +110 -0
- data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +73 -0
- data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
- data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +124 -0
- data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +46 -0
- data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +97 -0
- data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
- data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +211 -0
- data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +141 -0
- data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +211 -0
- data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +134 -0
- data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +41 -0
- data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +45 -0
- data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +30 -0
- data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +49 -0
- data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +46 -0
- data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +41 -0
- data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +41 -0
- data/doc/anti-amnesia/20250807-213025.md +116 -0
- data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +256 -0
- data/doc/banner_svg_config.md +114 -0
- data/doc/contrib.lt3 +8 -0
- data/doc/dependencies.md +281 -0
- data/doc/hacker.lt3 +5 -0
- data/doc/reddit_credentials_template.json +8 -0
- data/doc/reddit_integration.md +207 -0
- data/doc/user.lt3 +38 -0
- data/doc/user_guide_section_1.md +137 -0
- data/doc/user_guide_section_10.md +515 -0
- data/doc/user_guide_section_11.md +708 -0
- data/doc/user_guide_section_2.md +233 -0
- data/doc/user_guide_section_3.md +5 -0
- data/doc/user_guide_section_4.md +221 -0
- data/doc/user_guide_section_5.md +243 -0
- data/doc/user_guide_section_6.md +147 -0
- data/doc/user_guide_section_7.md +311 -0
- data/doc/user_guide_section_8.md +224 -0
- data/doc/user_guide_section_9.md +375 -0
- data/doc/userdoc-toc.txt +88 -0
- data/lib/rouge/lexers/livetext.rb +74 -0
- data/lib/scriptorium/api.rb +640 -0
- data/lib/scriptorium/banner_svg.rb +742 -0
- data/lib/scriptorium/contract.rb +33 -0
- data/lib/scriptorium/exceptions.rb +170 -1
- data/lib/scriptorium/helpers.rb +475 -0
- data/lib/scriptorium/post.rb +195 -0
- data/lib/scriptorium/reddit.rb +83 -0
- data/lib/scriptorium/repo.rb +624 -0
- data/lib/scriptorium/standard_files.rb +515 -0
- data/lib/scriptorium/syntax_highlighter.rb +234 -0
- data/lib/scriptorium/theme.rb +179 -0
- data/lib/scriptorium/version.rb +2 -2
- data/lib/scriptorium/view.rb +976 -0
- data/lib/scriptorium/widgets/featured_posts.rb +149 -0
- data/lib/scriptorium/widgets/links.rb +112 -0
- data/lib/scriptorium/widgets/pages.rb +133 -0
- data/lib/scriptorium/widgets/widget.rb +133 -0
- data/lib/scriptorium.rb +21 -40
- data/lib/skeleton.rb +8 -2
- data/scriptorium.gemspec +15 -4
- data/test/README.md +69 -0
- data/test/all +43 -0
- data/test/api_demo.rb +99 -0
- data/test/assets/imagenotfound.jpg +0 -0
- data/test/assets/images/.DS_Store +0 -0
- data/test/assets/images/README.md +27 -0
- data/test/assets/images/odd_aspect.png +0 -0
- data/test/assets/images/perfect.png +0 -0
- data/test/assets/images/small.png +0 -0
- data/test/assets/images/tall.png +0 -0
- data/test/assets/images/very_tall.png +0 -0
- data/test/assets/images/very_wide.png +0 -0
- data/test/assets/images/wide.png +0 -0
- data/test/assets/testbanner.jpg +0 -0
- data/test/banner_svg/simple_helpers.rb +13 -0
- data/test/banner_svg/unit.rb +768 -0
- data/test/ed_test.rb +204 -0
- data/test/integration/cursor_banner_combinations.rb +193 -0
- data/test/integration/cursor_banner_features.rb +374 -0
- data/test/integration/integration_test.rb +326 -0
- data/test/livetext_plugin_test.rb +229 -0
- data/test/manual/asset_mgmt.rb +67 -0
- data/test/manual/banner-tests/config.txt +3 -0
- data/test/manual/banner-tests/index.html +45 -0
- data/test/manual/banner-tests/test01.html +58 -0
- data/test/manual/banner-tests/test02.html +58 -0
- data/test/manual/banner-tests/test03.html +58 -0
- data/test/manual/banner-tests/test04.html +65 -0
- data/test/manual/banner-tests/test05.html +65 -0
- data/test/manual/banner-tests/test06.html +65 -0
- data/test/manual/banner-tests/test07.html +65 -0
- data/test/manual/banner-tests/test08.html +59 -0
- data/test/manual/banner-tests/test09.html +59 -0
- data/test/manual/banner-tests/test10.html +59 -0
- data/test/manual/banner-tests/test11.html +59 -0
- data/test/manual/banner-tests/test12.html +59 -0
- data/test/manual/banner-tests/test13.html +59 -0
- data/test/manual/banner-tests/test14.html +59 -0
- data/test/manual/banner-tests/test15.html +58 -0
- data/test/manual/banner-tests/test16.html +58 -0
- data/test/manual/banner-tests/test17.html +58 -0
- data/test/manual/banner-tests/test18.html +68 -0
- data/test/manual/banner-tests/test19.html +68 -0
- data/test/manual/banner-tests/test20.html +68 -0
- data/test/manual/banner-tests/test21.html +68 -0
- data/test/manual/banner-tests/test22.html +68 -0
- data/test/manual/banner-tests/test23.html +68 -0
- data/test/manual/banner-tests/test24.html +68 -0
- data/test/manual/banner-tests/test25.html +67 -0
- data/test/manual/banner_environment.rb +192 -0
- data/test/manual/deploy_symlink_demo.rb +142 -0
- data/test/manual/environment.rb +67 -0
- data/test/manual/make_banner.rb +153 -0
- data/test/manual/sample_banner_config.txt +12 -0
- data/test/manual/symlink_demo.rb +117 -0
- data/test/manual/test1.rb +47 -0
- data/test/manual/test2.rb +12 -0
- data/test/manual/test3.rb +38 -0
- data/test/manual/test4.rb +40 -0
- data/test/manual/test5.rb +24 -0
- data/test/manual/test6.rb +73 -0
- data/test/manual/test_banner_combinations.rb +120 -0
- data/test/manual/test_banner_features.rb +306 -0
- data/test/manual/test_banner_from_file.rb +150 -0
- data/test/manual/test_banner_in_header.rb +35 -0
- data/test/manual/test_code_highlighting.rb +68 -0
- data/test/manual/test_complex_header.rb +74 -0
- data/test/manual/test_empty_header.rb +32 -0
- data/test/manual/test_radial_custom.rb +58 -0
- data/test/manual/test_radial_large_radius.rb +52 -0
- data/test/manual/test_svg_debug.rb +47 -0
- data/test/manual/test_syntax_highlighting.rb +147 -0
- data/test/pages-demo/config/currentview.txt +1 -0
- data/test/pages-demo/views/demo/config/bootstrap_css.txt +5 -0
- data/test/pages-demo/views/demo/config/bootstrap_js.txt +4 -0
- data/test/pages-demo/views/demo/config/common.js +57 -0
- data/test/pages-demo/views/demo/config/footer.txt +1 -0
- data/test/pages-demo/views/demo/config/global-head.txt +8 -0
- data/test/pages-demo/views/demo/config/header.txt +1 -0
- data/test/pages-demo/views/demo/config/layout.txt +1 -0
- data/test/pages-demo/views/demo/config/left.txt +1 -0
- data/test/pages-demo/views/demo/config/main.txt +1 -0
- data/test/pages-demo/views/demo/config/right.txt +1 -0
- data/test/pages-demo/views/demo/config.txt +3 -0
- data/test/pages-demo/views/demo/output/panes/footer.html +1 -0
- data/test/pages-demo/views/demo/output/panes/header.html +1 -0
- data/test/pages-demo/views/demo/output/panes/left.html +1 -0
- data/test/pages-demo/views/demo/output/panes/main.html +1 -0
- data/test/pages-demo/views/demo/output/panes/right.html +1 -0
- data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
- data/test/rubytext/rubytext_demo_test.rb +42 -0
- data/test/rubytext/rubytext_testing_guide.md +277 -0
- data/test/run_automated_tests.rb +45 -0
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/config/common.js +57 -0
- data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +9 -0
- data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +4 -0
- data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +8 -0
- data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +6 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 +12 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt +2 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/layout.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +1 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +14 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +13 -0
- data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +57 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +2 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +9 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +4 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +5 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/reddit.txt +10 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/social.txt +7 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +7 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +3 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +1 -0
- data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +1 -0
- data/test/staging/.DS_Store +0 -0
- data/test/syntax_highlighting_test.lt3 +124 -0
- data/test/test_helpers.rb +230 -0
- data/test/tui_editor_integration_test.rb +296 -0
- data/test/tui_integration_test.rb +637 -0
- data/test/unit/api.rb +1056 -0
- data/test/unit/asset_management.rb +245 -0
- data/test/unit/clipboard_test.rb +60 -0
- data/test/unit/contract_test.rb +91 -0
- data/test/unit/core.rb +857 -0
- data/test/unit/deploy_test.rb +187 -0
- data/test/unit/gem_asset_management.rb +189 -0
- data/test/unit/livetext_basic.rb +69 -0
- data/test/unit/livetext_compatibility.rb +89 -0
- data/test/unit/post.rb +244 -0
- data/test/unit/read_commented_file_test.rb +276 -0
- data/test/unit/reddit_test.rb +235 -0
- data/test/unit/repo.rb +548 -0
- data/test/unit/social_test.rb +369 -0
- data/test/unit/symlink_test.rb +213 -0
- data/test/unit/view.rb +431 -0
- data/test/unit/widgets.rb +669 -0
- data/test/wizard_test.rb +123 -0
- data/ui/README.md +67 -0
- data/ui/common/lib/ui_common.rb +8 -0
- data/ui/rubytext/README.md +191 -0
- data/ui/rubytext/bin/scriptorium-rubytext +402 -0
- data/ui/rubytext/lib/rubytext_ui.rb +300 -0
- data/ui/tui/bin/scriptorium +1420 -0
- data/ui/tui/test/tui_test.rb +23 -0
- data/ui/web/app/app.rb +1378 -0
- data/ui/web/app/error_helpers.rb +150 -0
- data/ui/web/app/views/advanced_config.erb +190 -0
- data/ui/web/app/views/asset_management.erb +589 -0
- data/ui/web/app/views/banner_config.erb +200 -0
- data/ui/web/app/views/configure_view.erb +401 -0
- data/ui/web/app/views/dashboard.erb +162 -0
- data/ui/web/app/views/deploy_config.erb +146 -0
- data/ui/web/app/views/edit_pages.erb +195 -0
- data/ui/web/app/views/edit_post.erb +54 -0
- data/ui/web/app/views/error_page.erb +29 -0
- data/ui/web/app/views/header_config.erb +155 -0
- data/ui/web/app/views/layout_config.erb +147 -0
- data/ui/web/app/views/navbar_config.erb +411 -0
- data/ui/web/app/views/view_dashboard.erb +138 -0
- data/ui/web/bin/scriptorium-web +153 -0
- data/ui/web/test/web_basic_test.rb +38 -0
- data/ui/web/test_navbar.txt +7 -0
- data/ui/web/tmp/web_server.log +5 -0
- data/ui/web/tmp/web_server.pid +1 -0
- metadata +359 -7
- data/lib/scriptorium/engine.rb +0 -22
- data/test/engine/unit.rb +0 -44
data/test/unit/api.rb
ADDED
@@ -0,0 +1,1056 @@
|
|
1
|
+
# test/unit/api.rb
|
2
|
+
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'open3'
|
6
|
+
require_relative '../../lib/scriptorium'
|
7
|
+
require_relative '../test_helpers'
|
8
|
+
|
9
|
+
class TestScriptoriumAPI < Minitest::Test
|
10
|
+
include Scriptorium::Exceptions
|
11
|
+
include Scriptorium::Helpers
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
FileUtils.rm_rf(@test_dir) if Dir.exist?(@test_dir)
|
17
|
+
@api = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup
|
21
|
+
@test_dir = "test/scriptorium-TEST"
|
22
|
+
# Clean up any existing test directory first
|
23
|
+
FileUtils.rm_rf(@test_dir) if Dir.exist?(@test_dir)
|
24
|
+
@api = Scriptorium::API.new(testmode: true)
|
25
|
+
@api.create_repo(@test_dir)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Basic API functionality tests
|
29
|
+
|
30
|
+
def test_001_api_initialization
|
31
|
+
assert_instance_of Scriptorium::API, @api
|
32
|
+
assert_instance_of Scriptorium::Repo, @api.repo
|
33
|
+
refute_nil @api.current_view
|
34
|
+
assert_equal "sample", @api.current_view.name
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_002_create_view
|
38
|
+
@api.create_view("test_view", "Test View", "A test view")
|
39
|
+
|
40
|
+
assert_equal "test_view", @api.current_view.name
|
41
|
+
assert_equal "Test View", @api.current_view.title
|
42
|
+
assert_equal "A test view", @api.current_view.subtitle
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_003_create_post
|
46
|
+
@api.create_view("test_view", "Test View")
|
47
|
+
post = @api.create_post("Test Post", "Test body", tags: ["test"])
|
48
|
+
|
49
|
+
assert_instance_of Scriptorium::Post, post
|
50
|
+
assert_equal "Test Post", post.title
|
51
|
+
assert_equal "test", post.tags
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_004_posts
|
55
|
+
@api.create_view("test_view", "Test View")
|
56
|
+
@api.create_post("Post 1", "Body 1")
|
57
|
+
@api.create_post("Post 2", "Body 2")
|
58
|
+
|
59
|
+
posts = @api.posts
|
60
|
+
assert_equal 2, posts.length
|
61
|
+
# Posts might not be in creation order, so check both exist
|
62
|
+
titles = posts.map(&:title)
|
63
|
+
assert_includes titles, "Post 1"
|
64
|
+
assert_includes titles, "Post 2"
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_005_post
|
68
|
+
@api.create_view("test_view", "Test View")
|
69
|
+
created_post = @api.create_post("Test Post", "Test body")
|
70
|
+
|
71
|
+
retrieved_post = @api.post(created_post.id)
|
72
|
+
assert_equal created_post.id, retrieved_post.id
|
73
|
+
assert_equal "Test Post", retrieved_post.title
|
74
|
+
end
|
75
|
+
|
76
|
+
# New API methods tests
|
77
|
+
def test_006_views
|
78
|
+
@api.create_view("view1", "View 1")
|
79
|
+
@api.create_view("view2", "View 2")
|
80
|
+
|
81
|
+
views = @api.views.map(&:name)
|
82
|
+
assert_includes views, "view1"
|
83
|
+
assert_includes views, "view2"
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_007_post_attrs
|
87
|
+
@api.create_view("test_view", "Test View")
|
88
|
+
post = @api.create_post("Test Post", "Test body", tags: ["test", "demo"])
|
89
|
+
|
90
|
+
attrs = @api.post_attrs(post.id, :title, :tags)
|
91
|
+
assert_equal ["Test Post", "test, demo"], attrs
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_008_post_attrs_with_post_object
|
95
|
+
@api.create_view("test_view", "Test View")
|
96
|
+
post = @api.create_post("Test Post", "Test body", tags: ["test"])
|
97
|
+
|
98
|
+
attrs = @api.post_attrs(post, :title, :tags)
|
99
|
+
assert_equal ["Test Post", "test"], attrs
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_009_views_for
|
103
|
+
@api.create_view("test_view", "Test View")
|
104
|
+
post = @api.create_post("Test Post", "Test body")
|
105
|
+
|
106
|
+
views = @api.views_for(post)
|
107
|
+
assert_equal ["test_view"], views
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_010_views_for_with_id
|
111
|
+
@api.create_view("test_view", "Test View")
|
112
|
+
post = @api.create_post("Test Post", "Test body")
|
113
|
+
|
114
|
+
views = @api.views_for(post.id)
|
115
|
+
assert_equal ["test_view"], views
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_011_apply_theme
|
119
|
+
@api.create_view("test_view", "Test View")
|
120
|
+
|
121
|
+
# Should not raise an error
|
122
|
+
@api.apply_theme("standard")
|
123
|
+
assert_equal "standard", @api.current_view.theme
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
# Empty methods tests (should not raise errors)
|
129
|
+
def test_012_empty_methods
|
130
|
+
assert_equal [], @api.drafts
|
131
|
+
# Widgets are now available from widgets.txt
|
132
|
+
widgets = @api.widgets_available
|
133
|
+
assert_includes widgets, "links"
|
134
|
+
assert_includes widgets, "pages"
|
135
|
+
|
136
|
+
# These should not raise errors
|
137
|
+
# @api.delete_draft("some_path") # This would fail since it's not a valid draft path
|
138
|
+
# @api.delete_post(1) # This would fail since post doesn't exist
|
139
|
+
# @api.generate_widget("some_widget") # This would fail since no current view set
|
140
|
+
@api.select_posts { |p| true }
|
141
|
+
@api.search_posts(title: "query")
|
142
|
+
# @api.unlink_post(nil, nil) # This would fail since no current view and invalid post
|
143
|
+
# @api.generate_all # This would fail since no current view set
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_013_drafts
|
147
|
+
drafts = @api.drafts
|
148
|
+
assert_instance_of Array, drafts
|
149
|
+
# Should return empty array if no drafts directory exists
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_014_themes_available
|
153
|
+
themes = @api.themes_available
|
154
|
+
assert_instance_of Array, themes
|
155
|
+
assert_includes themes, "standard" # Should have the standard theme
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_015_widgets_available
|
159
|
+
widgets = @api.widgets_available
|
160
|
+
assert_instance_of Array, widgets
|
161
|
+
# Should return available widgets from widgets.txt
|
162
|
+
assert_includes widgets, "links"
|
163
|
+
assert_includes widgets, "pages"
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_016_generate_view
|
167
|
+
@api.create_view("test_view", "Test View")
|
168
|
+
|
169
|
+
# Should not raise an error
|
170
|
+
@api.generate_view
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_017_generate_view_with_specific_view
|
174
|
+
@api.create_view("view1", "View 1")
|
175
|
+
@api.create_view("view2", "View 2")
|
176
|
+
|
177
|
+
# Should not raise an error
|
178
|
+
@api.generate_view("view1")
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
# Error handling tests
|
184
|
+
def test_018_create_post_without_view
|
185
|
+
# Clear the current view directly
|
186
|
+
@api.repo.instance_variable_set(:@current_view, nil)
|
187
|
+
|
188
|
+
assert_raises(RuntimeError) do
|
189
|
+
@api.create_post("Test Post", "Test body")
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_019_safe_delete_post
|
194
|
+
@api.create_view("test_view", "Test View")
|
195
|
+
post = @api.create_post("Test Post", "Test body")
|
196
|
+
|
197
|
+
# Initially visible
|
198
|
+
posts = @api.posts
|
199
|
+
assert_equal 1, posts.length
|
200
|
+
|
201
|
+
# Delete the post
|
202
|
+
@api.delete_post(post.id)
|
203
|
+
|
204
|
+
# Should not appear in posts list
|
205
|
+
posts = @api.posts
|
206
|
+
assert_equal 0, posts.length
|
207
|
+
|
208
|
+
# But post object still exists and can be retrieved
|
209
|
+
retrieved_post = @api.post(post.id)
|
210
|
+
assert_equal "Test Post", retrieved_post.title
|
211
|
+
end
|
212
|
+
|
213
|
+
def test_020_undelete_post
|
214
|
+
@api.create_view("test_view", "Test View")
|
215
|
+
post = @api.create_post("Test Post", "Test body")
|
216
|
+
|
217
|
+
# Delete the post
|
218
|
+
@api.delete_post(post.id)
|
219
|
+
assert_equal 0, @api.posts.length
|
220
|
+
|
221
|
+
# Undelete the post
|
222
|
+
@api.undelete_post(post.id)
|
223
|
+
|
224
|
+
# Should appear in posts list again
|
225
|
+
posts = @api.posts
|
226
|
+
assert_equal 1, posts.length
|
227
|
+
assert_equal "Test Post", posts[0].title
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_021_update_post
|
231
|
+
@api.create_view("test_view", "Test View")
|
232
|
+
@api.create_view("other_view", "Other View")
|
233
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view", "other_view"])
|
234
|
+
|
235
|
+
# Update the views field
|
236
|
+
result = @api.update_post(post.id, {views: ["test_view"]})
|
237
|
+
assert result
|
238
|
+
|
239
|
+
# Check that the source file was updated
|
240
|
+
source_file = post.dir/"source.lt3"
|
241
|
+
content = read_file(source_file)
|
242
|
+
assert_includes content, ".views test_view"
|
243
|
+
assert_includes content, "# updated views"
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_022_update_post_preserves_comments
|
247
|
+
@api.create_view("test_view", "Test View")
|
248
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view"])
|
249
|
+
|
250
|
+
# Manually add a comment to the source file
|
251
|
+
source_file = post.dir/"source.lt3"
|
252
|
+
lines = read_file(source_file, lines: true, chomp: false)
|
253
|
+
lines.map! do |line|
|
254
|
+
if line.strip.start_with?('.views')
|
255
|
+
".views test_view # original comment\n"
|
256
|
+
else
|
257
|
+
line
|
258
|
+
end
|
259
|
+
end
|
260
|
+
write_file(source_file, lines.join)
|
261
|
+
|
262
|
+
# Update the views field
|
263
|
+
result = @api.update_post(post.id, {views: ["new_view"]})
|
264
|
+
assert result
|
265
|
+
|
266
|
+
# Check that original comment is preserved
|
267
|
+
content = read_file(source_file)
|
268
|
+
assert_includes content, "# original comment"
|
269
|
+
assert_includes content, "# updated views"
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_023_update_post_multiple_fields
|
273
|
+
@api.create_view("test_view", "Test View")
|
274
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view"])
|
275
|
+
|
276
|
+
# Update multiple fields at once
|
277
|
+
result = @api.update_post(post.id, {
|
278
|
+
title: "Updated Title",
|
279
|
+
tags: ["new", "tags"]
|
280
|
+
})
|
281
|
+
assert result
|
282
|
+
|
283
|
+
# Check that both fields were updated
|
284
|
+
source_file = post.dir/"source.lt3"
|
285
|
+
content = read_file(source_file)
|
286
|
+
assert_includes content, ".title Updated Title"
|
287
|
+
assert_includes content, ".tags new, tags"
|
288
|
+
assert_includes content, "# updated title"
|
289
|
+
assert_includes content, "# updated tags"
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
|
294
|
+
def test_024_unlink_post
|
295
|
+
@api.create_view("test_view", "Test View")
|
296
|
+
@api.create_view("other_view", "Other View")
|
297
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view", "other_view"])
|
298
|
+
|
299
|
+
# Initially post should be in both views
|
300
|
+
assert_includes post.views, "test_view"
|
301
|
+
assert_includes post.views, "other_view"
|
302
|
+
|
303
|
+
# Unlink from current view
|
304
|
+
result = @api.unlink_post(post.id)
|
305
|
+
assert result
|
306
|
+
|
307
|
+
# Post should now only be in test_view (since we unlinked from other_view)
|
308
|
+
updated_post = @api.post(post.id)
|
309
|
+
updated_views = updated_post.views.strip.split(/\s+/)
|
310
|
+
assert_includes updated_views, "test_view"
|
311
|
+
refute_includes updated_views, "other_view"
|
312
|
+
end
|
313
|
+
|
314
|
+
def test_025_link_post
|
315
|
+
@api.create_view("test_view", "Test View")
|
316
|
+
@api.create_view("other_view", "Other View")
|
317
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view"])
|
318
|
+
|
319
|
+
# Initially post should only be in test_view
|
320
|
+
assert_includes post.views, "test_view"
|
321
|
+
refute_includes post.views, "other_view"
|
322
|
+
|
323
|
+
# Link to other_view
|
324
|
+
result = @api.link_post(post.id, "other_view")
|
325
|
+
assert result
|
326
|
+
|
327
|
+
# Post should now be in both views
|
328
|
+
updated_post = @api.post(post.id)
|
329
|
+
updated_views = updated_post.views.strip.split(/\s+/)
|
330
|
+
assert_includes updated_views, "test_view"
|
331
|
+
assert_includes updated_views, "other_view"
|
332
|
+
end
|
333
|
+
|
334
|
+
def test_026_link_post_current_view
|
335
|
+
@api.create_view("test_view", "Test View")
|
336
|
+
@api.create_view("other_view", "Other View")
|
337
|
+
post = @api.create_post("Test Post", "Test body", views: ["other_view"])
|
338
|
+
|
339
|
+
# Initially post should only be in other_view
|
340
|
+
assert_includes post.views, "other_view"
|
341
|
+
refute_includes post.views, "test_view"
|
342
|
+
|
343
|
+
# Set current view to test_view
|
344
|
+
@api.view("test_view")
|
345
|
+
|
346
|
+
# Link to current view (test_view)
|
347
|
+
result = @api.link_post(post.id)
|
348
|
+
assert result
|
349
|
+
|
350
|
+
# Post should now be in both views
|
351
|
+
updated_post = @api.post(post.id)
|
352
|
+
updated_views = updated_post.views.strip.split(/\s+/)
|
353
|
+
assert_includes updated_views, "test_view"
|
354
|
+
assert_includes updated_views, "other_view"
|
355
|
+
end
|
356
|
+
|
357
|
+
def test_027_link_post_duplicate
|
358
|
+
@api.create_view("test_view", "Test View")
|
359
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view"])
|
360
|
+
|
361
|
+
# Initially post should be in test_view
|
362
|
+
assert_includes post.views, "test_view"
|
363
|
+
|
364
|
+
# Try to link to the same view (should not add duplicate)
|
365
|
+
result = @api.link_post(post.id, "test_view")
|
366
|
+
assert result
|
367
|
+
|
368
|
+
# Post should still only be in test_view (no duplicates)
|
369
|
+
updated_post = @api.post(post.id)
|
370
|
+
updated_views = updated_post.views.strip.split(/\s+/)
|
371
|
+
assert_equal ["test_view"], updated_views
|
372
|
+
end
|
373
|
+
|
374
|
+
def test_028_post_add_view
|
375
|
+
@api.create_view("test_view", "Test View")
|
376
|
+
@api.create_view("other_view", "Other View")
|
377
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view"])
|
378
|
+
|
379
|
+
# Initially post should only be in test_view
|
380
|
+
assert_includes post.views, "test_view"
|
381
|
+
refute_includes post.views, "other_view"
|
382
|
+
|
383
|
+
# Add other_view to the post
|
384
|
+
result = @api.post_add_view(post.id, "other_view")
|
385
|
+
assert result
|
386
|
+
|
387
|
+
# Post should now be in both views
|
388
|
+
updated_post = @api.post(post.id)
|
389
|
+
updated_views = updated_post.views.strip.split(/\s+/)
|
390
|
+
assert_includes updated_views, "test_view"
|
391
|
+
assert_includes updated_views, "other_view"
|
392
|
+
end
|
393
|
+
|
394
|
+
def test_029_post_add_view_with_view_object
|
395
|
+
@api.create_view("test_view", "Test View")
|
396
|
+
@api.create_view("other_view", "Other View")
|
397
|
+
other_view = @api.view("other_view") # Get the View object
|
398
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view"])
|
399
|
+
|
400
|
+
# Add other_view to the post using View object
|
401
|
+
result = @api.post_add_view(post.id, other_view)
|
402
|
+
assert result
|
403
|
+
|
404
|
+
# Post should now be in both views
|
405
|
+
updated_post = @api.post(post.id)
|
406
|
+
updated_views = updated_post.views.strip.split(/\s+/)
|
407
|
+
assert_includes updated_views, "test_view"
|
408
|
+
assert_includes updated_views, "other_view"
|
409
|
+
end
|
410
|
+
|
411
|
+
def test_030_post_remove_view
|
412
|
+
@api.create_view("test_view", "Test View")
|
413
|
+
@api.create_view("other_view", "Other View")
|
414
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view", "other_view"])
|
415
|
+
|
416
|
+
# Initially post should be in both views
|
417
|
+
assert_includes post.views, "test_view"
|
418
|
+
assert_includes post.views, "other_view"
|
419
|
+
|
420
|
+
# Remove other_view from the post
|
421
|
+
result = @api.post_remove_view(post.id, "other_view")
|
422
|
+
assert result
|
423
|
+
|
424
|
+
# Post should now only be in test_view
|
425
|
+
updated_post = @api.post(post.id)
|
426
|
+
updated_views = updated_post.views.strip.split(/\s+/)
|
427
|
+
assert_includes updated_views, "test_view"
|
428
|
+
refute_includes updated_views, "other_view"
|
429
|
+
end
|
430
|
+
|
431
|
+
def test_031_post_remove_view_with_view_object
|
432
|
+
@api.create_view("test_view", "Test View")
|
433
|
+
@api.create_view("other_view", "Other View")
|
434
|
+
other_view = @api.view("other_view") # Get the View object
|
435
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view", "other_view"])
|
436
|
+
|
437
|
+
# Remove other_view from the post using View object
|
438
|
+
result = @api.post_remove_view(post.id, other_view)
|
439
|
+
assert result
|
440
|
+
|
441
|
+
# Post should now only be in test_view
|
442
|
+
updated_post = @api.post(post.id)
|
443
|
+
updated_views = updated_post.views.strip.split(/\s+/)
|
444
|
+
assert_includes updated_views, "test_view"
|
445
|
+
refute_includes updated_views, "other_view"
|
446
|
+
end
|
447
|
+
|
448
|
+
def test_032_update_post_blurb
|
449
|
+
@api.create_view("test_view", "Test View")
|
450
|
+
post = @api.create_post("Test Post", "Test body")
|
451
|
+
|
452
|
+
# Manually add a blurb line to the source file
|
453
|
+
source_file = post.dir/"source.lt3"
|
454
|
+
lines = read_file(source_file, lines: true, chomp: false)
|
455
|
+
lines.insert(-2, ".blurb This is just a short intro to this post.\n") # Insert before the body
|
456
|
+
write_file(source_file, lines.join)
|
457
|
+
|
458
|
+
# Update the blurb
|
459
|
+
result = @api.update_post(post.id, {blurb: "Updated blurb for this post"})
|
460
|
+
assert result
|
461
|
+
|
462
|
+
# Check that the blurb was updated
|
463
|
+
content = read_file(source_file)
|
464
|
+
assert_includes content, ".blurb Updated blurb for this post"
|
465
|
+
assert_includes content, "# updated blurb"
|
466
|
+
end
|
467
|
+
|
468
|
+
def test_033_delete_draft
|
469
|
+
@api.create_view("test_view", "Test View")
|
470
|
+
|
471
|
+
# Create a draft
|
472
|
+
draft_path = @api.draft(title: "Test Draft", body: "Test body")
|
473
|
+
|
474
|
+
# Verify draft exists
|
475
|
+
drafts = @api.drafts
|
476
|
+
assert_equal 1, drafts.length
|
477
|
+
assert_equal draft_path, drafts.first[:path]
|
478
|
+
|
479
|
+
# Delete the draft
|
480
|
+
result = @api.delete_draft(draft_path)
|
481
|
+
assert result
|
482
|
+
|
483
|
+
# Verify draft is gone
|
484
|
+
drafts = @api.drafts
|
485
|
+
assert_equal 0, drafts.length
|
486
|
+
end
|
487
|
+
|
488
|
+
def test_034_delete_draft_invalid_path
|
489
|
+
@api.create_view("test_view", "Test View")
|
490
|
+
|
491
|
+
# Test with non-draft file
|
492
|
+
assert_raises(RuntimeError) do
|
493
|
+
@api.delete_draft("not-a-draft.txt")
|
494
|
+
end
|
495
|
+
|
496
|
+
# Test with non-existent file
|
497
|
+
assert_raises(RuntimeError) do
|
498
|
+
@api.delete_draft("nonexistent-draft.lt3")
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def test_035_generate_view
|
503
|
+
@api.create_view("test_view", "Test View")
|
504
|
+
@api.create_post("Test Post", "Test body")
|
505
|
+
|
506
|
+
# Should not raise an error
|
507
|
+
result = @api.generate_view
|
508
|
+
assert result
|
509
|
+
end
|
510
|
+
|
511
|
+
|
512
|
+
|
513
|
+
def test_036_generate_widget
|
514
|
+
@api.create_view("test_view", "Test View")
|
515
|
+
|
516
|
+
# Create the widget directory and sample data
|
517
|
+
widget_dir = @api.repo.root/:views/"test_view"/:widgets/"links"
|
518
|
+
make_dir(widget_dir)
|
519
|
+
write_file(widget_dir/"list.txt", "https://example.com, Example Link")
|
520
|
+
|
521
|
+
# Should not raise an error for a valid widget
|
522
|
+
result = @api.generate_widget("links")
|
523
|
+
assert result
|
524
|
+
|
525
|
+
# Verify the widget files were created
|
526
|
+
assert File.exist?(widget_dir/"links-card.html")
|
527
|
+
end
|
528
|
+
|
529
|
+
|
530
|
+
|
531
|
+
def test_037_generate_widget_invalid_name
|
532
|
+
@api.create_view("test_view", "Test View")
|
533
|
+
|
534
|
+
# Test with invalid widget name
|
535
|
+
assert_raises(RuntimeError) do
|
536
|
+
@api.generate_widget("invalid-widget")
|
537
|
+
end
|
538
|
+
|
539
|
+
# Test with nil
|
540
|
+
assert_raises(RuntimeError) do
|
541
|
+
@api.generate_widget(nil)
|
542
|
+
end
|
543
|
+
|
544
|
+
# Test with empty string
|
545
|
+
assert_raises(RuntimeError) do
|
546
|
+
@api.generate_widget("")
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
def test_038_generate_widget_nonexistent
|
551
|
+
@api.create_view("test_view", "Test View")
|
552
|
+
|
553
|
+
# Test with non-existent widget class
|
554
|
+
assert_raises(RuntimeError) do
|
555
|
+
@api.generate_widget("nonexistent")
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
def test_039_select_posts
|
560
|
+
@api.create_view("test_view", "Test View")
|
561
|
+
@api.create_view("other_view", "Other View")
|
562
|
+
|
563
|
+
# Create posts in different views
|
564
|
+
post1 = @api.create_post("Post 1", "Body 1", views: ["test_view"])
|
565
|
+
post2 = @api.create_post("Post 2", "Body 2", views: ["other_view"])
|
566
|
+
post3 = @api.create_post("Post 3", "Body 3", views: ["test_view", "other_view"])
|
567
|
+
|
568
|
+
# Test filtering by view
|
569
|
+
test_view_posts = @api.select_posts { |post| post.views.include?("test_view") }
|
570
|
+
assert_equal 2, test_view_posts.length
|
571
|
+
assert_includes test_view_posts.map(&:title), "Post 1"
|
572
|
+
assert_includes test_view_posts.map(&:title), "Post 3"
|
573
|
+
|
574
|
+
# Test filtering by title
|
575
|
+
title_posts = @api.select_posts { |post| post.title.include?("Post 2") }
|
576
|
+
assert_equal 1, title_posts.length
|
577
|
+
assert_equal "Post 2", title_posts.first.title
|
578
|
+
end
|
579
|
+
|
580
|
+
def test_040_search_posts
|
581
|
+
@api.create_view("test_view", "Test View")
|
582
|
+
|
583
|
+
# Create posts with different content
|
584
|
+
post1 = @api.create_post("Ruby Programming", "Learn Ruby basics", tags: "ruby, programming")
|
585
|
+
post2 = @api.create_post("Python Guide", "Python vs Ruby", tags: "python, comparison")
|
586
|
+
post3 = @api.create_post("Scriptorium API", "API documentation", tags: "api, scriptorium")
|
587
|
+
|
588
|
+
# Test title search with regex
|
589
|
+
ruby_posts = @api.search_posts(title: /Ruby/)
|
590
|
+
assert_equal 1, ruby_posts.length
|
591
|
+
assert_equal "Ruby Programming", ruby_posts.first.title
|
592
|
+
|
593
|
+
# Test body search with string
|
594
|
+
body_posts = @api.search_posts(body: "Ruby")
|
595
|
+
assert_equal 2, body_posts.length
|
596
|
+
assert_includes body_posts.map(&:title), "Ruby Programming"
|
597
|
+
assert_includes body_posts.map(&:title), "Python Guide"
|
598
|
+
|
599
|
+
# Test tags search
|
600
|
+
api_posts = @api.search_posts(tags: "api")
|
601
|
+
assert_equal 1, api_posts.length
|
602
|
+
assert_equal "Scriptorium API", api_posts.first.title
|
603
|
+
|
604
|
+
# Test multiple criteria (AND)
|
605
|
+
ruby_api_posts = @api.search_posts(title: /Ruby/, body: "basics")
|
606
|
+
assert_equal 1, ruby_api_posts.length
|
607
|
+
assert_equal "Ruby Programming", ruby_api_posts.first.title
|
608
|
+
end
|
609
|
+
|
610
|
+
def test_041_search_posts_with_blurb
|
611
|
+
@api.create_view("test_view", "Test View")
|
612
|
+
post = @api.create_post("Test Post", "Test body", blurb: "This is a test blurb for searching.")
|
613
|
+
|
614
|
+
# Test blurb search
|
615
|
+
blurb_posts = @api.search_posts(blurb: "test blurb")
|
616
|
+
assert_equal 1, blurb_posts.length
|
617
|
+
assert_equal "Test Post", blurb_posts.first.title
|
618
|
+
|
619
|
+
# Test blurb search with regex
|
620
|
+
regex_posts = @api.search_posts(blurb: /blurb.*search/)
|
621
|
+
assert_equal 1, regex_posts.length
|
622
|
+
assert_equal "Test Post", regex_posts.first.title
|
623
|
+
end
|
624
|
+
|
625
|
+
def test_042_search_posts_unknown_field
|
626
|
+
@api.create_view("test_view", "Test View")
|
627
|
+
|
628
|
+
# Create a post so the search actually processes something
|
629
|
+
@api.create_post("Test Post", "Test body")
|
630
|
+
|
631
|
+
assert_raises(RuntimeError) do
|
632
|
+
@api.search_posts(unknown_field: "value")
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
def test_043_unlink_post_specific_view
|
637
|
+
@api.create_view("test_view", "Test View")
|
638
|
+
@api.create_view("other_view", "Other View")
|
639
|
+
post = @api.create_post("Test Post", "Test body", views: ["test_view", "other_view"])
|
640
|
+
|
641
|
+
# Unlink from specific view
|
642
|
+
result = @api.unlink_post(post.id, "other_view")
|
643
|
+
assert result
|
644
|
+
|
645
|
+
# Post should now only be in test_view
|
646
|
+
updated_post = @api.post(post.id)
|
647
|
+
assert_includes updated_post.views, "test_view"
|
648
|
+
refute_includes updated_post.views, "other_view"
|
649
|
+
end
|
650
|
+
|
651
|
+
def test_044_post_add_tag
|
652
|
+
@api.create_view("test_view", "Test View")
|
653
|
+
post = @api.create_post("Test Post", "Test body", tags: ["ruby"])
|
654
|
+
|
655
|
+
# Initially post should only have ruby tag
|
656
|
+
assert_includes post.tags, "ruby"
|
657
|
+
refute_includes post.tags, "scriptorium"
|
658
|
+
|
659
|
+
# Add scriptorium tag to the post
|
660
|
+
result = @api.post_add_tag(post.id, "scriptorium")
|
661
|
+
assert result
|
662
|
+
|
663
|
+
# Post should now have both tags
|
664
|
+
updated_post = @api.post(post.id)
|
665
|
+
updated_tags = updated_post.tags.strip.split(/,\s*/)
|
666
|
+
assert_includes updated_tags, "ruby"
|
667
|
+
assert_includes updated_tags, "scriptorium"
|
668
|
+
end
|
669
|
+
|
670
|
+
def test_045_post_add_tag_duplicate
|
671
|
+
@api.create_view("test_view", "Test View")
|
672
|
+
post = @api.create_post("Test Post", "Test body", tags: ["ruby"])
|
673
|
+
|
674
|
+
# Initially post should have ruby tag
|
675
|
+
assert_includes post.tags, "ruby"
|
676
|
+
|
677
|
+
# Try to add the same tag (should not add duplicate)
|
678
|
+
result = @api.post_add_tag(post.id, "ruby")
|
679
|
+
assert result
|
680
|
+
|
681
|
+
# Post should still only have ruby tag (no duplicates)
|
682
|
+
updated_post = @api.post(post.id)
|
683
|
+
updated_tags = updated_post.tags.strip.split(/,\s*/)
|
684
|
+
assert_equal ["ruby"], updated_tags
|
685
|
+
end
|
686
|
+
|
687
|
+
def test_046_post_remove_tag
|
688
|
+
@api.create_view("test_view", "Test View")
|
689
|
+
post = @api.create_post("Test Post", "Test body", tags: ["ruby", "scriptorium"])
|
690
|
+
|
691
|
+
# Initially post should have both tags
|
692
|
+
assert_includes post.tags, "ruby"
|
693
|
+
assert_includes post.tags, "scriptorium"
|
694
|
+
|
695
|
+
# Remove scriptorium tag from the post
|
696
|
+
result = @api.post_remove_tag(post.id, "scriptorium")
|
697
|
+
assert result
|
698
|
+
|
699
|
+
# Post should now only have ruby tag
|
700
|
+
updated_post = @api.post(post.id)
|
701
|
+
updated_tags = updated_post.tags.strip.split(/,\s*/)
|
702
|
+
assert_includes updated_tags, "ruby"
|
703
|
+
refute_includes updated_tags, "scriptorium"
|
704
|
+
end
|
705
|
+
|
706
|
+
def test_047_post_remove_tag_nonexistent
|
707
|
+
@api.create_view("test_view", "Test View")
|
708
|
+
post = @api.create_post("Test Post", "Test body", tags: ["ruby"])
|
709
|
+
|
710
|
+
# Initially post should have ruby tag
|
711
|
+
assert_includes post.tags, "ruby"
|
712
|
+
|
713
|
+
# Try to remove a tag that doesn't exist
|
714
|
+
result = @api.post_remove_tag(post.id, "nonexistent")
|
715
|
+
assert result # Should succeed even if tag doesn't exist
|
716
|
+
|
717
|
+
# Post should still have ruby tag
|
718
|
+
updated_post = @api.post(post.id)
|
719
|
+
updated_tags = updated_post.tags.strip.split(/,\s*/)
|
720
|
+
assert_includes updated_tags, "ruby"
|
721
|
+
refute_includes updated_tags, "nonexistent"
|
722
|
+
end
|
723
|
+
|
724
|
+
# edit_file tests
|
725
|
+
def test_048_edit_file_validation_nil_path
|
726
|
+
assert_raises(CannotEditFilePathNil) do
|
727
|
+
@api.edit_file(nil)
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
def test_049_edit_file_validation_empty_path
|
732
|
+
assert_raises(CannotEditFilePathEmpty) do
|
733
|
+
@api.edit_file("")
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
737
|
+
def test_050_edit_file_validation_whitespace_path
|
738
|
+
assert_raises(CannotEditFilePathEmpty) do
|
739
|
+
@api.edit_file(" ")
|
740
|
+
end
|
741
|
+
end
|
742
|
+
|
743
|
+
def test_051_edit_file_uses_editor_from_env
|
744
|
+
# Mock ENV to return a specific editor
|
745
|
+
ENV.stub :[], "nano" do
|
746
|
+
# Mock system! to verify it's called with the right editor
|
747
|
+
mock_system = Minitest::Mock.new
|
748
|
+
mock_system.expect :call, true, ["nano", "/path/to/file"]
|
749
|
+
|
750
|
+
@api.stub :system!, mock_system do
|
751
|
+
@api.edit_file("/path/to/file")
|
752
|
+
end
|
753
|
+
|
754
|
+
mock_system.verify
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
def test_052_edit_file_uses_vim_fallback
|
759
|
+
# Mock ENV to return nil (no EDITOR set)
|
760
|
+
ENV.stub :[], nil do
|
761
|
+
# Mock system to return true (vim available)
|
762
|
+
@api.stub :system, true do
|
763
|
+
# Mock Open3.popen3 to return a mock process
|
764
|
+
mock_process = Minitest::Mock.new
|
765
|
+
mock_process.expect :pid, 123
|
766
|
+
mock_process.expect :wait, nil
|
767
|
+
|
768
|
+
Open3.stub :popen3, mock_process do
|
769
|
+
@api.edit_file("test.txt")
|
770
|
+
end
|
771
|
+
end
|
772
|
+
end
|
773
|
+
end
|
774
|
+
|
775
|
+
# Convenience file editing method tests
|
776
|
+
|
777
|
+
def test_053_edit_layout
|
778
|
+
@api.create_view("test_view", "Test View")
|
779
|
+
|
780
|
+
# Mock edit_file to track calls
|
781
|
+
called_path = nil
|
782
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
783
|
+
@api.edit_layout
|
784
|
+
assert_equal "views/test_view/layout.txt", called_path
|
785
|
+
end
|
786
|
+
end
|
787
|
+
|
788
|
+
def test_054_edit_layout_with_specific_view
|
789
|
+
@api.create_view("test_view", "Test View")
|
790
|
+
@api.create_view("other_view", "Other View")
|
791
|
+
|
792
|
+
# Mock edit_file to track calls
|
793
|
+
called_path = nil
|
794
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
795
|
+
@api.edit_layout("other_view")
|
796
|
+
assert_equal "views/other_view/layout.txt", called_path
|
797
|
+
end
|
798
|
+
end
|
799
|
+
|
800
|
+
def test_055_edit_layout_no_view
|
801
|
+
# Clear the current view
|
802
|
+
@api.repo.instance_variable_set(:@current_view, nil)
|
803
|
+
|
804
|
+
assert_raises(RuntimeError, "No view specified and no current view set") do
|
805
|
+
@api.edit_layout
|
806
|
+
end
|
807
|
+
end
|
808
|
+
|
809
|
+
def test_065_edit_config
|
810
|
+
@api.create_view("test_view", "Test View")
|
811
|
+
|
812
|
+
# Mock edit_file to track calls
|
813
|
+
called_path = nil
|
814
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
815
|
+
@api.edit_config
|
816
|
+
assert_equal "views/test_view/config.txt", called_path
|
817
|
+
end
|
818
|
+
end
|
819
|
+
|
820
|
+
def test_066_edit_config_with_specific_view
|
821
|
+
@api.create_view("test_view", "Test View")
|
822
|
+
@api.create_view("other_view", "Other View")
|
823
|
+
|
824
|
+
# Mock edit_file to track calls
|
825
|
+
called_path = nil
|
826
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
827
|
+
@api.edit_config("other_view")
|
828
|
+
assert_equal "views/other_view/config.txt", called_path
|
829
|
+
end
|
830
|
+
end
|
831
|
+
|
832
|
+
def test_067_edit_config_no_view
|
833
|
+
# Clear the current view
|
834
|
+
@api.repo.instance_variable_set(:@current_view, nil)
|
835
|
+
|
836
|
+
assert_raises(RuntimeError, "No view specified and no current view set") do
|
837
|
+
@api.edit_config
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
841
|
+
def test_056_edit_widget_data
|
842
|
+
@api.create_view("test_view", "Test View")
|
843
|
+
|
844
|
+
# Mock edit_file to track calls
|
845
|
+
called_path = nil
|
846
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
847
|
+
@api.edit_widget_data(nil, "links")
|
848
|
+
assert_equal "views/test_view/widgets/links/list.txt", called_path
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
def test_057_edit_widget_data_with_specific_view
|
853
|
+
@api.create_view("test_view", "Test View")
|
854
|
+
@api.create_view("other_view", "Other View")
|
855
|
+
|
856
|
+
# Mock edit_file to track calls
|
857
|
+
called_path = nil
|
858
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
859
|
+
@api.edit_widget_data("other_view", "news")
|
860
|
+
assert_equal "views/other_view/widgets/news/list.txt", called_path
|
861
|
+
end
|
862
|
+
end
|
863
|
+
|
864
|
+
def test_058_edit_widget_data_nil_widget
|
865
|
+
@api.create_view("test_view", "Test View")
|
866
|
+
|
867
|
+
assert_raises(RuntimeError, "Widget name cannot be nil") do
|
868
|
+
@api.edit_widget_data(nil, nil)
|
869
|
+
end
|
870
|
+
end
|
871
|
+
|
872
|
+
def test_060_edit_repo_config
|
873
|
+
# Mock edit_file to track calls
|
874
|
+
called_path = nil
|
875
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
876
|
+
@api.edit_repo_config
|
877
|
+
assert_equal "config/repo.txt", called_path
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
def test_061_edit_deploy_config
|
882
|
+
# Mock edit_file to track calls
|
883
|
+
called_path = nil
|
884
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
885
|
+
@api.edit_deploy_config
|
886
|
+
assert_equal "config/deploy.txt", called_path
|
887
|
+
end
|
888
|
+
end
|
889
|
+
|
890
|
+
def test_062_edit_post_with_source
|
891
|
+
@api.create_view("test_view", "Test View")
|
892
|
+
post = @api.create_post("Test Post", "Test body")
|
893
|
+
|
894
|
+
# Create source.lt3 file to test smart selection
|
895
|
+
source_path = "posts/#{post.num}/source.lt3"
|
896
|
+
write_file(source_path, "Test source content")
|
897
|
+
|
898
|
+
# Mock edit_file to track calls
|
899
|
+
called_path = nil
|
900
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
901
|
+
@api.edit_post(post.id)
|
902
|
+
assert_equal source_path, called_path
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
def test_063_edit_post_without_source
|
907
|
+
@api.create_view("test_view", "Test View")
|
908
|
+
post = @api.create_post("Test Post", "Test body")
|
909
|
+
|
910
|
+
# Ensure source.lt3 doesn't exist, only body.html
|
911
|
+
source_path = "posts/#{post.num}/source.lt3"
|
912
|
+
File.delete(source_path) if File.exist?(source_path)
|
913
|
+
|
914
|
+
# Mock edit_file to track calls
|
915
|
+
called_path = nil
|
916
|
+
@api.stub :edit_file, ->(path) { called_path = path } do
|
917
|
+
@api.edit_post(post.id)
|
918
|
+
assert_equal "posts/#{post.num}/body.html", called_path
|
919
|
+
end
|
920
|
+
end
|
921
|
+
|
922
|
+
def test_064_edit_post_nonexistent
|
923
|
+
assert_raises(NoMethodError) do
|
924
|
+
@api.edit_post(999)
|
925
|
+
end
|
926
|
+
end
|
927
|
+
|
928
|
+
# Publication system tests
|
929
|
+
|
930
|
+
|
931
|
+
def test_068_publish_post
|
932
|
+
@api.create_view("test_view", "Test View")
|
933
|
+
post = @api.create_post("Test Post", "Test body")
|
934
|
+
|
935
|
+
# Initially unpublished
|
936
|
+
refute @api.post_published?(post.id)
|
937
|
+
|
938
|
+
# Publish the post
|
939
|
+
published_post = @api.publish_post(post.id)
|
940
|
+
|
941
|
+
# Should now be published
|
942
|
+
assert @api.post_published?(post.id)
|
943
|
+
assert_equal "Test Post", published_post.title
|
944
|
+
|
945
|
+
# Should have generated the post
|
946
|
+
assert File.exist?("#{@test_dir}/posts/#{post.num}/body.html")
|
947
|
+
end
|
948
|
+
|
949
|
+
def test_069_publish_post_already_published
|
950
|
+
@api.create_view("test_view", "Test View")
|
951
|
+
post = @api.create_post("Test Post", "Test body")
|
952
|
+
|
953
|
+
# Publish once
|
954
|
+
@api.publish_post(post.id)
|
955
|
+
|
956
|
+
# Try to publish again
|
957
|
+
assert_raises(RuntimeError, "Post #{post.id} is already published") do
|
958
|
+
@api.publish_post(post.id)
|
959
|
+
end
|
960
|
+
end
|
961
|
+
|
962
|
+
def test_070_publish_post_nonexistent
|
963
|
+
assert_raises(RequiredFileNotFound) do
|
964
|
+
@api.publish_post(999)
|
965
|
+
end
|
966
|
+
end
|
967
|
+
|
968
|
+
def test_071_post_published_status
|
969
|
+
@api.create_view("test_view", "Test View")
|
970
|
+
post = @api.create_post("Test Post", "Test body")
|
971
|
+
|
972
|
+
# Initially unpublished
|
973
|
+
refute @api.post_published?(post.id)
|
974
|
+
|
975
|
+
# Publish
|
976
|
+
@api.publish_post(post.id)
|
977
|
+
|
978
|
+
# Now published
|
979
|
+
assert @api.post_published?(post.id)
|
980
|
+
end
|
981
|
+
|
982
|
+
def test_072_get_published_posts
|
983
|
+
@api.create_view("test_view", "Test View")
|
984
|
+
|
985
|
+
# Create multiple posts
|
986
|
+
post1 = @api.create_post("Post 1", "Body 1")
|
987
|
+
post2 = @api.create_post("Post 2", "Body 2")
|
988
|
+
post3 = @api.create_post("Post 3", "Body 3")
|
989
|
+
|
990
|
+
# Initially no published posts
|
991
|
+
published_posts = @api.get_published_posts
|
992
|
+
assert_equal 0, published_posts.length
|
993
|
+
|
994
|
+
# Publish two posts
|
995
|
+
@api.publish_post(post1.id)
|
996
|
+
@api.publish_post(post3.id)
|
997
|
+
|
998
|
+
# Should have 2 published posts
|
999
|
+
published_posts = @api.get_published_posts
|
1000
|
+
assert_equal 2, published_posts.length
|
1001
|
+
assert_includes published_posts.map(&:id), post1.id
|
1002
|
+
assert_includes published_posts.map(&:id), post3.id
|
1003
|
+
refute_includes published_posts.map(&:id), post2.id
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
def test_073_get_published_posts_with_view
|
1007
|
+
@api.create_view("test_view", "Test View")
|
1008
|
+
@api.create_view("other_view", "Other View")
|
1009
|
+
|
1010
|
+
# Create posts in different views
|
1011
|
+
post1 = @api.create_post("Post 1", "Body 1", views: "test_view")
|
1012
|
+
post2 = @api.create_post("Post 2", "Body 2", views: "other_view")
|
1013
|
+
|
1014
|
+
# Publish both
|
1015
|
+
@api.publish_post(post1.id)
|
1016
|
+
@api.publish_post(post2.id)
|
1017
|
+
|
1018
|
+
# Get published posts for specific view
|
1019
|
+
test_view_posts = @api.get_published_posts("test_view")
|
1020
|
+
assert_equal 1, test_view_posts.length
|
1021
|
+
assert_equal post1.id, test_view_posts.first.id
|
1022
|
+
|
1023
|
+
other_view_posts = @api.get_published_posts("other_view")
|
1024
|
+
assert_equal 1, other_view_posts.length
|
1025
|
+
assert_equal post2.id, other_view_posts.first.id
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
def test_074_create_post_with_generation
|
1029
|
+
@api.create_view("test_view", "Test View")
|
1030
|
+
post = @api.create_post("Test Post", "Test body")
|
1031
|
+
|
1032
|
+
# Post should exist and be generated by default
|
1033
|
+
assert File.exist?("#{@test_dir}/posts/#{post.num}/source.lt3")
|
1034
|
+
assert File.exist?("#{@test_dir}/posts/#{post.num}/meta.txt")
|
1035
|
+
assert File.exist?("#{@test_dir}/posts/#{post.num}/body.html")
|
1036
|
+
|
1037
|
+
# Should not be published
|
1038
|
+
refute @api.post_published?(post.id)
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
def test_075_create_post_with_generation_default
|
1042
|
+
@api.create_view("test_view", "Test View")
|
1043
|
+
post = @api.create_post("Test Post", "Test body")
|
1044
|
+
|
1045
|
+
# Post should be generated by default (backward compatibility)
|
1046
|
+
assert File.exist?("#{@test_dir}/posts/#{post.num}/body.html")
|
1047
|
+
|
1048
|
+
# Should NOT be published (generation and publication are separate)
|
1049
|
+
# Check the metadata file directly to be sure
|
1050
|
+
metadata_file = "#{@test_dir}/posts/#{post.num}/meta.txt"
|
1051
|
+
assert File.exist?(metadata_file)
|
1052
|
+
metadata_content = read_file(metadata_file)
|
1053
|
+
assert_match /post\.published\s+no/, metadata_content
|
1054
|
+
refute @api.post_published?(post.id)
|
1055
|
+
end
|
1056
|
+
end
|