From 0f049e40ee4d9264cd2b7d48078a8fcabc04be81 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Thu, 29 Aug 2024 15:53:56 +0200 Subject: [PATCH] Extract browse --- src/tui/ui/browse.rs | 233 ++++++++++++++++ src/tui/ui/mod.rs | 644 +++++++++++++++---------------------------- 2 files changed, 451 insertions(+), 426 deletions(-) create mode 100644 src/tui/ui/browse.rs diff --git a/src/tui/ui/browse.rs b/src/tui/ui/browse.rs new file mode 100644 index 0000000..e63ecc3 --- /dev/null +++ b/src/tui/ui/browse.rs @@ -0,0 +1,233 @@ +use musichoard::collection::{ + album::{Album, AlbumStatus}, + artist::Artist, + track::{Track, TrackFormat}, +}; +use ratatui::{ + layout::Rect, + text::Line, + widgets::{List, ListItem, Paragraph}, +}; + +use crate::tui::app::WidgetState; + +use super::{UiColor, UiDisplay}; + +pub struct ArtistArea { + pub list: Rect, +} + +pub struct AlbumArea { + pub list: Rect, + pub info: Rect, +} + +pub struct TrackArea { + pub list: Rect, + pub info: Rect, +} + +pub struct FrameArea { + pub artist: ArtistArea, + pub album: AlbumArea, + pub track: TrackArea, + pub minibuffer: Rect, +} + +impl FrameArea { + pub fn new(frame: Rect) -> Self { + let minibuffer_height = 3; + let buffer_height = frame.height.saturating_sub(minibuffer_height); + + let width_one_third = frame.width / 3; + let height_one_third = buffer_height / 3; + + let panel_width = width_one_third; + let panel_width_last = frame.width.saturating_sub(2 * panel_width); + let panel_height_top = buffer_height.saturating_sub(height_one_third); + let panel_height_bottom = height_one_third; + + let artist_list = Rect { + x: frame.x, + y: frame.y, + width: panel_width, + height: buffer_height, + }; + + let album_list = Rect { + x: artist_list.x + artist_list.width, + y: frame.y, + width: panel_width, + height: panel_height_top, + }; + + let album_info = Rect { + x: album_list.x, + y: album_list.y + album_list.height, + width: album_list.width, + height: panel_height_bottom, + }; + + let track_list = Rect { + x: album_list.x + album_list.width, + y: frame.y, + width: panel_width_last, + height: panel_height_top, + }; + + let track_info = Rect { + x: track_list.x, + y: track_list.y + track_list.height, + width: track_list.width, + height: panel_height_bottom, + }; + + let minibuffer = Rect { + x: frame.x, + y: frame.y + buffer_height, + width: frame.width, + height: minibuffer_height, + }; + + FrameArea { + artist: ArtistArea { list: artist_list }, + album: AlbumArea { + list: album_list, + info: album_info, + }, + track: TrackArea { + list: track_list, + info: track_info, + }, + minibuffer, + } + } +} + +pub struct ArtistState<'a, 'b> { + pub active: bool, + pub list: List<'a>, + pub state: &'b mut WidgetState, +} + +impl<'a, 'b> ArtistState<'a, 'b> { + pub fn new( + active: bool, + artists: &'a [Artist], + state: &'b mut WidgetState, + ) -> ArtistState<'a, 'b> { + let list = List::new( + artists + .iter() + .map(|a| ListItem::new(a.id.name.as_str())) + .collect::>(), + ); + + ArtistState { + active, + list, + state, + } + } +} + +pub struct AlbumState<'a, 'b> { + pub active: bool, + pub list: List<'a>, + pub state: &'b mut WidgetState, + pub info: Paragraph<'a>, +} + +impl<'a, 'b> AlbumState<'a, 'b> { + pub fn new( + active: bool, + albums: &'a [Album], + state: &'b mut WidgetState, + ) -> AlbumState<'a, 'b> { + let list = List::new( + albums + .iter() + .map(Self::to_list_item) + .collect::>(), + ); + + let album = state.list.selected().map(|i| &albums[i]); + let info = Paragraph::new(format!( + "Title: {}\n\ + Date: {}\n\ + Type: {}\n\ + Status: {}", + album.map(|a| a.id.title.as_str()).unwrap_or(""), + album + .map(|a| UiDisplay::display_date(&a.date, &a.seq)) + .unwrap_or_default(), + album + .map(|a| UiDisplay::display_type(&a.primary_type, &a.secondary_types)) + .unwrap_or_default(), + album + .map(|a| UiDisplay::display_album_status(&a.get_status())) + .unwrap_or("") + )); + + AlbumState { + active, + list, + state, + info, + } + } + + fn to_list_item(album: &Album) -> ListItem { + let line = match album.get_status() { + AlbumStatus::None => Line::raw(album.id.title.as_str()), + AlbumStatus::Owned(format) => match format { + TrackFormat::Mp3 => Line::styled(album.id.title.as_str(), UiColor::FG_WARN), + TrackFormat::Flac => Line::styled(album.id.title.as_str(), UiColor::FG_GOOD), + }, + }; + ListItem::new(line) + } +} + +pub struct TrackState<'a, 'b> { + pub active: bool, + pub list: List<'a>, + pub state: &'b mut WidgetState, + pub info: Paragraph<'a>, +} + +impl<'a, 'b> TrackState<'a, 'b> { + pub fn new( + active: bool, + tracks: &'a [Track], + state: &'b mut WidgetState, + ) -> TrackState<'a, 'b> { + let list = List::new( + tracks + .iter() + .map(|tr| ListItem::new(tr.id.title.as_str())) + .collect::>(), + ); + + let track = state.list.selected().map(|i| &tracks[i]); + let info = Paragraph::new(format!( + "Track: {}\n\ + Title: {}\n\ + Artist: {}\n\ + Quality: {}", + track.map(|t| t.number.0.to_string()).unwrap_or_default(), + track.map(|t| t.id.title.as_str()).unwrap_or(""), + track.map(|t| t.artist.join("; ")).unwrap_or_default(), + track + .map(|t| UiDisplay::display_track_quality(&t.quality)) + .unwrap_or_default(), + )); + + TrackState { + active, + list, + state, + info, + } + } +} diff --git a/src/tui/ui/mod.rs b/src/tui/ui/mod.rs index 4b079f5..3b8c6a3 100644 --- a/src/tui/ui/mod.rs +++ b/src/tui/ui/mod.rs @@ -1,7 +1,9 @@ +mod browse; mod minibuffer; use std::collections::HashMap; +use browse::{AlbumArea, AlbumState, ArtistArea, ArtistState, FrameArea, TrackArea, TrackState}; use minibuffer::Minibuffer; use musichoard::collection::{ album::{Album, AlbumDate, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq, AlbumStatus}, @@ -13,7 +15,6 @@ use musichoard::collection::{ use ratatui::{ layout::{Alignment, Rect}, style::{Color, Style}, - text::Line, widgets::{Block, BorderType, Borders, Clear, List, ListItem, ListState, Paragraph, Wrap}, Frame, }; @@ -23,108 +24,21 @@ use crate::tui::{ lib::interface::musicbrainz::Match, }; -const COLOR_BG: Color = Color::Black; -const COLOR_BG_HL: Color = Color::DarkGray; -const COLOR_FG: Color = Color::White; -const COLOR_FG_ERR: Color = Color::Red; -const COLOR_FG_WARN: Color = Color::LightYellow; -const COLOR_FG_GOOD: Color = Color::LightGreen; +pub struct UiColor; + +impl UiColor { + const BG: Color = Color::Black; + const BG_HL: Color = Color::DarkGray; + const FG: Color = Color::White; + const FG_ERR: Color = Color::Red; + const FG_WARN: Color = Color::LightYellow; + const FG_GOOD: Color = Color::LightGreen; +} pub trait IUi { fn render(app: &mut APP, frame: &mut Frame); } -struct ArtistArea { - list: Rect, -} - -struct AlbumArea { - list: Rect, - info: Rect, -} - -struct TrackArea { - list: Rect, - info: Rect, -} - -struct FrameArea { - artist: ArtistArea, - album: AlbumArea, - track: TrackArea, - minibuffer: Rect, -} - -impl FrameArea { - fn new(frame: Rect) -> Self { - let minibuffer_height = 3; - let buffer_height = frame.height.saturating_sub(minibuffer_height); - - let width_one_third = frame.width / 3; - let height_one_third = buffer_height / 3; - - let panel_width = width_one_third; - let panel_width_last = frame.width.saturating_sub(2 * panel_width); - let panel_height_top = buffer_height.saturating_sub(height_one_third); - let panel_height_bottom = height_one_third; - - let artist_list = Rect { - x: frame.x, - y: frame.y, - width: panel_width, - height: buffer_height, - }; - - let album_list = Rect { - x: artist_list.x + artist_list.width, - y: frame.y, - width: panel_width, - height: panel_height_top, - }; - - let album_info = Rect { - x: album_list.x, - y: album_list.y + album_list.height, - width: album_list.width, - height: panel_height_bottom, - }; - - let track_list = Rect { - x: album_list.x + album_list.width, - y: frame.y, - width: panel_width_last, - height: panel_height_top, - }; - - let track_info = Rect { - x: track_list.x, - y: track_list.y + track_list.height, - width: track_list.width, - height: panel_height_bottom, - }; - - let minibuffer = Rect { - x: frame.x, - y: frame.y + buffer_height, - width: frame.width, - height: minibuffer_height, - }; - - FrameArea { - artist: ArtistArea { list: artist_list }, - album: AlbumArea { - list: album_list, - info: album_info, - }, - track: TrackArea { - list: track_list, - info: track_info, - }, - minibuffer, - } - } -} - enum OverlaySize { MarginFactor(u16), Value(u16), @@ -181,29 +95,6 @@ impl OverlayBuilder { } } -struct ArtistState<'a, 'b> { - active: bool, - list: List<'a>, - state: &'b mut WidgetState, -} - -impl<'a, 'b> ArtistState<'a, 'b> { - fn new(active: bool, artists: &'a [Artist], state: &'b mut WidgetState) -> ArtistState<'a, 'b> { - let list = List::new( - artists - .iter() - .map(|a| ListItem::new(a.id.name.as_str())) - .collect::>(), - ); - - ArtistState { - active, - list, - state, - } - } -} - struct InfoOverlay; impl InfoOverlay { @@ -288,142 +179,6 @@ impl<'a> ArtistOverlay<'a> { } } -struct AlbumState<'a, 'b> { - active: bool, - list: List<'a>, - state: &'b mut WidgetState, - info: Paragraph<'a>, -} - -impl<'a, 'b> AlbumState<'a, 'b> { - fn new(active: bool, albums: &'a [Album], state: &'b mut WidgetState) -> AlbumState<'a, 'b> { - let list = List::new( - albums - .iter() - .map(Self::to_list_item) - .collect::>(), - ); - - let album = state.list.selected().map(|i| &albums[i]); - let info = Paragraph::new(format!( - "Title: {}\n\ - Date: {}\n\ - Type: {}\n\ - Status: {}", - album.map(|a| a.id.title.as_str()).unwrap_or(""), - album - .map(|a| Self::display_date(&a.date, &a.seq)) - .unwrap_or_default(), - album - .map(|a| Self::display_type(&a.primary_type, &a.secondary_types)) - .unwrap_or_default(), - album - .map(|a| Self::display_album_status(&a.get_status())) - .unwrap_or("") - )); - - AlbumState { - active, - list, - state, - info, - } - } - - fn to_list_item(album: &Album) -> ListItem { - let line = match album.get_status() { - AlbumStatus::None => Line::raw(album.id.title.as_str()), - AlbumStatus::Owned(format) => match format { - TrackFormat::Mp3 => Line::styled(album.id.title.as_str(), COLOR_FG_WARN), - TrackFormat::Flac => Line::styled(album.id.title.as_str(), COLOR_FG_GOOD), - }, - }; - ListItem::new(line) - } - - fn display_date(date: &AlbumDate, seq: &AlbumSeq) -> String { - if seq.0 > 0 { - format!("{} ({})", Self::display_album_date(date), seq.0) - } else { - Self::display_album_date(date) - } - } - - fn display_album_date(date: &AlbumDate) -> String { - match date.year { - Some(year) => match date.month { - Some(month) => match date.day { - Some(day) => format!("{year}‐{month:02}‐{day:02}"), - None => format!("{year}‐{month:02}"), - }, - None => format!("{year}"), - }, - None => String::from(""), - } - } - - fn display_type( - primary: &Option, - secondary: &Vec, - ) -> String { - match primary { - Some(ref primary) => { - if secondary.is_empty() { - Self::display_primary_type(primary).to_string() - } else { - format!( - "{} ({})", - Self::display_primary_type(primary), - Self::display_secondary_types(secondary) - ) - } - } - None => String::default(), - } - } - - fn display_primary_type(value: &AlbumPrimaryType) -> &'static str { - match value { - AlbumPrimaryType::Album => "Album", - AlbumPrimaryType::Single => "Single", - AlbumPrimaryType::Ep => "EP", - AlbumPrimaryType::Broadcast => "Broadcast", - AlbumPrimaryType::Other => "Other", - } - } - - fn display_secondary_types(values: &Vec) -> String { - let mut types: Vec<&'static str> = vec![]; - for value in values { - match value { - AlbumSecondaryType::Compilation => types.push("Compilation"), - AlbumSecondaryType::Soundtrack => types.push("Soundtrack"), - AlbumSecondaryType::Spokenword => types.push("Spokenword"), - AlbumSecondaryType::Interview => types.push("Interview"), - AlbumSecondaryType::Audiobook => types.push("Audiobook"), - AlbumSecondaryType::AudioDrama => types.push("Audio drama"), - AlbumSecondaryType::Live => types.push("Live"), - AlbumSecondaryType::Remix => types.push("Remix"), - AlbumSecondaryType::DjMix => types.push("DJ-mix"), - AlbumSecondaryType::MixtapeStreet => types.push("Mixtape/Street"), - AlbumSecondaryType::Demo => types.push("Demo"), - AlbumSecondaryType::FieldRecording => types.push("Field recording"), - } - } - types.join(", ") - } - - fn display_album_status(status: &AlbumStatus) -> &'static str { - match status { - AlbumStatus::None => "None", - AlbumStatus::Owned(format) => match format { - TrackFormat::Mp3 => "MP3", - TrackFormat::Flac => "FLAC", - }, - } - } -} - struct AlbumOverlay<'a> { properties: Paragraph<'a>, } @@ -447,52 +202,6 @@ impl<'a> AlbumOverlay<'a> { } } -struct TrackState<'a, 'b> { - active: bool, - list: List<'a>, - state: &'b mut WidgetState, - info: Paragraph<'a>, -} - -impl<'a, 'b> TrackState<'a, 'b> { - fn new(active: bool, tracks: &'a [Track], state: &'b mut WidgetState) -> TrackState<'a, 'b> { - let list = List::new( - tracks - .iter() - .map(|tr| ListItem::new(tr.id.title.as_str())) - .collect::>(), - ); - - let track = state.list.selected().map(|i| &tracks[i]); - let info = Paragraph::new(format!( - "Track: {}\n\ - Title: {}\n\ - Artist: {}\n\ - Quality: {}", - track.map(|t| t.number.0.to_string()).unwrap_or_default(), - track.map(|t| t.id.title.as_str()).unwrap_or(""), - track.map(|t| t.artist.join("; ")).unwrap_or_default(), - track - .map(|t| Self::display_track_quality(&t.quality)) - .unwrap_or_default(), - )); - - TrackState { - active, - list, - state, - info, - } - } - - fn display_track_quality(quality: &TrackQuality) -> String { - match quality.format { - TrackFormat::Flac => "FLAC".to_string(), - TrackFormat::Mp3 => format!("MP3 {}kbps", quality.bitrate), - } - } -} - struct ReloadMenu; impl ReloadMenu { @@ -513,11 +222,11 @@ pub struct Ui; impl Ui { fn style(_active: bool, error: bool) -> Style { - let style = Style::default().bg(COLOR_BG); + let style = Style::default().bg(UiColor::BG); if error { - style.fg(COLOR_FG_ERR) + style.fg(UiColor::FG_ERR) } else { - style.fg(COLOR_FG) + style.fg(UiColor::FG) } } @@ -528,9 +237,9 @@ impl Ui { fn highlight_style(active: bool) -> Style { // Do not set the fg color here as it will overwrite any list-specific customisation. if active { - Style::default().bg(COLOR_BG_HL) + Style::default().bg(UiColor::BG_HL) } else { - Style::default().bg(COLOR_BG) + Style::default().bg(UiColor::BG) } } @@ -751,9 +460,9 @@ impl Ui { fn display_match_string(match_album: &Match) -> String { format!( "{:010} | {} [{}] ({}%)", - AlbumState::display_album_date(&match_album.item.date), + UiDisplay::display_album_date(&match_album.item.date), &match_album.item.id.title, - AlbumState::display_type( + UiDisplay::display_type( &match_album.item.primary_type, &match_album.item.secondary_types ), @@ -832,16 +541,105 @@ impl IUi for Ui { pub struct UiDisplay; impl UiDisplay { + fn display_date(date: &AlbumDate, seq: &AlbumSeq) -> String { + if seq.0 > 0 { + format!("{} ({})", Self::display_album_date(date), seq.0) + } else { + Self::display_album_date(date) + } + } + + fn display_album_date(date: &AlbumDate) -> String { + match date.year { + Some(year) => match date.month { + Some(month) => match date.day { + Some(day) => format!("{year}‐{month:02}‐{day:02}"), + None => format!("{year}‐{month:02}"), + }, + None => format!("{year}"), + }, + None => String::from(""), + } + } + + fn display_type( + primary: &Option, + secondary: &Vec, + ) -> String { + match primary { + Some(ref primary) => { + if secondary.is_empty() { + Self::display_primary_type(primary).to_string() + } else { + format!( + "{} ({})", + Self::display_primary_type(primary), + Self::display_secondary_types(secondary) + ) + } + } + None => String::default(), + } + } + + fn display_primary_type(value: &AlbumPrimaryType) -> &'static str { + match value { + AlbumPrimaryType::Album => "Album", + AlbumPrimaryType::Single => "Single", + AlbumPrimaryType::Ep => "EP", + AlbumPrimaryType::Broadcast => "Broadcast", + AlbumPrimaryType::Other => "Other", + } + } + + fn display_secondary_types(values: &Vec) -> String { + let mut types: Vec<&'static str> = vec![]; + for value in values { + match value { + AlbumSecondaryType::Compilation => types.push("Compilation"), + AlbumSecondaryType::Soundtrack => types.push("Soundtrack"), + AlbumSecondaryType::Spokenword => types.push("Spokenword"), + AlbumSecondaryType::Interview => types.push("Interview"), + AlbumSecondaryType::Audiobook => types.push("Audiobook"), + AlbumSecondaryType::AudioDrama => types.push("Audio drama"), + AlbumSecondaryType::Live => types.push("Live"), + AlbumSecondaryType::Remix => types.push("Remix"), + AlbumSecondaryType::DjMix => types.push("DJ-mix"), + AlbumSecondaryType::MixtapeStreet => types.push("Mixtape/Street"), + AlbumSecondaryType::Demo => types.push("Demo"), + AlbumSecondaryType::FieldRecording => types.push("Field recording"), + } + } + types.join(", ") + } + + fn display_album_status(status: &AlbumStatus) -> &'static str { + match status { + AlbumStatus::None => "None", + AlbumStatus::Owned(format) => match format { + TrackFormat::Mp3 => "MP3", + TrackFormat::Flac => "FLAC", + }, + } + } + fn display_matching_info(matching: Option<&Album>) -> String { match matching { Some(matching) => format!( "Matching: {} | {}", - AlbumState::display_album_date(&matching.date), + UiDisplay::display_album_date(&matching.date), &matching.id.title ), None => String::from("Matching: nothing"), } } + + fn display_track_quality(quality: &TrackQuality) -> String { + match quality.format { + TrackFormat::Flac => "FLAC".to_string(), + TrackFormat::Mp3 => format!("MP3 {}kbps", quality.bitrate), + } + } } #[cfg(test)] @@ -941,121 +739,6 @@ mod tests { terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); } - #[test] - fn display_album_date() { - assert_eq!(AlbumState::display_album_date(&AlbumDate::default()), ""); - assert_eq!(AlbumState::display_album_date(&1990.into()), "1990"); - assert_eq!(AlbumState::display_album_date(&(1990, 5).into()), "1990‐05"); - assert_eq!( - AlbumState::display_album_date(&(1990, 5, 6).into()), - "1990‐05‐06" - ); - } - - #[test] - fn display_date() { - let date: AlbumDate = 1990.into(); - assert_eq!( - AlbumState::display_date(&date, &AlbumSeq::default()), - "1990" - ); - assert_eq!(AlbumState::display_date(&date, &AlbumSeq(0)), "1990"); - assert_eq!(AlbumState::display_date(&date, &AlbumSeq(5)), "1990 (5)"); - } - - #[test] - fn display_primary_type() { - assert_eq!( - AlbumState::display_primary_type(&AlbumPrimaryType::Album), - "Album" - ); - assert_eq!( - AlbumState::display_primary_type(&AlbumPrimaryType::Single), - "Single" - ); - assert_eq!( - AlbumState::display_primary_type(&AlbumPrimaryType::Ep), - "EP" - ); - assert_eq!( - AlbumState::display_primary_type(&AlbumPrimaryType::Broadcast), - "Broadcast" - ); - assert_eq!( - AlbumState::display_primary_type(&AlbumPrimaryType::Other), - "Other" - ); - } - - #[test] - fn display_secondary_types() { - assert_eq!( - AlbumState::display_secondary_types(&vec![ - AlbumSecondaryType::Compilation, - AlbumSecondaryType::Soundtrack, - AlbumSecondaryType::Spokenword, - AlbumSecondaryType::Interview, - AlbumSecondaryType::Audiobook, - AlbumSecondaryType::AudioDrama, - AlbumSecondaryType::Live, - AlbumSecondaryType::Remix, - AlbumSecondaryType::DjMix, - AlbumSecondaryType::MixtapeStreet, - AlbumSecondaryType::Demo, - AlbumSecondaryType::FieldRecording, - ]), - "Compilation, Soundtrack, Spokenword, Interview, Audiobook, Audio drama, Live, Remix, \ - DJ-mix, Mixtape/Street, Demo, Field recording" - ); - } - - #[test] - fn display_type() { - assert_eq!(AlbumState::display_type(&None, &vec![]), ""); - assert_eq!( - AlbumState::display_type(&Some(AlbumPrimaryType::Album), &vec![]), - "Album" - ); - assert_eq!( - AlbumState::display_type( - &Some(AlbumPrimaryType::Album), - &vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation] - ), - "Album (Live, Compilation)" - ); - } - - #[test] - fn display_album_status() { - assert_eq!(AlbumState::display_album_status(&AlbumStatus::None), "None"); - assert_eq!( - AlbumState::display_album_status(&AlbumStatus::Owned(TrackFormat::Mp3)), - "MP3" - ); - assert_eq!( - AlbumState::display_album_status(&AlbumStatus::Owned(TrackFormat::Flac)), - "FLAC" - ); - } - - #[test] - fn display_track_quality() { - assert_eq!( - TrackState::display_track_quality(&TrackQuality { - format: TrackFormat::Flac, - bitrate: 1411 - }), - "FLAC" - ); - assert_eq!( - TrackState::display_track_quality(&TrackQuality { - format: TrackFormat::Mp3, - bitrate: 218 - }), - "MP3 218kbps" - ); - } - #[test] fn empty() { let artists: Vec = vec![]; @@ -1096,4 +779,113 @@ mod tests { draw_test_suite(artists, &mut selection); } + + #[test] + fn display_album_date() { + assert_eq!(UiDisplay::display_album_date(&AlbumDate::default()), ""); + assert_eq!(UiDisplay::display_album_date(&1990.into()), "1990"); + assert_eq!(UiDisplay::display_album_date(&(1990, 5).into()), "1990‐05"); + assert_eq!( + UiDisplay::display_album_date(&(1990, 5, 6).into()), + "1990‐05‐06" + ); + } + + #[test] + fn display_date() { + let date: AlbumDate = 1990.into(); + assert_eq!(UiDisplay::display_date(&date, &AlbumSeq::default()), "1990"); + assert_eq!(UiDisplay::display_date(&date, &AlbumSeq(0)), "1990"); + assert_eq!(UiDisplay::display_date(&date, &AlbumSeq(5)), "1990 (5)"); + } + + #[test] + fn display_primary_type() { + assert_eq!( + UiDisplay::display_primary_type(&AlbumPrimaryType::Album), + "Album" + ); + assert_eq!( + UiDisplay::display_primary_type(&AlbumPrimaryType::Single), + "Single" + ); + assert_eq!(UiDisplay::display_primary_type(&AlbumPrimaryType::Ep), "EP"); + assert_eq!( + UiDisplay::display_primary_type(&AlbumPrimaryType::Broadcast), + "Broadcast" + ); + assert_eq!( + UiDisplay::display_primary_type(&AlbumPrimaryType::Other), + "Other" + ); + } + + #[test] + fn display_secondary_types() { + assert_eq!( + UiDisplay::display_secondary_types(&vec![ + AlbumSecondaryType::Compilation, + AlbumSecondaryType::Soundtrack, + AlbumSecondaryType::Spokenword, + AlbumSecondaryType::Interview, + AlbumSecondaryType::Audiobook, + AlbumSecondaryType::AudioDrama, + AlbumSecondaryType::Live, + AlbumSecondaryType::Remix, + AlbumSecondaryType::DjMix, + AlbumSecondaryType::MixtapeStreet, + AlbumSecondaryType::Demo, + AlbumSecondaryType::FieldRecording, + ]), + "Compilation, Soundtrack, Spokenword, Interview, Audiobook, Audio drama, Live, Remix, \ + DJ-mix, Mixtape/Street, Demo, Field recording" + ); + } + + #[test] + fn display_type() { + assert_eq!(UiDisplay::display_type(&None, &vec![]), ""); + assert_eq!( + UiDisplay::display_type(&Some(AlbumPrimaryType::Album), &vec![]), + "Album" + ); + assert_eq!( + UiDisplay::display_type( + &Some(AlbumPrimaryType::Album), + &vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation] + ), + "Album (Live, Compilation)" + ); + } + + #[test] + fn display_album_status() { + assert_eq!(UiDisplay::display_album_status(&AlbumStatus::None), "None"); + assert_eq!( + UiDisplay::display_album_status(&AlbumStatus::Owned(TrackFormat::Mp3)), + "MP3" + ); + assert_eq!( + UiDisplay::display_album_status(&AlbumStatus::Owned(TrackFormat::Flac)), + "FLAC" + ); + } + + #[test] + fn display_track_quality() { + assert_eq!( + UiDisplay::display_track_quality(&TrackQuality { + format: TrackFormat::Flac, + bitrate: 1411 + }), + "FLAC" + ); + assert_eq!( + UiDisplay::display_track_quality(&TrackQuality { + format: TrackFormat::Mp3, + bitrate: 218 + }), + "MP3 218kbps" + ); + } }