spotify_cli/io/formatters/
playlist.rs

1//! Playlist formatting functions
2
3use serde_json::Value;
4
5use crate::io::common::{extract_artist_names, format_number};
6
7pub fn format_playlists(items: &[Value]) {
8    println!("Your Playlists:");
9    for (i, item) in items.iter().enumerate() {
10        let name = item
11            .get("name")
12            .and_then(|v| v.as_str())
13            .unwrap_or("Unknown");
14        let tracks = item
15            .get("tracks")
16            .and_then(|t| t.get("total"))
17            .and_then(|v| v.as_u64())
18            .unwrap_or(0);
19        let owner = item
20            .get("owner")
21            .and_then(|o| o.get("display_name"))
22            .and_then(|v| v.as_str())
23            .unwrap_or("Unknown");
24
25        println!("  {}. {} ({} tracks) - {}", i + 1, name, tracks, owner);
26    }
27}
28
29pub fn format_playlist_detail(payload: &Value) {
30    let name = payload
31        .get("name")
32        .and_then(|v| v.as_str())
33        .unwrap_or("Unknown");
34    let owner = payload
35        .get("owner")
36        .and_then(|o| o.get("display_name"))
37        .and_then(|v| v.as_str())
38        .unwrap_or("Unknown");
39    let description = payload
40        .get("description")
41        .and_then(|v| v.as_str())
42        .filter(|s| !s.is_empty());
43    let total = payload
44        .get("tracks")
45        .and_then(|t| t.get("total"))
46        .and_then(|v| v.as_u64())
47        .unwrap_or(0);
48    let followers = payload
49        .get("followers")
50        .and_then(|f| f.get("total"))
51        .and_then(|v| v.as_u64())
52        .unwrap_or(0);
53
54    println!("{}", name);
55    println!(
56        "  by {} | {} tracks | {} followers",
57        owner,
58        total,
59        format_number(followers)
60    );
61    if let Some(desc) = description {
62        println!("  {}", desc);
63    }
64
65    // Show first few tracks if available
66    if let Some(tracks) = payload
67        .get("tracks")
68        .and_then(|t| t.get("items"))
69        .and_then(|i| i.as_array())
70        && !tracks.is_empty()
71    {
72        println!("\nTracks:");
73        for (i, item) in tracks.iter().take(10).enumerate() {
74            if let Some(track) = item.get("track") {
75                let track_name = track
76                    .get("name")
77                    .and_then(|v| v.as_str())
78                    .unwrap_or("Unknown");
79                let artists = extract_artist_names(track);
80                println!("  {}. {} - {}", i + 1, track_name, artists);
81            }
82        }
83        if tracks.len() > 10 {
84            println!("  ... and {} more", total as usize - 10);
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use serde_json::json;
93
94    #[test]
95    fn format_playlists_with_items() {
96        let items = vec![
97            json!({
98                "name": "My Playlist",
99                "tracks": { "total": 50 },
100                "owner": { "display_name": "user123" }
101            }),
102            json!({
103                "name": "Another Playlist",
104                "tracks": { "total": 100 },
105                "owner": { "display_name": "user456" }
106            }),
107        ];
108        format_playlists(&items);
109    }
110
111    #[test]
112    fn format_playlists_empty() {
113        let items: Vec<Value> = vec![];
114        format_playlists(&items);
115    }
116
117    #[test]
118    fn format_playlists_minimal_data() {
119        let items = vec![json!({})];
120        format_playlists(&items);
121    }
122
123    #[test]
124    fn format_playlist_detail_full() {
125        let payload = json!({
126            "name": "Test Playlist",
127            "owner": { "display_name": "Test User" },
128            "description": "A great playlist for testing",
129            "tracks": {
130                "total": 25,
131                "items": [
132                    { "track": { "name": "Track 1", "artists": [{ "name": "Artist 1" }] } },
133                    { "track": { "name": "Track 2", "artists": [{ "name": "Artist 2" }] } }
134                ]
135            },
136            "followers": { "total": 5000 }
137        });
138        format_playlist_detail(&payload);
139    }
140
141    #[test]
142    fn format_playlist_detail_minimal() {
143        let payload = json!({});
144        format_playlist_detail(&payload);
145    }
146
147    #[test]
148    fn format_playlist_detail_empty_description() {
149        let payload = json!({
150            "name": "Playlist",
151            "description": "",
152            "owner": { "display_name": "User" }
153        });
154        format_playlist_detail(&payload);
155    }
156
157    #[test]
158    fn format_playlist_detail_many_tracks() {
159        let tracks: Vec<Value> = (0..15)
160            .map(|i| json!({ "track": { "name": format!("Track {}", i), "artists": [{ "name": "Artist" }] } }))
161            .collect();
162        let payload = json!({
163            "name": "Big Playlist",
164            "owner": { "display_name": "User" },
165            "tracks": { "total": 100, "items": tracks },
166            "followers": { "total": 1000000 }
167        });
168        format_playlist_detail(&payload);
169    }
170
171    #[test]
172    fn format_playlist_detail_no_tracks() {
173        let payload = json!({
174            "name": "Empty Playlist",
175            "owner": { "display_name": "User" },
176            "tracks": { "total": 0, "items": [] }
177        });
178        format_playlist_detail(&payload);
179    }
180}