use gwm::tui::{draw, App};
use ratatui::{backend::TestBackend, Terminal};
use std::path::Path;
use tempfile::TempDir;
fn repo_with_commits(commit_count: usize) -> TempDir {
let dir = TempDir::new().unwrap();
let repo = git2::Repository::init(dir.path()).unwrap();
repo.set_head("refs/heads/main").ok();
let sig = git2::Signature::now("gwm-test", "gwm@test").unwrap();
std::fs::write(dir.path().join("file.txt"), "seed").unwrap();
repo.index().unwrap().add_path(Path::new("file.txt")).unwrap();
repo.index().unwrap().write().unwrap();
{
let tree_id = repo.index().unwrap().write_tree().unwrap();
let tree = repo.find_tree(tree_id).unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "init", &tree, &[]).unwrap();
}
for i in 0..commit_count {
std::fs::write(dir.path().join("file.txt"), format!("commit-{i}")).unwrap();
repo.index().unwrap().add_path(Path::new("file.txt")).unwrap();
repo.index().unwrap().write().unwrap();
let parent = repo.head().unwrap().peel_to_commit().unwrap();
let tree_id = repo.index().unwrap().write_tree().unwrap();
let tree = repo.find_tree(tree_id).unwrap();
repo
.commit(Some("HEAD"), &sig, &sig, &format!("commit-{i}"), &tree, &[&parent])
.unwrap();
}
dir
}
fn buffer_text(terminal: &Terminal<TestBackend>) -> String {
let buffer = terminal.backend().buffer();
buffer.content().iter().map(|c| c.symbol()).collect()
}
#[test]
fn sidebar_renders_header_name_and_commit_subject_on_warm_cache() {
let dir = repo_with_commits(8);
let mut app = App::new_at_layered(Some(dir.path()), None).unwrap();
assert!(app.selected().is_some(), "expected the main worktree to be selected");
let name = app.selected().unwrap().name.clone();
let backend = TestBackend::new(120, 40);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(|f| draw(f, &mut app)).unwrap();
assert!(app.sidebar.cache.is_some(), "first draw must warm the sidebar cache");
terminal.draw(|f| draw(f, &mut app)).unwrap();
let text = buffer_text(&terminal);
assert!(
text.contains(&name),
"sidebar header must render the worktree name, got:\n{text}"
);
assert!(
text.contains("commit-7"),
"sidebar recent-commits must render the most recent commit subject, got:\n{text}"
);
}
#[test]
fn sidebar_warm_cache_render_is_stable_across_frames() {
let dir = repo_with_commits(8);
let mut app = App::new_at_layered(Some(dir.path()), None).unwrap();
let backend = TestBackend::new(120, 40);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(|f| draw(f, &mut app)).unwrap();
let first = buffer_text(&terminal);
terminal.draw(|f| draw(f, &mut app)).unwrap();
let second = buffer_text(&terminal);
assert_eq!(
first, second,
"two consecutive warm-cache draws must produce byte-identical sidebar buffers"
);
}
#[test]
fn working_tree_section_renders_colored_status_counts_footer() {
let dir = repo_with_commits(1);
for i in 0..11 {
std::fs::write(dir.path().join(format!("dirty-{i}.txt")), "dirty").unwrap();
}
let mut app = App::new_at_layered(Some(dir.path()), None).unwrap();
let backend = TestBackend::new(120, 40);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(|f| draw(f, &mut app)).unwrap();
terminal.draw(|f| draw(f, &mut app)).unwrap();
let text = buffer_text(&terminal);
assert!(
text.contains("Working Tree"),
"sidebar must render the Working Tree pane: {text}"
);
assert!(
text.contains(gwm::tui::WT_CREATED_ICON),
"footer must render the created (diff-added) nerdfont glyph: {text}"
);
assert!(text.contains("11"), "footer must render the created-file count: {text}");
}
#[test]
fn working_tree_section_renders_file_tree_with_icons() {
let dir = repo_with_commits(1);
std::fs::create_dir_all(dir.path().join("src/app")).unwrap();
std::fs::write(dir.path().join("src/app/mod.rs"), "fn x() {}").unwrap();
let mut app = App::new_at_layered(Some(dir.path()), None).unwrap();
let backend = TestBackend::new(120, 40);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(|f| draw(f, &mut app)).unwrap();
terminal.draw(|f| draw(f, &mut app)).unwrap();
let text = buffer_text(&terminal);
assert!(
text.contains("src/app"),
"single-child dir chain renders collapsed: {text}"
);
assert!(text.contains("mod.rs"), "the leaf file name renders: {text}");
assert!(
text.contains(gwm::tui::wt_tree::WT_DIR_OPEN_ICON),
"directory row carries a folder glyph: {text}"
);
assert!(
text.contains(gwm::tui::wt_tree::WT_RUST_ICON),
"the .rs leaf carries the Rust file-type glyph: {text}"
);
assert!(
text.contains('└') || text.contains('├'),
"rows are drawn with tree connector lines: {text}"
);
}
fn git_in(dir: &std::path::Path, args: &[&str]) {
let out = std::process::Command::new("git")
.current_dir(dir)
.args(args)
.env("GIT_AUTHOR_NAME", "gwm-test")
.env("GIT_AUTHOR_EMAIL", "gwm@test")
.env("GIT_COMMITTER_NAME", "gwm-test")
.env("GIT_COMMITTER_EMAIL", "gwm@test")
.output()
.unwrap();
assert!(
out.status.success(),
"git {:?} failed: {}",
args,
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn status_pane_renders_diff_vs_base_line_on_a_feature_branch() {
let dir = repo_with_commits(1);
let path = dir.path();
std::fs::write(path.join("f.txt"), "a\nb\nc\n").unwrap();
git_in(path, &["add", "f.txt"]);
git_in(path, &["commit", "-m", "base file"]);
git_in(path, &["checkout", "-b", "feat/#287-diff"]);
std::fs::write(path.join("f.txt"), "a\nB\nc\nd\n").unwrap();
git_in(path, &["commit", "-am", "tweak"]);
let mut app = App::new_at_layered(Some(path), None).unwrap();
let backend = TestBackend::new(120, 40);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(|f| draw(f, &mut app)).unwrap();
terminal.draw(|f| draw(f, &mut app)).unwrap();
let text = buffer_text(&terminal);
assert!(text.contains("Diff"), "Status pane must show the Diff label: {text}");
assert!(text.contains("+2"), "Status pane must show insertions: {text}");
assert!(text.contains("-1"), "Status pane must show deletions: {text}");
}