diff --git a/src/ui/lidarr_ui/system/mod.rs b/src/ui/lidarr_ui/system/mod.rs index e34362d9..56da00c9 100644 --- a/src/ui/lidarr_ui/system/mod.rs +++ b/src/ui/lidarr_ui/system/mod.rs @@ -165,7 +165,7 @@ fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let logs_box = SelectableList::new(&mut app.data.lidarr_data.logs, |log| { let log_line = log.to_string(); - let level = log_line.split('|').collect::>()[1].to_string(); + let level = log.text.split('|').collect::>()[1].to_string(); style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level) }) diff --git a/src/ui/lidarr_ui/system/system_ui_tests.rs b/src/ui/lidarr_ui/system/system_ui_tests.rs index 58d9fcf7..b6fd236d 100644 --- a/src/ui/lidarr_ui/system/system_ui_tests.rs +++ b/src/ui/lidarr_ui/system/system_ui_tests.rs @@ -25,6 +25,83 @@ mod tests { }); } + mod test_log_level_styling { + use std::sync::atomic::Ordering; + + use pretty_assertions::assert_eq; + use ratatui::style::{Modifier, Style}; + + use crate::models::HorizontallyScrollableText; + use crate::ui::styles::failure_style; + use crate::ui::ui_test_utils::test_utils::{TerminalSize, create_test_terminal}; + + use super::*; + + #[test] + fn test_system_ui_styles_a_scrolled_log_using_its_unscrolled_level() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::System.into()); + app.data.lidarr_data.logs.set_items(log_lines_vec()); + app.data.lidarr_data.logs.items[1] + .offset + .store(30, Ordering::SeqCst); + + let style = rendered_row_style(&mut app, "CommandExecutor|log line gamma"); + + assert_eq!(style.fg, failure_style().fg); + assert!(style.add_modifier.contains(Modifier::BOLD)); + } + + #[test] + fn test_system_ui_styles_a_fully_scrolled_log_using_its_unscrolled_level() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveLidarrBlock::System.into()); + app.data.lidarr_data.logs.set_items(log_lines_vec()); + app.data.lidarr_data.logs.items[1] + .offset + .store(46, Ordering::SeqCst); + + let style = rendered_row_style(&mut app, "log line gamma"); + + assert_eq!(style.fg, failure_style().fg); + assert!(style.add_modifier.contains(Modifier::BOLD)); + } + + 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| { + SystemUi::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}"); + } + + fn log_lines_vec() -> Vec { + vec![ + "2025-12-16 16:40:59 UTC|INFO|ImportListSyncService|log line alpha".into(), + "2025-12-16 16:41:30 UTC|FATAL|CommandExecutor|log line gamma".into(), + ] + } + } + mod snapshot_tests { use crate::models::stateful_list::StatefulList; use crate::ui::ui_test_utils::test_utils::TerminalSize; diff --git a/src/ui/radarr_ui/system/mod.rs b/src/ui/radarr_ui/system/mod.rs index 8bec01e2..270286fa 100644 --- a/src/ui/radarr_ui/system/mod.rs +++ b/src/ui/radarr_ui/system/mod.rs @@ -172,7 +172,7 @@ fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let logs_box = SelectableList::new(&mut app.data.radarr_data.logs, |log| { let log_line = log.to_string(); - let level = log_line.split('|').collect::>()[1].to_string(); + let level = log.text.split('|').collect::>()[1].to_string(); style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level) }) diff --git a/src/ui/radarr_ui/system/system_ui_tests.rs b/src/ui/radarr_ui/system/system_ui_tests.rs index 31bd1610..fec72e7a 100644 --- a/src/ui/radarr_ui/system/system_ui_tests.rs +++ b/src/ui/radarr_ui/system/system_ui_tests.rs @@ -25,6 +25,83 @@ mod tests { }); } + mod test_log_level_styling { + use std::sync::atomic::Ordering; + + use pretty_assertions::assert_eq; + use ratatui::style::{Modifier, Style}; + + use crate::models::HorizontallyScrollableText; + use crate::ui::styles::failure_style; + use crate::ui::ui_test_utils::test_utils::create_test_terminal; + + use super::*; + + #[test] + fn test_system_ui_styles_a_scrolled_log_using_its_unscrolled_level() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveRadarrBlock::System.into()); + app.data.radarr_data.logs.set_items(log_lines_vec()); + app.data.radarr_data.logs.items[1] + .offset + .store(30, Ordering::SeqCst); + + let style = rendered_row_style(&mut app, "CommandExecutor|log line gamma"); + + assert_eq!(style.fg, failure_style().fg); + assert!(style.add_modifier.contains(Modifier::BOLD)); + } + + #[test] + fn test_system_ui_styles_a_fully_scrolled_log_using_its_unscrolled_level() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveRadarrBlock::System.into()); + app.data.radarr_data.logs.set_items(log_lines_vec()); + app.data.radarr_data.logs.items[1] + .offset + .store(46, Ordering::SeqCst); + + let style = rendered_row_style(&mut app, "log line gamma"); + + assert_eq!(style.fg, failure_style().fg); + assert!(style.add_modifier.contains(Modifier::BOLD)); + } + + 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| { + SystemUi::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}"); + } + + fn log_lines_vec() -> Vec { + vec![ + "2025-12-15 16:14:45 UTC|INFO|DownloadDecisionMaker|log line alpha".into(), + "2025-12-16 16:41:30 UTC|FATAL|CommandExecutor|log line gamma".into(), + ] + } + } + mod snapshot_tests { use super::*; use crate::models::stateful_list::StatefulList; diff --git a/src/ui/readarr_ui/system/mod.rs b/src/ui/readarr_ui/system/mod.rs index 2c4f3059..d353adae 100644 --- a/src/ui/readarr_ui/system/mod.rs +++ b/src/ui/readarr_ui/system/mod.rs @@ -161,7 +161,7 @@ fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let logs_box = SelectableList::new(&mut app.data.readarr_data.logs, |log| { let log_line = log.to_string(); - let level = extract_log_level(&log_line); + let level = extract_log_level(&log.text); style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level) }) diff --git a/src/ui/readarr_ui/system/system_ui_tests.rs b/src/ui/readarr_ui/system/system_ui_tests.rs index 4716ac80..8aa54a35 100644 --- a/src/ui/readarr_ui/system/system_ui_tests.rs +++ b/src/ui/readarr_ui/system/system_ui_tests.rs @@ -63,6 +63,75 @@ mod tests { } } + mod test_log_level_styling { + use std::sync::atomic::Ordering; + + use pretty_assertions::assert_eq; + use ratatui::style::{Modifier, Style}; + + use crate::ui::styles::failure_style; + use crate::ui::ui_test_utils::test_utils::{TerminalSize, create_test_terminal}; + + use super::*; + + #[test] + fn test_system_ui_styles_a_scrolled_log_using_its_unscrolled_level() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.logs.set_items(log_lines_vec()); + app.data.readarr_data.logs.items[2] + .offset + .store(30, Ordering::SeqCst); + + let style = rendered_row_style(&mut app, "CommandExecutor|log line gamma"); + + assert_eq!(style.fg, failure_style().fg); + assert!(style.add_modifier.contains(Modifier::BOLD)); + } + + #[test] + fn test_system_ui_styles_a_fully_scrolled_log_using_its_unscrolled_level() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveReadarrBlock::System.into()); + app.data.readarr_data.logs.set_items(log_lines_vec()); + app.data.readarr_data.logs.items[2] + .offset + .store(46, Ordering::SeqCst); + + let style = rendered_row_style(&mut app, "log line gamma"); + + assert_eq!(style.fg, failure_style().fg); + assert!(style.add_modifier.contains(Modifier::BOLD)); + } + + 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| { + SystemUi::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::models::stateful_list::StatefulList; use crate::ui::ui_test_utils::test_utils::TerminalSize; diff --git a/src/ui/sonarr_ui/system/mod.rs b/src/ui/sonarr_ui/system/mod.rs index 233acd7f..5e51a301 100644 --- a/src/ui/sonarr_ui/system/mod.rs +++ b/src/ui/sonarr_ui/system/mod.rs @@ -165,7 +165,7 @@ fn draw_logs(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let logs_box = SelectableList::new(&mut app.data.sonarr_data.logs, |log| { let log_line = log.to_string(); - let level = log_line.split('|').collect::>()[1].to_string(); + let level = log.text.split('|').collect::>()[1].to_string(); style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level) }) diff --git a/src/ui/sonarr_ui/system/system_ui_tests.rs b/src/ui/sonarr_ui/system/system_ui_tests.rs index 39b258c1..97d2d6e0 100644 --- a/src/ui/sonarr_ui/system/system_ui_tests.rs +++ b/src/ui/sonarr_ui/system/system_ui_tests.rs @@ -25,6 +25,83 @@ mod tests { }); } + mod test_log_level_styling { + use std::sync::atomic::Ordering; + + use pretty_assertions::assert_eq; + use ratatui::style::{Modifier, Style}; + + use crate::models::HorizontallyScrollableText; + use crate::ui::styles::failure_style; + use crate::ui::ui_test_utils::test_utils::{TerminalSize, create_test_terminal}; + + use super::*; + + #[test] + fn test_system_ui_styles_a_scrolled_log_using_its_unscrolled_level() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(log_lines_vec()); + app.data.sonarr_data.logs.items[1] + .offset + .store(30, Ordering::SeqCst); + + let style = rendered_row_style(&mut app, "CommandExecutor|log line gamma"); + + assert_eq!(style.fg, failure_style().fg); + assert!(style.add_modifier.contains(Modifier::BOLD)); + } + + #[test] + fn test_system_ui_styles_a_fully_scrolled_log_using_its_unscrolled_level() { + let mut app = App::test_default(); + app.push_navigation_stack(ActiveSonarrBlock::System.into()); + app.data.sonarr_data.logs.set_items(log_lines_vec()); + app.data.sonarr_data.logs.items[1] + .offset + .store(46, Ordering::SeqCst); + + let style = rendered_row_style(&mut app, "log line gamma"); + + assert_eq!(style.fg, failure_style().fg); + assert!(style.add_modifier.contains(Modifier::BOLD)); + } + + 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| { + SystemUi::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}"); + } + + fn log_lines_vec() -> Vec { + vec![ + "2025-12-16 16:40:59 UTC|INFO|ImportListSyncService|log line alpha".into(), + "2025-12-16 16:41:30 UTC|FATAL|CommandExecutor|log line gamma".into(), + ] + } + } + mod snapshot_tests { use crate::models::stateful_list::StatefulList; use crate::ui::ui_test_utils::test_utils::TerminalSize;