Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
refactor: extract shared test helpers and consolidate editable playli…
…st API
  • Loading branch information
LargeModGames committed Mar 17, 2026
commit fa132bc90614e46c0553a0ec97c8781a49b78024
86 changes: 3 additions & 83 deletions src/core/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1147,15 +1147,6 @@ impl App {
playlist.owner.id.id() == user.id.id() || playlist.collaborative
}

pub fn editable_playlist_indices(&self) -> Vec<usize> {
self
.all_playlists
.iter()
.enumerate()
.filter_map(|(index, playlist)| self.playlist_is_editable(playlist).then_some(index))
.collect()
}

pub fn editable_playlists(&self) -> Vec<&SimplifiedPlaylist> {
self
.all_playlists
Expand All @@ -1164,24 +1155,6 @@ impl App {
.collect()
}

pub fn editable_playlist_count(&self) -> usize {
self
.all_playlists
.iter()
.filter(|playlist| self.playlist_is_editable(playlist))
.count()
}

pub fn editable_playlist_at_picker_index(
&self,
picker_index: usize,
) -> Option<&SimplifiedPlaylist> {
let editable_indices = self.editable_playlist_indices();
editable_indices
.get(picker_index)
.and_then(|index| self.all_playlists.get(*index))
}

pub fn begin_add_track_to_playlist_flow(
&mut self,
track_id: Option<TrackId<'static>>,
Expand All @@ -1206,7 +1179,7 @@ impl App {
return;
}

if self.editable_playlist_count() == 0 {
if self.editable_playlists().is_empty() {
self.set_status_message("No editable playlists available".to_string(), 4);
return;
}
Expand Down Expand Up @@ -3545,14 +3518,10 @@ impl App {

#[cfg(test)]
mod tests {
use crate::core::test_helpers::{private_user, simplified_playlist};
use super::*;
use chrono::{Duration as ChronoDuration, Utc};
use rspotify::model::{
artist::SimplifiedArtist,
idtypes::{PlaylistId, UserId},
playlist::PlaylistTracksRef,
user::PublicUser,
};
use rspotify::model::{artist::SimplifiedArtist, idtypes::PlaylistId};
use rspotify::prelude::Id;
use std::collections::HashMap;
use std::sync::mpsc::channel;
Expand Down Expand Up @@ -3626,55 +3595,6 @@ mod tests {
}
}

fn private_user(id: &str) -> PrivateUser {
PrivateUser {
country: None,
display_name: Some("Test User".to_string()),
email: None,
explicit_content: None,
external_urls: HashMap::new(),
followers: None,
href: "https://api.spotify.com/v1/me".to_string(),
id: UserId::from_id(id).unwrap().into_static(),
images: None,
product: None,
}
}

fn public_user(id: &str, display_name: &str) -> PublicUser {
PublicUser {
display_name: Some(display_name.to_string()),
external_urls: HashMap::new(),
followers: None,
href: format!("https://api.spotify.com/v1/users/{id}"),
id: UserId::from_id(id).unwrap().into_static(),
images: Vec::new(),
}
}

fn simplified_playlist(
id: &str,
name: &str,
owner_id: &str,
collaborative: bool,
) -> SimplifiedPlaylist {
SimplifiedPlaylist {
collaborative,
external_urls: HashMap::new(),
href: format!("https://api.spotify.com/v1/playlists/{id}"),
id: PlaylistId::from_id(id).unwrap().into_static(),
images: Vec::new(),
name: name.to_string(),
owner: public_user(owner_id, owner_id),
public: Some(false),
snapshot_id: "snapshot".to_string(),
tracks: PlaylistTracksRef {
href: format!("https://api.spotify.com/v1/playlists/{id}/tracks"),
total: 5,
},
}
}

fn playlist_id(id: &str) -> PlaylistId<'static> {
PlaylistId::from_id(id).unwrap().into_static()
}
Expand Down
2 changes: 2 additions & 0 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ pub mod app;
pub mod config;
pub mod layout;
pub mod sort;
#[cfg(test)]
pub mod test_helpers;
pub mod user_config;
58 changes: 58 additions & 0 deletions src/core/test_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#![cfg(test)]

use rspotify::model::{
idtypes::{PlaylistId, UserId},
playlist::PlaylistTracksRef,
user::{PrivateUser, PublicUser},
SimplifiedPlaylist,
};
use std::collections::HashMap;

pub fn private_user(id: &str) -> PrivateUser {
PrivateUser {
country: None,
display_name: Some("Test User".to_string()),
email: None,
explicit_content: None,
external_urls: HashMap::new(),
followers: None,
href: "https://api.spotify.com/v1/me".to_string(),
id: UserId::from_id(id).unwrap().into_static(),
images: None,
product: None,
}
}

pub fn public_user(id: &str, display_name: &str) -> PublicUser {
PublicUser {
display_name: Some(display_name.to_string()),
external_urls: HashMap::new(),
followers: None,
href: format!("https://api.spotify.com/v1/users/{id}"),
id: UserId::from_id(id).unwrap().into_static(),
images: Vec::new(),
}
}

pub fn simplified_playlist(
id: &str,
name: &str,
owner_id: &str,
collaborative: bool,
) -> SimplifiedPlaylist {
SimplifiedPlaylist {
collaborative,
external_urls: HashMap::new(),
href: format!("https://api.spotify.com/v1/playlists/{id}"),
id: PlaylistId::from_id(id).unwrap().into_static(),
images: Vec::new(),
name: name.to_string(),
owner: public_user(owner_id, owner_id),
public: Some(false),
snapshot_id: "snapshot".to_string(),
tracks: PlaylistTracksRef {
href: format!("https://api.spotify.com/v1/playlists/{id}/tracks"),
total: 5,
},
}
}
71 changes: 8 additions & 63 deletions src/tui/handlers/dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fn handle_confirmation_dialog(key: Key, app: &mut App, dialog_context: DialogCon
}

fn handle_add_to_playlist_picker(key: Key, app: &mut App) {
let editable_playlists = app.editable_playlist_indices();
let editable_playlists = app.editable_playlists();
let playlist_count = editable_playlists.len();
match key {
k if common_key_events::down_event(k) => {
Expand Down Expand Up @@ -93,11 +93,10 @@ fn handle_add_to_playlist_picker(key: Key, app: &mut App) {
}
Key::Enter => {
if let Some(pending_add) = app.pending_playlist_track_add.clone() {
if let Some(playlist) = app.editable_playlist_at_picker_index(
app
.playlist_picker_selected_index
.min(playlist_count.saturating_sub(1)),
) {
let selected = app
.playlist_picker_selected_index
.min(playlist_count.saturating_sub(1));
if let Some(playlist) = editable_playlists.get(selected) {
app.dispatch(IoEvent::AddTrackToPlaylist(
playlist.id.clone().into_static(),
pending_add.track_id,
Expand Down Expand Up @@ -141,66 +140,12 @@ mod tests {
use super::*;
use crate::core::{
app::{PendingPlaylistTrackAdd, RouteId},
test_helpers::{private_user, simplified_playlist},
user_config::UserConfig,
};
use rspotify::model::{
idtypes::{PlaylistId, TrackId, UserId},
page::Page,
playlist::PlaylistTracksRef,
user::{PrivateUser, PublicUser},
SimplifiedPlaylist,
};
use rspotify::model::{idtypes::TrackId, page::Page};
use rspotify::prelude::Id;
use std::{collections::HashMap, sync::mpsc::channel, time::SystemTime};

fn private_user(id: &str) -> PrivateUser {
PrivateUser {
country: None,
display_name: Some("Test User".to_string()),
email: None,
explicit_content: None,
external_urls: HashMap::new(),
followers: None,
href: "https://api.spotify.com/v1/me".to_string(),
id: UserId::from_id(id).unwrap().into_static(),
images: None,
product: None,
}
}

fn public_user(id: &str) -> PublicUser {
PublicUser {
display_name: Some(id.to_string()),
external_urls: HashMap::new(),
followers: None,
href: format!("https://api.spotify.com/v1/users/{id}"),
id: UserId::from_id(id).unwrap().into_static(),
images: Vec::new(),
}
}

fn simplified_playlist(
id: &str,
name: &str,
owner_id: &str,
collaborative: bool,
) -> SimplifiedPlaylist {
SimplifiedPlaylist {
collaborative,
external_urls: HashMap::new(),
href: format!("https://api.spotify.com/v1/playlists/{id}"),
id: PlaylistId::from_id(id).unwrap().into_static(),
images: Vec::new(),
name: name.to_string(),
owner: public_user(owner_id),
public: Some(false),
snapshot_id: "snapshot".to_string(),
tracks: PlaylistTracksRef {
href: format!("https://api.spotify.com/v1/playlists/{id}/tracks"),
total: 5,
},
}
}
use std::{sync::mpsc::channel, time::SystemTime};

#[test]
fn confirmation_dialog_toggles_with_vim_hl() {
Expand Down
55 changes: 4 additions & 51 deletions src/tui/handlers/search_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,17 +564,12 @@ mod tests {
use super::*;
use crate::core::{
app::{ActiveBlock, RouteId},
test_helpers::{private_user, simplified_playlist},
user_config::UserConfig,
};
use chrono::Duration as ChronoDuration;
use rspotify::model::{
artist::SimplifiedArtist,
idtypes::{PlaylistId, TrackId, UserId},
page::Page,
playlist::PlaylistTracksRef,
track::FullTrack,
user::{PrivateUser, PublicUser},
SimplifiedAlbum, SimplifiedPlaylist,
artist::SimplifiedArtist, idtypes::TrackId, page::Page, track::FullTrack, SimplifiedAlbum,
};
use std::{collections::HashMap, sync::mpsc::channel, time::SystemTime};

Expand Down Expand Up @@ -607,50 +602,6 @@ mod tests {
}
}

fn private_user(id: &str) -> PrivateUser {
PrivateUser {
country: None,
display_name: Some("Test User".to_string()),
email: None,
explicit_content: None,
external_urls: HashMap::new(),
followers: None,
href: "https://api.spotify.com/v1/me".to_string(),
id: UserId::from_id(id).unwrap().into_static(),
images: None,
product: None,
}
}

fn public_user(id: &str) -> PublicUser {
PublicUser {
display_name: Some(id.to_string()),
external_urls: HashMap::new(),
followers: None,
href: format!("https://api.spotify.com/v1/users/{id}"),
id: UserId::from_id(id).unwrap().into_static(),
images: Vec::new(),
}
}

fn simplified_playlist(id: &str, owner_id: &str) -> SimplifiedPlaylist {
SimplifiedPlaylist {
collaborative: false,
external_urls: HashMap::new(),
href: format!("https://api.spotify.com/v1/playlists/{id}"),
id: PlaylistId::from_id(id).unwrap().into_static(),
images: Vec::new(),
name: "Owned Playlist".to_string(),
owner: public_user(owner_id),
public: Some(false),
snapshot_id: "snapshot".to_string(),
tracks: PlaylistTracksRef {
href: format!("https://api.spotify.com/v1/playlists/{id}/tracks"),
total: 1,
},
}
}

#[test]
fn pressing_w_on_search_song_opens_add_to_playlist_picker() {
let (tx, _rx) = channel();
Expand All @@ -667,7 +618,9 @@ mod tests {
});
app.all_playlists = vec![simplified_playlist(
"37i9dQZF1DXcBWIGoYBM5M",
"Owned Playlist",
"spotatui-owner",
false,
)];
app.search_results.tracks = Some(Page {
href: "https://api.spotify.com/v1/search".to_string(),
Expand Down
22 changes: 14 additions & 8 deletions src/tui/ui/popups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,18 +378,24 @@ fn draw_add_track_to_playlist_picker_dialog(f: &mut Frame<'_>, app: &App) {
.alignment(Alignment::Center);
f.render_widget(empty_text, vchunks[1]);
} else {
let is_own_playlist = |playlist: &rspotify::model::SimplifiedPlaylist| -> bool {
app
.user
.as_ref()
.is_some_and(|user| user.id.id() == playlist.owner.id.id())
};
let items: Vec<ListItem> = editable_playlists
.iter()
.map(|playlist| {
let owner = playlist
.owner
.display_name
.as_deref()
.unwrap_or_else(|| playlist.owner.id.id());
let label = if playlist.collaborative {
format!("{} - {} (collab)", playlist.name, owner)
let label = if is_own_playlist(playlist) {
playlist.name.clone()
} else {
format!("{} - {}", playlist.name, owner)
let owner = playlist
.owner
.display_name
.as_deref()
.unwrap_or_else(|| playlist.owner.id.id());
format!("{} - {} (collab)", playlist.name, owner)
};
ListItem::new(Span::raw(label))
})
Expand Down