From 3227495947c28c690a40d1d337ae56dc7caa9457 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Thu, 10 Sep 2026 13:51:25 -0600 Subject: [PATCH] test(lidarr): extract and cover the album row style branches Row styling is invisible to the snapshot harness, because render_to_string_with_app returns a plain String and discards colour and modifiers. The album row style branch was therefore entirely uncovered, and any arm could be swapped for another without a single test noticing. decorate_album_row_with_style now holds that branch, matching decorate_artist_row_with_style in the library view and decorate_book_row_with_style in the Readarr author details view. The extraction is behaviour preserving: the branch is token identical to the one it replaces, and no snapshot changed. Verified by mutation. Against the previous tree all eight mutations left the suite green: replacing each of the unmonitored, downloaded, unreleased, both missing and indeterminate arms, flipping the track file count comparison, and inverting the monitored check. Against the new tests every one of those eight fails. --- src/ui/lidarr_ui/library/artist_details_ui.rs | 59 +++++---- .../library/artist_details_ui_tests.rs | 125 +++++++++++++++++- 2 files changed, 156 insertions(+), 28 deletions(-) diff --git a/src/ui/lidarr_ui/library/artist_details_ui.rs b/src/ui/lidarr_ui/library/artist_details_ui.rs index a0ea6167..168b61ee 100644 --- a/src/ui/lidarr_ui/library/artist_details_ui.rs +++ b/src/ui/lidarr_ui/library/artist_details_ui.rs @@ -288,33 +288,18 @@ fn draw_albums_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .map_or(0f64, |s| convert_to_gb(s.size_on_disk)); let duration_mins = album.duration / 60000; - let row = Row::new(vec![ - Cell::from(monitored.to_owned()), - Cell::from(album.title.to_string()), - Cell::from(album_type), - Cell::from(track_count), - Cell::from(format!("{duration_mins} min")), - Cell::from(release_date), - Cell::from(format!("{size:.2} GB")), - ]); - - if !album.monitored { - row.unmonitored() - } else if let Some(stats) = album.statistics.as_ref() { - if stats.track_file_count == stats.total_track_count && stats.total_track_count > 0 { - row.downloaded() - } else if let Some(release_date) = album.release_date.as_ref() { - if release_date > &Utc::now() { - row.unreleased() - } else { - row.missing() - } - } else { - row.missing() - } - } else { - row.indeterminate() - } + decorate_album_row_with_style( + album, + Row::new(vec![ + Cell::from(monitored.to_owned()), + Cell::from(album.title.to_string()), + Cell::from(album_type), + Cell::from(track_count), + Cell::from(format!("{duration_mins} min")), + Cell::from(release_date), + Cell::from(format!("{size:.2} GB")), + ]), + ) }; let is_searching = active_lidarr_block == ActiveLidarrBlock::SearchAlbums; @@ -350,6 +335,26 @@ fn draw_albums_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { } } +fn decorate_album_row_with_style<'a>(album: &Album, row: Row<'a>) -> Row<'a> { + if !album.monitored { + row.unmonitored() + } else if let Some(stats) = album.statistics.as_ref() { + if stats.track_file_count == stats.total_track_count && stats.total_track_count > 0 { + row.downloaded() + } else if let Some(release_date) = album.release_date.as_ref() { + if release_date > &Utc::now() { + row.unreleased() + } else { + row.missing() + } + } else { + row.missing() + } + } else { + row.indeterminate() + } +} + fn draw_artist_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if !app.is_loading { let current_selection = if app.data.lidarr_data.artist_history.is_empty() { diff --git a/src/ui/lidarr_ui/library/artist_details_ui_tests.rs b/src/ui/lidarr_ui/library/artist_details_ui_tests.rs index bd9a00dd..3f3110e6 100644 --- a/src/ui/lidarr_ui/library/artist_details_ui_tests.rs +++ b/src/ui/lidarr_ui/library/artist_details_ui_tests.rs @@ -1,13 +1,20 @@ #[cfg(test)] mod tests { + use chrono::{Duration, Utc}; + use pretty_assertions::assert_eq; + use ratatui::widgets::{Cell, Row}; use strum::IntoEnumIterator; + use crate::models::lidarr_models::{Album, AlbumStatistics}; use crate::models::servarr_data::lidarr::lidarr_data::{ ALBUM_DETAILS_BLOCKS, ARTIST_DETAILS_BLOCKS, ARTIST_OVERVIEW_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, TRACK_DETAILS_BLOCKS, }; use crate::ui::DrawUi; - use crate::ui::lidarr_ui::library::artist_details_ui::ArtistDetailsUi; + use crate::ui::lidarr_ui::library::artist_details_ui::{ + ArtistDetailsUi, decorate_album_row_with_style, + }; + use crate::ui::styles::ManagarrStyle; #[test] fn test_artist_details_ui_accepts() { @@ -26,6 +33,122 @@ mod tests { }); } + #[test] + fn test_decorate_album_row_with_style_unmonitored() { + let album = Album::default(); + let row = Row::new(vec![Cell::from("test".to_owned())]); + + let style = decorate_album_row_with_style(&album, row.clone()); + + assert_eq!(style, row.unmonitored()); + } + + #[test] + fn test_decorate_album_row_with_style_downloaded_when_all_tracks_present() { + let album = Album { + monitored: true, + statistics: Some(AlbumStatistics { + track_file_count: 3, + total_track_count: 3, + ..AlbumStatistics::default() + }), + ..Album::default() + }; + let row = Row::new(vec![Cell::from("test".to_owned())]); + + let style = decorate_album_row_with_style(&album, row.clone()); + + assert_eq!(style, row.downloaded()); + } + + #[test] + fn test_decorate_album_row_with_style_unreleased_when_tracks_are_missing_and_release_date_is_future() + { + let album = Album { + monitored: true, + release_date: Some(Utc::now() + Duration::days(1)), + statistics: Some(AlbumStatistics { + track_file_count: 0, + total_track_count: 3, + ..AlbumStatistics::default() + }), + ..Album::default() + }; + let row = Row::new(vec![Cell::from("test".to_owned())]); + + let style = decorate_album_row_with_style(&album, row.clone()); + + assert_eq!(style, row.unreleased()); + } + + #[test] + fn test_decorate_album_row_with_style_missing_when_tracks_are_missing_and_release_date_has_passed() + { + let album = Album { + monitored: true, + release_date: Some(Utc::now() - Duration::days(1)), + statistics: Some(AlbumStatistics { + track_file_count: 1, + total_track_count: 3, + ..AlbumStatistics::default() + }), + ..Album::default() + }; + let row = Row::new(vec![Cell::from("test".to_owned())]); + + let style = decorate_album_row_with_style(&album, row.clone()); + + assert_eq!(style, row.missing()); + } + + #[test] + fn test_decorate_album_row_with_style_missing_when_tracks_are_missing_and_no_release_date() { + let album = Album { + monitored: true, + release_date: None, + statistics: Some(AlbumStatistics { + track_file_count: 1, + total_track_count: 3, + ..AlbumStatistics::default() + }), + ..Album::default() + }; + let row = Row::new(vec![Cell::from("test".to_owned())]); + + let style = decorate_album_row_with_style(&album, row.clone()); + + assert_eq!(style, row.missing()); + } + + #[test] + fn test_decorate_album_row_with_style_missing_when_total_track_count_is_zero() { + let album = Album { + monitored: true, + release_date: Some(Utc::now() - Duration::days(1)), + statistics: Some(AlbumStatistics::default()), + ..Album::default() + }; + let row = Row::new(vec![Cell::from("test".to_owned())]); + + let style = decorate_album_row_with_style(&album, row.clone()); + + assert_eq!(style, row.missing()); + } + + #[test] + fn test_decorate_album_row_with_style_indeterminate_when_no_statistics() { + let album = Album { + monitored: true, + statistics: None, + ..Album::default() + }; + let row = Row::new(vec![Cell::from("test".to_owned())]); + + let style = decorate_album_row_with_style(&album, row.clone()); + + assert_eq!(style, row.indeterminate()); + } + mod snapshot_tests { use pretty_assertions::assert_str_eq; use rstest::rstest;