use egui::{Pos2, Rect, Vec2};
use par_term::tab::TabId;
use par_term::tab_bar_ui::{TabBarAction, TabBarUI};
#[test]
fn test_tab_bar_action_none() {
let action = TabBarAction::None;
assert!(matches!(action, TabBarAction::None));
}
#[test]
fn test_tab_bar_action_switch_to() {
let action = TabBarAction::SwitchTo(42);
match action {
TabBarAction::SwitchTo(id) => assert_eq!(id, 42),
_ => panic!("Expected SwitchTo action"),
}
}
#[test]
fn test_tab_bar_action_close() {
let action = TabBarAction::Close(7);
match action {
TabBarAction::Close(id) => assert_eq!(id, 7),
_ => panic!("Expected Close action"),
}
}
#[test]
fn test_tab_bar_action_new_tab() {
let action = TabBarAction::NewTab;
assert!(matches!(action, TabBarAction::NewTab));
}
#[test]
fn test_tab_bar_action_reorder() {
let action = TabBarAction::Reorder(5, 2);
match action {
TabBarAction::Reorder(id, pos) => {
assert_eq!(id, 5);
assert_eq!(pos, 2);
}
_ => panic!("Expected Reorder action"),
}
}
#[test]
fn test_tab_bar_actions_not_equal() {
assert_ne!(TabBarAction::None, TabBarAction::NewTab);
assert_ne!(TabBarAction::SwitchTo(1), TabBarAction::Close(1));
assert_ne!(TabBarAction::SwitchTo(1), TabBarAction::SwitchTo(2));
}
#[test]
fn test_tab_bar_actions_clone() {
let actions = vec![
TabBarAction::None,
TabBarAction::SwitchTo(1),
TabBarAction::Close(2),
TabBarAction::NewTab,
TabBarAction::Reorder(3, 4),
TabBarAction::SetColor(5, [255, 128, 64]),
TabBarAction::ClearColor(6),
];
for action in actions {
let cloned = action.clone();
assert_eq!(action, cloned);
}
}
#[test]
fn test_tab_bar_actions_debug() {
let actions: Vec<TabBarAction> = vec![
TabBarAction::None,
TabBarAction::SwitchTo(1),
TabBarAction::Close(2),
TabBarAction::NewTab,
TabBarAction::Reorder(3, 4),
TabBarAction::SetColor(5, [255, 128, 64]),
TabBarAction::ClearColor(6),
];
for action in actions {
let debug_str = format!("{:?}", action);
assert!(!debug_str.is_empty());
}
}
#[test]
fn test_close_vs_switch_action_distinction() {
let switch = TabBarAction::SwitchTo(1);
let close = TabBarAction::Close(1);
assert_ne!(
switch, close,
"Close and SwitchTo must be different actions"
);
match switch {
TabBarAction::SwitchTo(id) => assert_eq!(id, 1),
_ => panic!("Switch action pattern match failed"),
}
match close {
TabBarAction::Close(id) => assert_eq!(id, 1),
_ => panic!("Close action pattern match failed"),
}
}
#[test]
fn test_action_none_is_default_when_no_interaction() {
let action = TabBarAction::None;
assert_eq!(action, TabBarAction::None);
assert_ne!(action, TabBarAction::NewTab);
assert_ne!(action, TabBarAction::SwitchTo(1));
assert_ne!(action, TabBarAction::Close(1));
}
#[test]
fn test_reorder_action_components() {
let action = TabBarAction::Reorder(5, 2);
if let TabBarAction::Reorder(tab_id, new_pos) = action {
assert_eq!(tab_id, 5, "Tab ID should be preserved");
assert_eq!(new_pos, 2, "New position should be preserved");
} else {
panic!("Expected Reorder action");
}
}
#[test]
fn test_set_color_action_components() {
let color = [128, 64, 255];
let action = TabBarAction::SetColor(3, color);
if let TabBarAction::SetColor(tab_id, c) = action {
assert_eq!(tab_id, 3, "Tab ID should be preserved");
assert_eq!(c, color, "Color should be preserved");
} else {
panic!("Expected SetColor action");
}
}
#[test]
fn test_clear_color_action_components() {
let action = TabBarAction::ClearColor(7);
if let TabBarAction::ClearColor(tab_id) = action {
assert_eq!(tab_id, 7, "Tab ID should be preserved");
} else {
panic!("Expected ClearColor action");
}
}
#[test]
fn test_tab_bar_is_dragging_default_false() {
let tab_bar = TabBarUI::new();
assert!(
!tab_bar.is_dragging(),
"Drag should not be in progress on init"
);
}
#[test]
fn test_tab_bar_default_is_not_dragging() {
let tab_bar = TabBarUI::default();
assert!(
!tab_bar.is_dragging(),
"Default tab bar should not be dragging"
);
}
#[allow(dead_code)]
fn make_tab_rect(left_x: f32, width: f32) -> Rect {
Rect::from_min_size(Pos2::new(left_x, 0.0), Vec2::new(width, 30.0))
}
#[test]
fn test_drag_state_idle_on_creation() {
let tab_bar = TabBarUI::new();
assert!(!tab_bar.is_dragging(), "Initial drag state should be idle");
assert!(
tab_bar.test_dragging_tab().is_none(),
"No tab should be dragging initially"
);
}
#[test]
fn test_drag_state_transition_to_dragging() {
let mut tab_bar = TabBarUI::new();
let tab_id: TabId = 42;
tab_bar.test_set_drag_state(Some(tab_id), true);
assert!(tab_bar.is_dragging(), "Should be in dragging state");
assert_eq!(
tab_bar.test_dragging_tab(),
Some(tab_id),
"Dragging tab id should match"
);
}
#[test]
fn test_drag_state_transition_to_dropped() {
let mut tab_bar = TabBarUI::new();
let tab_id: TabId = 7;
tab_bar.test_set_drag_state(Some(tab_id), true);
assert!(tab_bar.is_dragging());
tab_bar.test_set_drag_state(None, false);
tab_bar.test_set_drop_target(None);
assert!(!tab_bar.is_dragging(), "Should be idle after drop");
assert!(
tab_bar.test_dragging_tab().is_none(),
"No tab should be dragging after drop"
);
assert!(
tab_bar.test_drop_target_index().is_none(),
"Drop target should be cleared"
);
}
#[test]
fn test_drag_state_cancel_clears_all_drag_fields() {
let mut tab_bar = TabBarUI::new();
let tab_id: TabId = 3;
tab_bar.test_set_drag_state(Some(tab_id), true);
tab_bar.test_set_drop_target(Some(2));
tab_bar.test_set_drag_state(None, false);
tab_bar.test_set_drop_target(None);
assert!(!tab_bar.is_dragging());
assert!(tab_bar.test_dragging_tab().is_none());
assert!(tab_bar.test_drop_target_index().is_none());
}
#[test]
fn test_drag_state_multiple_tabs_only_one_dragging() {
let mut tab_bar = TabBarUI::new();
let tab_a: TabId = 1;
tab_bar.test_set_drag_state(Some(tab_a), true);
assert_eq!(tab_bar.test_dragging_tab(), Some(tab_a));
assert!(tab_bar.is_dragging());
}
fn make_tab_rects(count: usize, tab_width: f32, spacing: f32) -> Vec<(TabId, Rect)> {
(0..count)
.map(|i| {
let left = i as f32 * (tab_width + spacing);
let rect = Rect::from_min_size(Pos2::new(left, 0.0), Vec2::new(tab_width, 30.0));
(i as TabId, rect)
})
.collect()
}
#[test]
fn test_drop_target_before_first_tab() {
let rects = make_tab_rects(3, 100.0, 4.0);
let result = TabBarUI::calculate_drop_target_horizontal(&rects, None, 10.0);
assert_eq!(
result,
Some(0),
"Pointer before center of first tab → insert at 0"
);
}
#[test]
fn test_drop_target_between_tabs() {
let rects = make_tab_rects(3, 100.0, 4.0);
let result = TabBarUI::calculate_drop_target_horizontal(&rects, None, 160.0);
assert_eq!(
result,
Some(2),
"Pointer past center of tab 1 → insert at 2"
);
}
#[test]
fn test_drop_target_after_last_tab() {
let rects = make_tab_rects(3, 100.0, 4.0);
let result = TabBarUI::calculate_drop_target_horizontal(&rects, None, 999.0);
assert_eq!(result, Some(3), "Pointer after all tabs → insert at end");
}
#[test]
fn test_drop_target_noop_same_position() {
let rects = make_tab_rects(3, 100.0, 4.0);
let result = TabBarUI::calculate_drop_target_horizontal(&rects, Some(0), 30.0);
assert_eq!(result, None, "Dropping in the same slot should be a no-op");
}
#[test]
fn test_drop_target_noop_adjacent_position() {
let rects = make_tab_rects(3, 100.0, 4.0);
let result = TabBarUI::calculate_drop_target_horizontal(&rects, Some(0), 160.0);
assert_eq!(result, Some(2));
}
#[test]
fn test_drop_target_noop_next_slot() {
let rects = make_tab_rects(3, 100.0, 4.0);
let result = TabBarUI::calculate_drop_target_horizontal(&rects, Some(1), 220.0);
assert_eq!(result, None, "Inserting right after source is a no-op");
}
#[test]
fn test_drop_target_empty_tab_list() {
let rects: Vec<(TabId, Rect)> = vec![];
let result = TabBarUI::calculate_drop_target_horizontal(&rects, None, 0.0);
assert_eq!(result, Some(0), "Empty tab list: insert at 0");
}
#[test]
fn test_insertion_to_target_index_insert_before_source() {
assert_eq!(
TabBarUI::insertion_to_target_index(0, Some(2)),
0,
"Inserting before source: index unchanged"
);
}
#[test]
fn test_insertion_to_target_index_insert_after_source() {
assert_eq!(
TabBarUI::insertion_to_target_index(3, Some(1)),
2,
"Inserting after source: index decremented by 1"
);
}
#[test]
fn test_insertion_to_target_index_no_source() {
assert_eq!(
TabBarUI::insertion_to_target_index(5, None),
5,
"No source: index unchanged"
);
}
#[test]
fn test_drop_target_followed_by_reorder_action() {
let rects = make_tab_rects(4, 100.0, 4.0);
let dragging_id: TabId = 0; let source_idx = Some(0usize);
let insert_idx = TabBarUI::calculate_drop_target_horizontal(&rects, source_idx, 400.0);
assert_eq!(
insert_idx,
Some(4),
"Pointer after all tab centers → insert at end (index 4)"
);
let effective = TabBarUI::insertion_to_target_index(insert_idx.unwrap(), source_idx);
assert_eq!(
effective, 3,
"After removing tab 0, insert_idx 4 → effective target 3"
);
let action = TabBarAction::Reorder(dragging_id, effective);
match action {
TabBarAction::Reorder(id, pos) => {
assert_eq!(id, dragging_id);
assert_eq!(pos, 3);
}
_ => panic!("Expected Reorder action"),
}
}