mod common;
use common::{OutputAssertions, TestRepo};
#[test]
fn test_reparent_rejects_circular_dependency() {
let repo = TestRepo::new();
repo.create_stack(&["branch-a", "branch-b", "branch-c"]);
let branch_a = repo.find_branch_containing("branch-a").unwrap();
let branch_c = repo.find_branch_containing("branch-c").unwrap();
let output = repo.run_stax(&["branch", "reparent", "-b", &branch_a, "-p", &branch_c]);
output.assert_failure();
let stderr = TestRepo::stderr(&output);
assert!(
stderr.contains("circular") || stderr.contains("descendant") || stderr.contains("ancestor"),
"Expected error about circular dependency, got: {}",
stderr
);
}
#[test]
fn test_reparent_rejects_direct_child_as_parent() {
let repo = TestRepo::new();
repo.create_stack(&["parent-branch", "child-branch"]);
let parent = repo.find_branch_containing("parent-branch").unwrap();
let child = repo.find_branch_containing("child-branch").unwrap();
let output = repo.run_stax(&["branch", "reparent", "-b", &parent, "-p", &child]);
output.assert_failure();
}
#[test]
fn test_fold_restores_branch_on_conflict() {
let repo = TestRepo::new();
repo.run_stax(&["create", "feature"]).assert_success();
let feature_branch = repo.current_branch();
repo.create_file("conflict.txt", "feature content");
repo.commit("Feature commit");
repo.run_stax(&["checkout", "main"]).assert_success();
repo.create_file("conflict.txt", "main content");
repo.commit("Main commit");
repo.run_stax(&["create", "middle"]).assert_success();
let middle_branch = repo.current_branch();
repo.create_file("middle.txt", "middle content");
repo.commit("Middle commit");
repo.run_stax(&["checkout", &feature_branch])
.assert_success();
repo.run_stax(&[
"branch",
"reparent",
"-b",
&feature_branch,
"-p",
&middle_branch,
])
.assert_success();
let output = repo.run_stax(&["branch", "fold", "--yes"]);
let current = repo.current_branch();
assert!(
current.contains("feature") || output.status.success(),
"After failed fold, should be on original branch '{}', but on '{}'",
feature_branch,
current
);
}
#[test]
fn test_fold_yes_flag_no_prompt() {
let repo = TestRepo::new();
repo.run_stax(&["create", "parent-fold"]).assert_success();
repo.create_file("parent.txt", "parent");
repo.commit("Parent commit");
repo.run_stax(&["create", "child-fold"]).assert_success();
repo.create_file("child.txt", "child");
repo.commit("Child commit");
let output = repo.run_stax(&["branch", "fold", "--yes"]);
output.assert_success();
assert!(repo.current_branch().contains("parent-fold"));
}
#[test]
fn test_merge_warns_on_dirty_tree() {
let repo = TestRepo::new_with_remote();
repo.run_stax(&["create", "dirty-test"]).assert_success();
repo.create_file("feature.txt", "feature");
repo.commit("Feature commit");
repo.create_file("uncommitted.txt", "dirty");
let output = repo.run_stax(&["merge", "--yes"]);
let combined = format!("{}{}", TestRepo::stdout(&output), TestRepo::stderr(&output));
if output.status.success() {
assert!(
combined.contains("stash")
|| combined.contains("dirty")
|| combined.contains("uncommitted")
|| combined.contains("Nothing to merge"),
"Merge with dirty tree should warn about uncommitted changes"
);
}
}
#[test]
fn test_status_handles_detached_head() {
let repo = TestRepo::new();
repo.run_stax(&["create", "detach-test"]).assert_success();
repo.create_file("test.txt", "test");
repo.commit("Test commit");
let commit_sha = repo.head_sha();
repo.git(&["checkout", "--detach", &commit_sha]);
let output = repo.run_stax(&["status"]);
let combined = format!("{}{}", TestRepo::stdout(&output), TestRepo::stderr(&output));
assert!(
output.status.success() || combined.contains("detached") || combined.contains("HEAD"),
"Status in detached HEAD should handle gracefully, got: {}",
combined
);
}
#[test]
fn test_navigate_handles_detached_head() {
let repo = TestRepo::new();
repo.create_stack(&["nav-a", "nav-b"]);
let commit_sha = repo.head_sha();
repo.git(&["checkout", "--detach", &commit_sha]);
let output = repo.run_stax(&["down"]);
let stderr = TestRepo::stderr(&output);
assert!(
output.status.success() || stderr.contains("detached") || stderr.contains("branch"),
"Navigation in detached HEAD should handle gracefully, got: {}",
stderr
);
}
#[test]
fn test_squash_yes_flag_no_prompt() {
let repo = TestRepo::new();
repo.run_stax(&["create", "squash-test"]).assert_success();
repo.create_file("file1.txt", "content1");
repo.commit("Commit 1");
repo.create_file("file2.txt", "content2");
repo.commit("Commit 2");
let output = repo.run_stax(&["branch", "squash", "--yes", "-m", "Squashed commit"]);
output.assert_success();
}
#[test]
fn test_undo_yes_flag_no_prompt() {
let repo = TestRepo::new();
repo.run_stax(&["create", "undo-test"]).assert_success();
repo.create_file("test.txt", "test");
repo.commit("Test commit");
repo.run_stax(&["checkout", "main"]).assert_success();
repo.create_file("main.txt", "main update");
repo.commit("Main update");
let undo_branch = repo.find_branch_containing("undo-test").unwrap();
repo.run_stax(&["checkout", &undo_branch]).assert_success();
repo.run_stax(&["restack"]).assert_success();
let output = repo.run_stax(&["undo", "--yes"]);
let combined = format!("{}{}", TestRepo::stdout(&output), TestRepo::stderr(&output));
assert!(
output.status.success() || combined.contains("nothing") || combined.contains("No undo"),
"Undo --yes should work without prompting"
);
}
#[test]
fn test_redo_yes_flag_no_prompt() {
let repo = TestRepo::new();
let output = repo.run_stax(&["redo", "--yes"]);
let combined = format!("{}{}", TestRepo::stdout(&output), TestRepo::stderr(&output));
assert!(
output.status.success()
|| combined.contains("nothing")
|| combined.contains("No redo")
|| combined.contains("No operations"),
"Redo --yes should work without prompting, got: {}",
combined
);
}
#[test]
fn test_status_detects_circular_references() {
let repo = TestRepo::new();
repo.create_stack(&["branch-a", "branch-b"]);
let output = repo.run_stax(&["status", "--json"]);
assert!(
output.status.success(),
"Status should complete without hanging"
);
}
#[test]
fn test_restack_reports_stash_pop_failure() {
let repo = TestRepo::new();
repo.run_stax(&["create", "stash-test"]).assert_success();
repo.create_file("feature.txt", "feature content");
repo.commit("Feature commit");
repo.run_stax(&["checkout", "main"]).assert_success();
repo.create_file("main.txt", "main content");
repo.commit("Main update");
let feature = repo.find_branch_containing("stash-test").unwrap();
repo.run_stax(&["checkout", &feature]).assert_success();
repo.create_file("conflict.txt", "dirty state");
repo.git(&["stash", "push", "-m", "test stash"]);
repo.create_file("conflict.txt", "different content");
repo.commit("Conflicting commit");
let output = repo.run_stax(&["restack", "--yes"]);
assert!(
output.status.success() || !TestRepo::stderr(&output).is_empty(),
"Restack should handle stash pop issues gracefully"
);
}