use crate::common::harness::EditorTestHarness;
use crossterm::event::{KeyCode, KeyModifiers};
use tempfile::TempDir;
#[test]
fn test_initial_split_has_buffer_in_tabs() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
std::fs::write(&file_path, "Hello").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("test.txt");
}
#[test]
fn test_initial_scratch_buffer_in_tabs() {
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
eprintln!("Initial editor screen:\n{}", screen);
}
#[test]
fn test_open_file_adds_to_split_tabs() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
std::fs::write(&file1, "Content 1").unwrap();
std::fs::write(&file2, "Content 2").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file1).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("file1.txt");
harness.open_file(&file2).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("file1.txt");
harness.assert_screen_contains("file2.txt");
}
#[test]
fn test_new_split_has_buffer_in_tabs() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
std::fs::write(&file_path, "Hello").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("test.txt");
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("split horiz").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
let count = screen.matches("test.txt").count();
assert!(
count >= 2,
"Expected at least 2 occurrences of 'test.txt' in split tabs, found {}. Screen:\n{}",
count,
screen
);
}
#[test]
fn test_splits_have_independent_tabs() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
std::fs::write(&file1, "Content 1").unwrap();
std::fs::write(&file2, "Content 2").unwrap();
let mut harness = EditorTestHarness::new(100, 30).unwrap();
harness.open_file(&file1).unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("split vert").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.open_file(&file2).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("file1.txt");
harness.assert_screen_contains("file2.txt");
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("next split").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
eprintln!("Screen after switching to first split:\n{}", screen);
}
#[test]
fn test_buffer_cycling_within_split() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
let file3 = temp_dir.path().join("file3.txt");
std::fs::write(&file1, "Content 1").unwrap();
std::fs::write(&file2, "Content 2").unwrap();
std::fs::write(&file3, "Content 3").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file1).unwrap();
harness.open_file(&file2).unwrap();
harness.open_file(&file3).unwrap();
harness.render().unwrap();
harness.assert_buffer_content("Content 3");
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("next buffer").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_buffer_content("Content 1");
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("next buffer").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_buffer_content("Content 2");
}
#[test]
fn test_tab_bar_in_split_area() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
std::fs::write(&file_path, "Hello world").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
eprintln!("Screen content:\n{}", screen);
harness.assert_screen_contains("test.txt");
}
#[test]
fn test_close_buffer_removes_from_tabs() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
std::fs::write(&file1, "Content 1").unwrap();
std::fs::write(&file2, "Content 2").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file1).unwrap();
harness.open_file(&file2).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("file1.txt");
harness.assert_screen_contains("file2.txt");
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("close buffer").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("file1.txt");
harness.assert_screen_not_contains("file2.txt");
}
#[test]
fn test_git_log_split_tabs() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path();
std::process::Command::new("git")
.args(["init"])
.current_dir(repo_path)
.output()
.expect("Failed to init git repo");
std::process::Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(repo_path)
.output()
.expect("Failed to set git email");
std::process::Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(repo_path)
.output()
.expect("Failed to set git name");
let file_path = repo_path.join("test.txt");
std::fs::write(&file_path, "Hello world").unwrap();
std::process::Command::new("git")
.args(["add", "test.txt"])
.current_dir(repo_path)
.output()
.expect("Failed to git add");
std::process::Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(repo_path)
.output()
.expect("Failed to git commit");
let mut harness = EditorTestHarness::new(100, 30).unwrap();
harness.open_file(&file_path).unwrap();
harness.render().unwrap();
eprintln!("\n=== Before git log ===");
eprintln!("{}", harness.screen_to_string());
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("git log").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.sleep(std::time::Duration::from_millis(100));
harness.render().unwrap();
eprintln!("\n=== After git log ===");
let screen = harness.screen_to_string();
eprintln!("{}", screen);
}
#[test]
fn test_debug_split_tabs_rendering() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("alpha.txt");
let file2 = temp_dir.path().join("beta.txt");
std::fs::write(&file1, "Alpha content").unwrap();
std::fs::write(&file2, "Beta content").unwrap();
let mut harness = EditorTestHarness::new(100, 30).unwrap();
harness.open_file(&file1).unwrap();
harness.render().unwrap();
eprintln!("\n=== After opening alpha.txt ===");
eprintln!("{}", harness.screen_to_string());
harness.open_file(&file2).unwrap();
harness.render().unwrap();
eprintln!("\n=== After opening beta.txt ===");
eprintln!("{}", harness.screen_to_string());
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("split vert").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
eprintln!("\n=== After vertical split ===");
eprintln!("{}", harness.screen_to_string());
let screen = harness.screen_to_string();
assert!(
screen.contains("alpha.txt") || screen.contains("beta.txt"),
"Expected to see file tabs in screen. Screen:\n{}",
screen
);
}
#[test]
fn test_close_split_preserves_tabs_and_focus() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("first.txt");
let file2 = temp_dir.path().join("second.txt");
std::fs::write(&file1, "").unwrap();
std::fs::write(&file2, "").unwrap();
let mut harness = EditorTestHarness::new(100, 30).unwrap();
harness.open_file(&file1).unwrap();
harness.render().unwrap();
harness.type_text("First buffer content").unwrap();
harness.render().unwrap();
harness.assert_screen_contains("First buffer content");
harness.assert_screen_contains("first.txt");
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("split horiz").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains('─'),
"Expected horizontal split separator. Screen:\n{}",
screen
);
harness.open_file(&file2).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("second.txt");
harness.type_text("Second buffer content").unwrap();
harness.render().unwrap();
let screen_with_two_splits = harness.screen_to_string();
eprintln!(
"Screen with two splits and two files:\n{}",
screen_with_two_splits
);
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("close split").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_after_close = harness.screen_to_string();
eprintln!("Screen after closing split:\n{}", screen_after_close);
assert!(
screen_after_close.contains("first.txt"),
"Expected first.txt tab to be present in remaining split. Screen:\n{}",
screen_after_close
);
assert!(
screen_after_close.contains("second.txt"),
"Expected second.txt tab to be preserved from closed split. Screen:\n{}",
screen_after_close
);
harness.type_text("XYZ").unwrap();
harness.render().unwrap();
let final_screen = harness.screen_to_string();
eprintln!("Final screen after typing XYZ:\n{}", final_screen);
assert!(
final_screen.contains("XYZ"),
"Expected typed characters 'XYZ' to appear. The remaining split should be focused. Screen:\n{}",
final_screen
);
}
#[test]
fn test_close_tab_keeps_buffer_in_other_split() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("shared.txt");
std::fs::write(&file1, "Shared content").unwrap();
let mut harness = EditorTestHarness::new(100, 30).unwrap();
harness.open_file(&file1).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("shared.txt");
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("split vert").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
let count = screen.matches("shared.txt").count();
assert!(
count >= 2,
"Expected shared.txt in both splits. Found {} occurrences. Screen:\n{}",
count,
screen
);
harness
.send_key(KeyCode::Char('w'), KeyModifiers::ALT)
.unwrap();
harness.render().unwrap();
let screen_after = harness.screen_to_string();
eprintln!("Screen after close tab:\n{}", screen_after);
assert!(
screen_after.contains("shared.txt") || screen_after.contains("Cannot close"),
"Expected shared.txt to remain or error message. Screen:\n{}",
screen_after
);
}
#[test]
fn test_close_tab_closes_buffer_when_last_viewport() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
std::fs::write(&file1, "Content 1").unwrap();
std::fs::write(&file2, "Content 2").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file1).unwrap();
harness.open_file(&file2).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("file1.txt");
harness.assert_screen_contains("file2.txt");
harness.assert_buffer_content("Content 2");
harness
.send_key(KeyCode::Char('w'), KeyModifiers::ALT)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("file1.txt");
harness.assert_screen_not_contains("file2.txt");
harness.assert_buffer_content("Content 1");
}
#[test]
#[cfg_attr(target_os = "macos", ignore = "flaky on macOS CI")]
fn test_close_tab_prompts_for_modified_buffer() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
std::fs::write(&file1, "Content 1").unwrap();
std::fs::write(&file2, "Content 2").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file1).unwrap();
harness.open_file(&file2).unwrap();
harness.render().unwrap();
harness.type_text("MODIFIED").unwrap();
harness.render().unwrap();
harness
.send_key(KeyCode::Char('w'), KeyModifiers::ALT)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
screen.contains("modified") || screen.contains("save") || screen.contains("discard"),
"Expected save/discard prompt for modified buffer. Screen:\n{}",
screen
);
harness
.send_key(KeyCode::Char('c'), KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
harness.assert_screen_contains("file1.txt");
harness.assert_screen_contains("file2.txt");
}
#[test]
fn test_close_tab_transfers_focus_to_remaining_tab() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
std::fs::write(&file1, "First file content").unwrap();
std::fs::write(&file2, "Second file content").unwrap();
let mut harness = EditorTestHarness::new(80, 24).unwrap();
harness.open_file(&file1).unwrap();
harness.open_file(&file2).unwrap();
harness.render().unwrap();
harness.assert_screen_contains("file2.txt");
harness.assert_screen_contains("Second file content");
harness
.send_key(KeyCode::Char('w'), KeyModifiers::ALT)
.unwrap();
harness.render().unwrap();
let screen = harness.screen_to_string();
assert!(
!screen.contains("file2.txt"),
"file2.txt should be closed. Screen:\n{}",
screen
);
harness.assert_screen_contains("file1.txt");
harness.assert_screen_contains("First file content");
harness.type_text("TYPED").unwrap();
harness.render().unwrap();
harness.assert_screen_contains("TYPED");
harness
.send_key(KeyCode::Char('s'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
let file1_content = std::fs::read_to_string(&file1).unwrap();
assert!(
file1_content.contains("TYPED"),
"Typed text should be saved to file1. Content: {}",
file1_content
);
}
#[test]
fn test_close_last_tab_in_split_closes_split() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("shared.txt");
std::fs::write(&file1, "Shared content").unwrap();
let mut harness = EditorTestHarness::new(100, 30).unwrap();
harness.open_file(&file1).unwrap();
harness.render().unwrap();
let screen_one_split = harness.screen_to_string();
eprintln!("Screen with one split:\n{}", screen_one_split);
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("split vert").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_two_splits = harness.screen_to_string();
let count = screen_two_splits.matches("shared.txt").count();
eprintln!("Screen with two splits:\n{}", screen_two_splits);
assert!(
count >= 2,
"Expected shared.txt in both splits. Found {} occurrences. Screen:\n{}",
count,
screen_two_splits
);
assert!(
screen_two_splits.contains('│'),
"Expected vertical split separator. Screen:\n{}",
screen_two_splits
);
harness
.send_key(KeyCode::Char('w'), KeyModifiers::ALT)
.unwrap();
harness.render().unwrap();
let screen_after_close = harness.screen_to_string();
eprintln!("Screen after closing tab:\n{}", screen_after_close);
let has_single_split = !screen_after_close.contains('│')
|| screen_after_close.matches('│').count() < screen_two_splits.matches('│').count();
assert!(
has_single_split,
"Split should be closed after closing the only tab. Screen:\n{}",
screen_after_close
);
assert!(
screen_after_close.contains("shared.txt"),
"Buffer should still exist in remaining split. Screen:\n{}",
screen_after_close
);
assert!(
screen_after_close.contains("Shared content"),
"Buffer content should be visible. Screen:\n{}",
screen_after_close
);
}
#[test]
fn test_close_tab_with_multiple_tabs_removes_tab_only() {
let temp_dir = TempDir::new().unwrap();
let file1 = temp_dir.path().join("file1.txt");
let file2 = temp_dir.path().join("file2.txt");
std::fs::write(&file1, "Content 1").unwrap();
std::fs::write(&file2, "Content 2").unwrap();
let mut harness = EditorTestHarness::new(100, 30).unwrap();
harness.open_file(&file1).unwrap();
harness.open_file(&file2).unwrap();
harness.render().unwrap();
let screen_both_tabs = harness.screen_to_string();
assert!(
screen_both_tabs.contains("file1.txt") && screen_both_tabs.contains("file2.txt"),
"Both files should be in tabs. Screen:\n{}",
screen_both_tabs
);
harness
.send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
.unwrap();
harness.render().unwrap();
harness.type_text("split vert").unwrap();
harness
.send_key(KeyCode::Enter, KeyModifiers::NONE)
.unwrap();
harness.render().unwrap();
let screen_with_split = harness.screen_to_string();
eprintln!("Screen with split:\n{}", screen_with_split);
harness
.send_key(KeyCode::Char('w'), KeyModifiers::ALT)
.unwrap();
harness.render().unwrap();
let screen_after_close = harness.screen_to_string();
eprintln!("Screen after closing file2 tab:\n{}", screen_after_close);
assert!(
screen_after_close.contains('│'),
"Split should still exist when there are other tabs. Screen:\n{}",
screen_after_close
);
assert!(
screen_after_close.contains("file1.txt"),
"file1.txt should still be in tabs. Screen:\n{}",
screen_after_close
);
}