aboutsummaryrefslogtreecommitdiff
path: root/tests/test-music-config--renumber-rows.el
blob: d5bc5141bff9605e965fea0a0a719e998c6d9cc2 (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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
;;; test-music-config--renumber-rows.el --- Tests for playlist row numbering -*- coding: utf-8; lexical-binding: t; -*-
;;
;; Author: Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;; Playlist rows carry a numeric overlay prefix so the cursor stays visible
;; when it sits on a cover-art thumbnail and each row's position in the list
;; is readable.  The renumber walks the buffer and rebuilds the overlays; a
;; buffer-local after-change hook debounces it behind an idle timer.  Overlays
;; leave the buffer text untouched (EMMS owns it), so these tests drive plain
;; temp buffers.

;;; Code:

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

(defvar cj/custom-keymap (make-sparse-keymap)
  "Stub keymap for testing.")

(require 'music-config)

(defun test-music-renumber--numbers (buffer)
  "Return the overlay number strings in BUFFER, in position order."
  (with-current-buffer buffer
    (mapcar (lambda (ov) (overlay-get ov 'before-string))
            (sort (seq-filter (lambda (ov) (overlay-get ov 'cj-music-row-number))
                              (overlays-in (point-min) (point-max)))
                  (lambda (a b) (< (overlay-start a) (overlay-start b)))))))

;;; Normal Cases

(ert-deftest test-music-renumber-rows-numbers-each-line ()
  "Normal: every non-blank line gets a sequential number overlay."
  (with-temp-buffer
    (insert "track one\ntrack two\ntrack three\n")
    (cj/music--renumber-rows (current-buffer))
    (should (equal (mapcar #'substring-no-properties
                           (test-music-renumber--numbers (current-buffer)))
                   '("  1 " "  2 " "  3 ")))))

(ert-deftest test-music-renumber-rows-idempotent ()
  "Normal: renumbering twice leaves one overlay per line, not two."
  (with-temp-buffer
    (insert "track one\ntrack two\n")
    (cj/music--renumber-rows (current-buffer))
    (cj/music--renumber-rows (current-buffer))
    (should (= 2 (length (test-music-renumber--numbers (current-buffer)))))))

(ert-deftest test-music-renumber-rows-number-carries-cursor-property ()
  "Normal: the number string carries a cursor property.  Point is pinned at
the row start, and without the property redisplay draws the cursor after
the before-string -- on the album-art thumbnail, where it's invisible."
  (with-temp-buffer
    (insert "track one\n")
    (cj/music--renumber-rows (current-buffer))
    (let ((s (car (test-music-renumber--numbers (current-buffer)))))
      (should (get-text-property 0 'cursor s)))))

;;; Boundary Cases

(ert-deftest test-music-renumber-rows-skips-blank-lines ()
  "Boundary: blank lines are not numbered and don't advance the count."
  (with-temp-buffer
    (insert "track one\n\ntrack two\n")
    (cj/music--renumber-rows (current-buffer))
    (should (equal (mapcar #'substring-no-properties
                           (test-music-renumber--numbers (current-buffer)))
                   '("  1 " "  2 ")))))

(ert-deftest test-music-renumber-rows-empty-buffer-no-overlays ()
  "Boundary: an empty buffer gets no overlays and no error."
  (with-temp-buffer
    (cj/music--renumber-rows (current-buffer))
    (should-not (test-music-renumber--numbers (current-buffer)))))

;;; Error Cases

(ert-deftest test-music-renumber-rows-dead-buffer-noop ()
  "Error: renumbering a killed buffer is a silent no-op (the debounce timer
can fire after the playlist buffer is gone)."
  (let ((buf (generate-new-buffer " *test-renumber-dead*")))
    (kill-buffer buf)
    (should-not (cj/music--renumber-rows buf))))

(ert-deftest test-music-renumber-rows-number-outranks-header-overlay ()
  "Normal: number overlays carry a priority above the header overlay's 100.
The header block is a same-position overlay string at the buffer start;
without the higher priority, row 1's number renders above the header
instead of next to its own track."
  (with-temp-buffer
    (insert "track one\n")
    (cj/music--renumber-rows (current-buffer))
    (let ((ov (car (seq-filter (lambda (o) (overlay-get o 'cj-music-row-number))
                               (overlays-in (point-min) (point-max))))))
      (should (> (or (overlay-get ov 'priority) 0) 100)))))

(ert-deftest test-music-ensure-playlist-buffer-logical-line-motion ()
  "Normal: the playlist moves by logical lines, not screen lines.  The
multi-line header overlay string at position 1 otherwise absorbs every
next-line from the top row -- vertical motion steps through the header's
display and maps back to the same buffer position, so arrows look dead."
  (let (created)
    (cl-letf (((symbol-function 'emms-playlist-mode) #'ignore))
      (unwind-protect
          (progn
            (setq created (cj/music--ensure-playlist-buffer))
            (with-current-buffer created
              (should (local-variable-p 'line-move-visual))
              (should-not line-move-visual)))
        (when (buffer-live-p created) (kill-buffer created))))))

;;; Current-row indicator

(ert-deftest test-music-highlight-current-number-marks-current-row ()
  "Normal: the current row's number renders inverse-video; moving to another
row restores the old one and marks the new one.  The block cursor only
draws in the selected window, so the number itself carries the mark."
  (with-temp-buffer
    (insert "track one\ntrack two\ntrack three\n")
    (cj/music--renumber-rows (current-buffer))
    (goto-char (point-min))
    (forward-line 1)
    (cj/music--highlight-current-number)
    (let ((numbers (test-music-renumber--numbers (current-buffer))))
      (should-not (plist-get (get-text-property 0 'face (nth 0 numbers)) :inverse-video))
      (should (plist-get (get-text-property 0 'face (nth 1 numbers)) :inverse-video)))
    (forward-line 1)
    (cj/music--highlight-current-number)
    (let ((numbers (test-music-renumber--numbers (current-buffer))))
      (should-not (plist-get (get-text-property 0 'face (nth 1 numbers)) :inverse-video))
      (should (plist-get (get-text-property 0 'face (nth 2 numbers)) :inverse-video)))))

(ert-deftest test-music-highlight-current-number-survives-renumber ()
  "Boundary: a renumber rebuilds the overlays; the highlight re-applies to
the current row rather than pointing at a dead overlay."
  (with-temp-buffer
    (insert "track one\ntrack two\n")
    (cj/music--renumber-rows (current-buffer))
    (goto-char (point-min))
    (forward-line 1)
    (cj/music--highlight-current-number)
    (cj/music--renumber-rows (current-buffer))
    (let ((numbers (test-music-renumber--numbers (current-buffer))))
      (should (plist-get (get-text-property 0 'face (nth 1 numbers)) :inverse-video)))))

(ert-deftest test-music-highlight-current-number-keeps-cursor-property ()
  "Boundary: re-facing a number keeps the cursor property intact."
  (with-temp-buffer
    (insert "track one\n")
    (cj/music--renumber-rows (current-buffer))
    (goto-char (point-min))
    (cj/music--highlight-current-number)
    (let ((s (car (test-music-renumber--numbers (current-buffer)))))
      (should (get-text-property 0 'cursor s)))))

(ert-deftest test-music-ensure-playlist-buffer-sticky-hl-line ()
  "Normal: hl-line in the playlist stays visible when the window isn't
selected -- the dock is glanced at from other windows constantly."
  (let (created)
    (cl-letf (((symbol-function 'emms-playlist-mode) #'ignore))
      (unwind-protect
          (progn
            (setq created (cj/music--ensure-playlist-buffer))
            (should (buffer-local-value 'hl-line-sticky-flag created)))
        (when (buffer-live-p created) (kill-buffer created))))))

;;; Sticky header

(ert-deftest test-music-stick-header-moves-overlay-to-window-start ()
  "Normal: a scroll re-anchors the header overlay at the new window start,
so the header block stays at the top of the window while the list scrolls."
  (with-temp-buffer
    (insert "track one\ntrack two\ntrack three\ntrack four\n")
    (setq cj/music--header-overlay (make-overlay (point-min) (point-min)))
    (save-window-excursion
      (set-window-buffer (selected-window) (current-buffer))
      (let ((start (save-excursion (goto-char (point-min)) (forward-line 2) (point))))
        (cj/music--stick-header (selected-window) start)
        (should (= (overlay-start cj/music--header-overlay) start))
        ;; Converges: the same start again is a no-op, not a loop.
        (cj/music--stick-header (selected-window) start)
        (should (= (overlay-start cj/music--header-overlay) start))))))

(ert-deftest test-music-stick-header-no-overlay-noop ()
  "Boundary: no header overlay yet -- the scroll handler is a silent no-op."
  (with-temp-buffer
    (insert "track one\n")
    (setq cj/music--header-overlay nil)
    (save-window-excursion
      (set-window-buffer (selected-window) (current-buffer))
      (should-not (cj/music--stick-header (selected-window) (point-min))))))

(ert-deftest test-music-header-anchor-position-displayed-vs-not ()
  "Normal: the header anchors at the displaying window's start; an
undisplayed buffer anchors at the top."
  (with-temp-buffer
    (insert "track one\ntrack two\ntrack three\ntrack four\n")
    (save-window-excursion
      (set-window-buffer (selected-window) (current-buffer))
      (let ((start (save-excursion (goto-char (point-min)) (forward-line 2) (point))))
        (set-window-start (selected-window) start)
        (should (= (cj/music--header-anchor-position) start))))
    ;; Not displayed after the excursion restores the old config.
    (should (= (cj/music--header-anchor-position) (point-min)))))

(ert-deftest test-music-ensure-playlist-buffer-wires-scroll-hook ()
  "Normal: the playlist buffer re-sticks its header on every window scroll."
  (let (created)
    (cl-letf (((symbol-function 'emms-playlist-mode) #'ignore))
      (unwind-protect
          (progn
            (setq created (cj/music--ensure-playlist-buffer))
            (with-current-buffer created
              (should (member #'cj/music--stick-header window-scroll-functions))))
        (when (buffer-live-p created) (kill-buffer created))))))

;;; Hook wiring

(ert-deftest test-music-renumber-ensure-playlist-buffer-wires-hook ()
  "Normal: the playlist buffer gets the debounced renumber on after-change."
  (let (created)
    (cl-letf (((symbol-function 'emms-playlist-mode) #'ignore))
      (unwind-protect
          (progn
            (setq created (cj/music--ensure-playlist-buffer))
            (with-current-buffer created
              (should (member #'cj/music--schedule-renumber after-change-functions))))
        (when (buffer-live-p created) (kill-buffer created))))))

(provide 'test-music-config--renumber-rows)
;;; test-music-config--renumber-rows.el ends here