use crate::common::harness::EditorTestHarness;
use tempfile::TempDir;
#[test]
fn test_empty_buffer_rendering() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(!screen.is_empty());
harness.assert_screen_contains("[No Name]");
}
#[test]
fn test_file_content_rendering() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("render_test.txt");
std::fs::write(&file_path, "Line 1\nLine 2\nLine 3\n").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("Line 1");
harness.assert_screen_contains("Line 2");
harness.assert_screen_contains("Line 3");
harness.assert_screen_contains("render_test.txt");
}
#[test]
fn test_screen_cursor_position() {
let mut harness = EditorTestHarness::new_no_wrap(80, 24).unwrap();
harness.type_text("abc").unwrap();
harness.assert_buffer_content("abc");
harness.render().unwrap();
let (content_first_row, _content_last_row) = harness.content_area_rows();
let cursor_pos = harness.screen_cursor_position();
println!("Cursor position after typing 'abc': {{cursor_pos:?}}");
println!("Expected: x=11 (1 + 4 + 3 + 3), y={content_first_row}");
assert_eq!(
cursor_pos.1, content_first_row as u16,
"Cursor Y should be at row {content_first_row} (content area start)"
);
assert_eq!(
cursor_pos.0, 11,
"Cursor X should be at column 11 (after 'abc')"
);
}
#[test]
fn test_cursor_x_position_advances() {
let mut harness = EditorTestHarness::new_no_wrap(80, 24).unwrap();
harness.render().unwrap();
let (content_first_row, _content_last_row) = harness.content_area_rows();
let pos0 = harness.screen_cursor_position();
println!("Initial cursor position: {{pos0:?}}");
harness.type_text("a").unwrap();
harness.render().unwrap();
let pos1 = harness.screen_cursor_position();
println!("After 'a': {{pos1:?}}");
harness.type_text("b").unwrap();
harness.render().unwrap();
let pos2 = harness.screen_cursor_position();
println!("After 'ab': {{pos2:?}}");
harness.type_text("c").unwrap();
harness.render().unwrap();
let pos3 = harness.screen_cursor_position();
println!("After 'abc': {{pos3:?}}");
let expected_y = content_first_row as u16;
assert_eq!(pos0.1, expected_y, "Initial Y should be {expected_y}");
assert_eq!(
pos1.1, expected_y,
"Y should stay at {expected_y} after 'a'"
);
assert_eq!(
pos2.1, expected_y,
"Y should stay at {expected_y} after 'ab'"
);
assert_eq!(
pos3.1, expected_y,
"Y should stay at {expected_y} after 'abc'"
);
assert_eq!(pos1.0, pos0.0 + 1, "X should advance by 1 after 'a'");
assert_eq!(pos2.0, pos1.0 + 1, "X should advance by 1 after 'b'");
assert_eq!(pos3.0, pos2.0 + 1, "X should advance by 1 after 'c'");
}
#[test]
fn test_cursor_position_with_large_line_numbers() {
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large_file.txt");
let mut content = String::new();
for i in 0..1_000_000 {
content.push_str(&format!(
"Line {i:07} with some padding text to reach approximately 80 characters\n"
));
}
std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness
.send_key(
crossterm::event::KeyCode::End,
crossterm::event::KeyModifiers::CONTROL,
)
.unwrap();
let buffer_len = harness.editor().active_state().buffer.len();
let gutter_width = harness
.editor()
.active_viewport()
.gutter_width(&harness.editor().active_state().buffer);
println!("\nBuffer length: {buffer_len} bytes");
println!("Estimated lines (buffer_len / 80): {}", buffer_len / 80);
println!("Calculated gutter_width: {gutter_width}");
harness.render().unwrap();
let screen_pos = harness.screen_cursor_position();
let screen = harness.screen_to_string();
let lines: Vec<&str> = screen.lines().collect();
println!("\nWith 7-digit line numbers (file with 1,000,000 lines - at end of file):");
println!("Full screen dump (last visible lines):");
for (i, line) in lines.iter().take(5).enumerate() {
println!("Row {i}: {line:?}");
}
println!("\nVisual character position ruler:");
println!(" 1111111111222222222233333333334");
println!("01234567890123456789012345678901234567890");
if let Some(content_line) = lines.get(screen_pos.1 as usize) {
println!("{}", &content_line.chars().take(40).collect::<String>());
println!("{}^", " ".repeat(screen_pos.0 as usize));
println!(" cursor is here (pos {})", screen_pos.0);
}
println!(
"\nScreen cursor position: ({}, {})",
screen_pos.0, screen_pos.1
);
let content_lines: Vec<&str> = lines
.iter()
.skip(1) .filter(|line| line.contains(" │ "))
.copied()
.collect();
println!("\nValidating line numbers:");
let numbered_lines: Vec<&str> = content_lines
.iter()
.filter(|line| {
let part = line.split("│").next().unwrap_or("").trim();
!part.is_empty() && part.chars().all(|c| c.is_ascii_digit())
})
.copied()
.collect();
if let Some(last_line) = numbered_lines.last() {
let line_num_part = last_line.split("│").next().unwrap_or("").trim();
let line_num: usize = line_num_part.parse().unwrap_or(0);
println!("Last visible line number: {line_num} (may be estimated)");
let expected_estimate = buffer_len / 80;
println!("Expected estimated line number: ~{expected_estimate}");
let lower_bound = expected_estimate.saturating_sub(expected_estimate / 10);
let upper_bound = expected_estimate + (expected_estimate / 10);
assert!(
line_num >= lower_bound && line_num <= upper_bound,
"Expected line number near {expected_estimate}, but got {line_num}"
);
assert!(
line_num.to_string().len() >= 6,
"Expected 6+ digit line number, but {} has {} digits",
line_num,
line_num.to_string().len()
);
} else {
panic!("No content lines found!");
}
println!("\nExpected gutter width: 10 (1 + 6 + 3 for 6-digit estimated line numbers)");
println!("Actual gutter_width: {gutter_width}");
assert_eq!(
gutter_width, 10,
"Gutter width {gutter_width} doesn't match expected 10"
);
println!("Expected: cursor x = {gutter_width} (at gutter width)");
println!("Actual: cursor x = {}", screen_pos.0);
assert_eq!(
screen_pos.0 as usize, gutter_width,
"Cursor x position {} should be at gutter width {}",
screen_pos.0, gutter_width
);
}
#[test]
#[ignore] fn test_line_numbers_rendered_correctly() {
use crossterm::event::{KeyCode, KeyModifiers};
use tempfile::TempDir;
let test_cases = vec![
(1, "1-line file"),
(100, "100-line file"),
(3900, "3900-line file (just under 4k)"),
(4000, "4000-line file"),
(4100, "4100-line file (just over 4k)"),
(10000, "10000-line file"),
];
for (line_count, description) in test_cases {
println!(
"\n{}\nTesting: {}\n{}",
"=".repeat(60),
description,
"=".repeat(60)
);
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join(format!("test_{line_count}_lines.txt"));
let mut content = String::new();
for i in 1..=line_count {
content.push_str(&format!("Line {i}\n"));
}
std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness
.send_key(KeyCode::End, KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
let lines: Vec<&str> = screen.lines().collect();
println!("Full screen dump:");
for (i, line) in lines.iter().enumerate() {
println!("Row {i:2}: {line:?}");
}
let content_lines: Vec<&str> = lines
.iter()
.skip(1) .filter(|line| line.contains(" │ "))
.copied()
.collect();
if let Some(last_line) = content_lines.last() {
println!("\nLast content line: {last_line:?}");
let line_num_part = last_line.split("│").next().unwrap_or("").trim();
println!("Line number extracted: {line_num_part:?}");
let line_num: usize = line_num_part.parse().unwrap_or(0);
println!("Parsed line number: {line_num}");
let expected_min = if line_count > 20 { line_count - 20 } else { 1 };
assert!(
line_num >= expected_min && line_num <= line_count,
"{description}: Expected to see line numbers between {expected_min} and {line_count}, but got line {line_num}"
);
assert_eq!(
line_num, line_count,
"{description}: Expected last visible line to be {line_count}, but got {line_num}"
);
} else {
panic!("{description}: No content lines found on screen!");
}
}
}
#[test]
#[ignore] fn test_page_down_line_numbers() {
use crossterm::event::{KeyCode, KeyModifiers};
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
let content: String = (1..=100).map(|i| format!("x{i}\n")).collect();
std::fs::write(&file_path, content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
let initial_line = harness.top_line_number();
assert_eq!(initial_line, 0, "Should start at line 0");
harness.assert_screen_contains("x1");
let initial_cursor = harness.cursor_position();
println!("Initial state: line {initial_line}, cursor at {initial_cursor}, screen contains x1");
println!("Initial screen:\n{}", harness.screen_to_string());
harness
.send_key(KeyCode::PageDown, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let after_first_pagedown = harness.top_line_number();
let cursor_after_first = harness.cursor_position();
println!("\nAfter first PageDown: line {after_first_pagedown}, cursor at {cursor_after_first}");
println!(
"Screen after first PageDown:\n{}",
harness.screen_to_string()
);
assert!(
after_first_pagedown > 0,
"After first PageDown, should have scrolled down from line 0, but got line {after_first_pagedown}"
);
let screen = harness.screen_to_string();
assert!(
screen.contains("x") && after_first_pagedown > 0,
"Should see content after scrolling"
);
println!(
"After first PageDown: screen contains lines starting from line {after_first_pagedown}"
);
harness
.send_key(KeyCode::PageDown, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let after_second_pagedown = harness.top_line_number();
let cursor_after_second = harness.cursor_position();
println!(
"\nAfter second PageDown: line {after_second_pagedown}, cursor at {cursor_after_second}"
);
println!(
"Screen after second PageDown:\n{}",
harness.screen_to_string()
);
assert!(
after_second_pagedown > after_first_pagedown,
"After second PageDown, should have scrolled down more (from {after_first_pagedown} to {after_second_pagedown})"
);
let screen = harness.screen_to_string();
assert!(
screen.contains("x") && after_second_pagedown > after_first_pagedown,
"Should see content after second page down"
);
println!(
"After second PageDown: screen contains lines starting from line {after_second_pagedown}"
);
harness.assert_screen_not_contains("x1");
println!("\n=== Testing upward movement ===");
let line_before_up = harness.top_line_number();
for i in 0..10 {
harness.send_key(KeyCode::Up, KeyModifiers::NONE).unwrap();
harness.render().unwrap();
let current_line = harness.top_line_number();
let cursor_pos = harness.cursor_position();
if current_line < line_before_up {
println!(
"After {} Up presses: line {} (scrolled up!), cursor at {}",
i + 1,
current_line,
cursor_pos
);
assert!(
current_line < line_before_up,
"Line number should decrease when scrolling up"
);
let expected_content = format!("x{}", current_line + 1);
harness.assert_screen_contains(&expected_content);
println!("Screen now shows {expected_content}");
break;
}
}
let final_line = harness.top_line_number();
assert!(
final_line < after_second_pagedown,
"After moving up, viewport should have scrolled up from line {after_second_pagedown} to {final_line}"
);
}
#[test]
fn test_ansi_rgb_color_rendering() {
use ratatui::style::Color;
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("ansi_rgb_test.txt");
let mut content = String::new();
for i in 0..20 {
let r = 100 + i * 5;
let g = 50 + i * 3;
let b = 150 + i * 2;
content.push_str(&format!("\x1b[38;2;{r};{g};{b}mâ–ˆ"));
}
content.push_str("\x1b[0m"); std::fs::write(&file_path, &content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let (content_row, _) = harness.content_area_rows();
let gutter_width = 8;
let screen = harness.screen_to_string();
println!("Screen content:\n{screen}");
harness.assert_screen_not_contains(";2;"); harness.assert_screen_not_contains("38;2"); harness.assert_screen_not_contains(";50;");
let first_block_style = harness.get_cell_style(gutter_width, content_row as u16);
println!(
"Style at first block position ({gutter_width}, {content_row}): {first_block_style:?}"
);
assert!(
first_block_style.is_some(),
"Expected to find a cell at position ({gutter_width}, {content_row})"
);
let style = first_block_style.unwrap();
assert_eq!(
style.fg,
Some(Color::Rgb(100, 50, 150)),
"Expected first block to have RGB(100,50,150) foreground from ANSI code, got {:?}",
style.fg
);
let mid_block_style = harness.get_cell_style(gutter_width + 10, content_row as u16);
println!("Style at block 10 position: {mid_block_style:?}");
if let Some(mid_style) = mid_block_style {
assert_eq!(
mid_style.fg,
Some(Color::Rgb(150, 80, 170)),
"Expected block 10 to have RGB(150,80,170) foreground, got {:?}",
mid_style.fg
);
}
}