Generated monogram fallback + optional dark-mode icon per application
Some checks failed
Some checks failed
When an application has no icon attached, render a deterministic monogram SVG instead of the generic picture-frame placeholder. Initials are picked from capital letters in the name (ShelfLife -> SL); fall back to the first two letters when fewer than two capitals exist (Audiobookshelf -> AU). Background colour is hashed from the name for stable per-app identity across visits. Adds an optional second icon attachment, icon_dark, alongside the main icon. When present, render a <picture> with a prefers-color-scheme: dark source so the browser swaps automatically; when absent, the main icon is used in both modes. The SVG sanitization, content-type fix, and size/format validation now run over both attachments uniformly. Bumps to 0.14.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
34
test/helpers/application_helper_test.rb
Normal file
34
test/helpers/application_helper_test.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
require "test_helper"
|
||||
|
||||
class ApplicationHelperTest < ActionView::TestCase
|
||||
test "monogram_initials picks capitals from camelCase" do
|
||||
assert_equal "SL", monogram_initials("ShelfLife")
|
||||
assert_equal "KR", monogram_initials("KavitaReader")
|
||||
assert_equal "AB", monogram_initials("AudioBookShelf") # first two of 4 capitals
|
||||
end
|
||||
|
||||
test "monogram_initials falls back to first two letters when fewer than two capitals" do
|
||||
assert_equal "AU", monogram_initials("Audiobookshelf")
|
||||
assert_equal "ME", monogram_initials("metube")
|
||||
assert_equal "GI", monogram_initials("git")
|
||||
end
|
||||
|
||||
test "monogram_initials handles single-character and unusual names" do
|
||||
assert_equal "X", monogram_initials("X")
|
||||
assert_equal "X1", monogram_initials("X1")
|
||||
assert_equal "?", monogram_initials("")
|
||||
assert_equal "?", monogram_initials(nil)
|
||||
end
|
||||
|
||||
test "monogram_color is deterministic for the same name" do
|
||||
a = monogram_color("ShelfLife")
|
||||
b = monogram_color("ShelfLife")
|
||||
assert_equal a, b
|
||||
assert_match(/\A#[0-9a-f]{6}\z/i, a)
|
||||
end
|
||||
|
||||
test "monogram_color differs for different names" do
|
||||
# not a guarantee for all pairs, but should hold for at least one pair
|
||||
assert_not_equal monogram_color("Kavita"), monogram_color("Navidrome")
|
||||
end
|
||||
end
|
||||
@@ -29,4 +29,31 @@ class ApplicationTest < ActiveSupport::TestCase
|
||||
tempfile&.close
|
||||
tempfile&.unlink
|
||||
end
|
||||
|
||||
test "icon_dark is independently attachable and SVG-sanitized" do
|
||||
app = applications(:kavita_app)
|
||||
|
||||
svg = %(<svg xmlns="http://www.w3.org/2000/svg"><script>boom()</script><circle cx="5" cy="5" r="3"/></svg>)
|
||||
tempfile = Tempfile.new(["dark", ".svg"]).tap do |t|
|
||||
t.write(svg)
|
||||
t.rewind
|
||||
end
|
||||
uploaded = ActionDispatch::Http::UploadedFile.new(
|
||||
tempfile: tempfile,
|
||||
filename: "dark.svg",
|
||||
type: "image/svg+xml"
|
||||
)
|
||||
|
||||
assert_nothing_raised do
|
||||
app.update!(icon_dark: uploaded)
|
||||
end
|
||||
|
||||
assert app.icon_dark.attached?
|
||||
cleaned = app.icon_dark.download
|
||||
refute_match(/<script/i, cleaned)
|
||||
assert_match(/<circle/, cleaned)
|
||||
ensure
|
||||
tempfile&.close
|
||||
tempfile&.unlink
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user