#[cfg(test)]
mod mouse_click_tests {
use crate::model::choice::Choice;
use crate::model::common::{Bounds, InputBounds};
use crate::model::layout::Layout;
use crate::model::muxbox::MuxBox;
use crate::tests::test_utils::TestDataFactory;
use crate::thread_manager::Message;
use crate::{
color_utils::get_bg_color, draw_loop::apply_calibration_cursor_overlay_at, ScreenBuffer,
};
#[test]
fn test_mouse_click_message_creation() {
let x = 10u16;
let y = 20u16;
let message = Message::MouseClick(x, y);
match message {
Message::MouseClick(click_x, click_y) => {
assert_eq!(click_x, x);
assert_eq!(click_y, y);
}
_ => panic!("Expected MouseClick message"),
}
}
#[test]
fn test_mouse_click_message_hash() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let msg1 = Message::MouseClick(10, 20);
let msg2 = Message::MouseClick(10, 20);
let msg3 = Message::MouseClick(10, 21);
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
let mut hasher3 = DefaultHasher::new();
msg1.hash(&mut hasher1);
msg2.hash(&mut hasher2);
msg3.hash(&mut hasher3);
assert_eq!(hasher1.finish(), hasher2.finish());
assert_ne!(hasher1.finish(), hasher3.finish());
}
#[test]
fn test_find_muxbox_at_coordinates() {
let mut muxbox1 = TestDataFactory::create_test_muxbox("muxbox1");
let mut muxbox2 = TestDataFactory::create_test_muxbox("muxbox2");
let layout =
TestDataFactory::create_test_layout("test_layout", Some(vec![muxbox1, muxbox2]));
let found_muxbox = layout.find_muxbox_at_coordinates(50, 25);
let not_found = layout.find_muxbox_at_coordinates(1000, 1000);
}
#[test]
fn test_every_terminal_pixel_maps_to_topmost_muxbox() {
let mut left = TestDataFactory::create_test_muxbox("left");
left.position = InputBounds {
x1: "1%".to_string(),
y1: "8%".to_string(),
x2: "24%".to_string(),
y2: "91%".to_string(),
};
let mut center = TestDataFactory::create_test_muxbox("center");
center.position = InputBounds {
x1: "25%".to_string(),
y1: "8%".to_string(),
x2: "73%".to_string(),
y2: "70%".to_string(),
};
let mut overlay = TestDataFactory::create_test_muxbox("overlay");
overlay.position = InputBounds {
x1: "39%".to_string(),
y1: "11%".to_string(),
x2: "52%".to_string(),
y2: "18%".to_string(),
};
overlay.z_index = Some(10);
let mut right = TestDataFactory::create_test_muxbox("right");
right.position = InputBounds {
x1: "74%".to_string(),
y1: "8%".to_string(),
x2: "99%".to_string(),
y2: "91%".to_string(),
};
let mut far_corner = TestDataFactory::create_test_muxbox("far_corner");
far_corner.position = InputBounds {
x1: "82.5%".to_string(),
y1: "74.5%".to_string(),
x2: "99.6%".to_string(),
y2: "99.2%".to_string(),
};
far_corner.z_index = Some(20);
let layout = TestDataFactory::create_test_layout(
"pixel_map",
Some(vec![left, center, right, overlay, far_corner]),
);
for (width, height) in [
(80usize, 24usize),
(100, 30),
(132, 43),
(192, 54),
(241, 67),
(319, 91),
] {
let root_bounds = Bounds {
x1: 0,
y1: 0,
x2: width - 1,
y2: height - 1,
};
let children = layout.children.as_ref().expect("pixel map children");
let left_bounds = children[0].bounds_with_parent(&root_bounds);
let center_bounds = children[1].bounds_with_parent(&root_bounds);
let right_bounds = children[2].bounds_with_parent(&root_bounds);
let overlay_bounds = children[3].bounds_with_parent(&root_bounds);
let far_corner_bounds = children[4].bounds_with_parent(&root_bounds);
for y in 0..height as u16 {
for x in 0..width as u16 {
let ux = x as usize;
let uy = y as usize;
let expected = if far_corner_bounds.contains_point(ux, uy) {
Some("far_corner")
} else if overlay_bounds.contains_point(ux, uy) {
Some("overlay")
} else if right_bounds.contains_point(ux, uy) {
Some("right")
} else if center_bounds.contains_point(ux, uy) {
Some("center")
} else if left_bounds.contains_point(ux, uy) {
Some("left")
} else {
None
};
let actual = layout
.find_muxbox_at_coordinates_with_bounds(x, y, &root_bounds)
.map(|muxbox| muxbox.id.as_str());
assert_eq!(
actual, expected,
"coordinate ({},{}) at terminal {}x{} mapped to the wrong muxbox",
x, y, width, height
);
}
}
}
}
#[test]
fn test_shared_edge_row_resolves_to_box_rendered_on_top() {
let mut status = TestDataFactory::create_test_muxbox("status");
status.position = InputBounds {
x1: "0%".to_string(),
y1: "0%".to_string(),
x2: "100%".to_string(),
y2: "7%".to_string(),
};
let mut main = TestDataFactory::create_test_muxbox("main");
main.position = InputBounds {
x1: "25%".to_string(),
y1: "8%".to_string(),
x2: "73%".to_string(),
y2: "70%".to_string(),
};
let layout =
TestDataFactory::create_test_layout("share", Some(vec![status, main]));
let root_bounds = Bounds {
x1: 0,
y1: 0,
x2: 99,
y2: 29,
};
let children = layout.children.as_ref().unwrap();
let status_bounds = children[0].bounds_with_parent(&root_bounds);
let main_bounds = children[1].bounds_with_parent(&root_bounds);
assert_eq!(
status_bounds.y2, main_bounds.y1,
"test requires the shared-edge condition (status.bottom == main.top)"
);
let shared_row = main_bounds.y1 as u16;
let x = ((main_bounds.x1 + main_bounds.x2) / 2) as u16;
assert!(status_bounds.contains_point(x as usize, shared_row as usize));
assert!(main_bounds.contains_point(x as usize, shared_row as usize));
let hit = layout
.find_muxbox_at_coordinates_with_bounds(x, shared_row, &root_bounds)
.map(|m| m.id.clone());
assert_eq!(
hit.as_deref(),
Some("main"),
"the shared tab-bar row must belong to the box rendered on top (main)"
);
}
#[test]
fn test_calibration_marks_only_cursor_cell_background_for_detected_muxbox() {
let mut muxbox = TestDataFactory::create_test_muxbox("target");
muxbox.position = InputBounds {
x1: "25%".to_string(),
y1: "8%".to_string(),
x2: "73%".to_string(),
y2: "70%".to_string(),
};
let layout = TestDataFactory::create_test_layout("calibration", Some(vec![muxbox]));
let root_bounds = Bounds {
x1: 0,
y1: 0,
x2: 240,
y2: 66,
};
let mut buffer = ScreenBuffer::new_custom(241, 67);
let before_cursor = buffer.get(90, 20).cloned().expect("cursor cell");
let before_neighbor = buffer.get(91, 20).cloned().expect("neighbor cell");
assert!(apply_calibration_cursor_overlay_at(
&layout,
&mut buffer,
90,
20,
&root_bounds
));
let cursor = buffer.get(90, 20).expect("calibrated cursor cell");
let neighbor = buffer.get(91, 20).expect("neighbor cell after calibration");
assert_eq!(cursor.ch, before_cursor.ch);
assert_eq!(cursor.fg_color, before_cursor.fg_color);
assert_eq!(cursor.bg_color, get_bg_color("red"));
assert_eq!(neighbor, &before_neighbor);
}
#[test]
fn test_calibration_does_not_mark_unowned_gap_cells() {
let mut muxbox = TestDataFactory::create_test_muxbox("target");
muxbox.position = InputBounds {
x1: "25%".to_string(),
y1: "8%".to_string(),
x2: "73%".to_string(),
y2: "70%".to_string(),
};
let layout = TestDataFactory::create_test_layout("calibration", Some(vec![muxbox]));
let root_bounds = Bounds {
x1: 0,
y1: 0,
x2: 240,
y2: 66,
};
let mut buffer = ScreenBuffer::new_custom(241, 67);
let before = buffer.get(1, 1).cloned().expect("gap cell");
assert!(!apply_calibration_cursor_overlay_at(
&layout,
&mut buffer,
1,
1,
&root_bounds
));
assert_eq!(buffer.get(1, 1), Some(&before));
}
#[test]
fn test_choice_mouse_activation_properties() {
let choice = Choice {
id: "test_choice".to_string(),
content: Some("Click Me".to_string()),
script: Some(vec!["echo clicked".to_string()]),
redirect_output: Some("output_muxbox".to_string()),
append_output: Some(false),
execution_mode: crate::model::common::ExecutionMode::default(),
selected: false,
hovered: false,
waiting: false,
};
assert!(choice.script.is_some());
assert_eq!(choice.redirect_output, Some("output_muxbox".to_string()));
}
#[test]
fn test_muxbox_selection_properties() {
let mut muxbox = TestDataFactory::create_test_muxbox("selectable_muxbox");
muxbox.tab_order = Some("1".to_string());
assert!(muxbox.tab_order.is_some());
assert_eq!(muxbox.tab_order, Some("1".to_string()));
}
#[test]
fn test_muxbox_with_choices_for_menu() {
let choice1 = Choice {
id: "choice1".to_string(),
content: Some("First Choice".to_string()),
script: Some(vec!["echo first".to_string()]),
redirect_output: None,
append_output: None,
execution_mode: crate::model::common::ExecutionMode::default(),
selected: false,
hovered: false,
waiting: false,
};
let choice2 = Choice {
id: "choice2".to_string(),
content: Some("Second Choice".to_string()),
script: Some(vec!["echo second".to_string()]),
redirect_output: Some("output".to_string()),
append_output: Some(true),
execution_mode: crate::model::common::ExecutionMode::default(),
selected: false,
hovered: false,
waiting: false,
};
let mut muxbox = TestDataFactory::create_test_muxbox("menu_muxbox");
muxbox.choices = Some(vec![choice1, choice2]);
assert!(muxbox.choices.is_some());
let choices = muxbox.choices.as_ref().unwrap();
assert_eq!(choices.len(), 2);
assert_eq!(choices[0].id, "choice1");
assert_eq!(choices[1].id, "choice2");
assert_eq!(choices[1].redirect_output, Some("output".to_string()));
}
#[test]
fn test_mouse_click_coordinate_bounds() {
let min_coords = Message::MouseClick(0, 0);
let max_coords = Message::MouseClick(u16::MAX, u16::MAX);
match min_coords {
Message::MouseClick(x, y) => {
assert_eq!(x, 0);
assert_eq!(y, 0);
}
_ => panic!("Expected MouseClick message"),
}
match max_coords {
Message::MouseClick(x, y) => {
assert_eq!(x, u16::MAX);
assert_eq!(y, u16::MAX);
}
_ => panic!("Expected MouseClick message"),
}
}
#[test]
fn test_nested_muxboxes_coordinate_detection() {
let child_muxbox = TestDataFactory::create_test_muxbox("child");
let mut parent_muxbox = TestDataFactory::create_test_muxbox("parent");
parent_muxbox.children = Some(vec![child_muxbox]);
let layout =
TestDataFactory::create_test_layout("nested_layout", Some(vec![parent_muxbox]));
let _result = layout.find_muxbox_at_coordinates(10, 10);
assert!(
true,
"Nested muxbox coordinate detection completed without panic"
);
}
#[test]
fn test_choice_index_calculation_logic() {
let num_choices = 5;
let muxbox_height = 20u16;
let content_start_offset = 3u16;
let content_height = muxbox_height - content_start_offset;
if content_height > 0 && num_choices > 0 {
let choice_height = content_height / num_choices as u16;
let click_positions = vec![
0,
choice_height / 2,
choice_height,
choice_height * 2,
choice_height * 4,
];
for click_pos in click_positions {
let choice_index = (click_pos / choice_height.max(1)) as usize;
assert!(
choice_index < num_choices || click_pos >= content_height,
"Choice index {} should be valid for click position {}",
choice_index,
click_pos
);
}
}
assert_eq!(0u16 / 1u16.max(1), 0);
assert_eq!(5u16 / 1u16.max(1), 5);
}
}