From bdeaf6ae353e80d0b5f7dc4a590455aaaf0a4528 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Thu, 17 Sep 2026 18:44:34 -0600 Subject: [PATCH] test: prove the track, episode and movie row styles reach the rendered buffer The row-style decorators were unit-tested as pure functions and the populated test apps do carry a downloading queue entry, but text snapshots discard colour, so nothing proved the draw functions actually call the decorator with the live downloads list. Passing an empty slice instead left every test green. Render the lidarr tracks table, the sonarr episodes table and the radarr movies table into a Buffer and assert the probe row's foreground: downloading_style with the queue populated, missing_style once the queue is emptied. The probe is the second row so the selection highlight never masks its style. --- .../library/album_details_ui_tests.rs | 74 ++++++++++++++++++ src/ui/radarr_ui/library/library_ui_tests.rs | 74 ++++++++++++++++++ .../library/season_details_ui_tests.rs | 75 +++++++++++++++++++ 3 files changed, 223 insertions(+) diff --git a/src/ui/lidarr_ui/library/album_details_ui_tests.rs b/src/ui/lidarr_ui/library/album_details_ui_tests.rs index a74df72c..3ae7b035 100644 --- a/src/ui/lidarr_ui/library/album_details_ui_tests.rs +++ b/src/ui/lidarr_ui/library/album_details_ui_tests.rs @@ -138,6 +138,80 @@ mod tests { assert_eq!(style, row.downloaded()); } + mod test_track_row_styling { + use pretty_assertions::assert_eq; + use ratatui::style::Style; + + use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::track; + use crate::ui::styles::{downloading_style, missing_style}; + use crate::ui::ui_test_utils::test_utils::{TerminalSize, create_test_terminal}; + + use super::*; + + #[test] + fn test_album_details_ui_renders_downloading_track_with_downloading_style() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + set_tracks_with_unselected_probe(&mut app); + + let style = rendered_row_style(&mut app, "Unselected track"); + + assert_eq!(style.fg, downloading_style().fg); + } + + #[test] + fn test_album_details_ui_renders_missing_track_with_missing_style_when_queue_is_empty() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveLidarrBlock::AlbumDetails.into()); + app.data.lidarr_data.downloads.set_items(vec![]); + set_tracks_with_unselected_probe(&mut app); + + let style = rendered_row_style(&mut app, "Unselected track"); + + assert_eq!(style.fg, missing_style().fg); + } + + fn set_tracks_with_unselected_probe(app: &mut App<'_>) { + let album_details_modal = app.data.lidarr_data.album_details_modal.as_mut().unwrap(); + album_details_modal.album_details_tabs.set_index(0); + album_details_modal.tracks.set_items(vec![ + track(), + Track { + has_file: false, + title: "Unselected track".to_owned(), + ..track() + }, + ]); + } + + fn rendered_row_style(app: &mut App<'_>, needle: &str) -> Style { + let (width, height) = TerminalSize::Large.to_cartesian(); + let mut terminal = create_test_terminal(width, height); + + terminal + .draw(|f| { + AlbumDetailsUi::draw(f, app, f.area()); + }) + .unwrap(); + + let buffer = terminal.backend().buffer(); + + for y in 0..height { + let row = (0..width) + .map(|x| buffer.cell((x, y)).expect("a rendered cell").symbol()) + .collect::(); + + if let Some(byte_index) = row.find(needle) { + let column = row[..byte_index].chars().count() as u16; + + return buffer.cell((column, y)).expect("a rendered cell").style(); + } + } + + panic!("no rendered row contained {needle}"); + } + } + mod snapshot_tests { use crate::ui::ui_test_utils::test_utils::TerminalSize; use rstest::rstest; diff --git a/src/ui/radarr_ui/library/library_ui_tests.rs b/src/ui/radarr_ui/library/library_ui_tests.rs index 629c9555..5b7753cc 100644 --- a/src/ui/radarr_ui/library/library_ui_tests.rs +++ b/src/ui/radarr_ui/library/library_ui_tests.rs @@ -29,6 +29,80 @@ mod tests { }); } + mod test_movie_row_styling { + use pretty_assertions::assert_eq; + use ratatui::style::Style; + + use crate::models::radarr_models::Movie; + use crate::network::radarr_network::radarr_network_test_utils::test_utils::movie; + use crate::ui::styles::{downloading_style, missing_style}; + use crate::ui::ui_test_utils::test_utils::create_test_terminal; + + use super::*; + + #[test] + fn test_library_ui_renders_downloading_movie_with_downloading_style() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + set_movies_with_unselected_probe(&mut app); + + let style = rendered_row_style(&mut app, "Unselected movie"); + + assert_eq!(style.fg, downloading_style().fg); + } + + #[test] + fn test_library_ui_renders_missing_movie_with_missing_style_when_queue_is_empty() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); + app.data.radarr_data.downloads.set_items(vec![]); + set_movies_with_unselected_probe(&mut app); + + let style = rendered_row_style(&mut app, "Unselected movie"); + + assert_eq!(style.fg, missing_style().fg); + } + + fn set_movies_with_unselected_probe(app: &mut App<'_>) { + app.data.radarr_data.movies.set_items(vec![ + movie(), + Movie { + has_file: false, + monitored: true, + title: "Unselected movie".into(), + ..movie() + }, + ]); + } + + fn rendered_row_style(app: &mut App<'_>, needle: &str) -> Style { + let (width, height) = TerminalSize::Large.to_cartesian(); + let mut terminal = create_test_terminal(width, height); + + terminal + .draw(|f| { + LibraryUi::draw(f, app, f.area()); + }) + .unwrap(); + + let buffer = terminal.backend().buffer(); + + for y in 0..height { + let row = (0..width) + .map(|x| buffer.cell((x, y)).expect("a rendered cell").symbol()) + .collect::(); + + if let Some(byte_index) = row.find(needle) { + let column = row[..byte_index].chars().count() as u16; + + return buffer.cell((column, y)).expect("a rendered cell").style(); + } + } + + panic!("no rendered row contained {needle}"); + } + } + mod snapshot_tests { use super::*; use crate::models::BlockSelectionState; diff --git a/src/ui/sonarr_ui/library/season_details_ui_tests.rs b/src/ui/sonarr_ui/library/season_details_ui_tests.rs index 1d8d2923..1d8c1fc4 100644 --- a/src/ui/sonarr_ui/library/season_details_ui_tests.rs +++ b/src/ui/sonarr_ui/library/season_details_ui_tests.rs @@ -264,6 +264,81 @@ mod tests { assert_eq!(style, row.downloaded()); } + mod test_episode_row_styling { + use pretty_assertions::assert_eq; + use ratatui::style::Style; + + use crate::network::sonarr_network::sonarr_network_test_utils::test_utils::episode; + use crate::ui::styles::{downloading_style, missing_style}; + use crate::ui::ui_test_utils::test_utils::{TerminalSize, create_test_terminal}; + + use super::*; + + #[test] + fn test_season_details_ui_renders_downloading_episode_with_downloading_style() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into()); + set_episodes_with_unselected_probe(&mut app); + + let style = rendered_row_style(&mut app, "Unselected episode"); + + assert_eq!(style.fg, downloading_style().fg); + } + + #[test] + fn test_season_details_ui_renders_missing_episode_with_missing_style_when_queue_is_empty() { + let mut app = App::test_default_fully_populated(); + app.push_navigation_stack(ActiveSonarrBlock::SeasonDetails.into()); + app.data.sonarr_data.downloads.set_items(vec![]); + set_episodes_with_unselected_probe(&mut app); + + let style = rendered_row_style(&mut app, "Unselected episode"); + + assert_eq!(style.fg, missing_style().fg); + } + + fn set_episodes_with_unselected_probe(app: &mut App<'_>) { + let season_details_modal = app.data.sonarr_data.season_details_modal.as_mut().unwrap(); + season_details_modal.season_details_tabs.set_index(0); + season_details_modal.episodes.set_items(vec![ + episode(), + Episode { + has_file: false, + monitored: true, + title: "Unselected episode".to_owned(), + ..episode() + }, + ]); + } + + fn rendered_row_style(app: &mut App<'_>, needle: &str) -> Style { + let (width, height) = TerminalSize::Large.to_cartesian(); + let mut terminal = create_test_terminal(width, height); + + terminal + .draw(|f| { + SeasonDetailsUi::draw(f, app, f.area()); + }) + .unwrap(); + + let buffer = terminal.backend().buffer(); + + for y in 0..height { + let row = (0..width) + .map(|x| buffer.cell((x, y)).expect("a rendered cell").symbol()) + .collect::(); + + if let Some(byte_index) = row.find(needle) { + let column = row[..byte_index].chars().count() as u16; + + return buffer.cell((column, y)).expect("a rendered cell").style(); + } + } + + panic!("no rendered row contained {needle}"); + } + } + mod snapshot_tests { use crate::ui::ui_test_utils::test_utils::TerminalSize; use rstest::rstest;