aboutsummaryrefslogtreecommitdiffstats
path: root/tests/queue.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-05-08 00:40:40 +0200
committersalmonik <raisersoft@proton.me>2026-05-08 00:40:40 +0200
commitcfd0781ebae76447cc8072fd60883aabf8158d32 (patch)
tree9cb5bf7da4b1c4070cdb827cd0387968e09161eb /tests/queue.rs
parentFix queue bugs: play -i on empty queue, stale track replay after queue ends, ... (diff)
downloadrsplayer-cfd0781ebae76447cc8072fd60883aabf8158d32.tar.gz
rsplayer-cfd0781ebae76447cc8072fd60883aabf8158d32.zip
Extract queue module, add comprehensive tests, fix queue/play bugs
• Moved Queue, Track, ShuffleMode, AudioMetadata from daemon.rs into new src/queue.rs module with public API\n• Fixed rsp play <file> interrupting active playback (now checks is_playing)\n• Fixed directory play -n/-i inserting files in reverse order (now uses .iter().rev())\n• Removed dead duplicate shuffle handler in process_command\n• Fixed queue display showing (playing) when playback is stopped\n• Added 34 tests covering navigation, shuffle, insert ordering, edge cases, display
Diffstat (limited to 'tests/queue.rs')
-rw-r--r--tests/queue.rs459
1 files changed, 459 insertions, 0 deletions
diff --git a/tests/queue.rs b/tests/queue.rs
new file mode 100644
index 0000000..0ceaba0
--- /dev/null
+++ b/tests/queue.rs
@@ -0,0 +1,459 @@
1use rsp::queue::{Queue, ShuffleMode, Track};
2use std::path::PathBuf;
3
4fn track(path: &str, title: Option<&str>, artist: Option<&str>, album: Option<&str>) -> Track {
5 Track {
6 path: PathBuf::from(path),
7 title: title.map(|s| s.to_string()),
8 artist: artist.map(|s| s.to_string()),
9 album: album.map(|s| s.to_string()),
10 }
11}
12
13fn track_no_meta(path: &str) -> Track {
14 Track {
15 path: PathBuf::from(path),
16 title: None,
17 artist: None,
18 album: None,
19 }
20}
21
22fn add_track(q: &mut Queue, path: &str) {
23 q.add_track(PathBuf::from(path));
24}
25
26// ---------- Queue basics ----------
27
28#[test]
29fn test_new_queue_is_empty() {
30 let q = Queue::new();
31 assert!(q.is_empty());
32 assert_eq!(q.len(), 0);
33 assert!(q.current().is_none());
34 assert_eq!(q.position(), 0);
35}
36
37#[test]
38fn test_add_track_and_current() {
39 let mut q = Queue::new();
40 add_track(&mut q, "/music/song1.mp3");
41 assert!(!q.is_empty());
42 assert_eq!(q.len(), 1);
43 let cur = q.current();
44 assert!(cur.is_some());
45 assert_eq!(cur.unwrap().path, PathBuf::from("/music/song1.mp3"));
46}
47
48#[test]
49fn test_current_on_empty_queue() {
50 let q = Queue::new();
51 assert!(q.current().is_none());
52}
53
54#[test]
55fn test_next_advances_position() {
56 let mut q = Queue::new();
57 add_track(&mut q, "/music/song1.mp3");
58 add_track(&mut q, "/music/song2.mp3");
59 assert_eq!(q.position(), 0);
60 let next = q.next();
61 assert!(next.is_some());
62 assert_eq!(next.unwrap(), PathBuf::from("/music/song2.mp3"));
63 assert_eq!(q.position(), 1);
64 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3"));
65}
66
67#[test]
68fn test_next_at_end_returns_none() {
69 let mut q = Queue::new();
70 add_track(&mut q, "/music/song1.mp3");
71 assert!(q.next().is_none());
72 assert_eq!(q.position(), 0);
73}
74
75#[test]
76fn test_next_from_last_track_returns_none() {
77 let mut q = Queue::new();
78 add_track(&mut q, "/music/song1.mp3");
79 add_track(&mut q, "/music/song2.mp3");
80 assert!(q.next().is_some());
81 assert!(q.next().is_none());
82 assert_eq!(q.position(), 1);
83}
84
85#[test]
86fn test_previous_goes_back() {
87 let mut q = Queue::new();
88 add_track(&mut q, "/music/song1.mp3");
89 add_track(&mut q, "/music/song2.mp3");
90 add_track(&mut q, "/music/song3.mp3");
91 q.next();
92 q.next();
93 assert_eq!(q.position(), 2);
94 let prev = q.previous();
95 assert!(prev.is_some());
96 assert_eq!(prev.unwrap(), PathBuf::from("/music/song2.mp3"));
97 assert_eq!(q.position(), 1);
98}
99
100#[test]
101fn test_previous_at_start_returns_none() {
102 let mut q = Queue::new();
103 add_track(&mut q, "/music/song1.mp3");
104 assert!(q.previous().is_none());
105 assert_eq!(q.position(), 0);
106}
107
108#[test]
109fn test_previous_multiple_steps() {
110 let mut q = Queue::new();
111 add_track(&mut q, "/music/song1.mp3");
112 add_track(&mut q, "/music/song2.mp3");
113 add_track(&mut q, "/music/song3.mp3");
114 q.next();
115 q.next();
116 q.previous();
117 assert_eq!(q.position(), 1);
118 q.previous();
119 assert_eq!(q.position(), 0);
120 assert!(q.previous().is_none());
121 assert_eq!(q.position(), 0);
122}
123
124// ---------- Clear ----------
125
126#[test]
127fn test_clear_queue() {
128 let mut q = Queue::new();
129 add_track(&mut q, "/music/song1.mp3");
130 add_track(&mut q, "/music/song2.mp3");
131 q.next();
132 q.clear();
133 assert!(q.is_empty());
134 assert_eq!(q.len(), 0);
135 assert_eq!(q.position(), 0);
136 assert!(q.current().is_none());
137}
138
139#[test]
140fn test_clear_empty_queue() {
141 let mut q = Queue::new();
142 q.clear();
143 assert!(q.is_empty());
144}
145
146// ---------- Insert after current ----------
147
148#[test]
149fn test_insert_after_current_empty_queue() {
150 let mut q = Queue::new();
151 q.insert_after_current(PathBuf::from("/music/song1.mp3"));
152 assert_eq!(q.len(), 1);
153 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song1.mp3"));
154}
155
156#[test]
157fn test_insert_after_current_middle() {
158 let mut q = Queue::new();
159 add_track(&mut q, "/music/song1.mp3");
160 add_track(&mut q, "/music/song3.mp3");
161 // Currently at position 0 (song1)
162 q.insert_after_current(PathBuf::from("/music/song2.mp3"));
163 assert_eq!(q.len(), 3);
164 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song1.mp3"));
165 q.next();
166 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3"));
167 q.next();
168 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song3.mp3"));
169}
170
171#[test]
172fn test_insert_after_current_at_end() {
173 let mut q = Queue::new();
174 add_track(&mut q, "/music/song1.mp3");
175 add_track(&mut q, "/music/song2.mp3");
176 q.next();
177 q.insert_after_current(PathBuf::from("/music/song3.mp3"));
178 assert_eq!(q.len(), 3);
179 q.next();
180 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song3.mp3"));
181}
182
183#[test]
184fn test_insert_after_current_multiple_ordering() {
185 let mut q = Queue::new();
186 add_track(&mut q, "/music/song1.mp3");
187 add_track(&mut q, "/music/song4.mp3");
188 // Queue: [song1, song4], pos=0
189 // Insert song2, then song3 after current
190 q.insert_after_current(PathBuf::from("/music/song2.mp3"));
191 q.insert_after_current(PathBuf::from("/music/song3.mp3"));
192 // Expected: [song1, song3, song2, song4]
193 // Each insert goes at pos+1, so song3 ends up right after song1, pushing song2 right
194 assert_eq!(q.len(), 4);
195 q.next();
196 assert_eq!(
197 q.current().unwrap().path,
198 PathBuf::from("/music/song3.mp3"),
199 "last insert goes right after current"
200 );
201 q.next();
202 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3"));
203 q.next();
204 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song4.mp3"));
205}
206
207// ---------- Track display ----------
208
209#[test]
210fn test_track_display_with_metadata() {
211 let t = track(
212 "/music/test.mp3",
213 Some("Test Title"),
214 Some("Test Artist"),
215 Some("Test Album"),
216 );
217 let display = t.display_string(0);
218 assert_eq!(display, "1. Test Artist - Test Title");
219}
220
221#[test]
222fn test_track_display_artist_fallback_to_album() {
223 let t = track(
224 "/music/test.mp3",
225 Some("Test Title"),
226 None,
227 Some("Test Album"),
228 );
229 let display = t.display_string(0);
230 assert_eq!(display, "1. Test Album - Test Title");
231}
232
233#[test]
234fn test_track_display_no_metadata() {
235 let t = track_no_meta("/music/song.mp3");
236 let display = t.display_string(0);
237 assert_eq!(display, "1. song.mp3");
238}
239
240#[test]
241fn test_track_display_index_offset() {
242 let t = track(
243 "/music/test.mp3",
244 Some("Title"),
245 Some("Artist"),
246 Some("Album"),
247 );
248 assert_eq!(t.display_string(4), "5. Artist - Title");
249}
250
251// ---------- Shuffle ----------
252
253#[test]
254fn test_shuffle_modes() {
255 let mut q = Queue::new();
256 assert_eq!(*q.shuffle_mode(), ShuffleMode::None);
257 q.set_shuffle(ShuffleMode::All);
258 assert_eq!(*q.shuffle_mode(), ShuffleMode::All);
259 q.set_shuffle(ShuffleMode::Album);
260 assert_eq!(*q.shuffle_mode(), ShuffleMode::Album);
261 q.set_shuffle(ShuffleMode::None);
262 assert_eq!(*q.shuffle_mode(), ShuffleMode::None);
263}
264
265#[test]
266fn test_shuffle_all_navigates_in_shuffled_order() {
267 let mut q = Queue::new();
268 for i in 0..10 {
269 add_track(&mut q, &format!("/music/song{}.mp3", i));
270 }
271 q.set_shuffle(ShuffleMode::All);
272 // Current should still return the first track
273 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song0.mp3"));
274 // Navigate through all tracks, should visit all 10 (in shuffled order)
275 let mut visited = std::collections::HashSet::new();
276 visited.insert(q.current().unwrap().path.clone());
277 while let Some(next_path) = q.next() {
278 assert!(
279 visited.insert(next_path),
280 "duplicate track in shuffle navigation"
281 );
282 }
283 assert_eq!(visited.len(), 10, "should visit all 10 tracks");
284}
285
286#[test]
287fn test_shuffle_all_current_stays_at_position_zero() {
288 let mut q = Queue::new();
289 for i in 0..5 {
290 add_track(&mut q, &format!("/music/song{}.mp3", i));
291 }
292 q.set_shuffle(ShuffleMode::All);
293 assert_eq!(q.position(), 0);
294 assert_eq!(q.shuffled_indices()[0], 0);
295}
296
297#[test]
298fn test_set_shuffle_regenerates_indices() {
299 let mut q = Queue::new();
300 for i in 0..5 {
301 add_track(&mut q, &format!("/music/song{}.mp3", i));
302 }
303 q.set_shuffle(ShuffleMode::All);
304 let indices1 = q.shuffled_indices().to_vec();
305 // Each set_shuffle should regenerate (small chance of same order)
306 q.set_shuffle(ShuffleMode::All);
307 let indices2 = q.shuffled_indices().to_vec();
308 // Check that all indices are still valid (0..5)
309 let mut sorted1 = indices1.clone();
310 sorted1.sort();
311 let mut sorted2 = indices2.clone();
312 sorted2.sort();
313 assert_eq!(sorted1, vec![0, 1, 2, 3, 4]);
314 assert_eq!(sorted2, vec![0, 1, 2, 3, 4]);
315}
316
317#[test]
318fn test_shuffle_none_linear_navigation() {
319 let mut q = Queue::new();
320 for i in 0..5 {
321 add_track(&mut q, &format!("/music/song{}.mp3", i));
322 }
323 assert_eq!(*q.shuffle_mode(), ShuffleMode::None);
324 assert_eq!(q.shuffled_indices(), &[0, 1, 2, 3, 4]);
325 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song0.mp3"));
326 q.next();
327 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song1.mp3"));
328 q.next();
329 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3"));
330}
331
332// ---------- Edge cases ----------
333
334#[test]
335fn test_single_track_queue() {
336 let mut q = Queue::new();
337 add_track(&mut q, "/music/only.mp3");
338 assert_eq!(q.len(), 1);
339 assert!(q.current().is_some());
340 assert!(q.next().is_none());
341 assert_eq!(q.position(), 0);
342 assert!(q.previous().is_none());
343 assert_eq!(q.position(), 0);
344}
345
346#[test]
347fn test_set_position() {
348 let mut q = Queue::new();
349 add_track(&mut q, "/music/song0.mp3");
350 add_track(&mut q, "/music/song1.mp3");
351 add_track(&mut q, "/music/song2.mp3");
352 q.set_position(2);
353 assert_eq!(q.position(), 2);
354 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3"));
355 q.set_position(0);
356 assert_eq!(q.position(), 0);
357 assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song0.mp3"));
358}
359
360#[test]
361fn test_set_position_beyond_end() {
362 let mut q = Queue::new();
363 add_track(&mut q, "/music/song0.mp3");
364 add_track(&mut q, "/music/song1.mp3");
365 q.set_position(5);
366 // current() should handle out-of-bounds position gracefully
367 let cur = q.current();
368 assert!(cur.is_none());
369 assert!(q.next().is_none());
370}
371
372#[test]
373fn test_len_and_is_empty() {
374 let mut q = Queue::new();
375 assert_eq!(q.len(), 0);
376 assert!(q.is_empty());
377 add_track(&mut q, "/music/a.mp3");
378 assert_eq!(q.len(), 1);
379 assert!(!q.is_empty());
380 add_track(&mut q, "/music/b.mp3");
381 assert_eq!(q.len(), 2);
382 q.clear();
383 assert_eq!(q.len(), 0);
384 assert!(q.is_empty());
385}
386
387#[test]
388fn test_track_at() {
389 let mut q = Queue::new();
390 add_track(&mut q, "/music/song0.mp3");
391 add_track(&mut q, "/music/song1.mp3");
392 assert_eq!(
393 q.track_at(0).unwrap().path,
394 PathBuf::from("/music/song0.mp3")
395 );
396 assert_eq!(
397 q.track_at(1).unwrap().path,
398 PathBuf::from("/music/song1.mp3")
399 );
400 assert!(q.track_at(2).is_none());
401}
402
403// ---------- Shuffle + add interaction ----------
404
405#[test]
406fn test_add_track_updates_shuffle_indices() {
407 let mut q = Queue::new();
408 q.set_shuffle(ShuffleMode::All);
409 add_track(&mut q, "/music/a.mp3");
410 assert_eq!(q.shuffled_indices().len(), 1);
411 add_track(&mut q, "/music/b.mp3");
412 assert_eq!(q.shuffled_indices().len(), 2);
413 add_track(&mut q, "/music/c.mp3");
414 assert_eq!(q.shuffled_indices().len(), 3);
415}
416
417#[test]
418fn test_insert_after_current_updates_shuffle_indices() {
419 let mut q = Queue::new();
420 q.set_shuffle(ShuffleMode::All);
421 add_track(&mut q, "/music/a.mp3");
422 q.insert_after_current(PathBuf::from("/music/b.mp3"));
423 assert_eq!(q.shuffled_indices().len(), 2);
424}
425
426#[test]
427fn test_clear_resets_shuffle_indices() {
428 let mut q = Queue::new();
429 q.set_shuffle(ShuffleMode::All);
430 add_track(&mut q, "/music/a.mp3");
431 add_track(&mut q, "/music/b.mp3");
432 q.clear();
433 assert!(q.shuffled_indices().is_empty());
434}
435
436// ---------- Track struct ----------
437
438#[test]
439fn test_track_fields() {
440 let t = track(
441 "/path/to/file.flac",
442 Some("Title"),
443 Some("Artist"),
444 Some("Album"),
445 );
446 assert_eq!(t.path, PathBuf::from("/path/to/file.flac"));
447 assert_eq!(t.title.unwrap(), "Title");
448 assert_eq!(t.artist.unwrap(), "Artist");
449 assert_eq!(t.album.unwrap(), "Album");
450}
451
452#[test]
453fn test_track_no_metadata_fields() {
454 let t = track_no_meta("/path/to/file.flac");
455 assert_eq!(t.path, PathBuf::from("/path/to/file.flac"));
456 assert!(t.title.is_none());
457 assert!(t.artist.is_none());
458 assert!(t.album.is_none());
459}