fix: parse the log level from the unscrolled log text
draw_logs read the level from the log line's Display output, which drops the first offset characters. Once a log line is scrolled, the field at index 1 is no longer the level: past the timestamp it yields the logger name and colours the row wrong, and past the final delimiter the index is out of bounds and panics, taking the TUI down. Parse log.text instead, matching what draw_logs_popup already does. The displayed text is unchanged.
This commit is contained in:
8 files changed
+304
-4
No files matched your search
@@ -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::<Vec<&str>>()[1].to_string();
|
||||
let level = log.text.split('|').collect::<Vec<&str>>()[1].to_string();
|
||||
|
||||
style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level)
|
||||
})
|
||||
|
||||
@@ -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::<String>();
|
||||
|
||||
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<HorizontallyScrollableText> {
|
||||
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;
|
||||
|
||||
@@ -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::<Vec<&str>>()[1].to_string();
|
||||
let level = log.text.split('|').collect::<Vec<&str>>()[1].to_string();
|
||||
|
||||
style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level)
|
||||
})
|
||||
|
||||
@@ -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::<String>();
|
||||
|
||||
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<HorizontallyScrollableText> {
|
||||
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;
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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::<String>();
|
||||
|
||||
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;
|
||||
|
||||
@@ -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::<Vec<&str>>()[1].to_string();
|
||||
let level = log.text.split('|').collect::<Vec<&str>>()[1].to_string();
|
||||
|
||||
style_log_list_item(ListItem::new(Text::from(Span::raw(log_line))), level)
|
||||
})
|
||||
|
||||
@@ -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::<String>();
|
||||
|
||||
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<HorizontallyScrollableText> {
|
||||
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;
|
||||
|
||||
Reference in new issue
Block a user