mod common;
use common::{run_git, run_stkd, run_stkd_success, TestContext};
#[test]
fn test_init_creates_stack_directory() {
let ctx = TestContext::new();
run_stkd_success(&ctx.path, &["init"]);
assert!(ctx.path.join(".git/stkd").exists());
assert!(ctx.path.join(".git/stkd/config.json").exists());
assert!(ctx.path.join(".git/stkd/state.json").exists());
}
#[test]
fn test_init_warns_if_already_initialized() {
let ctx = TestContext::new();
let output1 = run_stkd(&ctx.path, &["init"]);
assert!(output1.status.success());
let output2 = run_stkd(&ctx.path, &["init"]);
assert!(output2.status.success());
let stderr = String::from_utf8_lossy(&output2.stderr);
let stdout = String::from_utf8_lossy(&output2.stdout);
assert!(
stderr.contains("already initialized") || stdout.contains("already initialized"),
"Expected warning about already initialized, got stdout: {}, stderr: {}",
stdout,
stderr
);
}
#[test]
fn test_create_branch() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/test"]);
assert!(ctx.branch_exists("feature/test"));
assert_eq!(ctx.current_branch(), "feature/test");
}
#[test]
fn test_create_branch_with_parent() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/parent"]);
ctx.commit_file("parent.txt", "parent content", "Add parent file");
run_stkd_success(&ctx.path, &["create", "feature/child"]);
ctx.commit_file("child.txt", "child content", "Add child file");
assert!(ctx.branch_exists("feature/parent"));
assert!(ctx.branch_exists("feature/child"));
assert_eq!(ctx.current_branch(), "feature/child");
}
#[test]
fn test_create_from_trunk() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/first"]);
ctx.commit_file("first.txt", "first", "Add first");
run_stkd_success(&ctx.path, &["create", "--from-trunk", "feature/second"]);
assert_eq!(ctx.current_branch(), "feature/second");
}
#[test]
fn test_navigation_up_down() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/a"]);
ctx.commit_file("a.txt", "a", "Add a");
run_stkd_success(&ctx.path, &["create", "feature/b"]);
ctx.commit_file("b.txt", "b", "Add b");
run_stkd_success(&ctx.path, &["create", "feature/c"]);
ctx.commit_file("c.txt", "c", "Add c");
run_stkd_success(&ctx.path, &["down"]);
assert_eq!(ctx.current_branch(), "feature/b");
run_stkd_success(&ctx.path, &["down"]);
assert_eq!(ctx.current_branch(), "feature/a");
run_stkd_success(&ctx.path, &["up"]);
assert_eq!(ctx.current_branch(), "feature/b");
}
#[test]
fn test_navigation_top_bottom() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/a"]);
run_stkd_success(&ctx.path, &["create", "feature/b"]);
run_stkd_success(&ctx.path, &["create", "feature/c"]);
run_stkd_success(&ctx.path, &["bottom"]);
assert_eq!(ctx.current_branch(), "feature/a");
run_stkd_success(&ctx.path, &["top"]);
assert_eq!(ctx.current_branch(), "feature/c");
}
#[test]
fn test_log_shows_stack() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/a"]);
run_stkd_success(&ctx.path, &["create", "feature/b"]);
let output = run_stkd_success(&ctx.path, &["log"]);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("feature/a") || stdout.contains("feature/b"));
}
#[test]
fn test_status_command() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/test"]);
let output = run_stkd_success(&ctx.path, &["status"]);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(!stdout.is_empty());
}
#[test]
fn test_delete_branch() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/to-delete"]);
assert!(ctx.branch_exists("feature/to-delete"));
run_git(&ctx.path, &["checkout", "master"]);
run_stkd_success(&ctx.path, &["delete", "feature/to-delete", "--force"]);
assert!(!ctx.branch_exists("feature/to-delete"));
}
#[test]
fn test_rename_branch() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/old-name"]);
assert!(ctx.branch_exists("feature/old-name"));
run_stkd_success(&ctx.path, &["rename", "feature/new-name"]);
assert!(!ctx.branch_exists("feature/old-name"));
assert!(ctx.branch_exists("feature/new-name"));
assert_eq!(ctx.current_branch(), "feature/new-name");
}
#[test]
fn test_checkout_by_name() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/a"]);
run_stkd_success(&ctx.path, &["create", "feature/b"]);
run_stkd_success(&ctx.path, &["checkout", "feature/a"]);
assert_eq!(ctx.current_branch(), "feature/a");
run_stkd_success(&ctx.path, &["checkout", "feature/b"]);
assert_eq!(ctx.current_branch(), "feature/b");
}
#[test]
fn test_fails_without_init() {
let ctx = TestContext::new();
let output = run_stkd(&ctx.path, &["create", "feature/test"]);
assert!(!output.status.success());
}
#[test]
fn test_modify_amends_commit() {
let ctx = TestContext::with_stkd();
run_stkd_success(&ctx.path, &["create", "feature/test"]);
ctx.commit_file("test.txt", "initial", "Initial commit");
let initial_sha = ctx.head_sha();
std::fs::write(ctx.path.join("test.txt"), "modified").unwrap();
run_git(&ctx.path, &["add", "test.txt"]);
run_stkd_success(&ctx.path, &["modify"]);
assert_ne!(ctx.head_sha(), initial_sha);
}
#[test]
fn test_version_command() {
let ctx = TestContext::new();
let output = run_stkd_success(&ctx.path, &["--version"]);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("gt") || stdout.contains("0."));
}
#[test]
fn test_help_command() {
let ctx = TestContext::new();
let output = run_stkd_success(&ctx.path, &["--help"]);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Usage") || stdout.contains("usage"));
}