use rsp::queue::{Queue, ShuffleMode, Track}; use std::path::PathBuf; fn track(path: &str, title: Option<&str>, artist: Option<&str>, album: Option<&str>) -> Track { Track { path: PathBuf::from(path), title: title.map(|s| s.to_string()), artist: artist.map(|s| s.to_string()), album: album.map(|s| s.to_string()), } } fn track_no_meta(path: &str) -> Track { Track { path: PathBuf::from(path), title: None, artist: None, album: None, } } fn add_track(q: &mut Queue, path: &str) { q.add_track(PathBuf::from(path)); } // ---------- Queue basics ---------- #[test] fn test_new_queue_is_empty() { let q = Queue::new(); assert!(q.is_empty()); assert_eq!(q.len(), 0); assert!(q.current().is_none()); assert_eq!(q.position(), 0); } #[test] fn test_add_track_and_current() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); assert!(!q.is_empty()); assert_eq!(q.len(), 1); let cur = q.current(); assert!(cur.is_some()); assert_eq!(cur.unwrap().path, PathBuf::from("/music/song1.mp3")); } #[test] fn test_current_on_empty_queue() { let q = Queue::new(); assert!(q.current().is_none()); } #[test] fn test_next_advances_position() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); add_track(&mut q, "/music/song2.mp3"); assert_eq!(q.position(), 0); let next = q.next(); assert!(next.is_some()); assert_eq!(next.unwrap(), PathBuf::from("/music/song2.mp3")); assert_eq!(q.position(), 1); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3")); } #[test] fn test_next_at_end_returns_none() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); assert!(q.next().is_none()); assert_eq!(q.position(), 0); } #[test] fn test_next_from_last_track_returns_none() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); add_track(&mut q, "/music/song2.mp3"); assert!(q.next().is_some()); assert!(q.next().is_none()); assert_eq!(q.position(), 1); } #[test] fn test_previous_goes_back() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); add_track(&mut q, "/music/song2.mp3"); add_track(&mut q, "/music/song3.mp3"); q.next(); q.next(); assert_eq!(q.position(), 2); let prev = q.previous(); assert!(prev.is_some()); assert_eq!(prev.unwrap(), PathBuf::from("/music/song2.mp3")); assert_eq!(q.position(), 1); } #[test] fn test_previous_at_start_returns_none() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); assert!(q.previous().is_none()); assert_eq!(q.position(), 0); } #[test] fn test_previous_multiple_steps() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); add_track(&mut q, "/music/song2.mp3"); add_track(&mut q, "/music/song3.mp3"); q.next(); q.next(); q.previous(); assert_eq!(q.position(), 1); q.previous(); assert_eq!(q.position(), 0); assert!(q.previous().is_none()); assert_eq!(q.position(), 0); } // ---------- Clear ---------- #[test] fn test_clear_queue() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); add_track(&mut q, "/music/song2.mp3"); q.next(); q.clear(); assert!(q.is_empty()); assert_eq!(q.len(), 0); assert_eq!(q.position(), 0); assert!(q.current().is_none()); } #[test] fn test_clear_empty_queue() { let mut q = Queue::new(); q.clear(); assert!(q.is_empty()); } // ---------- Insert after current ---------- #[test] fn test_insert_after_current_empty_queue() { let mut q = Queue::new(); q.insert_after_current(PathBuf::from("/music/song1.mp3")); assert_eq!(q.len(), 1); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song1.mp3")); } #[test] fn test_insert_after_current_middle() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); add_track(&mut q, "/music/song3.mp3"); // Currently at position 0 (song1) q.insert_after_current(PathBuf::from("/music/song2.mp3")); assert_eq!(q.len(), 3); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song1.mp3")); q.next(); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3")); q.next(); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song3.mp3")); } #[test] fn test_insert_after_current_at_end() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); add_track(&mut q, "/music/song2.mp3"); q.next(); q.insert_after_current(PathBuf::from("/music/song3.mp3")); assert_eq!(q.len(), 3); q.next(); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song3.mp3")); } #[test] fn test_insert_after_current_multiple_ordering() { let mut q = Queue::new(); add_track(&mut q, "/music/song1.mp3"); add_track(&mut q, "/music/song4.mp3"); // Queue: [song1, song4], pos=0 // Insert song2, then song3 after current q.insert_after_current(PathBuf::from("/music/song2.mp3")); q.insert_after_current(PathBuf::from("/music/song3.mp3")); // Expected: [song1, song3, song2, song4] // Each insert goes at pos+1, so song3 ends up right after song1, pushing song2 right assert_eq!(q.len(), 4); q.next(); assert_eq!( q.current().unwrap().path, PathBuf::from("/music/song3.mp3"), "last insert goes right after current" ); q.next(); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3")); q.next(); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song4.mp3")); } // ---------- Track display ---------- #[test] fn test_track_display_with_metadata() { let t = track( "/music/test.mp3", Some("Test Title"), Some("Test Artist"), Some("Test Album"), ); let display = t.display_string(0); assert_eq!(display, "1. Test Artist - Test Title"); } #[test] fn test_track_display_artist_fallback_to_album() { let t = track( "/music/test.mp3", Some("Test Title"), None, Some("Test Album"), ); let display = t.display_string(0); assert_eq!(display, "1. Test Album - Test Title"); } #[test] fn test_track_display_no_metadata() { let t = track_no_meta("/music/song.mp3"); let display = t.display_string(0); assert_eq!(display, "1. song.mp3"); } #[test] fn test_track_display_index_offset() { let t = track( "/music/test.mp3", Some("Title"), Some("Artist"), Some("Album"), ); assert_eq!(t.display_string(4), "5. Artist - Title"); } // ---------- Shuffle ---------- #[test] fn test_shuffle_modes() { let mut q = Queue::new(); assert_eq!(*q.shuffle_mode(), ShuffleMode::None); q.set_shuffle(ShuffleMode::All); assert_eq!(*q.shuffle_mode(), ShuffleMode::All); q.set_shuffle(ShuffleMode::Album); assert_eq!(*q.shuffle_mode(), ShuffleMode::Album); q.set_shuffle(ShuffleMode::None); assert_eq!(*q.shuffle_mode(), ShuffleMode::None); } #[test] fn test_shuffle_all_navigates_in_shuffled_order() { let mut q = Queue::new(); for i in 0..10 { add_track(&mut q, &format!("/music/song{}.mp3", i)); } q.set_shuffle(ShuffleMode::All); // Navigate through all tracks, should visit all 10 (in shuffled order) let mut visited = std::collections::HashSet::new(); visited.insert(q.current().unwrap().path.clone()); while let Some(next_path) = q.next() { assert!( visited.insert(next_path), "duplicate track in shuffle navigation" ); } assert_eq!(visited.len(), 10, "should visit all 10 tracks"); } #[test] fn test_shuffle_all_current_stays_at_position_zero() { let mut q = Queue::new(); for i in 0..5 { add_track(&mut q, &format!("/music/song{}.mp3", i)); } q.set_shuffle(ShuffleMode::All); assert_eq!(q.position(), 0); let mut sorted = q.shuffled_indices().to_vec(); sorted.sort(); assert_eq!(sorted, vec![0, 1, 2, 3, 4]); } #[test] fn test_set_shuffle_regenerates_indices() { let mut q = Queue::new(); for i in 0..5 { add_track(&mut q, &format!("/music/song{}.mp3", i)); } q.set_shuffle(ShuffleMode::All); let indices1 = q.shuffled_indices().to_vec(); // Each set_shuffle should regenerate (small chance of same order) q.set_shuffle(ShuffleMode::All); let indices2 = q.shuffled_indices().to_vec(); // Check that all indices are still valid (0..5) let mut sorted1 = indices1.clone(); sorted1.sort(); let mut sorted2 = indices2.clone(); sorted2.sort(); assert_eq!(sorted1, vec![0, 1, 2, 3, 4]); assert_eq!(sorted2, vec![0, 1, 2, 3, 4]); } #[test] fn test_shuffle_none_linear_navigation() { let mut q = Queue::new(); for i in 0..5 { add_track(&mut q, &format!("/music/song{}.mp3", i)); } assert_eq!(*q.shuffle_mode(), ShuffleMode::None); assert_eq!(q.shuffled_indices(), &[0, 1, 2, 3, 4]); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song0.mp3")); q.next(); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song1.mp3")); q.next(); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3")); } // ---------- Edge cases ---------- #[test] fn test_single_track_queue() { let mut q = Queue::new(); add_track(&mut q, "/music/only.mp3"); assert_eq!(q.len(), 1); assert!(q.current().is_some()); assert!(q.next().is_none()); assert_eq!(q.position(), 0); assert!(q.previous().is_none()); assert_eq!(q.position(), 0); } #[test] fn test_set_position() { let mut q = Queue::new(); add_track(&mut q, "/music/song0.mp3"); add_track(&mut q, "/music/song1.mp3"); add_track(&mut q, "/music/song2.mp3"); q.set_position(2); assert_eq!(q.position(), 2); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song2.mp3")); q.set_position(0); assert_eq!(q.position(), 0); assert_eq!(q.current().unwrap().path, PathBuf::from("/music/song0.mp3")); } #[test] fn test_set_position_beyond_end() { let mut q = Queue::new(); add_track(&mut q, "/music/song0.mp3"); add_track(&mut q, "/music/song1.mp3"); q.set_position(5); // current() should handle out-of-bounds position gracefully let cur = q.current(); assert!(cur.is_none()); assert!(q.next().is_none()); } #[test] fn test_len_and_is_empty() { let mut q = Queue::new(); assert_eq!(q.len(), 0); assert!(q.is_empty()); add_track(&mut q, "/music/a.mp3"); assert_eq!(q.len(), 1); assert!(!q.is_empty()); add_track(&mut q, "/music/b.mp3"); assert_eq!(q.len(), 2); q.clear(); assert_eq!(q.len(), 0); assert!(q.is_empty()); } #[test] fn test_track_at() { let mut q = Queue::new(); add_track(&mut q, "/music/song0.mp3"); add_track(&mut q, "/music/song1.mp3"); assert_eq!( q.track_at(0).unwrap().path, PathBuf::from("/music/song0.mp3") ); assert_eq!( q.track_at(1).unwrap().path, PathBuf::from("/music/song1.mp3") ); assert!(q.track_at(2).is_none()); } // ---------- Shuffle + add interaction ---------- #[test] fn test_add_track_updates_shuffle_indices() { let mut q = Queue::new(); q.set_shuffle(ShuffleMode::All); add_track(&mut q, "/music/a.mp3"); assert_eq!(q.shuffled_indices().len(), 1); add_track(&mut q, "/music/b.mp3"); assert_eq!(q.shuffled_indices().len(), 2); add_track(&mut q, "/music/c.mp3"); assert_eq!(q.shuffled_indices().len(), 3); } #[test] fn test_insert_after_current_updates_shuffle_indices() { let mut q = Queue::new(); q.set_shuffle(ShuffleMode::All); add_track(&mut q, "/music/a.mp3"); q.insert_after_current(PathBuf::from("/music/b.mp3")); assert_eq!(q.shuffled_indices().len(), 2); } #[test] fn test_clear_resets_shuffle_indices() { let mut q = Queue::new(); q.set_shuffle(ShuffleMode::All); add_track(&mut q, "/music/a.mp3"); add_track(&mut q, "/music/b.mp3"); q.clear(); assert!(q.shuffled_indices().is_empty()); } // ---------- Track struct ---------- #[test] fn test_track_fields() { let t = track( "/path/to/file.flac", Some("Title"), Some("Artist"), Some("Album"), ); assert_eq!(t.path, PathBuf::from("/path/to/file.flac")); assert_eq!(t.title.unwrap(), "Title"); assert_eq!(t.artist.unwrap(), "Artist"); assert_eq!(t.album.unwrap(), "Album"); } #[test] fn test_track_no_metadata_fields() { let t = track_no_meta("/path/to/file.flac"); assert_eq!(t.path, PathBuf::from("/path/to/file.flac")); assert!(t.title.is_none()); assert!(t.artist.is_none()); assert!(t.album.is_none()); }