aboutsummaryrefslogtreecommitdiff
path: root/tests/test-music-config--art-favicon-url.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-06 17:14:58 -0500
committerCraig Jennings <c@cjennings.net>2026-07-06 17:14:58 -0500
commite74bb871ac0aa7ab6e63cd39759b8850f1181ffe (patch)
treeb974964c460dbba843ad2a8f891e360b9e31824e /tests/test-music-config--art-favicon-url.el
parent0a7afa70fa9c2c20771696b303c9cae4aa7c58a2 (diff)
downloaddotemacs-e74bb871ac0aa7ab6e63cd39759b8850f1181ffe.tar.gz
dotemacs-e74bb871ac0aa7ab6e63cd39759b8850f1181ffe.zip
feat(music): cover-art fetch and cache for the player (fancy UI phase 2)
Phase 2 gives each track a local cover-image path so phase 3's GUI has art to draw. A radio station uses its logo, a local file a sibling cover image, and anything without either falls back to a shipped vinyl placeholder. I consolidated the .m3u parse into one richer cj/music--m3u-entries that reads :name, :uuid, and :favicon per station. Phase 1's cj/music--m3u-labels is now a thin projection of it, so names and art share a single cached disk read. New stations capture their favicon into a #RADIOBROWSERFAVICON line at creation, so most need no lookup later. A legacy station with only a UUID resolves its favicon through a byuuid call. The render path never touches the network. cj/music-art--for-track reads only the cache and returns the placeholder until art exists. cj/music-art--ensure does the blocking fetch off that path. A fetched response is validated as an actual image before it's cached, so an HTML error page or an empty body becomes the placeholder, not a poisoned cache entry. Only http and https URLs are fetched, so an external favicon field can't reach a file:// resource. Art lands under data/music-art/, keyed by UUID or a file hash. cj/music-clear-art-cache empties it, with no automatic expiry. The pure helpers carry the tests: the parser, the cache key, the favicon URL, and the image validation, each with Normal, Boundary, and Error cases. The fetch, the byuuid lookup, and the placeholder fallback are verified live against a real station. Local files use a sibling cover image for now. Embedded-tag extraction is deferred. The full suite is green.
Diffstat (limited to 'tests/test-music-config--art-favicon-url.el')
-rw-r--r--tests/test-music-config--art-favicon-url.el52
1 files changed, 52 insertions, 0 deletions
diff --git a/tests/test-music-config--art-favicon-url.el b/tests/test-music-config--art-favicon-url.el
new file mode 100644
index 00000000..d9759ab3
--- /dev/null
+++ b/tests/test-music-config--art-favicon-url.el
@@ -0,0 +1,52 @@
+;;; test-music-config--art-favicon-url.el --- Tests for stream favicon URL -*- coding: utf-8; lexical-binding: t; -*-
+;;
+;; Author: Craig Jennings <c@cjennings.net>
+;;
+;;; Commentary:
+;; Unit tests for `cj/music-art--favicon-url': the direct favicon image URL for
+;; a url track, taken from the #RADIOBROWSERFAVICON captured at station creation.
+;; A station with only a UUID resolves its favicon via a separate byuuid lookup
+;; (done in the impure orchestrator), so this pure helper returns nil there.
+;;
+;;; Code:
+
+(require 'ert)
+
+(defvar-keymap cj/custom-keymap :doc "Stub keymap for testing")
+
+(let ((emms-dir (car (file-expand-wildcards
+ (expand-file-name "elpa/emms-*" user-emacs-directory)))))
+ (when emms-dir (add-to-list 'load-path emms-dir)))
+
+(require 'emms)
+(require 'music-config)
+
+(ert-deftest test-music-config--art-favicon-url-normal-captured ()
+ "Normal: a captured #RADIOBROWSERFAVICON is returned directly."
+ (let ((track (emms-track 'url "https://fav.somafm.com/gs"))
+ (entries '(("https://fav.somafm.com/gs"
+ :name "GS" :uuid "u1" :favicon "https://cdn.example/gs.png"))))
+ (should (string= (cj/music-art--favicon-url track entries)
+ "https://cdn.example/gs.png"))))
+
+(ert-deftest test-music-config--art-favicon-url-boundary-uuid-only ()
+ "Boundary: a station with a UUID but no captured favicon returns nil
+\(the byuuid lookup is the orchestrator's job)."
+ (let ((track (emms-track 'url "https://fav2.example.net/live"))
+ (entries '(("https://fav2.example.net/live"
+ :name "X" :uuid "u2" :favicon nil))))
+ (should (null (cj/music-art--favicon-url track entries)))))
+
+(ert-deftest test-music-config--art-favicon-url-boundary-empty-favicon ()
+ "Boundary: an empty-string favicon is treated as absent."
+ (let ((track (emms-track 'url "https://fav3.example.net/live"))
+ (entries '(("https://fav3.example.net/live" :name "X" :uuid "u3" :favicon ""))))
+ (should (null (cj/music-art--favicon-url track entries)))))
+
+(ert-deftest test-music-config--art-favicon-url-error-file-track ()
+ "Error: a file track has no stream favicon URL."
+ (let ((track (emms-track 'file "/music/x.flac")))
+ (should (null (cj/music-art--favicon-url track nil)))))
+
+(provide 'test-music-config--art-favicon-url)
+;;; test-music-config--art-favicon-url.el ends here