mod common;
use common::{OutputAssertions, TestRepo};
#[test]
fn test_split_help() {
let repo = TestRepo::new();
let output = repo.run_stax(&["split", "--help"]);
output.assert_success();
let stdout = TestRepo::stdout(&output);
assert!(stdout.contains("split") || stdout.contains("Split"));
assert!(stdout.contains("branch"));
}
#[test]
fn test_split_on_trunk_fails() {
let repo = TestRepo::new();
let output = repo.run_stax(&["split"]);
output.assert_failure();
let stderr = TestRepo::stderr(&output);
assert!(
stderr.contains("trunk") || stderr.contains("Cannot split"),
"Expected message about trunk branch, got: {}",
stderr
);
}
#[test]
fn test_split_untracked_branch_fails() {
let repo = TestRepo::new();
repo.git(&["checkout", "-b", "untracked-branch"]);
repo.create_file("file1.txt", "content 1");
repo.commit("commit 1");
repo.create_file("file2.txt", "content 2");
repo.commit("commit 2");
let output = repo.run_stax(&["split"]);
output.assert_failure();
let stderr = TestRepo::stderr(&output);
assert!(
stderr.contains("not tracked") || stderr.contains("track"),
"Expected message about untracked branch, got: {}",
stderr
);
}
#[test]
fn test_split_no_commits_fails() {
let repo = TestRepo::new();
repo.create_stack(&["feature-a"]);
assert!(repo.current_branch_contains("feature-a"));
let output = repo.run_stax(&["split"]);
output.assert_failure();
let stderr = TestRepo::stderr(&output);
assert!(
stderr.contains("Only 1 commit")
|| stderr.contains("at least 2")
|| stderr.contains("No commits"),
"Expected message about insufficient commits, got: {}",
stderr
);
}
#[test]
fn test_split_single_commit_fails() {
let repo = TestRepo::new();
repo.create_stack(&["feature-a"]);
let output = repo.run_stax(&["split"]);
output.assert_failure();
let stderr = TestRepo::stderr(&output);
assert!(
stderr.contains("Only 1 commit")
|| stderr.contains("at least 2")
|| stderr.contains("commits"),
"Expected message about needing 2+ commits, got: {}",
stderr
);
}
#[test]
fn test_split_with_two_commits_passes_validation() {
let repo = TestRepo::new();
repo.create_stack(&["feature-a"]);
repo.create_file("extra.txt", "extra content");
repo.commit("second commit");
let output = repo.run_stax(&["split"]);
output.assert_failure();
let stderr = TestRepo::stderr(&output);
assert!(
stderr.contains("interactive terminal"),
"Should have passed validation and failed on terminal check, got: {}",
stderr
);
}
#[test]
fn test_split_with_many_commits_passes_validation() {
let repo = TestRepo::new();
repo.create_stack(&["feature-a"]);
for i in 2..=5 {
repo.create_file(&format!("file{}.txt", i), &format!("content {}", i));
repo.commit(&format!("commit {}", i));
}
let output = repo.run_stax(&["split"]);
output.assert_failure();
let stderr = TestRepo::stderr(&output);
assert!(
stderr.contains("interactive terminal"),
"Should have passed validation and failed on terminal check, got: {}",
stderr
);
}
#[test]
fn test_split_from_middle_of_stack_passes_validation() {
let repo = TestRepo::new();
let branches = repo.create_stack(&["feature-a", "feature-b"]);
repo.run_stax(&["checkout", &branches[0]]);
repo.create_file("extra1.txt", "content");
repo.commit("extra commit 1");
repo.create_file("extra2.txt", "content");
repo.commit("extra commit 2");
let output = repo.run_stax(&["split"]);
output.assert_failure();
let stderr = TestRepo::stderr(&output);
assert!(
stderr.contains("interactive terminal"),
"Should have passed validation and failed on terminal check, got: {}",
stderr
);
}
#[test]
fn test_split_file_extracts_matching_paths_into_new_parent_branch() {
let repo = TestRepo::new();
repo.run_stax(&["status"]).assert_success();
repo.run_stax(&["create", "feature"]).assert_success();
repo.create_file("keep.txt", "keep");
repo.create_file("move.txt", "move");
repo.commit("add keep and move");
let feature_branch = repo.current_branch();
let output = repo.run_stax(&["split", "--file", "move.txt"]);
output.assert_success();
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains("Created") && stdout.contains("Reparented"),
"Expected split summary, got: {}",
stdout
);
let split_branch = repo
.find_branch_containing("feature-split")
.expect("expected split branch to be created");
assert_eq!(repo.current_branch(), feature_branch);
let parent_output = repo.git(&["show", &format!("refs/branch-metadata/{}", feature_branch)]);
let parent_metadata = TestRepo::stdout(&parent_output);
assert!(
parent_metadata.contains(&split_branch),
"Expected current branch metadata to point to split branch, got: {}",
parent_metadata
);
let split_diff = repo.git(&["diff", "--name-only", "main", &split_branch]);
let split_files = TestRepo::stdout(&split_diff);
assert!(
split_files.contains("move.txt"),
"Split branch should contain move.txt, got: {}",
split_files
);
let feature_diff = repo.git(&["diff", "--name-only", "main", &feature_branch]);
let feature_files = TestRepo::stdout(&feature_diff);
assert!(
feature_files.contains("keep.txt"),
"Current branch should still contain keep.txt vs main, got: {}",
feature_files
);
assert!(
!feature_files.contains("move.txt"),
"Current branch should no longer carry move.txt in its own history, got: {}",
feature_files
);
}
#[test]
fn test_split_file_on_multi_commit_branch_fails() {
let repo = TestRepo::new();
repo.run_stax(&["create", "feature"]).assert_success();
repo.create_file("move.txt", "v1");
repo.commit("add move.txt");
repo.create_file("move.txt", "v2");
repo.commit("modify move.txt");
let branch_before = repo.current_branch();
let head_before = repo.head_sha();
let output = repo.run_stax(&["split", "--file", "move.txt"]);
output.assert_failure();
let stderr = TestRepo::stderr(&output);
assert!(
stderr.contains("multi-commit") && stderr.contains("stax split --hunk"),
"Expected guidance toward --hunk, got: {}",
stderr
);
assert_eq!(repo.current_branch(), branch_before);
assert_eq!(
head_before,
repo.head_sha(),
"HEAD should not move when split --file aborts"
);
let branches = repo.list_branches();
assert!(
!branches.iter().any(|b| b.contains("feature-split")),
"No split branch should be created on hard-fail, got branches: {:?}",
branches
);
}