From db8e97a70081bce6c3bef50863f2d70651506fbc Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Thu, 10 Sep 2026 15:09:40 -0600 Subject: [PATCH] fix(sonarr): stop marking seasons fully downloaded when no episodes are known A monitored season rendered in the fully downloaded colour whenever Sonarr returned no statistics for it, and whenever it genuinely had zero known episodes. Both cases compared zero against zero and short circuited to the downloaded style, claiming the user had every episode of a season that was only announced, or that had never been scanned. Seasons whose statistics are absent now render as indeterminate, matching the series row and the equivalent album and book rows in the other trees, and the downloaded style now requires a positive episode count. --- src/ui/sonarr_ui/library/series_details_ui.rs | 27 +++++------ .../library/series_details_ui_tests.rs | 45 +++++++++++++------ 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/ui/sonarr_ui/library/series_details_ui.rs b/src/ui/sonarr_ui/library/series_details_ui.rs index 94116624..489edae1 100644 --- a/src/ui/sonarr_ui/library/series_details_ui.rs +++ b/src/ui/sonarr_ui/library/series_details_ui.rs @@ -278,29 +278,22 @@ fn draw_seasons_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { } fn decorate_season_row_with_style<'a>(season: &Season, row: Row<'a>) -> Row<'a> { - let SeasonStatistics { - episode_file_count, - episode_count, - next_airing, - .. - } = if let Some(stats) = &season.statistics { - stats - } else { - &SeasonStatistics::default() - }; - if !season.monitored { row.unmonitored() - } else if episode_file_count == episode_count { - row.downloaded() - } else if let Some(next_airing_utc) = next_airing.as_ref() { - if next_airing_utc > &Utc::now() { - row.unreleased() + } else if let Some(stats) = season.statistics.as_ref() { + if stats.episode_file_count == stats.episode_count && stats.episode_count > 0 { + row.downloaded() + } else if let Some(next_airing) = stats.next_airing.as_ref() { + if next_airing > &Utc::now() { + row.unreleased() + } else { + row.missing() + } } else { row.missing() } } else { - row.missing() + row.indeterminate() } } diff --git a/src/ui/sonarr_ui/library/series_details_ui_tests.rs b/src/ui/sonarr_ui/library/series_details_ui_tests.rs index f607129a..302716a3 100644 --- a/src/ui/sonarr_ui/library/series_details_ui_tests.rs +++ b/src/ui/sonarr_ui/library/series_details_ui_tests.rs @@ -62,20 +62,6 @@ mod tests { assert_eq!(style, row.downloaded()); } - #[test] - fn test_decorate_season_row_with_style_downloaded_when_no_statistics() { - let season = Season { - monitored: true, - statistics: None, - ..Season::default() - }; - let row = Row::new(vec![Cell::from("test".to_owned())]); - - let style = decorate_season_row_with_style(&season, row.clone()); - - assert_eq!(style, row.downloaded()); - } - #[test] fn test_decorate_season_row_with_style_unreleased_when_episodes_are_missing_and_next_airing_is_future() { @@ -135,6 +121,37 @@ mod tests { assert_eq!(style, row.missing()); } + #[test] + fn test_decorate_season_row_with_style_missing_when_episode_count_is_zero() { + let season = Season { + monitored: true, + statistics: Some(SeasonStatistics { + next_airing: Some(Utc::now() - Duration::days(1)), + ..SeasonStatistics::default() + }), + ..Season::default() + }; + let row = Row::new(vec![Cell::from("test".to_owned())]); + + let style = decorate_season_row_with_style(&season, row.clone()); + + assert_eq!(style, row.missing()); + } + + #[test] + fn test_decorate_season_row_with_style_indeterminate_when_no_statistics() { + let season = Season { + monitored: true, + statistics: None, + ..Season::default() + }; + let row = Row::new(vec![Cell::from("test".to_owned())]); + + let style = decorate_season_row_with_style(&season, row.clone()); + + assert_eq!(style, row.indeterminate()); + } + mod snapshot_tests { use crate::models::stateful_table::StatefulTable; use crate::ui::ui_test_utils::test_utils::TerminalSize;