aboutsummaryrefslogtreecommitdiff
path: root/tests/test-music-config-more-commands.el
blob: 530aa379b2908fd517813a2210f184610062fde2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
;;; test-music-config-more-commands.el --- More music-config command tests -*- lexical-binding: t; -*-

;;; Commentary:
;; Sibling `test-music-config-commands.el' covers add/fuzzy/clear/
;; next/previous/consume.  This file covers more wrappers:
;;
;;   cj/music-playlist-load
;;   cj/music-playlist-save
;;   cj/music-playlist-edit
;;   cj/music-playlist-toggle
;;   cj/music-playlist-show
;;
;; cj/music-create-radio-station lives in
;; test-music-config-create-radio-station.el.

;;; Code:

(require 'ert)
(require 'cl-lib)

(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))

;; Stub dependencies before loading the module.
(defvar cj/custom-keymap (make-sparse-keymap)
  "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)

;; Top-level defvars so let-binds reach the dynamic var under lexical
;; scope.
(defvar cj/music-playlist-file nil)

;;; cj/music-playlist-load

(ert-deftest test-music-playlist-load-plays-selected-playlist ()
  "Normal: load picks an entry, clears the playlist, and plays the file."
  (let* ((tmp (file-name-as-directory (make-temp-file "cj-music-m3u-" t)))
         (rock (expand-file-name "rock.m3u" tmp))
         played)
    (with-temp-file rock (insert "/song.mp3\n"))
    (unwind-protect
        (cl-letf (((symbol-function 'cj/music--assert-m3u-files-exist)
                   (lambda () (list (cons "rock" rock))))
                  ((symbol-function 'completing-read)
                   (lambda (&rest _) "rock"))
                  ((symbol-function 'emms-playlist-clear) #'ignore)
                  ((symbol-function 'emms-play-playlist)
                   (lambda (f) (setq played f)))
                  ((symbol-function 'cj/music--sync-playlist-file) #'ignore)
                  ((symbol-function 'message) #'ignore))
          (cj/music-playlist-load))
      (delete-directory tmp t))
    (should (equal played rock))))

(ert-deftest test-music-playlist-load-errors-when-target-missing ()
  "Error: a missing target file signals user-error."
  (cl-letf (((symbol-function 'cj/music--assert-m3u-files-exist)
             (lambda () (list (cons "gone" "/no/such/file.m3u"))))
            ((symbol-function 'completing-read)
             (lambda (&rest _) "gone")))
    (should-error (cj/music-playlist-load) :type 'user-error)))

;;; cj/music-playlist-save

(defmacro test-music-save--with-env (&rest body)
  "Run BODY with a fresh playlist buffer and temp save directories.
Binds TMP-M3U and TMP-RADIO (both cleaned up) and captures completing-read's
INITIAL argument in CR-INITIAL."
  `(let* ((cj/music-playlist-buffer-name
           (generate-new-buffer-name "*test-save*"))
          (tmp-m3u (file-name-as-directory (make-temp-file "cj-save-m3u-" t)))
          (tmp-radio (file-name-as-directory (make-temp-file "cj-save-radio-" t)))
          (cj/music-m3u-root tmp-m3u)
          (cj/music-radio-save-dir tmp-radio)
          (cr-initial 'unset))
     (ignore cr-initial)
     (unwind-protect
         (cl-letf (((symbol-function 'message) #'ignore)
                   ((symbol-function 'completing-read)
                    (lambda (_prompt _coll &optional _pred _req initial _hist def &rest _)
                      (setq cr-initial initial)
                      (or initial def "fallback"))))
           ,@body)
       (when (get-buffer cj/music-playlist-buffer-name)
         (kill-buffer cj/music-playlist-buffer-name))
       (delete-directory tmp-m3u t)
       (delete-directory tmp-radio t))))

(defun test-music-save--queue (tracks)
  "Put TRACKS into the (fresh) test playlist buffer."
  (with-current-buffer (cj/music--ensure-playlist-buffer)
    (save-excursion
      (goto-char (point-max))
      (dolist (tr tracks)
        (emms-playlist-insert-track tr)))))

(ert-deftest test-music-playlist-save-station-queue-prefills-and-targets-radio-dir ()
  "Normal: an all-stream queue pre-fills the station name and saves into the
radio dir with the station metadata written."
  (test-music-save--with-env
   (let ((tr (emms-track 'url "https://gs.example/stream")))
     (emms-track-set tr 'info-title "Groove Salad")
     (emms-track-set tr 'radio-uuid "uuid-gs")
     (test-music-save--queue (list tr)))
   (cj/music-playlist-save)
   (should (equal cr-initial "Groove Salad"))
   (let ((file (expand-file-name "Groove Salad.m3u" tmp-radio)))
     (should (file-exists-p file))
     (with-temp-buffer
       (insert-file-contents file)
       (let ((text (buffer-string)))
         (should (string-match-p "^#EXTINF:-1,Groove Salad$" text))
         (should (string-match-p "^#RADIOBROWSERUUID:uuid-gs$" text))
         (should (string-match-p "^https://gs\\.example/stream$" text)))))
   (should-not (directory-files tmp-m3u nil "\\.m3u\\'"))))

(ert-deftest test-music-playlist-save-file-queue-targets-m3u-root-no-prefill ()
  "Normal: a file queue saves into the music root with no station pre-fill."
  (test-music-save--with-env
   (test-music-save--queue (list (emms-track 'file "/music/a.flac")))
   (cj/music-playlist-save)
   (should-not cr-initial)
   (should (= (length (directory-files tmp-m3u nil "\\.m3u\\'")) 1))
   (should-not (directory-files tmp-radio nil "\\.m3u\\'"))))

(ert-deftest test-music-playlist-save-associated-file-name-wins-over-station ()
  "Normal: a queue with an associated playlist file defaults to that name,
even when it contains stations."
  (test-music-save--with-env
   (let ((tr (emms-track 'url "https://gs.example/stream")))
     (emms-track-set tr 'info-title "Groove Salad")
     (test-music-save--queue (list tr)))
   (with-current-buffer (cj/music--ensure-playlist-buffer)
     (setq cj/music-playlist-file (expand-file-name "morning.m3u" tmp-radio)))
   (cj/music-playlist-save)
   (should-not cr-initial)
   (should (file-exists-p (expand-file-name "morning.m3u" tmp-radio)))))

(ert-deftest test-music-playlist-save-error-empty-name ()
  "Error: an empty name at the prompt signals user-error, not a hidden .m3u."
  (test-music-save--with-env
   (test-music-save--queue (list (emms-track 'url "https://x.example/s")))
   (cl-letf (((symbol-function 'completing-read) (lambda (&rest _) "")))
     (should-error (cj/music-playlist-save) :type 'user-error))
   (should-not (directory-files tmp-radio nil "m3u"))))

;;; cj/music-playlist-edit

(ert-deftest test-music-playlist-edit-opens-file-other-window ()
  "Normal: with an unmodified playlist file present, edit opens it."
  (let* ((tmp (make-temp-file "cj-music-edit-" nil ".m3u"))
         (cj/music-playlist-file tmp)
         opened)
    (with-temp-file tmp (insert "/song.mp3\n"))
    (unwind-protect
        (cl-letf (((symbol-function 'cj/music--assert-valid-playlist-file) #'ignore)
                  ((symbol-function 'cj/music--ensure-playlist-buffer)
                   (lambda () (current-buffer)))
                  ((symbol-function 'cj/music--playlist-modified-p)
                   (lambda () nil))
                  ((symbol-function 'find-file-other-window)
                   (lambda (f &rest _) (setq opened f))))
          (cj/music-playlist-edit))
      (delete-file tmp))
    (should (equal opened tmp))))

;;; cj/music-playlist-toggle

(ert-deftest test-music-playlist-toggle-hides-when-visible ()
  "Normal: with the playlist buffer showing, toggle deletes the window."
  (let* ((cj/music-playlist-buffer-name "*test-cj-music*")
         (buf (get-buffer-create cj/music-playlist-buffer-name))
         deleted msg)
    (unwind-protect
        (cl-letf (((symbol-function 'get-buffer-window)
                   (lambda (&rest _) 'fake-win))
                  ((symbol-function 'delete-window)
                   (lambda (w) (setq deleted w)))
                  ((symbol-function 'message)
                   (lambda (fmt &rest args) (setq msg (apply #'format fmt args)))))
          (cj/music-playlist-toggle))
      (kill-buffer buf))
    (should (eq deleted 'fake-win))
    (should (string-match-p "closed" msg))))

;;; cj/music-playlist-show

(ert-deftest test-music-playlist-show-switches-to-playlist-buffer ()
  "Normal: show calls switch-to-buffer with the playlist buffer."
  (let* ((cj/music-playlist-buffer-name "*test-cj-music-show*")
         (buf (get-buffer-create cj/music-playlist-buffer-name))
         switched msg)
    (unwind-protect
        (cl-letf (((symbol-function 'cj/emms--setup) #'ignore)
                  ((symbol-function 'cj/music--ensure-playlist-buffer)
                   (lambda () buf))
                  ((symbol-function 'switch-to-buffer)
                   (lambda (b &rest _) (setq switched b)))
                  ((symbol-function 'message)
                   (lambda (fmt &rest args) (setq msg (apply #'format fmt args)))))
          (cj/music-playlist-show))
      (kill-buffer buf))
    (should (eq switched buf))
    (should msg)))

(provide 'test-music-config-more-commands)
;;; test-music-config-more-commands.el ends here