test(sonarr): extract and cover the season 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 season row style branch was therefore entirely uncovered, and any arm could be swapped for another without a single test noticing. decorate_season_row_with_style now holds that branch, matching decorate_series_row_with_style in the library view. The extraction is behaviour preserving: the branch is token identical to the one it replaces, the statistics fallback resolves the same way, and no snapshot changed. The season row deliberately has no positive episode count guard, so a season without statistics falls back to zero of zero episodes and reads as downloaded; a test now pins that. Verified by mutation. Against the previous tree all seven mutations left the suite green: replacing each of the unmonitored, downloaded, unreleased and both missing arms, flipping the episode file count comparison, and inverting the monitored check. Against the new tests every one of those seven fails.
This commit is contained in:
2 files changed
+142
-21
No files matched your search
@@ -230,16 +230,12 @@ fn draw_seasons_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let content = Some(&mut app.data.sonarr_data.seasons);
|
||||
let season_row_mapping = |season: &Season| {
|
||||
let Season {
|
||||
title,
|
||||
monitored,
|
||||
statistics,
|
||||
..
|
||||
title, statistics, ..
|
||||
} = season;
|
||||
let SeasonStatistics {
|
||||
episode_file_count,
|
||||
episode_count,
|
||||
size_on_disk,
|
||||
next_airing,
|
||||
..
|
||||
} = if let Some(stats) = statistics {
|
||||
stats
|
||||
@@ -249,25 +245,15 @@ fn draw_seasons_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
let season_monitored = if season.monitored { "🏷" } else { "" };
|
||||
let size = convert_to_gb(*size_on_disk);
|
||||
|
||||
let row = Row::new(vec![
|
||||
decorate_season_row_with_style(
|
||||
season,
|
||||
Row::new(vec![
|
||||
Cell::from(season_monitored.to_owned()),
|
||||
Cell::from(title.clone().unwrap_or_default()),
|
||||
Cell::from(format!("{episode_file_count}/{episode_count}")),
|
||||
Cell::from(format!("{size:.2} GB")),
|
||||
]);
|
||||
if !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 {
|
||||
row.missing()
|
||||
}
|
||||
} else {
|
||||
row.missing()
|
||||
}
|
||||
]),
|
||||
)
|
||||
};
|
||||
let is_searching = active_sonarr_block == ActiveSonarrBlock::SearchSeason;
|
||||
let season_table = ManagarrTable::new(content, season_row_mapping)
|
||||
@@ -291,6 +277,33 @@ 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 {
|
||||
row.missing()
|
||||
}
|
||||
} else {
|
||||
row.missing()
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_series_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
match app.data.sonarr_data.series_history.as_ref() {
|
||||
Some(series_history) if !app.is_loading => {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::{Duration, Utc};
|
||||
use pretty_assertions::assert_eq;
|
||||
use ratatui::widgets::{Cell, Row};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::app::App;
|
||||
@@ -7,8 +10,12 @@ mod tests {
|
||||
ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS, SEASON_DETAILS_BLOCKS, SERIES_DETAILS_BLOCKS,
|
||||
SERIES_OVERVIEW_BLOCKS,
|
||||
};
|
||||
use crate::models::sonarr_models::{Season, SeasonStatistics};
|
||||
use crate::ui::DrawUi;
|
||||
use crate::ui::sonarr_ui::library::series_details_ui::SeriesDetailsUi;
|
||||
use crate::ui::sonarr_ui::library::series_details_ui::{
|
||||
SeriesDetailsUi, decorate_season_row_with_style,
|
||||
};
|
||||
use crate::ui::styles::ManagarrStyle;
|
||||
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
|
||||
|
||||
#[test]
|
||||
@@ -27,6 +34,107 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_season_row_with_style_unmonitored() {
|
||||
let season = 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.unmonitored());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_season_row_with_style_downloaded_when_all_episodes_present() {
|
||||
let season = Season {
|
||||
monitored: true,
|
||||
statistics: Some(SeasonStatistics {
|
||||
episode_file_count: 3,
|
||||
episode_count: 3,
|
||||
..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.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()
|
||||
{
|
||||
let season = Season {
|
||||
monitored: true,
|
||||
statistics: Some(SeasonStatistics {
|
||||
episode_file_count: 0,
|
||||
episode_count: 3,
|
||||
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.unreleased());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decorate_season_row_with_style_missing_when_episodes_are_missing_and_next_airing_has_passed()
|
||||
{
|
||||
let season = Season {
|
||||
monitored: true,
|
||||
statistics: Some(SeasonStatistics {
|
||||
episode_file_count: 1,
|
||||
episode_count: 3,
|
||||
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_missing_when_episodes_are_missing_and_no_next_airing() {
|
||||
let season = Season {
|
||||
monitored: true,
|
||||
statistics: Some(SeasonStatistics {
|
||||
episode_file_count: 1,
|
||||
episode_count: 3,
|
||||
next_airing: None,
|
||||
..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());
|
||||
}
|
||||
|
||||
mod snapshot_tests {
|
||||
use crate::models::stateful_table::StatefulTable;
|
||||
use crate::ui::ui_test_utils::test_utils::TerminalSize;
|
||||
|
||||
Reference in new issue
Block a user