diff options
Diffstat (limited to 'test/models/helpers')
| -rw-r--r-- | test/models/helpers/content_helper_test.rb | 78 | ||||
| -rw-r--r-- | test/models/helpers/node_actions_helper_test.rb | 146 |
2 files changed, 222 insertions, 2 deletions
diff --git a/test/models/helpers/content_helper_test.rb b/test/models/helpers/content_helper_test.rb index a7ed478..d6c7b43 100644 --- a/test/models/helpers/content_helper_test.rb +++ b/test/models/helpers/content_helper_test.rb | |||
| @@ -2,7 +2,81 @@ require 'test_helper' | |||
| 2 | 2 | ||
| 3 | class ContentHelperTest < ActionView::TestCase | 3 | class ContentHelperTest < ActionView::TestCase |
| 4 | test "weekday_abbr delegates through the current I18n locale" do | 4 | test "weekday_abbr delegates through the current I18n locale" do |
| 5 | I18n.locale = :de | 5 | I18n.with_locale(:de) do |
| 6 | assert_equal "Mo", weekday_abbr(Time.parse("2026-07-06")) | 6 | assert_equal "Mo", weekday_abbr(Time.parse("2026-07-06")) |
| 7 | end | ||
| 8 | end | ||
| 9 | |||
| 10 | test "asset_credit returns nil for a nil asset" do | ||
| 11 | assert_nil asset_credit(nil) | ||
| 12 | end | ||
| 13 | |||
| 14 | test "asset_credit returns nil when creator, source, and license are all blank" do | ||
| 15 | assert_nil asset_credit(Asset.new(:name => "blank")) | ||
| 16 | end | ||
| 17 | |||
| 18 | test "asset_credit renders creator, source link, and license link when all are present" do | ||
| 19 | asset = Asset.new(:name => "demo", :creator => "Jane Doe", | ||
| 20 | :source_url => "https://example.org/photo", :license_key => "cc_by_4") | ||
| 21 | |||
| 22 | result = I18n.with_locale(:en) { asset_credit(asset) } | ||
| 23 | |||
| 24 | assert_match %r{<a href="https://example.org/photo">Photo demo</a>}, result | ||
| 25 | assert_match "by Jane Doe", result | ||
| 26 | assert_match %r{<a href="https://creativecommons.org/licenses/by/4.0/">Licensed under CC BY 4.0</a>}, result | ||
| 27 | end | ||
| 28 | |||
| 29 | test "asset_credit falls back to plain text when source_url is blank" do | ||
| 30 | asset = Asset.new(:name => "demo", :creator => "Jane Doe", :license_key => "cc_by_4") | ||
| 31 | assert_no_match %r{<a href="[^"]*">Photo demo</a>}, I18n.with_locale(:en) { asset_credit(asset) } | ||
| 32 | end | ||
| 33 | |||
| 34 | test "asset_credit omits the 'by' clause when creator is blank" do | ||
| 35 | asset = Asset.new(:name => "demo", :source_url => "https://example.org/photo", :license_key => "cc_by_4") | ||
| 36 | assert_no_match "by ", I18n.with_locale(:en) { asset_credit(asset) } | ||
| 37 | end | ||
| 38 | |||
| 39 | test "a note-style license renders with no 'Licensed under' prefix and no link" do | ||
| 40 | asset = Asset.new(:name => "demo", :creator => "CCC", :license_key => "own_work") | ||
| 41 | result = I18n.with_locale(:en) { asset_credit(asset) } | ||
| 42 | |||
| 43 | assert_match "Own work", result | ||
| 44 | assert_no_match "Licensed under", result | ||
| 45 | assert_no_match "<a", result | ||
| 46 | end | ||
| 47 | |||
| 48 | test "asset_credit degrades gracefully when the license_key is no longer in the dictionary" do | ||
| 49 | asset = Asset.new(:name => "demo", :creator => "Jane Doe") | ||
| 50 | asset.license_key = "a_retired_key" | ||
| 51 | |||
| 52 | result = I18n.with_locale(:en) { asset_credit(asset) } | ||
| 53 | |||
| 54 | assert_match "Photo demo by Jane Doe", result | ||
| 55 | assert_no_match "Licensed under", result | ||
| 56 | end | ||
| 57 | |||
| 58 | test "headline_image renders nothing when no images are attached" do | ||
| 59 | node = Node.root.children.create!(:slug => "headline_image_empty_test") | ||
| 60 | @page = node.draft | ||
| 61 | assert_nil headline_image | ||
| 62 | end | ||
| 63 | |||
| 64 | test "headline_image renders the flagged headline asset" do | ||
| 65 | node = Node.root.children.create!(:slug => "headline_image_flagged_test") | ||
| 66 | asset = Asset.create!(:name => "flagged", :upload_content_type => "image/png") | ||
| 67 | node.draft.assets << asset | ||
| 68 | node.draft.related_assets.find_by(:asset_id => asset.id).update!(:headline => true) | ||
| 69 | @page = node.draft | ||
| 70 | |||
| 71 | assert_match "glightbox", headline_image | ||
| 72 | end | ||
| 73 | |||
| 74 | test "headline_image falls back to a gallery-open caption when no headline is flagged" do | ||
| 75 | node = Node.root.children.create!(:slug => "headline_image_no_flag_test") | ||
| 76 | asset = Asset.create!(:name => "unflagged", :upload_content_type => "image/png") | ||
| 77 | node.draft.assets << asset | ||
| 78 | @page = node.draft | ||
| 79 | |||
| 80 | I18n.with_locale(:en) { assert_match t(:open_gallery), headline_image } | ||
| 7 | end | 81 | end |
| 8 | end | 82 | end |
diff --git a/test/models/helpers/node_actions_helper_test.rb b/test/models/helpers/node_actions_helper_test.rb new file mode 100644 index 0000000..1b72ec9 --- /dev/null +++ b/test/models/helpers/node_actions_helper_test.rb | |||
| @@ -0,0 +1,146 @@ | |||
| 1 | require 'test_helper' | ||
| 2 | |||
| 3 | class NodeActionsHelperTest < ActionView::TestCase | ||
| 4 | def setup | ||
| 5 | @original_locale = I18n.locale | ||
| 6 | I18n.locale = :en | ||
| 7 | end | ||
| 8 | |||
| 9 | def default_url_options | ||
| 10 | { :locale => nil } | ||
| 11 | end | ||
| 12 | |||
| 13 | def icon(_name, **) | ||
| 14 | '<svg class="stub-icon"></svg>'.html_safe | ||
| 15 | end | ||
| 16 | |||
| 17 | def teardown | ||
| 18 | I18n.locale = @original_locale | ||
| 19 | end | ||
| 20 | |||
| 21 | def entry action, metadata = {}, node: nil, user: nil, page: nil | ||
| 22 | NodeAction.create!(:node => node, :user => user, :page => page, | ||
| 23 | :action => action, :occurred_at => Time.now, | ||
| 24 | :metadata => { "username" => "quentin", | ||
| 25 | "human_readable_node_name" => "Subject" }.merge(metadata)) | ||
| 26 | end | ||
| 27 | |||
| 28 | test "publish renders the revision sentence, not the titles" do | ||
| 29 | out = action_summary(entry("publish", | ||
| 30 | { "via" => "draft", "title" => { "from" => "Old", "to" => "New" } })) | ||
| 31 | |||
| 32 | assert_includes out, "quentin" | ||
| 33 | assert_includes out, "new revision" | ||
| 34 | assert_not_includes out, "Old" | ||
| 35 | end | ||
| 36 | |||
| 37 | test "first publish uses its own sentence" do | ||
| 38 | out = action_summary(entry("publish", | ||
| 39 | { "via" => "draft", "title" => { "from" => nil, "to" => "New" } })) | ||
| 40 | |||
| 41 | assert_includes out, "for the first time" | ||
| 42 | end | ||
| 43 | |||
| 44 | test "rollback publishes get the rollback sentence" do | ||
| 45 | out = action_summary(entry("publish", | ||
| 46 | { "via" => "revision", "title" => { "from" => "Now", "to" => "Then" } })) | ||
| 47 | |||
| 48 | assert_includes out, "rolled" | ||
| 49 | end | ||
| 50 | |||
| 51 | test "move renders the path pair" do | ||
| 52 | out = action_summary(entry("move", | ||
| 53 | { "path" => { "from" => "a/b", "to" => "a/c" } })) | ||
| 54 | |||
| 55 | assert_includes out, "a/b" | ||
| 56 | assert_includes out, "a/c" | ||
| 57 | end | ||
| 58 | |||
| 59 | test "unknown verbs degrade to a generic sentence, never an error" do | ||
| 60 | out = action_summary(entry("frobnicate")) | ||
| 61 | |||
| 62 | assert_includes out, "frobnicate" | ||
| 63 | assert_includes out, "quentin" | ||
| 64 | end | ||
| 65 | |||
| 66 | test "dead references render as plain names from metadata, no links" do | ||
| 67 | out = action_summary(entry("publish", | ||
| 68 | { "title" => { "from" => "Old", "to" => "New" } })) | ||
| 69 | |||
| 70 | assert_not_includes out, "<a " | ||
| 71 | assert_includes out, "Subject" | ||
| 72 | end | ||
| 73 | |||
| 74 | test "metadata values are escaped" do | ||
| 75 | out = action_summary(entry("publish", | ||
| 76 | { "human_readable_node_name" => "<b>bold</b>" })) | ||
| 77 | |||
| 78 | assert_not_includes out, "<b>" | ||
| 79 | end | ||
| 80 | |||
| 81 | test "live associations upgrade names to links" do | ||
| 82 | out = action_summary(entry("publish", | ||
| 83 | { "title" => { "from" => "Old", "to" => "New" } }, | ||
| 84 | :user => users(:quentin))) | ||
| 85 | |||
| 86 | assert_includes out, "<a " | ||
| 87 | end | ||
| 88 | |||
| 89 | test "details are guarded off when nothing but an unchanged title is present" do | ||
| 90 | unchanged = entry("publish", { "title" => { "from" => "Same", "to" => "Same" } }) | ||
| 91 | changed = entry("publish", { "title" => { "from" => "Old", "to" => "New" } }) | ||
| 92 | |||
| 93 | assert_not action_details?(unchanged) | ||
| 94 | assert action_details?(changed) | ||
| 95 | end | ||
| 96 | |||
| 97 | test "first publish with a byline names the author" do | ||
| 98 | out = action_summary(entry("publish", | ||
| 99 | { "title" => { "from" => nil, "to" => "New" }, | ||
| 100 | "author" => { "from" => nil, "to" => "quentin" } })) | ||
| 101 | |||
| 102 | assert_includes out, "author" | ||
| 103 | end | ||
| 104 | |||
| 105 | test "create entries with their flat title render and are guarded off details" do | ||
| 106 | e = entry("create", { "title" => "Initial", "path" => "a/b" }) | ||
| 107 | |||
| 108 | assert_includes action_summary(e), "a/b" | ||
| 109 | assert_not action_details?(e) | ||
| 110 | end | ||
| 111 | |||
| 112 | test "trash, restore, and destroy render their paths" do | ||
| 113 | trash = entry("trash", { "path" => { "from" => "club/old", "to" => "trash/old" } }) | ||
| 114 | restore = entry("restore_from_trash", { "path" => { "from" => "trash/old", "to" => "club/new" } }) | ||
| 115 | doomed = entry("destroy", { "path" => "trash/old" }) | ||
| 116 | |||
| 117 | assert_includes action_summary(trash), "club/old" | ||
| 118 | assert_includes action_summary(restore), "club/new" | ||
| 119 | assert_includes action_summary(doomed), "trash/old" | ||
| 120 | end | ||
| 121 | |||
| 122 | test "revision lifecycle badges cover create, publish, rollback, and inference" do | ||
| 123 | created = entry("create", { "title" => "x", "path" => "a/b" }) | ||
| 124 | published = entry("publish", { "via" => "draft", "title" => { "from" => nil, "to" => "x" } }) | ||
| 125 | restored = entry("publish", { "via" => "revision", "title" => { "from" => "y", "to" => "x" } }) | ||
| 126 | restored.update!(:inferred_from => "from_page_revision") | ||
| 127 | |||
| 128 | out = revision_lifecycle_badges([created, published, restored]) | ||
| 129 | |||
| 130 | assert_includes out, "created" | ||
| 131 | assert_includes out, "published" | ||
| 132 | assert_includes out, "restored" | ||
| 133 | assert_includes out, "quentin" | ||
| 134 | assert_includes out, "node_action_inferred" | ||
| 135 | assert_includes out, "<span" | ||
| 136 | assert_not_includes out, "<span" | ||
| 137 | |||
| 138 | assert_equal "", revision_lifecycle_badges(nil) | ||
| 139 | end | ||
| 140 | |||
| 141 | test "verb icons map known verbs, distinguish rollbacks, and fall back" do | ||
| 142 | assert_includes verb_icon(entry("trash", { "path" => { "from" => "a", "to" => "t/a" } })), "node_action_icon--trash" | ||
| 143 | assert_includes verb_icon(entry("publish", { "via" => "revision" })), "node_action_icon--history" | ||
| 144 | assert_includes verb_icon(entry("frobnicate")), "node_action_icon--circle-dashed" | ||
| 145 | end | ||
| 146 | end | ||
