use crate::common::harness::EditorTestHarness;
use crossterm::event::{KeyCode, KeyModifiers};
use tempfile::TempDir;
#[test]
fn test_margin_line_numbers_rendering() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
std::fs::write(
&file_path,
"Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9\nLine 10\n",
)
.unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("Screen output:\n{screen}");
harness.assert_screen_contains(" 1 │");
harness.assert_screen_contains(" 2 │");
harness.assert_screen_contains(" 3 │");
harness.assert_screen_contains("Line 1");
harness.assert_screen_contains("Line 2");
harness.assert_screen_contains("Line 3");
}
#[test]
fn test_margin_empty_buffer() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("Empty buffer screen:\n{screen}");
harness.assert_screen_contains(" 1 │");
}
#[test]
fn test_initial_buffer_respects_line_numbers_config() {
let mut config = fresh::config::Config::default();
config.editor.line_numbers = false;
let mut harness = EditorTestHarness::with_config(80, 24, config).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("Empty buffer screen (line_numbers=false):\n{screen}");
harness.assert_screen_not_contains(" │ ");
harness.type_text("Hello").unwrap();
harness.assert_screen_contains("Hello");
}
#[test]
fn test_margin_large_file_line_numbers() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large.txt");
let content: String = (1..=1000).map(|i| format!("Line {i}\n")).collect();
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();
println!("Large file screen (at end):\n{screen}");
harness.assert_screen_contains("1000 │");
}
#[test]
fn test_margin_disable_line_numbers() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("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.assert_screen_contains(" │ ");
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.wait_for_prompt().unwrap();
harness.type_text("Toggle Line Numbers").unwrap();
harness
.wait_for_screen_contains("Toggle Line Numbers")
.unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.wait_for_prompt_closed().unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("Screen without line numbers:\n{screen}");
harness.assert_screen_not_contains(" │ ");
harness.assert_screen_contains("Line 1");
}
#[test]
fn test_margin_custom_annotations() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
std::fs::write(&file_path, "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness
.apply_event(fresh::model::event::Event::AddMarginAnnotation {
line: 2,
position: fresh::model::event::MarginPositionData::Left,
content: fresh::model::event::MarginContentData::Symbol {
text: "●".to_string(),
color: Some((255, 0, 0)), },
annotation_id: Some("breakpoint-1".to_string()),
})
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("Screen with breakpoint annotation:\n{screen}");
harness.assert_screen_contains("●");
harness
.apply_event(fresh::model::event::Event::RemoveMarginAnnotation {
annotation_id: "breakpoint-1".to_string(),
})
.unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
println!("Screen after removing annotation:\n{screen_after}");
harness.assert_screen_contains(" 3 │");
}
#[test]
fn test_margin_after_editing() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.type_text("First line").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.type_text("Second line").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.type_text("Third line").unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("Screen after typing:\n{screen}");
harness.assert_screen_contains(" 1 │");
harness.assert_screen_contains(" 2 │");
harness.assert_screen_contains(" 3 │");
harness.assert_screen_contains("First line");
harness.assert_screen_contains("Second line");
harness.assert_screen_contains("Third line");
}
#[test]
fn test_cursor_position_with_margin() {
let mut harness = EditorTestHarness::new_no_wrap(80, 24).unwrap();
harness.type_text("abc").unwrap();
harness.render().unwrap();
let (content_first_row, _content_last_row) = harness.content_area_rows();
let cursor_pos = harness.screen_cursor_position();
println!("Cursor position: {cursor_pos:?}");
assert_eq!(
cursor_pos.0, 11,
"Cursor X position should account for margin width"
);
assert_eq!(
cursor_pos.1, content_first_row as u16,
"Cursor Y position should be on first line (row {content_first_row})"
);
}
#[test]
fn test_margin_with_horizontal_scroll() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("long_line.txt");
let long_line = "X".repeat(200);
std::fs::write(&file_path, &long_line).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
for _ in 0..100 {
harness
.send_key(KeyCode::Right, KeyModifiers::NONE)
.unwrap();
}
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("Screen with horizontal scroll:\n{screen}");
harness.assert_screen_contains(" 1 │");
harness.assert_screen_contains("X");
}
#[test]
#[ignore = "Splits currently share the same active buffer (architectural limitation). All splits display the currently active buffer, so this test's assumption of independent buffers per split doesn't match current behavior."]
fn test_margin_per_buffer_in_split_view() {
let temp_dir = TempDir::new().unwrap();
let file1_path = temp_dir.path().join("file1.txt");
let file2_path = temp_dir.path().join("file2.txt");
std::fs::write(&file1_path, "File 1 Line 1\nFile 1 Line 2\n").unwrap();
std::fs::write(&file2_path, "File 2 Line 1\nFile 2 Line 2\nFile 2 Line 3\n").unwrap();
let mut harness = EditorTestHarness::new(120, 40).unwrap();
harness.open_file(&file1_path).unwrap();
harness
.send_key(KeyCode::Char('v'), KeyModifiers::ALT)
.unwrap();
harness.open_file(&file2_path).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("Split view screen:\n{screen}");
harness.assert_screen_contains(" 1 │");
harness.assert_screen_contains("File 1 Line 1");
harness.assert_screen_contains("File 2 Line 1");
harness
.apply_event(fresh::model::event::Event::SetLineNumbers { enabled: false })
.unwrap();
harness
.send_key(KeyCode::Char('o'), KeyModifiers::ALT)
.unwrap(); harness
.apply_event(fresh::model::event::Event::AddMarginAnnotation {
line: 0,
position: fresh::model::event::MarginPositionData::Left,
content: fresh::model::event::MarginContentData::Symbol {
text: "●".to_string(),
color: Some((255, 0, 0)),
},
annotation_id: Some("file1-marker".to_string()),
})
.unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
println!("Split view after modifications:\n{screen_after}");
}
#[test]
fn test_line_numbers_update_during_incremental_scroll() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("scroll_test.txt");
let content: String = (1..=100).map(|i| format!("Line {i}\n")).collect();
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 screen = harness.screen_to_string();
println!("Initial screen:\n{screen}");
harness.assert_screen_contains(" 1 │");
harness.assert_screen_contains("Line 1");
harness
.send_key(KeyCode::PageDown, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_after_pagedown = harness.screen_to_string();
println!("\nScreen after PageDown:\n{screen_after_pagedown}");
let should_contain_line_20_or_higher = screen_after_pagedown.contains(" 20 │")
|| screen_after_pagedown.contains(" 21 │")
|| screen_after_pagedown.contains(" 22 │")
|| screen_after_pagedown.contains(" 23 │")
|| screen_after_pagedown.contains(" 24 │")
|| screen_after_pagedown.contains(" 25 │");
assert!(
should_contain_line_20_or_higher,
"After PageDown, line numbers should show lines around 20-25, but screen shows:\n{}",
screen_after_pagedown
);
for _ in 0..5 {
harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
}
harness.render().unwrap();
let screen_after_down = harness.screen_to_string();
println!("\nScreen after 5x Down:\n{screen_after_down}");
let should_contain_line_27_or_higher = screen_after_down.contains(" 27 │")
|| screen_after_down.contains(" 28 │")
|| screen_after_down.contains(" 29 │")
|| screen_after_down.contains(" 30 │")
|| screen_after_down.contains(" 31 │");
assert!(
should_contain_line_27_or_higher,
"After 5 more Down keys, line numbers should show lines around 27-31, but screen shows:\n{}",
screen_after_down
);
harness.assert_screen_not_contains(" 1 │");
}
#[test]
fn test_line_numbers_update_with_navigation_keys() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("nav_test.txt");
let content: String = (1..=200).map(|i| format!("Line {i}\n")).collect();
std::fs::write(&file_path, content).unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains(" 1 │");
harness.assert_screen_contains("Line 1");
for i in 1..=3 {
harness
.send_key(KeyCode::PageDown, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("\nScreen after PageDown #{i}:\n{screen}");
}
let screen = harness.screen_to_string();
let should_be_around_line_60 = screen.contains(" 60 │")
|| screen.contains(" 61 │")
|| screen.contains(" 62 │")
|| screen.contains(" 63 │")
|| screen.contains(" 64 │")
|| screen.contains(" 65 │")
|| screen.contains(" 66 │")
|| screen.contains(" 67 │")
|| screen.contains(" 68 │")
|| screen.contains(" 69 │")
|| screen.contains(" 70 │");
assert!(
should_be_around_line_60,
"After 3 PageDowns, should be around line 60-70, but screen shows:\n{}",
screen
);
for i in 1..=2 {
harness
.send_key(KeyCode::PageUp, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("\nScreen after PageUp #{i}:\n{screen}");
}
let screen = harness.screen_to_string();
let should_be_around_line_20 = screen.contains(" 20 │")
|| screen.contains(" 21 │")
|| screen.contains(" 22 │")
|| screen.contains(" 23 │")
|| screen.contains(" 24 │")
|| screen.contains(" 25 │")
|| screen.contains(" 26 │")
|| screen.contains(" 27 │")
|| screen.contains(" 28 │");
assert!(
should_be_around_line_20,
"After 2 PageUps, should be around line 20-28, but screen shows:\n{}",
screen
);
harness
.send_key(KeyCode::End, KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("\nScreen after Ctrl+End:\n{screen}");
harness.assert_screen_contains(" 200 │");
harness.assert_screen_contains("Line 200");
harness.assert_screen_not_contains(" 1 │");
let has_high_lines = screen.contains(" 180 │")
|| screen.contains(" 185 │")
|| screen.contains(" 190 │")
|| screen.contains(" 195 │")
|| screen.contains(" 199 │");
assert!(
has_high_lines,
"At end of file, should show lines in 180s-190s range, but screen shows:\n{}",
screen
);
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
println!("\nScreen after Ctrl+Home:\n{screen}");
harness.assert_screen_contains(" 1 │");
harness.assert_screen_contains("Line 1");
harness.assert_screen_not_contains(" 200 │");
harness.assert_screen_contains(" 2 │");
harness.assert_screen_contains(" 3 │");
harness.assert_screen_contains(" 10 │");
harness.assert_screen_contains(" 20 │");
harness
.send_key(KeyCode::End, KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains(" 200 │");
harness
.send_key(KeyCode::Home, KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains(" 1 │");
}