aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-music-config--bar-fill.el63
-rw-r--r--tests/test-music-config--display-name.el139
-rw-r--r--tests/test-music-config--m3u-labels.el62
-rw-r--r--tests/test-music-config--tidy-host.el47
-rw-r--r--tests/test-music-config--track-description.el181
5 files changed, 311 insertions, 181 deletions
diff --git a/tests/test-music-config--bar-fill.el b/tests/test-music-config--bar-fill.el
new file mode 100644
index 00000000..f6d8dac8
--- /dev/null
+++ b/tests/test-music-config--bar-fill.el
@@ -0,0 +1,63 @@
+;;; test-music-config--bar-fill.el --- Tests for progress-bar fill math -*- coding: utf-8; lexical-binding: t; -*-
+;;
+;; Author: Craig Jennings <c@cjennings.net>
+;;
+;;; Commentary:
+;; Unit tests for `cj/music--bar-fill': the pure computation of how many cells
+;; of a WIDTH-cell progress bar are filled given ELAPSED and TOTAL seconds.
+;; A stream (no duration) is indeterminate; the live elapsed source (mpv) is
+;; wired in a later phase, so this helper only does the clamp-and-round math.
+;;
+;;; 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)
+
+;;; Normal
+
+(ert-deftest test-music-config--bar-fill-normal-half ()
+ "Normal: halfway through fills half the cells."
+ (should (= (cj/music--bar-fill 30 60 20) 10)))
+
+(ert-deftest test-music-config--bar-fill-normal-quarter ()
+ "Normal: a quarter elapsed rounds to a quarter of the cells."
+ (should (= (cj/music--bar-fill 15 60 20) 5)))
+
+;;; Boundary
+
+(ert-deftest test-music-config--bar-fill-boundary-zero-elapsed ()
+ "Boundary: nothing elapsed fills no cells."
+ (should (= (cj/music--bar-fill 0 60 20) 0)))
+
+(ert-deftest test-music-config--bar-fill-boundary-full ()
+ "Boundary: elapsed equal to total fills every cell."
+ (should (= (cj/music--bar-fill 60 60 20) 20)))
+
+(ert-deftest test-music-config--bar-fill-boundary-over-clamps ()
+ "Boundary: elapsed past total clamps to full, never overflows."
+ (should (= (cj/music--bar-fill 90 60 20) 20)))
+
+(ert-deftest test-music-config--bar-fill-boundary-nil-elapsed ()
+ "Boundary: an unknown elapsed (nil) fills no cells."
+ (should (= (cj/music--bar-fill nil 60 20) 0)))
+
+;;; Error / indeterminate
+
+(ert-deftest test-music-config--bar-fill-indeterminate-nil-total ()
+ "Error: a stream (nil total) is indeterminate, not a cell count."
+ (should (eq (cj/music--bar-fill 30 nil 20) 'indeterminate)))
+
+(ert-deftest test-music-config--bar-fill-indeterminate-zero-total ()
+ "Error: a zero total is indeterminate rather than a divide-by-zero."
+ (should (eq (cj/music--bar-fill 30 0 20) 'indeterminate)))
+
+(provide 'test-music-config--bar-fill)
+;;; test-music-config--bar-fill.el ends here
diff --git a/tests/test-music-config--display-name.el b/tests/test-music-config--display-name.el
new file mode 100644
index 00000000..c1065f3a
--- /dev/null
+++ b/tests/test-music-config--display-name.el
@@ -0,0 +1,139 @@
+;;; test-music-config--display-name.el --- Tests for track display-name -*- coding: utf-8; lexical-binding: t; -*-
+;;
+;; Author: Craig Jennings <c@cjennings.net>
+;;
+;;; Commentary:
+;; Unit tests for `cj/music--display-name' and `cj/music--format-meta'.
+;;
+;; display-name is the pure, name-only resolver shared by the header's Current
+;; line and the playlist row renderer: for a tagged track it returns
+;; "Artist - Title" (no duration -- duration is right-aligned meta, not name);
+;; for an untagged file the clean filename; for a url track the #EXTINF label
+;; from a passed name-map, else a tidied host; unknown types fall back to
+;; emms-track-simple-description.
+;;
+;; format-meta returns the right-aligned meta string for a row: a file's
+;; duration as "[M:SS]", empty otherwise.
+;;
+;; Track independence: `emms-track' returns a track cached by (type . name)
+;; once EMMS is loaded, so two tests sharing a name would share a mutated
+;; object. Every test below uses a UNIQUE track name to stay independent.
+;;
+;;; 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 'emms-playlist-mode)
+(require 'music-config)
+
+;;; Helpers
+
+(defun test-display-name--file (path &optional title artist duration)
+ "Create a file TRACK with PATH and optional TITLE ARTIST DURATION."
+ (let ((track (emms-track 'file path)))
+ (when title (emms-track-set track 'info-title title))
+ (when artist (emms-track-set track 'info-artist artist))
+ (when duration (emms-track-set track 'info-playing-time duration))
+ track))
+
+(defun test-display-name--url (url &optional title artist)
+ "Create a url TRACK with URL and optional TITLE ARTIST."
+ (let ((track (emms-track 'url url)))
+ (when title (emms-track-set track 'info-title title))
+ (when artist (emms-track-set track 'info-artist artist))
+ track))
+
+;;; Normal -- tagged tracks (no duration in the name)
+
+(ert-deftest test-music-config--display-name-normal-artist-title ()
+ "Normal: tagged track shows Artist - Title, no duration bracket."
+ (let ((track (test-display-name--file
+ "/dn/artist-title.flac" "So What" "Miles Davis" 562)))
+ (should (string= (cj/music--display-name track) "Miles Davis - So What"))))
+
+(ert-deftest test-music-config--display-name-normal-title-only ()
+ "Normal: title without artist shows the title alone."
+ (let ((track (test-display-name--file "/dn/title-only.mp3" "Flamenco Sketches" nil 566)))
+ (should (string= (cj/music--display-name track) "Flamenco Sketches"))))
+
+;;; Normal -- untagged file
+
+(ert-deftest test-music-config--display-name-normal-file-no-tags ()
+ "Normal: untagged file shows filename without path or extension."
+ (let ((track (test-display-name--file "/dn/Kind of Blue/02 - Freddie.flac")))
+ (should (string= (cj/music--display-name track) "02 - Freddie"))))
+
+;;; Normal -- url resolves to #EXTINF label from the name-map
+
+(ert-deftest test-music-config--display-name-normal-url-label-from-map ()
+ "Normal: a url track resolves to its #EXTINF label via the name-map."
+ (let ((track (test-display-name--url "https://ice6.somafm.com/groovesalad-256-mp3"))
+ (map '(("https://ice6.somafm.com/groovesalad-256-mp3" . "SomaFM Groove Salad"))))
+ (should (string= (cj/music--display-name track map) "SomaFM Groove Salad"))))
+
+;;; Normal -- url with tags formats like a tagged track
+
+(ert-deftest test-music-config--display-name-normal-url-with-tags ()
+ "Normal: a url track carrying tags uses Artist - Title, not the URL."
+ (let ((track (test-display-name--url "https://tagged.example.com/stream"
+ "Jazz FM" "Radio Station")))
+ (should (string= (cj/music--display-name track) "Radio Station - Jazz FM"))))
+
+;;; Boundary -- url with no label falls back to a tidied host
+
+(ert-deftest test-music-config--display-name-boundary-url-host-fallback ()
+ "Boundary: a url with no label and no map falls back to the tidied host."
+ (let ((track (test-display-name--url "https://ice6.hostonly.somafm.com/gs")))
+ (should (string= (cj/music--display-name track) "somafm.com"))))
+
+(ert-deftest test-music-config--display-name-boundary-url-not-in-map ()
+ "Boundary: a url absent from a non-empty map still falls to the host."
+ (let ((track (test-display-name--url "https://stream.other.net/live"))
+ (map '(("https://ice6.somafm.com/x" . "Groove Salad"))))
+ (should (string= (cj/music--display-name track map) "other.net"))))
+
+(ert-deftest test-music-config--display-name-boundary-unicode-title ()
+ "Boundary: unicode in the title is preserved."
+ (let ((track (test-display-name--file "/dn/unicode.mp3" "夜に駆ける" "YOASOBI" 258)))
+ (should (string= (cj/music--display-name track) "YOASOBI - 夜に駆ける"))))
+
+(ert-deftest test-music-config--display-name-boundary-file-multiple-dots ()
+ "Boundary: only the final extension is stripped."
+ (let ((track (test-display-name--file "/dn/disc.1.track.03.flac")))
+ (should (string= (cj/music--display-name track) "disc.1.track.03"))))
+
+;;; Error -- unknown type falls back without erroring
+
+(ert-deftest test-music-config--display-name-error-unknown-type ()
+ "Error: an unknown track type falls back to a string, no error."
+ (let* ((track (emms-track 'streamlist "https://unknown.example.com/playlist.m3u"))
+ (result (cj/music--display-name track)))
+ (should (stringp result))
+ (should (string-match-p "example\\.com" result))))
+
+;;; format-meta
+
+(ert-deftest test-music-config--format-meta-normal-file-duration ()
+ "Normal: a file with a duration yields the bracketed M:SS meta."
+ (let ((track (test-display-name--file "/dn/meta-dur.flac" "So What" "Miles" 562)))
+ (should (string= (cj/music--format-meta track) "[9:22]"))))
+
+(ert-deftest test-music-config--format-meta-boundary-no-duration ()
+ "Boundary: a track with no duration yields an empty meta string."
+ (let ((track (test-display-name--file "/dn/meta-nodur.flac" "So What" "Miles")))
+ (should (string= (cj/music--format-meta track) ""))))
+
+(ert-deftest test-music-config--format-meta-boundary-url-no-meta ()
+ "Boundary: a url stream with no duration yields empty meta."
+ (let ((track (test-display-name--url "https://meta.example.com/stream")))
+ (should (string= (cj/music--format-meta track) ""))))
+
+(provide 'test-music-config--display-name)
+;;; test-music-config--display-name.el ends here
diff --git a/tests/test-music-config--m3u-labels.el b/tests/test-music-config--m3u-labels.el
new file mode 100644
index 00000000..02f328cd
--- /dev/null
+++ b/tests/test-music-config--m3u-labels.el
@@ -0,0 +1,62 @@
+;;; test-music-config--m3u-labels.el --- Tests for #EXTINF label extraction -*- coding: utf-8; lexical-binding: t; -*-
+;;
+;; Author: Craig Jennings <c@cjennings.net>
+;;
+;;; Commentary:
+;; Unit tests for `cj/music--m3u-labels': parse .m3u text into an alist of
+;; (stream-url . #EXTINF-label) pairs. This is the pure core that lets a url
+;; track resolve to its station name (the label written at creation) instead
+;; of the raw stream URL.
+;;
+;;; 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--m3u-labels-normal-single ()
+ "Normal: one #EXTINF + url pair yields one (url . label) cons."
+ (let ((text "#EXTM3U\n#EXTINF:1,SomaFM Groove Salad\nhttps://ice6.somafm.com/gs\n"))
+ (should (equal (cj/music--m3u-labels text)
+ '(("https://ice6.somafm.com/gs" . "SomaFM Groove Salad"))))))
+
+(ert-deftest test-music-config--m3u-labels-normal-uuid-line-between ()
+ "Normal: a #RADIOBROWSERUUID line between #EXTINF and the url is skipped."
+ (let ((text (concat "#EXTM3U\n#EXTINF:1,Jazz24\n"
+ "#RADIOBROWSERUUID:abc-123\nhttps://jazz.example/live\n")))
+ (should (equal (cj/music--m3u-labels text)
+ '(("https://jazz.example/live" . "Jazz24"))))))
+
+(ert-deftest test-music-config--m3u-labels-normal-multiple ()
+ "Normal: multiple stations each yield their own pair."
+ (let ((text (concat "#EXTM3U\n"
+ "#EXTINF:1,Station A\nhttps://a.example/1\n"
+ "#EXTINF:-1,Station B\nhttps://b.example/2\n")))
+ (should (equal (cj/music--m3u-labels text)
+ '(("https://a.example/1" . "Station A")
+ ("https://b.example/2" . "Station B"))))))
+
+(ert-deftest test-music-config--m3u-labels-boundary-url-without-extinf ()
+ "Boundary: a bare url with no preceding #EXTINF produces no pair."
+ (let ((text "#EXTM3U\nhttps://plain.example/stream\n"))
+ (should (null (cj/music--m3u-labels text)))))
+
+(ert-deftest test-music-config--m3u-labels-boundary-empty ()
+ "Boundary: empty text yields nil."
+ (should (null (cj/music--m3u-labels ""))))
+
+(ert-deftest test-music-config--m3u-labels-boundary-comma-in-name ()
+ "Boundary: a comma inside the label is preserved (split on the first only)."
+ (let ((text "#EXTINF:1,Radio, the Good Kind\nhttps://x.example/s\n"))
+ (should (equal (cj/music--m3u-labels text)
+ '(("https://x.example/s" . "Radio, the Good Kind"))))))
+
+(provide 'test-music-config--m3u-labels)
+;;; test-music-config--m3u-labels.el ends here
diff --git a/tests/test-music-config--tidy-host.el b/tests/test-music-config--tidy-host.el
new file mode 100644
index 00000000..92b104a6
--- /dev/null
+++ b/tests/test-music-config--tidy-host.el
@@ -0,0 +1,47 @@
+;;; test-music-config--tidy-host.el --- Tests for stream-URL host tidying -*- coding: utf-8; lexical-binding: t; -*-
+;;
+;; Author: Craig Jennings <c@cjennings.net>
+;;
+;;; Commentary:
+;; Unit tests for `cj/music--tidy-host': reduce a stream URL to a readable host
+;; label (scheme dropped, a leading "www." removed), used as the last-resort
+;; display name for a url track with no #EXTINF label.
+;;
+;;; 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--tidy-host-normal ()
+ "Normal: scheme and path are dropped, host kept."
+ (should (string= (cj/music--tidy-host "https://ice6.somafm.com/groovesalad-256-mp3")
+ "somafm.com")))
+
+(ert-deftest test-music-config--tidy-host-normal-http ()
+ "Normal: plain http host with a port keeps the host, drops the port."
+ (should (string= (cj/music--tidy-host "http://stream.example.org:8000/live")
+ "example.org")))
+
+(ert-deftest test-music-config--tidy-host-boundary-strip-www ()
+ "Boundary: a leading www. is stripped."
+ (should (string= (cj/music--tidy-host "https://www.radioparadise.com/m3u/mp3-128.m3u")
+ "radioparadise.com")))
+
+(ert-deftest test-music-config--tidy-host-boundary-bare-domain ()
+ "Boundary: a two-label domain is returned unchanged."
+ (should (string= (cj/music--tidy-host "http://somafm.com/") "somafm.com")))
+
+(ert-deftest test-music-config--tidy-host-error-not-a-url ()
+ "Error: a non-URL string is returned as-is rather than erroring."
+ (should (string= (cj/music--tidy-host "not a url") "not a url")))
+
+(provide 'test-music-config--tidy-host)
+;;; test-music-config--tidy-host.el ends here
diff --git a/tests/test-music-config--track-description.el b/tests/test-music-config--track-description.el
deleted file mode 100644
index a1a1cc6d..00000000
--- a/tests/test-music-config--track-description.el
+++ /dev/null
@@ -1,181 +0,0 @@
-;;; test-music-config--track-description.el --- Tests for track description -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--track-description function.
-;; Tests the custom track description that replaces EMMS's default file-path display
-;; with human-readable formats based on track type and available metadata.
-;;
-;; Track construction: EMMS tracks are alists created with `emms-track' and
-;; populated with `emms-track-set'. No playlist buffer or player state needed.
-;;
-;; Test organization:
-;; - Normal Cases: Tagged tracks (artist+title+duration), partial metadata, file fallback, URL
-;; - Boundary Cases: Empty strings, missing fields, special characters, long names
-;; - Error Cases: Unknown track type fallback
-;;
-;;; Code:
-
-(require 'ert)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Add EMMS elpa directory to load path for batch 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 'emms-playlist-mode)
-(require 'music-config)
-
-;;; Test helpers
-
-(defun test-track-description--make-file-track (path &optional title artist duration)
- "Create a file TRACK with PATH and optional metadata TITLE, ARTIST, DURATION."
- (let ((track (emms-track 'file path)))
- (when title (emms-track-set track 'info-title title))
- (when artist (emms-track-set track 'info-artist artist))
- (when duration (emms-track-set track 'info-playing-time duration))
- track))
-
-(defun test-track-description--make-url-track (url &optional title artist duration)
- "Create a URL TRACK with URL and optional metadata TITLE, ARTIST, DURATION."
- (let ((track (emms-track 'url url)))
- (when title (emms-track-set track 'info-title title))
- (when artist (emms-track-set track 'info-artist artist))
- (when duration (emms-track-set track 'info-playing-time duration))
- track))
-
-;;; Normal Cases — Tagged tracks (artist + title + duration)
-
-(ert-deftest test-music-config--track-description-normal-full-metadata ()
- "Validate track with artist, title, and duration shows all three."
- (let ((track (test-track-description--make-file-track
- "/music/Kind of Blue/01 - So What.flac"
- "So What" "Miles Davis" 562)))
- (should (string= (cj/music--track-description track)
- "Miles Davis - So What [9:22]"))))
-
-(ert-deftest test-music-config--track-description-normal-title-and-artist-no-duration ()
- "Validate track with artist and title but no duration omits bracket."
- (let ((track (test-track-description--make-file-track
- "/test/uncached-nodur.mp3" "Blue in Green" "Miles Davis")))
- (should (string= (cj/music--track-description track)
- "Miles Davis - Blue in Green"))))
-
-(ert-deftest test-music-config--track-description-normal-title-only ()
- "Validate track with title but no artist shows title alone."
- (let ((track (test-track-description--make-file-track
- "/test/uncached-noartist.mp3" "Flamenco Sketches" nil 566)))
- (should (string= (cj/music--track-description track)
- "Flamenco Sketches [9:26]"))))
-
-(ert-deftest test-music-config--track-description-normal-title-only-no-duration ()
- "Validate track with only title shows just the title."
- (let ((track (test-track-description--make-file-track
- "/test/uncached-titleonly.mp3" "All Blues")))
- (should (string= (cj/music--track-description track)
- "All Blues"))))
-
-;;; Normal Cases — File tracks without tags
-
-(ert-deftest test-music-config--track-description-normal-file-no-tags ()
- "Validate untagged file shows filename without path or extension."
- (let ((track (test-track-description--make-file-track
- "/music/Kind of Blue/02 - Freddie Freeloader.flac")))
- (should (string= (cj/music--track-description track)
- "02 - Freddie Freeloader"))))
-
-(ert-deftest test-music-config--track-description-normal-file-nested-path ()
- "Validate deeply nested path still shows only the filename."
- (let ((track (test-track-description--make-file-track
- "/music/Jazz/Miles Davis/Kind of Blue/01 - So What.mp3")))
- (should (string= (cj/music--track-description track)
- "01 - So What"))))
-
-;;; Normal Cases — URL tracks
-
-(ert-deftest test-music-config--track-description-normal-url-plain ()
- "Validate plain URL is shown as-is."
- (let ((track (test-track-description--make-url-track
- "https://radio.example.com/stream")))
- (should (string= (cj/music--track-description track)
- "https://radio.example.com/stream"))))
-
-(ert-deftest test-music-config--track-description-normal-url-percent-encoded ()
- "Validate percent-encoded URL characters are decoded."
- (let ((track (test-track-description--make-url-track
- "https://radio.example.com/my%20station%21")))
- (should (string= (cj/music--track-description track)
- "https://radio.example.com/my station!"))))
-
-(ert-deftest test-music-config--track-description-normal-url-with-tags ()
- "Validate URL track with tags uses tag display, not URL."
- (let ((track (test-track-description--make-url-track
- "https://radio.example.com/stream"
- "Jazz FM" "Radio Station" 0)))
- ;; Duration 0 → nil from format-duration, so no bracket
- (should (string= (cj/music--track-description track)
- "Radio Station - Jazz FM"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--track-description-boundary-empty-title-string ()
- "Validate empty title string is still truthy, shows empty result."
- (let ((track (test-track-description--make-file-track
- "/music/track.mp3" "" "Artist")))
- ;; Empty string is non-nil, so title branch is taken
- (should (string= (cj/music--track-description track)
- "Artist - "))))
-
-(ert-deftest test-music-config--track-description-boundary-file-no-extension ()
- "Validate file without extension shows full filename."
- (let ((track (test-track-description--make-file-track "/music/README")))
- (should (string= (cj/music--track-description track)
- "README"))))
-
-(ert-deftest test-music-config--track-description-boundary-file-multiple-dots ()
- "Validate file with multiple dots strips only the final extension."
- (let ((track (test-track-description--make-file-track
- "/music/disc.1.track.03.flac")))
- (should (string= (cj/music--track-description track)
- "disc.1.track.03"))))
-
-(ert-deftest test-music-config--track-description-boundary-unicode-title ()
- "Validate unicode characters in metadata are preserved."
- (let ((track (test-track-description--make-file-track
- "/music/track.mp3" "夜に駆ける" "YOASOBI" 258)))
- (should (string= (cj/music--track-description track)
- "YOASOBI - 夜に駆ける [4:18]"))))
-
-(ert-deftest test-music-config--track-description-boundary-url-utf8-percent-encoded ()
- "Validate percent-encoded UTF-8 in URL is decoded correctly."
- (let ((track (test-track-description--make-url-track
- "https://example.com/caf%C3%A9")))
- (should (string= (cj/music--track-description track)
- "https://example.com/café"))))
-
-(ert-deftest test-music-config--track-description-boundary-short-duration ()
- "Validate 1-second track formats correctly in bracket."
- (let ((track (test-track-description--make-file-track
- "/music/t.mp3" "Beep" nil 1)))
- (should (string= (cj/music--track-description track)
- "Beep [0:01]"))))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--track-description-error-unknown-type-fallback ()
- "Validate unknown track type uses emms-track-simple-description fallback."
- (let ((track (emms-track 'streamlist "https://example.com/playlist.m3u")))
- ;; Should not error; falls through to simple-description
- (let ((result (cj/music--track-description track)))
- (should (stringp result))
- (should (string-match-p "example\\.com" result)))))
-
-(provide 'test-music-config--track-description)
-;;; test-music-config--track-description.el ends here