use super::*;
use ratatui::style::Color;
#[test]
fn test_level_default() {
let level = StatusLogLevel::default();
assert_eq!(level, StatusLogLevel::Info);
}
#[test]
fn test_level_colors() {
assert_eq!(StatusLogLevel::Info.color(), Color::Cyan);
assert_eq!(StatusLogLevel::Success.color(), Color::Green);
assert_eq!(StatusLogLevel::Warning.color(), Color::Yellow);
assert_eq!(StatusLogLevel::Error.color(), Color::Red);
}
#[test]
fn test_level_prefixes() {
assert_eq!(StatusLogLevel::Info.prefix(), "ℹ");
assert_eq!(StatusLogLevel::Success.prefix(), "✓");
assert_eq!(StatusLogLevel::Warning.prefix(), "⚠");
assert_eq!(StatusLogLevel::Error.prefix(), "✗");
}
#[test]
fn test_entry_new() {
let entry = StatusLogEntry::new(1, "Test message", StatusLogLevel::Info);
assert_eq!(entry.id(), 1);
assert_eq!(entry.message(), "Test message");
assert_eq!(entry.level(), StatusLogLevel::Info);
assert!(entry.timestamp().is_none());
}
#[test]
fn test_entry_with_timestamp() {
let entry = StatusLogEntry::with_timestamp(2, "Message", StatusLogLevel::Success, "12:34:56");
assert_eq!(entry.id(), 2);
assert_eq!(entry.timestamp(), Some("12:34:56"));
}
#[test]
fn test_state_new() {
let state = StatusLogState::new();
assert!(state.is_empty());
assert_eq!(state.max_entries(), 50);
assert!(!state.show_timestamps());
}
#[test]
fn test_state_with_max_entries() {
let state = StatusLogState::new().with_max_entries(100);
assert_eq!(state.max_entries(), 100);
}
#[test]
fn test_state_with_show_timestamps() {
let state = StatusLogState::new().with_show_timestamps(true);
assert!(state.show_timestamps());
}
#[test]
fn test_state_with_title() {
let state = StatusLogState::new().with_title("Log");
assert_eq!(state.title(), Some("Log"));
}
#[test]
fn test_state_default() {
let state = StatusLogState::default();
assert!(state.is_empty());
}
#[test]
fn test_info() {
let mut state = StatusLogState::new();
let id = state.info("Info message");
assert_eq!(state.len(), 1);
assert_eq!(state.entries()[0].level(), StatusLogLevel::Info);
assert_eq!(id, 0);
}
#[test]
fn test_success() {
let mut state = StatusLogState::new();
state.success("Success message");
assert_eq!(state.entries()[0].level(), StatusLogLevel::Success);
}
#[test]
fn test_warning() {
let mut state = StatusLogState::new();
state.warning("Warning message");
assert_eq!(state.entries()[0].level(), StatusLogLevel::Warning);
}
#[test]
fn test_error() {
let mut state = StatusLogState::new();
state.error("Error message");
assert_eq!(state.entries()[0].level(), StatusLogLevel::Error);
}
#[test]
fn test_info_with_timestamp() {
let mut state = StatusLogState::new();
state.info_with_timestamp("Message", "10:00:00");
assert_eq!(state.entries()[0].timestamp(), Some("10:00:00"));
}
#[test]
fn test_success_with_timestamp() {
let mut state = StatusLogState::new();
state.success_with_timestamp("Message", "10:00:01");
assert_eq!(state.entries()[0].timestamp(), Some("10:00:01"));
}
#[test]
fn test_warning_with_timestamp() {
let mut state = StatusLogState::new();
state.warning_with_timestamp("Message", "10:00:02");
assert_eq!(state.entries()[0].timestamp(), Some("10:00:02"));
}
#[test]
fn test_error_with_timestamp() {
let mut state = StatusLogState::new();
state.error_with_timestamp("Message", "10:00:03");
assert_eq!(state.entries()[0].timestamp(), Some("10:00:03"));
}
#[test]
fn test_id_increment() {
let mut state = StatusLogState::new();
let id1 = state.info("First");
let id2 = state.info("Second");
let id3 = state.info("Third");
assert_eq!(id1, 0);
assert_eq!(id2, 1);
assert_eq!(id3, 2);
}
#[test]
fn test_max_entries_enforcement() {
let mut state = StatusLogState::new().with_max_entries(3);
state.info("One");
state.info("Two");
state.info("Three");
assert_eq!(state.len(), 3);
let output = StatusLog::update(
&mut state,
StatusLogMessage::Push {
message: "Four".to_string(),
level: StatusLogLevel::Info,
timestamp: None,
},
);
assert_eq!(state.len(), 3);
assert_eq!(output, Some(StatusLogOutput::Evicted(0)));
}
#[test]
fn test_set_max_entries() {
let mut state = StatusLogState::new();
state.set_max_entries(10);
assert_eq!(state.max_entries(), 10);
}
#[test]
fn test_set_max_entries_evicts_oldest() {
let mut state = StatusLogState::new();
state.info("a");
state.info("b");
state.info("c");
state.info("d");
state.info("e");
assert_eq!(state.len(), 5);
state.set_max_entries(2);
assert_eq!(state.len(), 2);
assert_eq!(state.entries()[0].message(), "d");
assert_eq!(state.entries()[1].message(), "e");
}
#[test]
fn test_set_max_entries_no_eviction_when_under_limit() {
let mut state = StatusLogState::new();
state.info("a");
state.info("b");
assert_eq!(state.len(), 2);
state.set_max_entries(10);
assert_eq!(state.len(), 2);
}
#[test]
fn test_entries() {
let mut state = StatusLogState::new();
state.info("A");
state.info("B");
assert_eq!(state.entries().len(), 2);
}
#[test]
fn test_entries_newest_first() {
let mut state = StatusLogState::new();
state.info("First");
state.info("Second");
state.info("Third");
let messages: Vec<_> = state.entries_newest_first().map(|e| e.message()).collect();
assert_eq!(messages, vec!["Third", "Second", "First"]);
}
#[test]
fn test_len_and_is_empty() {
let mut state = StatusLogState::new();
assert!(state.is_empty());
assert_eq!(state.len(), 0);
state.info("Message");
assert!(!state.is_empty());
assert_eq!(state.len(), 1);
}
#[test]
fn test_remove() {
let mut state = StatusLogState::new();
let id = state.info("To remove");
assert!(state.remove(id));
assert!(state.is_empty());
}
#[test]
fn test_remove_nonexistent() {
let mut state = StatusLogState::new();
state.info("Message");
assert!(!state.remove(999));
assert_eq!(state.len(), 1);
}
#[test]
fn test_clear() {
let mut state = StatusLogState::new();
state.info("A");
state.info("B");
state.clear();
assert!(state.is_empty());
}
#[test]
fn test_set_show_timestamps() {
let mut state = StatusLogState::new();
state.set_show_timestamps(true);
assert!(state.show_timestamps());
}
#[test]
fn test_set_title() {
let mut state = StatusLogState::new();
state.set_title(Some("New Title".to_string()));
assert_eq!(state.title(), Some("New Title"));
state.set_title(None);
assert!(state.title().is_none());
}
#[test]
fn test_scroll_offset() {
let mut state = StatusLogState::new();
for i in 0..10 {
state.info(format!("Message {}", i));
}
assert_eq!(state.scroll_offset(), 0);
state.set_scroll_offset(5);
assert_eq!(state.scroll_offset(), 5);
}
#[test]
fn test_scroll_offset_clamped() {
let mut state = StatusLogState::new();
state.info("A");
state.info("B");
state.set_scroll_offset(100);
assert_eq!(state.scroll_offset(), 1); }
#[test]
fn test_init() {
let state = StatusLog::init();
assert!(state.is_empty());
}
#[test]
fn test_update_push() {
let mut state = StatusLog::init();
let output = StatusLog::update(
&mut state,
StatusLogMessage::Push {
message: "Test".to_string(),
level: StatusLogLevel::Info,
timestamp: None,
},
);
assert_eq!(state.len(), 1);
assert_eq!(output, Some(StatusLogOutput::Added(0)));
}
#[test]
fn test_update_push_with_timestamp() {
let mut state = StatusLog::init();
StatusLog::update(
&mut state,
StatusLogMessage::Push {
message: "Test".to_string(),
level: StatusLogLevel::Success,
timestamp: Some("12:00".to_string()),
},
);
assert_eq!(state.entries()[0].timestamp(), Some("12:00"));
}
#[test]
fn test_update_clear() {
let mut state = StatusLogState::new();
state.info("A");
let output = StatusLog::update(&mut state, StatusLogMessage::Clear);
assert!(state.is_empty());
assert_eq!(output, Some(StatusLogOutput::Cleared));
}
#[test]
fn test_update_clear_empty() {
let mut state = StatusLog::init();
let output = StatusLog::update(&mut state, StatusLogMessage::Clear);
assert!(output.is_none());
}
#[test]
fn test_update_remove() {
let mut state = StatusLogState::new();
let id = state.info("To remove");
let output = StatusLog::update(&mut state, StatusLogMessage::Remove(id));
assert!(state.is_empty());
assert_eq!(output, Some(StatusLogOutput::Removed(id)));
}
#[test]
fn test_update_remove_nonexistent() {
let mut state = StatusLogState::new();
state.info("Keep");
let output = StatusLog::update(&mut state, StatusLogMessage::Remove(999));
assert!(output.is_none());
}
#[test]
fn test_update_scroll_up() {
let mut state = StatusLogState::new();
for i in 0..5 {
state.info(format!("Msg {}", i));
}
state.set_scroll_offset(3);
StatusLog::update(&mut state, StatusLogMessage::ScrollUp);
assert_eq!(state.scroll_offset(), 2);
}
#[test]
fn test_update_scroll_up_at_top() {
let mut state = StatusLogState::new();
state.info("A");
StatusLog::update(&mut state, StatusLogMessage::ScrollUp);
assert_eq!(state.scroll_offset(), 0);
}
#[test]
fn test_update_scroll_down() {
let mut state = StatusLogState::new();
for i in 0..5 {
state.info(format!("Msg {}", i));
}
StatusLog::update(&mut state, StatusLogMessage::ScrollDown);
assert_eq!(state.scroll_offset(), 1);
}
#[test]
fn test_update_scroll_down_at_bottom() {
let mut state = StatusLogState::new();
state.info("A");
state.info("B");
state.set_scroll_offset(1);
StatusLog::update(&mut state, StatusLogMessage::ScrollDown);
assert_eq!(state.scroll_offset(), 1); }
#[test]
fn test_update_scroll_to_top() {
let mut state = StatusLogState::new();
for i in 0..5 {
state.info(format!("Msg {}", i));
}
state.set_scroll_offset(3);
StatusLog::update(&mut state, StatusLogMessage::ScrollToTop);
assert_eq!(state.scroll_offset(), 0);
}
#[test]
fn test_update_scroll_to_bottom() {
let mut state = StatusLogState::new();
for i in 0..5 {
state.info(format!("Msg {}", i));
}
StatusLog::update(&mut state, StatusLogMessage::ScrollToBottom);
assert_eq!(state.scroll_offset(), 4);
}
#[test]
fn test_view_empty() {
let state = StatusLogState::new();
let (mut terminal, theme) = crate::component::test_utils::setup_render(40, 10);
terminal
.draw(|frame| StatusLog::view(&state, &mut RenderContext::new(frame, frame.area(), &theme)))
.unwrap();
insta::assert_snapshot!(terminal.backend().to_string());
}
#[test]
fn test_view_with_messages() {
let mut state = StatusLogState::new();
state.info("Info message");
state.success("Success message");
let (mut terminal, theme) = crate::component::test_utils::setup_render(40, 10);
terminal
.draw(|frame| StatusLog::view(&state, &mut RenderContext::new(frame, frame.area(), &theme)))
.unwrap();
insta::assert_snapshot!(terminal.backend().to_string());
}
#[test]
fn test_view_with_title() {
let mut state = StatusLogState::new().with_title("Status");
state.info("Test");
let (mut terminal, theme) = crate::component::test_utils::setup_render(40, 10);
terminal
.draw(|frame| StatusLog::view(&state, &mut RenderContext::new(frame, frame.area(), &theme)))
.unwrap();
insta::assert_snapshot!(terminal.backend().to_string());
}
#[test]
fn test_view_with_show_timestamps() {
let mut state = StatusLogState::new().with_show_timestamps(true);
state.info_with_timestamp("Message", "12:34");
let (mut terminal, theme) = crate::component::test_utils::setup_render(60, 10);
terminal
.draw(|frame| StatusLog::view(&state, &mut RenderContext::new(frame, frame.area(), &theme)))
.unwrap();
insta::assert_snapshot!(terminal.backend().to_string());
}
#[test]
fn test_view_all_levels() {
let mut state = StatusLogState::new();
state.info("Info");
state.success("Success");
state.warning("Warning");
state.error("Error");
let (mut terminal, theme) = crate::component::test_utils::setup_render(40, 10);
terminal
.draw(|frame| StatusLog::view(&state, &mut RenderContext::new(frame, frame.area(), &theme)))
.unwrap();
insta::assert_snapshot!(terminal.backend().to_string());
}
#[test]
fn test_view_focused() {
let mut state = StatusLogState::new();
state.info("Server started");
state.success("Connection established");
state.warning("High memory usage");
let (mut terminal, theme) = crate::component::test_utils::setup_render(40, 10);
terminal
.draw(|frame| {
StatusLog::view(
&state,
&mut RenderContext::new(frame, frame.area(), &theme).focused(true),
)
})
.unwrap();
insta::assert_snapshot!(terminal.backend().to_string());
}
#[test]
fn test_view_unfocused() {
let mut state = StatusLogState::new();
state.info("Server started");
state.success("Connection established");
state.warning("High memory usage");
let (mut terminal, theme) = crate::component::test_utils::setup_render(40, 10);
terminal
.draw(|frame| StatusLog::view(&state, &mut RenderContext::new(frame, frame.area(), &theme)))
.unwrap();
insta::assert_snapshot!(terminal.backend().to_string());
}
use crate::input::{Event, Key};
#[test]
fn test_handle_event_scroll_up() {
let state = StatusLogState::new();
let msg = StatusLog::handle_event(
&state,
&Event::key(Key::Up),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(StatusLogMessage::ScrollUp));
let msg = StatusLog::handle_event(
&state,
&Event::char('k'),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(StatusLogMessage::ScrollUp));
}
#[test]
fn test_handle_event_scroll_down() {
let state = StatusLogState::new();
let msg = StatusLog::handle_event(
&state,
&Event::key(Key::Down),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(StatusLogMessage::ScrollDown));
let msg = StatusLog::handle_event(
&state,
&Event::char('j'),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(StatusLogMessage::ScrollDown));
}
#[test]
fn test_handle_event_scroll_to_top() {
let state = StatusLogState::new();
let msg = StatusLog::handle_event(
&state,
&Event::key(Key::Home),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(StatusLogMessage::ScrollToTop));
}
#[test]
fn test_handle_event_scroll_to_bottom() {
let state = StatusLogState::new();
let msg = StatusLog::handle_event(
&state,
&Event::key(Key::End),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(StatusLogMessage::ScrollToBottom));
}
#[test]
fn test_handle_event_ignored_when_unfocused() {
let state = StatusLogState::new();
let msg = StatusLog::handle_event(&state, &Event::key(Key::Up), &EventContext::default());
assert_eq!(msg, None);
}
#[test]
fn test_dispatch_event() {
let mut state = StatusLogState::new();
for i in 0..5 {
state.info(format!("Msg {}", i));
}
state.set_scroll_offset(3);
StatusLog::dispatch_event(
&mut state,
&Event::key(Key::Down),
&EventContext::new().focused(true),
);
assert_eq!(state.scroll_offset(), 4);
}
#[test]
fn test_instance_methods() {
let mut state = StatusLogState::new();
for i in 0..5 {
state.info(format!("Msg {}", i));
}
state.set_scroll_offset(2);
let msg = StatusLog::handle_event(
&state,
&Event::key(Key::Up),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(StatusLogMessage::ScrollUp));
state.update(StatusLogMessage::ScrollUp);
assert_eq!(state.scroll_offset(), 1);
StatusLog::dispatch_event(
&mut state,
&Event::key(Key::Down),
&EventContext::new().focused(true),
);
assert_eq!(state.scroll_offset(), 2);
}
#[test]
fn test_handle_event_ignored_when_disabled() {
let state = StatusLogState::new();
let msg = StatusLog::handle_event(
&state,
&Event::key(Key::Up),
&EventContext::new().focused(true).disabled(true),
);
assert_eq!(msg, None);
let msg = StatusLog::handle_event(
&state,
&Event::key(Key::Down),
&EventContext::new().focused(true).disabled(true),
);
assert_eq!(msg, None);
let msg = StatusLog::handle_event(
&state,
&Event::char('k'),
&EventContext::new().focused(true).disabled(true),
);
assert_eq!(msg, None);
let msg = StatusLog::handle_event(
&state,
&Event::key(Key::Home),
&EventContext::new().focused(true).disabled(true),
);
assert_eq!(msg, None);
let msg = StatusLog::handle_event(
&state,
&Event::key(Key::End),
&EventContext::new().focused(true).disabled(true),
);
assert_eq!(msg, None);
}
#[test]
fn test_dispatch_event_ignored_when_disabled() {
let mut state = StatusLogState::new();
for i in 0..5 {
state.info(format!("Msg {}", i));
}
state.set_scroll_offset(2);
let output = StatusLog::dispatch_event(
&mut state,
&Event::key(Key::Down),
&EventContext::new().focused(true).disabled(true),
);
assert_eq!(output, None);
assert_eq!(state.scroll_offset(), 2);
}
#[test]
fn test_default_matches_init() {
let default_state = StatusLogState::default();
let init_state = StatusLog::init();
assert_eq!(default_state.is_empty(), init_state.is_empty());
assert_eq!(default_state.len(), init_state.len());
assert_eq!(default_state.max_entries(), init_state.max_entries());
assert_eq!(
default_state.show_timestamps(),
init_state.show_timestamps()
);
assert_eq!(default_state.scroll_offset(), init_state.scroll_offset());
assert_eq!(default_state.title(), init_state.title());
}
#[test]
fn test_annotation_emitted() {
use crate::annotation::{WidgetType, with_annotations};
let mut state = StatusLogState::new();
StatusLog::update(
&mut state,
StatusLogMessage::Push {
message: "Hello".to_string(),
level: StatusLogLevel::Info,
timestamp: None,
},
);
let (mut terminal, theme) = crate::component::test_utils::setup_render(40, 10);
let registry = with_annotations(|| {
terminal
.draw(|frame| {
StatusLog::view(&state, &mut RenderContext::new(frame, frame.area(), &theme));
})
.unwrap();
});
assert_eq!(registry.len(), 1);
let regions = registry.find_by_type(&WidgetType::StatusLog);
assert_eq!(regions.len(), 1);
assert!(regions[0].annotation.has_id("status_log"));
}