use super::render::{contrasting_fg, format_value, truncate_str};
use super::*;
use crate::component::test_utils;
use crate::input::Event;
use ratatui::style::Color;
#[test]
fn test_new_creates_empty_grid() {
let state = HeatmapState::new(3, 5);
assert_eq!(state.rows(), 3);
assert_eq!(state.cols(), 5);
assert_eq!(state.get(0, 0), Some(0.0));
assert_eq!(state.get(2, 4), Some(0.0));
assert_eq!(state.selected(), Some((0, 0)));
}
#[test]
fn test_new_zero_rows() {
let state = HeatmapState::new(0, 5);
assert_eq!(state.rows(), 0);
assert_eq!(state.cols(), 0);
assert_eq!(state.selected(), None);
}
#[test]
fn test_new_zero_cols() {
let state = HeatmapState::new(3, 0);
assert_eq!(state.rows(), 3);
assert_eq!(state.cols(), 0);
assert_eq!(state.selected(), None);
}
#[test]
fn test_with_data() {
let data = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
let state = HeatmapState::with_data(data);
assert_eq!(state.rows(), 2);
assert_eq!(state.cols(), 3);
assert_eq!(state.get(0, 0), Some(1.0));
assert_eq!(state.get(1, 2), Some(6.0));
assert_eq!(state.selected(), Some((0, 0)));
}
#[test]
fn test_with_data_empty() {
let state = HeatmapState::with_data(vec![]);
assert_eq!(state.rows(), 0);
assert_eq!(state.cols(), 0);
assert_eq!(state.selected(), None);
}
#[test]
fn test_with_data_empty_rows() {
let state = HeatmapState::with_data(vec![vec![], vec![]]);
assert_eq!(state.rows(), 2);
assert_eq!(state.cols(), 0);
assert_eq!(state.selected(), None);
}
#[test]
fn test_default() {
let state = HeatmapState::default();
assert_eq!(state.rows(), 0);
assert_eq!(state.cols(), 0);
assert!(!state.show_values);
assert_eq!(state.title(), None);
assert_eq!(state.color_scale(), &HeatmapColorScale::default());
}
#[test]
fn test_with_row_labels() {
let state = HeatmapState::new(2, 2).with_row_labels(vec!["R1".into(), "R2".into()]);
assert_eq!(state.row_labels(), &["R1", "R2"]);
}
#[test]
fn test_with_col_labels() {
let state = HeatmapState::new(2, 3).with_col_labels(vec!["A".into(), "B".into(), "C".into()]);
assert_eq!(state.col_labels(), &["A", "B", "C"]);
}
#[test]
fn test_with_color_scale() {
let state = HeatmapState::new(2, 2).with_color_scale(HeatmapColorScale::BlueToRed);
assert_eq!(state.color_scale(), &HeatmapColorScale::BlueToRed);
}
#[test]
fn test_with_range() {
let state = HeatmapState::new(2, 2).with_range(0.0, 100.0);
assert_eq!(state.effective_min(), 0.0);
assert_eq!(state.effective_max(), 100.0);
}
#[test]
fn test_with_show_values() {
let state = HeatmapState::new(2, 2).with_show_values(true);
assert!(state.show_values());
}
#[test]
fn test_with_title() {
let state = HeatmapState::new(2, 2).with_title("Test Heatmap");
assert_eq!(state.title(), Some("Test Heatmap"));
}
#[test]
fn test_set_and_get() {
let mut state = HeatmapState::new(3, 3);
state.set(1, 2, 42.0);
assert_eq!(state.get(1, 2), Some(42.0));
assert_eq!(state.get(0, 0), Some(0.0));
}
#[test]
fn test_set_out_of_bounds() {
let mut state = HeatmapState::new(2, 2);
state.set(5, 5, 99.0);
assert_eq!(state.get(5, 5), None);
}
#[test]
fn test_get_out_of_bounds() {
let state = HeatmapState::new(2, 2);
assert_eq!(state.get(10, 0), None);
assert_eq!(state.get(0, 10), None);
}
#[test]
fn test_clear_data() {
let mut state = HeatmapState::with_data(vec![vec![1.0, 2.0], vec![3.0, 4.0]]);
state.update(HeatmapMessage::Clear);
assert_eq!(state.get(0, 0), Some(0.0));
assert_eq!(state.get(1, 1), Some(0.0));
assert_eq!(state.rows(), 2);
assert_eq!(state.cols(), 2);
}
fn focused_3x3() -> HeatmapState {
HeatmapState::with_data(vec![
vec![1.0, 2.0, 3.0],
vec![4.0, 5.0, 6.0],
vec![7.0, 8.0, 9.0],
])
}
#[test]
fn test_select_down() {
let mut state = focused_3x3();
let output = state.update(HeatmapMessage::SelectDown);
assert_eq!(state.selected(), Some((1, 0)));
assert_eq!(
output,
Some(HeatmapOutput::SelectionChanged { row: 1, col: 0 })
);
}
#[test]
fn test_select_up_at_top() {
let mut state = focused_3x3();
let output = state.update(HeatmapMessage::SelectUp);
assert_eq!(state.selected(), Some((0, 0)));
assert_eq!(output, None); }
#[test]
fn test_select_right() {
let mut state = focused_3x3();
let output = state.update(HeatmapMessage::SelectRight);
assert_eq!(state.selected(), Some((0, 1)));
assert_eq!(
output,
Some(HeatmapOutput::SelectionChanged { row: 0, col: 1 })
);
}
#[test]
fn test_select_left_at_left_edge() {
let mut state = focused_3x3();
let output = state.update(HeatmapMessage::SelectLeft);
assert_eq!(state.selected(), Some((0, 0)));
assert_eq!(output, None);
}
#[test]
fn test_select_down_at_bottom() {
let mut state = focused_3x3();
state.update(HeatmapMessage::SelectDown);
state.update(HeatmapMessage::SelectDown);
let output = state.update(HeatmapMessage::SelectDown);
assert_eq!(state.selected(), Some((2, 0)));
assert_eq!(output, None); }
#[test]
fn test_select_right_at_right_edge() {
let mut state = focused_3x3();
state.update(HeatmapMessage::SelectRight);
state.update(HeatmapMessage::SelectRight);
let output = state.update(HeatmapMessage::SelectRight);
assert_eq!(state.selected(), Some((0, 2)));
assert_eq!(output, None);
}
#[test]
fn test_navigation_full_traversal() {
let mut state = focused_3x3();
state.update(HeatmapMessage::SelectDown);
state.update(HeatmapMessage::SelectRight);
assert_eq!(state.selected(), Some((1, 1)));
assert_eq!(state.selected_value(), Some(5.0));
state.update(HeatmapMessage::SelectUp);
assert_eq!(state.selected(), Some((0, 1)));
assert_eq!(state.selected_value(), Some(2.0));
state.update(HeatmapMessage::SelectLeft);
assert_eq!(state.selected(), Some((0, 0)));
assert_eq!(state.selected_value(), Some(1.0));
}
#[test]
fn test_selected_value() {
let state = HeatmapState::with_data(vec![vec![42.0, 7.5]]);
assert_eq!(state.selected_value(), Some(42.0));
}
#[test]
fn test_selected_value_empty() {
let state = HeatmapState::default();
assert_eq!(state.selected_value(), None);
}
#[test]
fn test_effective_range_auto() {
let state = HeatmapState::with_data(vec![vec![5.0, 10.0], vec![15.0, 20.0]]);
assert_eq!(state.effective_min(), 5.0);
assert_eq!(state.effective_max(), 20.0);
}
#[test]
fn test_effective_range_manual() {
let state = HeatmapState::new(2, 2).with_range(0.0, 100.0);
assert_eq!(state.effective_min(), 0.0);
assert_eq!(state.effective_max(), 100.0);
}
#[test]
fn test_effective_range_empty() {
let state = HeatmapState::default();
assert_eq!(state.effective_min(), 0.0);
assert_eq!(state.effective_max(), 0.0);
}
#[test]
fn test_arrow_up_maps_to_select_up() {
let state = focused_3x3();
let msg = Heatmap::handle_event(
&state,
&Event::key(Key::Up),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(HeatmapMessage::SelectUp));
}
#[test]
fn test_arrow_down_maps_to_select_down() {
let state = focused_3x3();
let msg = Heatmap::handle_event(
&state,
&Event::key(Key::Down),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(HeatmapMessage::SelectDown));
}
#[test]
fn test_arrow_left_maps_to_select_left() {
let state = focused_3x3();
let msg = Heatmap::handle_event(
&state,
&Event::key(Key::Left),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(HeatmapMessage::SelectLeft));
}
#[test]
fn test_arrow_right_maps_to_select_right() {
let state = focused_3x3();
let msg = Heatmap::handle_event(
&state,
&Event::key(Key::Right),
&EventContext::new().focused(true),
);
assert_eq!(msg, Some(HeatmapMessage::SelectRight));
}
#[test]
fn test_hjkl_keys() {
let state = focused_3x3();
assert_eq!(
Heatmap::handle_event(
&state,
&Event::char('k'),
&EventContext::new().focused(true)
),
Some(HeatmapMessage::SelectUp)
);
assert_eq!(
Heatmap::handle_event(
&state,
&Event::char('j'),
&EventContext::new().focused(true)
),
Some(HeatmapMessage::SelectDown)
);
assert_eq!(
Heatmap::handle_event(
&state,
&Event::char('h'),
&EventContext::new().focused(true)
),
Some(HeatmapMessage::SelectLeft)
);
assert_eq!(
Heatmap::handle_event(
&state,
&Event::char('l'),
&EventContext::new().focused(true)
),
Some(HeatmapMessage::SelectRight)
);
}
#[test]
fn test_enter_emits_cell_selected() {
let mut state = focused_3x3();
let output = Heatmap::dispatch_event(
&mut state,
&Event::key(Key::Enter),
&EventContext::new().focused(true),
);
assert_eq!(
output,
Some(HeatmapOutput::CellSelected {
row: 0,
col: 0,
value: 1.0,
})
);
}
#[test]
fn test_disabled_ignores_events() {
let state = focused_3x3();
let msg = Heatmap::handle_event(
&state,
&Event::key(Key::Down),
&EventContext::new().focused(true).disabled(true),
);
assert_eq!(msg, None);
}
#[test]
fn test_unfocused_ignores_events() {
let state = HeatmapState::with_data(vec![vec![1.0]]);
let msg = Heatmap::handle_event(&state, &Event::key(Key::Down), &EventContext::default());
assert_eq!(msg, None);
}
#[test]
fn test_instance_update() {
let mut state = focused_3x3();
let output = state.update(HeatmapMessage::SelectDown);
assert_eq!(
output,
Some(HeatmapOutput::SelectionChanged { row: 1, col: 0 })
);
}
#[test]
fn test_set_data_message() {
let mut state = focused_3x3();
state.update(HeatmapMessage::SetData(vec![vec![10.0, 20.0]]));
assert_eq!(state.rows(), 1);
assert_eq!(state.cols(), 2);
assert_eq!(state.get(0, 0), Some(10.0));
assert_eq!(state.selected(), Some((0, 0)));
}
#[test]
fn test_set_data_empty() {
let mut state = focused_3x3();
state.update(HeatmapMessage::SetData(vec![]));
assert_eq!(state.rows(), 0);
assert_eq!(state.selected(), None);
}
#[test]
fn test_set_cell_message() {
let mut state = HeatmapState::new(2, 2);
state.update(HeatmapMessage::SelectDown);
state.update(HeatmapMessage::SelectRight);
state.update(HeatmapMessage::SetCell {
row: 0,
col: 0,
value: 99.0,
});
assert_eq!(state.get(0, 0), Some(99.0));
}
#[test]
fn test_set_row_labels_message() {
let mut state = HeatmapState::new(2, 2);
state.update(HeatmapMessage::SetRowLabels(vec!["A".into(), "B".into()]));
assert_eq!(state.row_labels(), &["A", "B"]);
}
#[test]
fn test_set_col_labels_message() {
let mut state = HeatmapState::new(2, 2);
state.update(HeatmapMessage::SetColLabels(vec!["X".into(), "Y".into()]));
assert_eq!(state.col_labels(), &["X", "Y"]);
}
#[test]
fn test_set_color_scale_message() {
let mut state = HeatmapState::new(2, 2);
state.update(HeatmapMessage::SetColorScale(HeatmapColorScale::CoolToWarm));
assert_eq!(state.color_scale(), &HeatmapColorScale::CoolToWarm);
}
#[test]
fn test_set_range_message() {
let mut state = HeatmapState::new(2, 2);
state.update(HeatmapMessage::SetRange(Some(-10.0), Some(10.0)));
assert_eq!(state.effective_min(), -10.0);
assert_eq!(state.effective_max(), 10.0);
}
#[test]
fn test_1x1_grid() {
let mut state = HeatmapState::with_data(vec![vec![42.0]]);
assert_eq!(state.selected(), Some((0, 0)));
assert_eq!(state.selected_value(), Some(42.0));
let output = state.update(HeatmapMessage::SelectUp);
assert_eq!(output, None);
let output = state.update(HeatmapMessage::SelectDown);
assert_eq!(output, None);
let output = state.update(HeatmapMessage::SelectLeft);
assert_eq!(output, None);
let output = state.update(HeatmapMessage::SelectRight);
assert_eq!(output, None);
}
#[test]
fn test_empty_grid_navigation() {
let mut state = HeatmapState::default();
let output = state.update(HeatmapMessage::SelectDown);
assert_eq!(output, None);
}
#[test]
fn test_uneven_row_lengths() {
let state = HeatmapState::with_data(vec![
vec![1.0, 2.0, 3.0],
vec![4.0, 5.0], ]);
assert_eq!(state.get(1, 2), None); assert_eq!(state.get(1, 1), Some(5.0));
}
#[test]
fn test_uneven_row_navigation() {
let mut state = HeatmapState::with_data(vec![
vec![1.0, 2.0, 3.0],
vec![4.0, 5.0], ]);
state.update(HeatmapMessage::SelectRight);
state.update(HeatmapMessage::SelectRight);
assert_eq!(state.selected(), Some((0, 2)));
state.update(HeatmapMessage::SelectDown);
assert_eq!(state.selected(), Some((1, 1))); }
#[test]
fn test_render_empty() {
let state = HeatmapState::default();
let (mut terminal, theme) = test_utils::setup_render(40, 10);
terminal
.draw(|frame| {
Heatmap::view(
&state,
&mut RenderContext::new(frame, frame.area(), &theme).focused(true),
);
})
.unwrap();
}
#[test]
fn test_render_small_grid() {
let state = HeatmapState::with_data(vec![vec![0.0, 0.5, 1.0], vec![0.3, 0.7, 0.9]]);
let (mut terminal, theme) = test_utils::setup_render(40, 10);
terminal
.draw(|frame| {
Heatmap::view(
&state,
&mut RenderContext::new(frame, frame.area(), &theme).focused(true),
);
})
.unwrap();
}
#[test]
fn test_render_with_labels() {
let state = HeatmapState::with_data(vec![vec![0.0, 0.5, 1.0], vec![0.3, 0.7, 0.9]])
.with_row_labels(vec!["AM".into(), "PM".into()])
.with_col_labels(vec!["Mon".into(), "Tue".into(), "Wed".into()])
.with_title("Schedule");
let (mut terminal, theme) = test_utils::setup_render(40, 10);
terminal
.draw(|frame| {
Heatmap::view(
&state,
&mut RenderContext::new(frame, frame.area(), &theme).focused(true),
);
})
.unwrap();
}
#[test]
fn test_render_with_values() {
let state = HeatmapState::with_data(vec![vec![1.5, 2.7], vec![3.1, 4.9]])
.with_show_values(true)
.with_title("Values");
let (mut terminal, theme) = test_utils::setup_render(40, 10);
terminal
.draw(|frame| {
Heatmap::view(
&state,
&mut RenderContext::new(frame, frame.area(), &theme).focused(true),
);
})
.unwrap();
}
#[test]
fn test_render_disabled() {
let state = HeatmapState::with_data(vec![vec![1.0, 2.0]]);
let (mut terminal, theme) = test_utils::setup_render(40, 10);
terminal
.draw(|frame| {
Heatmap::view(
&state,
&mut RenderContext::new(frame, frame.area(), &theme).disabled(true),
);
})
.unwrap();
}
#[test]
fn test_render_focused_with_selection() {
let mut state = HeatmapState::with_data(vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]]);
state.update(HeatmapMessage::SelectDown);
state.update(HeatmapMessage::SelectRight);
let (mut terminal, theme) = test_utils::setup_render(40, 10);
terminal
.draw(|frame| {
Heatmap::view(
&state,
&mut RenderContext::new(frame, frame.area(), &theme).focused(true),
);
})
.unwrap();
}
#[test]
fn test_render_small_area() {
let state = HeatmapState::with_data(vec![vec![1.0]]);
let (mut terminal, theme) = test_utils::setup_render(5, 2);
terminal
.draw(|frame| {
Heatmap::view(
&state,
&mut RenderContext::new(frame, frame.area(), &theme).focused(true),
);
})
.unwrap();
}
#[test]
fn test_truncate_str_fits() {
assert_eq!(truncate_str("abc", 5), "abc");
}
#[test]
fn test_truncate_str_exact() {
assert_eq!(truncate_str("abc", 3), "abc");
}
#[test]
fn test_truncate_str_too_long() {
assert_eq!(truncate_str("abcdef", 3), "abc");
}
#[test]
fn test_truncate_str_zero_width() {
assert_eq!(truncate_str("abc", 0), "");
}
#[test]
fn test_format_value_wide() {
let s = format_value(3.75, 6);
assert_eq!(s, "3.8");
}
#[test]
fn test_format_value_narrow() {
let s = format_value(42.0, 3);
assert_eq!(s, "42");
}
#[test]
fn test_format_value_zero_width() {
let s = format_value(1.0, 0);
assert_eq!(s, "");
}
#[test]
fn test_contrasting_fg_dark_bg() {
assert_eq!(contrasting_fg(Color::Rgb(0, 0, 0)), Color::White);
}
#[test]
fn test_contrasting_fg_light_bg() {
assert_eq!(contrasting_fg(Color::Rgb(255, 255, 255)), Color::Black);
}
#[test]
fn test_contrasting_fg_dark_gray() {
assert_eq!(contrasting_fg(Color::DarkGray), Color::White);
}
#[test]
fn test_annotation_emitted() {
use crate::annotation::with_annotations;
let state = HeatmapState::with_data(vec![vec![1.0, 2.0]]);
let (mut terminal, theme) = test_utils::setup_render(40, 10);
let registry = with_annotations(|| {
terminal
.draw(|frame| {
Heatmap::view(
&state,
&mut RenderContext::new(frame, frame.area(), &theme).focused(true),
);
})
.unwrap();
});
assert!(registry.get_by_id("heatmap").is_some());
}