#[cfg(test)]
mod visual_scroll_knob_drag_tests {
use crate::tests::visual_testing::{BoxMuxTester, TestConfig};
use std::time::Duration;
fn create_vertical_scroll_test_yaml() -> &'static str {
r#"
muxboxes:
- id: "vertical_scroll_test"
position:
x1: "5"
y1: "3"
x2: "35"
y2: "18"
style:
background_color: "white"
text_color: "black"
border_color: "blue"
overflow_behavior: "scroll"
choices:
- id: "choice_1"
content: "Choice 1 - First item in scrollable list"
- id: "choice_2"
content: "Choice 2 - Second item"
- id: "choice_3"
content: "Choice 3 - Third item"
- id: "choice_4"
content: "Choice 4 - Fourth item"
- id: "choice_5"
content: "Choice 5 - Fifth item"
- id: "choice_6"
content: "Choice 6 - Sixth item"
- id: "choice_7"
content: "Choice 7 - Seventh item"
- id: "choice_8"
content: "Choice 8 - Eighth item"
- id: "choice_9"
content: "Choice 9 - Ninth item"
- id: "choice_10"
content: "Choice 10 - Tenth item"
- id: "choice_11"
content: "Choice 11 - Eleventh item"
- id: "choice_12"
content: "Choice 12 - Twelfth item"
- id: "choice_13"
content: "Choice 13 - Thirteenth item"
- id: "choice_14"
content: "Choice 14 - Fourteenth item"
- id: "choice_15"
content: "Choice 15 - Fifteenth item"
- id: "choice_16"
content: "Choice 16 - Sixteenth item"
- id: "choice_17"
content: "Choice 17 - Seventeenth item"
- id: "choice_18"
content: "Choice 18 - Eighteenth item"
- id: "choice_19"
content: "Choice 19 - Nineteenth item"
- id: "choice_20"
content: "Choice 20 - Twentieth item"
"#
}
fn create_horizontal_scroll_test_yaml() -> &'static str {
r#"
muxboxes:
- id: "horizontal_scroll_test"
position:
x1: "5"
y1: "5"
x2: "25"
y2: "15"
style:
background_color: "white"
text_color: "black"
border_color: "green"
overflow_behavior: "scroll"
choices:
- id: "long_choice_1"
content: "This is an extremely long choice that should exceed the narrow muxbox width and trigger horizontal scrollbar generation - Choice 1"
- id: "long_choice_2"
content: "Another very long choice text that definitely exceeds the container width and should force horizontal scrolling - Choice 2"
- id: "long_choice_3"
content: "Yet another extremely long choice that is much wider than the container and will require horizontal scrolling - Choice 3"
- id: "long_choice_4"
content: "A fourth very long choice that extends way beyond the muxbox boundaries and necessitates horizontal scrolling - Choice 4"
- id: "long_choice_5"
content: "Fifth long choice with extensive text that overflows the narrow container width requiring horizontal scroll - Choice 5"
"#
}
fn create_combined_scroll_test_yaml() -> &'static str {
r#"
muxboxes:
- id: "combined_scroll_test"
position:
x1: "2"
y1: "2"
x2: "28"
y2: "12"
style:
background_color: "white"
text_color: "black"
border_color: "red"
overflow_behavior: "scroll"
choices:
- id: "combo_1"
content: "Very long choice 1 that exceeds width and there are many choices to exceed height - Item 1"
- id: "combo_2"
content: "Very long choice 2 that exceeds width and there are many choices to exceed height - Item 2"
- id: "combo_3"
content: "Very long choice 3 that exceeds width and there are many choices to exceed height - Item 3"
- id: "combo_4"
content: "Very long choice 4 that exceeds width and there are many choices to exceed height - Item 4"
- id: "combo_5"
content: "Very long choice 5 that exceeds width and there are many choices to exceed height - Item 5"
- id: "combo_6"
content: "Very long choice 6 that exceeds width and there are many choices to exceed height - Item 6"
- id: "combo_7"
content: "Very long choice 7 that exceeds width and there are many choices to exceed height - Item 7"
- id: "combo_8"
content: "Very long choice 8 that exceeds width and there are many choices to exceed height - Item 8"
- id: "combo_9"
content: "Very long choice 9 that exceeds width and there are many choices to exceed height - Item 9"
- id: "combo_10"
content: "Very long choice 10 that exceeds width and there are many choices to exceed height - Item 10"
- id: "combo_11"
content: "Very long choice 11 that exceeds width and there are many choices to exceed height - Item 11"
- id: "combo_12"
content: "Very long choice 12 that exceeds width and there are many choices to exceed height - Item 12"
- id: "combo_13"
content: "Very long choice 13 that exceeds width and there are many choices to exceed height - Item 13"
- id: "combo_14"
content: "Very long choice 14 that exceeds width and there are many choices to exceed height - Item 14"
- id: "combo_15"
content: "Very long choice 15 that exceeds width and there are many choices to exceed height - Item 15"
"#
}
#[test]
fn test_vertical_scroll_knob_drag_functionality() {
let mut tester = BoxMuxTester::with_config(TestConfig {
terminal_size: (80, 25),
operation_timeout: Duration::from_secs(2),
capture_history: true,
..Default::default()
});
tester.load_config_from_string(create_vertical_scroll_test_yaml())
.expect("Should load vertical scroll test config");
let initial_frame = tester.wait_for_frame().expect("Should capture initial frame");
let mut scrollbar_found = false;
let right_edge = 35; for y in 4..=17 { let cell = initial_frame.get_cell(right_edge, y);
if cell.ch == '│' || cell.ch == '█' || cell.ch == '▄' || cell.ch == '▀' {
scrollbar_found = true;
break;
}
}
assert!(scrollbar_found, "Vertical scrollbar should be visible with overflowing choices");
let initial_content = initial_frame.get_content_string();
assert!(initial_content.contains("Choice 1"), "Should initially show Choice 1 at top");
assert!(initial_content.contains("Choice 2"), "Should initially show Choice 2");
assert!(!initial_content.contains("Choice 20"), "Should not show Choice 20 at initial scroll position");
let drag_start_y = 7; let drag_end_y = 11;
println!("=== TESTING VERTICAL SCROLL KNOB DRAG ===");
println!("Initial frame content preview:");
initial_frame.print_debug_preview();
tester.drag_from_to(right_edge, drag_start_y, right_edge, drag_end_y)
.expect("Should perform drag operation");
let after_drag_frame = tester.wait_for_frame().expect("Should capture frame after drag");
println!("After drag frame content preview:");
after_drag_frame.print_debug_preview();
let after_drag_content = after_drag_frame.get_content_string();
let shows_middle_choices = after_drag_content.contains("Choice 10") ||
after_drag_content.contains("Choice 11") ||
after_drag_content.contains("Choice 12");
if !shows_middle_choices {
println!("❌ SCROLL KNOB DRAG BUG CONFIRMED:");
println!(" - Dragged from Y={} to Y={}", drag_start_y, drag_end_y);
println!(" - Expected to see middle choices (Choice 10-12)");
println!(" - Still showing same content as before drag");
println!(" - Scroll knob dragging is not working");
assert!(after_drag_content.contains("Choice 1"),
"Content unchanged - drag didn't work (still shows Choice 1)");
} else {
println!("✅ Scroll knob drag is working correctly");
}
let drag_to_bottom_y = 16;
tester.drag_from_to(right_edge, drag_end_y, right_edge, drag_to_bottom_y)
.expect("Should perform second drag operation");
let bottom_drag_frame = tester.wait_for_frame().expect("Should capture frame after bottom drag");
let bottom_content = bottom_drag_frame.get_content_string();
let shows_last_choices = bottom_content.contains("Choice 18") ||
bottom_content.contains("Choice 19") ||
bottom_content.contains("Choice 20");
if !shows_last_choices {
println!("❌ SCROLL TO BOTTOM BUG CONFIRMED:");
println!(" - Dragged to bottom position Y={}", drag_to_bottom_y);
println!(" - Expected to see last choices (Choice 18-20)");
println!(" - Still showing same content");
}
tester.save_snapshot("vertical_scroll_initial").expect("Should save initial snapshot");
}
#[test]
fn test_horizontal_scroll_knob_drag_functionality() {
let mut tester = BoxMuxTester::with_config(TestConfig {
terminal_size: (80, 25),
operation_timeout: Duration::from_secs(2),
capture_history: true,
..Default::default()
});
tester.load_config_from_string(create_horizontal_scroll_test_yaml())
.expect("Should load horizontal scroll test config");
let initial_frame = tester.wait_for_frame().expect("Should capture initial frame");
let mut h_scrollbar_found = false;
let bottom_edge = 15; for x in 6..=24 { let cell = initial_frame.get_cell(x, bottom_edge);
if cell.ch == '─' || cell.ch == '█' || cell.ch == '▌' || cell.ch == '▐' {
h_scrollbar_found = true;
break;
}
}
assert!(h_scrollbar_found, "Horizontal scrollbar should be visible with overflowing choice text");
let initial_content = initial_frame.get_content_string();
assert!(initial_content.contains("This is an extremely"), "Should show start of long choice text");
assert!(!initial_content.contains("- Choice 1"), "Should not show end of choice text initially");
let drag_start_x = 10; let drag_end_x = 18;
println!("=== TESTING HORIZONTAL SCROLL KNOB DRAG ===");
println!("Initial frame content preview:");
initial_frame.print_debug_preview();
tester.drag_from_to(drag_start_x, bottom_edge, drag_end_x, bottom_edge)
.expect("Should perform horizontal drag operation");
let after_drag_frame = tester.wait_for_frame().expect("Should capture frame after horizontal drag");
println!("After horizontal drag frame content preview:");
after_drag_frame.print_debug_preview();
let after_drag_content = after_drag_frame.get_content_string();
let shows_middle_text = after_drag_content.contains("trigger horizontal") ||
after_drag_content.contains("scrollbar generation") ||
after_drag_content.contains("- Choice 1");
if !shows_middle_text {
println!("❌ HORIZONTAL SCROLL KNOB DRAG BUG CONFIRMED:");
println!(" - Dragged from X={} to X={}", drag_start_x, drag_end_x);
println!(" - Expected to see middle/end of choice text");
println!(" - Still showing same content as before drag");
println!(" - Horizontal scroll knob dragging is not working");
assert!(after_drag_content.contains("This is an extremely"),
"Content unchanged - horizontal drag didn't work");
} else {
println!("✅ Horizontal scroll knob drag is working correctly");
}
let drag_to_right_x = 23;
tester.drag_from_to(drag_end_x, bottom_edge, drag_to_right_x, bottom_edge)
.expect("Should perform second horizontal drag operation");
let right_drag_frame = tester.wait_for_frame().expect("Should capture frame after right drag");
let right_content = right_drag_frame.get_content_string();
let shows_end_text = right_content.contains("- Choice 1") ||
right_content.contains("Choice 2") ||
right_content.contains("Choice 3");
if !shows_end_text {
println!("❌ SCROLL TO RIGHT BUG CONFIRMED:");
println!(" - Dragged to right position X={}", drag_to_right_x);
println!(" - Expected to see end of choice text");
println!(" - Still showing same content");
}
tester.save_snapshot("horizontal_scroll_initial").expect("Should save horizontal scroll snapshot");
}
#[test]
fn test_combined_vertical_and_horizontal_scroll_knob_drag() {
let mut tester = BoxMuxTester::with_config(TestConfig {
terminal_size: (80, 25),
operation_timeout: Duration::from_secs(3),
capture_history: true,
..Default::default()
});
tester.load_config_from_string(create_combined_scroll_test_yaml())
.expect("Should load combined scroll test config");
let initial_frame = tester.wait_for_frame().expect("Should capture initial frame");
let mut v_scrollbar_found = false;
let mut h_scrollbar_found = false;
let right_edge = 28; let bottom_edge = 12;
for y in 3..=11 {
let cell = initial_frame.get_cell(right_edge, y);
if cell.ch == '│' || cell.ch == '█' {
v_scrollbar_found = true;
break;
}
}
for x in 3..=27 {
let cell = initial_frame.get_cell(x, bottom_edge);
if cell.ch == '─' || cell.ch == '█' {
h_scrollbar_found = true;
break;
}
}
assert!(v_scrollbar_found, "Vertical scrollbar should be present in combined scroll test");
assert!(h_scrollbar_found, "Horizontal scrollbar should be present in combined scroll test");
println!("=== TESTING COMBINED SCROLL KNOB DRAG ===");
println!("Initial frame with both scrollbars:");
initial_frame.print_debug_preview();
let initial_content = initial_frame.get_content_string();
let v_drag_start_y = 5; let v_drag_end_y = 9;
tester.drag_from_to(right_edge, v_drag_start_y, right_edge, v_drag_end_y)
.expect("Should perform vertical drag in combined test");
let after_v_drag_frame = tester.wait_for_frame().expect("Should capture after vertical drag");
let after_v_drag_content = after_v_drag_frame.get_content_string();
let v_drag_worked = !initial_content.eq(&after_v_drag_content);
if !v_drag_worked {
println!("❌ VERTICAL DRAG FAILED in combined scroll scenario");
}
let h_drag_start_x = 8; let h_drag_end_x = 20;
tester.drag_from_to(h_drag_start_x, bottom_edge, h_drag_end_x, bottom_edge)
.expect("Should perform horizontal drag in combined test");
let after_h_drag_frame = tester.wait_for_frame().expect("Should capture after horizontal drag");
let after_h_drag_content = after_h_drag_frame.get_content_string();
let h_drag_worked = !after_v_drag_content.eq(&after_h_drag_content);
if !h_drag_worked {
println!("❌ HORIZONTAL DRAG FAILED in combined scroll scenario");
}
tester.drag_from_to(right_edge, v_drag_end_y, right_edge, 10)
.expect("Should perform second vertical drag");
tester.drag_from_to(h_drag_end_x, bottom_edge, 15, bottom_edge)
.expect("Should perform second horizontal drag");
let final_frame = tester.wait_for_frame().expect("Should capture final frame");
println!("Final frame after combined drags:");
final_frame.print_debug_preview();
tester.save_snapshot("combined_scroll_test").expect("Should save combined scroll snapshot");
}
#[test]
fn test_scroll_knob_position_calculation() {
let mut tester = BoxMuxTester::with_config(TestConfig {
terminal_size: (80, 25),
operation_timeout: Duration::from_secs(1),
capture_history: true,
..Default::default()
});
tester.load_config_from_string(create_vertical_scroll_test_yaml())
.expect("Should load config for knob position test");
let frame = tester.wait_for_frame().expect("Should capture frame");
let box_height = 15; let total_choices = 20;
let visible_choices = box_height - 2; let content_height = total_choices;
let track_height = box_height - 2;
let knob_size = (visible_choices as f64 / content_height as f64 * track_height as f64).max(1.0);
let knob_position_at_0_percent = 0.0;
println!("=== SCROLL KNOB POSITION CALCULATION TEST ===");
println!("Box height: {}", box_height);
println!("Total choices: {}", total_choices);
println!("Visible choices: {}", visible_choices);
println!("Track height: {}", track_height);
println!("Expected knob size: {:.1}", knob_size);
println!("Expected knob position at 0%: {:.1}", knob_position_at_0_percent);
let right_edge = 35;
let knob_y = 4;
let knob_cell = frame.get_cell(right_edge, knob_y);
let has_knob_char = knob_cell.ch == '█' || knob_cell.ch == '▄' || knob_cell.ch == '▀';
if !has_knob_char {
println!("❌ KNOB NOT FOUND at expected position ({}, {})", right_edge, knob_y);
println!(" Found character: '{}' (expected knob character)", knob_cell.ch);
}
let track_start_y = 4;
let track_end_y = 17;
let actual_track_height = track_end_y - track_start_y;
let click_25_percent_y = track_start_y + (actual_track_height / 4);
let calculated_25_percent = ((click_25_percent_y - track_start_y) as f64 / actual_track_height as f64) * 100.0;
println!("25% click position: Y={}, calculated: {:.1}%", click_25_percent_y, calculated_25_percent);
assert!((calculated_25_percent - 25.0).abs() < 10.0, "25% position calculation should be approximately correct");
let click_75_percent_y = track_start_y + (actual_track_height * 3 / 4);
let calculated_75_percent = ((click_75_percent_y - track_start_y) as f64 / actual_track_height as f64) * 100.0;
println!("75% click position: Y={}, calculated: {:.1}%", click_75_percent_y, calculated_75_percent);
assert!((calculated_75_percent - 75.0).abs() < 10.0, "75% position calculation should be approximately correct");
frame.print_debug_preview();
tester.save_snapshot("scroll_knob_position_test").expect("Should save position test snapshot");
}
#[test]
fn test_scroll_knob_click_vs_drag_behavior() {
let mut tester = BoxMuxTester::with_config(TestConfig {
terminal_size: (80, 25),
operation_timeout: Duration::from_secs(2),
capture_history: true,
..Default::default()
});
tester.load_config_from_string(create_vertical_scroll_test_yaml())
.expect("Should load config for click vs drag test");
let initial_frame = tester.wait_for_frame().expect("Should capture initial frame");
let initial_content = initial_frame.get_content_string();
println!("=== TESTING SCROLL CLICK VS DRAG BEHAVIOR ===");
let right_edge = 35;
let track_middle_y = 10;
tester.click_at(right_edge, track_middle_y)
.expect("Should click middle of scrollbar track");
let after_click_frame = tester.wait_for_frame().expect("Should capture frame after track click");
let after_click_content = after_click_frame.get_content_string();
let click_changed_content = !initial_content.eq(&after_click_content);
if !click_changed_content {
println!("❌ SCROLLBAR TRACK CLICK NOT WORKING");
println!(" Clicked at track position ({}, {})", right_edge, track_middle_y);
println!(" Expected to jump to ~50% scroll position");
println!(" Content did not change");
} else {
println!("✅ Scrollbar track click appears to work");
}
let knob_start_y = 8; let knob_end_y = 14;
tester.drag_from_to(right_edge, knob_start_y, right_edge, knob_end_y)
.expect("Should drag scroll knob");
let after_drag_frame = tester.wait_for_frame().expect("Should capture frame after knob drag");
let after_drag_content = after_drag_frame.get_content_string();
let drag_changed_content = !after_click_content.eq(&after_drag_content);
if !drag_changed_content {
println!("❌ SCROLL KNOB DRAG NOT WORKING");
println!(" Dragged knob from Y={} to Y={}", knob_start_y, knob_end_y);
println!(" Expected content to change following drag");
println!(" Content did not change");
} else {
println!("✅ Scroll knob drag appears to work");
}
let knob_cell_at_new_position = after_drag_frame.get_cell(right_edge, knob_end_y);
let knob_moved = knob_cell_at_new_position.ch == '█' ||
knob_cell_at_new_position.ch == '▄' ||
knob_cell_at_new_position.ch == '▀';
if !knob_moved {
println!("❌ KNOB DID NOT VISUALLY MOVE");
println!(" Expected knob at new position ({}, {})", right_edge, knob_end_y);
println!(" Found character: '{}'", knob_cell_at_new_position.ch);
}
println!("Initial content preview:");
initial_frame.print_debug_preview();
println!("After click content preview:");
after_click_frame.print_debug_preview();
println!("After drag content preview:");
after_drag_frame.print_debug_preview();
tester.save_snapshot("click_vs_drag_test").expect("Should save click vs drag snapshot");
}
}
trait FrameExt {
fn get_content_string(&self) -> String;
fn print_debug_preview(&self);
fn get_cell(&self, x: usize, y: usize) -> &crate::model::common::Cell;
}
impl FrameExt for crate::tests::visual_testing::TerminalFrame {
fn get_content_string(&self) -> String {
let mut content = String::new();
for row in &self.buffer {
let line: String = row.iter().map(|cell| cell.ch).collect();
content.push_str(&line);
content.push('\n');
}
content
}
fn print_debug_preview(&self) {
println!("Frame {}x{} at {:?}:", self.dimensions.0, self.dimensions.1, self.timestamp);
for (y, row) in self.buffer.iter().enumerate().take(20) { let line: String = row.iter().map(|cell| cell.ch).collect();
println!("{:2}|{}", y, line);
}
println!("");
}
fn get_cell(&self, x: usize, y: usize) -> &crate::model::common::Cell {
&self.buffer[y][x]
}
}