mod common;
use common::TestRepo;
use std::process::Command;
#[test]
fn test_git_worktree_operations() {
let repo = TestRepo::new();
let output = repo.run_twin(&[
"add",
&repo.worktree_path("feature"),
"-b",
"feature/test-1",
]);
if !output.status.success() {
eprintln!("Failed to add worktree:");
eprintln!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
}
assert!(output.status.success());
let output = repo.exec(&["git", "branch", "-a"]);
let branches = String::from_utf8_lossy(&output.stdout);
assert!(branches.contains("feature/test-1"));
let output = repo.exec(&["git", "worktree", "list", "--porcelain"]);
let worktrees = String::from_utf8_lossy(&output.stdout);
assert!(worktrees.contains("branch refs/heads/feature/test-1"));
let output = repo.run_twin(&[
"add",
&repo.worktree_path("another"),
"-b",
"feature/test-1",
]);
assert!(!output.status.success());
repo.exec(&["git", "branch", "existing-branch"]);
let output = repo.run_twin(&["add", &repo.worktree_path("force"), "-B", "existing-branch"]);
assert!(output.status.success());
}
#[test]
fn test_symlink_creation_with_config() {
let repo = TestRepo::new();
let config = r#"
[[files]]
path = "config.json"
mapping_type = "symlink"
[[files]]
path = "data/test.txt"
mapping_type = "symlink"
"#;
std::fs::write(repo.path().join(".twin.toml"), config).unwrap();
std::fs::write(repo.path().join("config.json"), "{}").unwrap();
std::fs::create_dir(repo.path().join("data")).unwrap();
std::fs::write(repo.path().join("data/test.txt"), "test data").unwrap();
let worktree_path_str = repo.worktree_path("with-symlinks");
let output = repo.run_twin(&[
"add",
&worktree_path_str,
"-b",
"feature/symlinks",
"--config",
".twin.toml",
]);
assert!(output.status.success());
let worktree_path = repo.path().parent().unwrap().join(&worktree_path_str[3..]);
assert!(worktree_path.join("config.json").exists());
assert!(worktree_path.join("data/test.txt").exists());
}
#[test]
fn test_no_symlinks_without_config() {
let repo = TestRepo::new();
std::fs::write(repo.path().join("config.json"), "{}").unwrap();
let worktree_path_str = repo.worktree_path("no-symlinks");
let output = repo.run_twin(&["add", &worktree_path_str, "-b", "feature/no-symlinks"]);
assert!(output.status.success());
let worktree_path = repo.path().parent().unwrap().join(&worktree_path_str[3..]);
assert!(!worktree_path.join("config.json").exists());
}
#[test]
fn test_hook_execution() {
let repo = TestRepo::new();
let config = r#"
[hooks]
post_create = [
{ command = "echo", args = ["Hook executed"] }
]
"#;
std::fs::write(repo.path().join(".twin.toml"), config).unwrap();
let output = repo.run_twin(&[
"add",
&repo.worktree_path("with-hooks"),
"-b",
"feature/hooks",
"--config",
".twin.toml",
]);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(output.status.success() || stderr.contains("Hook executed"));
}
#[test]
fn test_worktree_removal() {
let repo = TestRepo::new();
let worktree_path = repo.worktree_path("to-remove");
let output = repo.run_twin(&["add", &worktree_path, "-b", "feature/removal"]);
assert!(output.status.success());
let output = repo.exec(&["git", "worktree", "list"]);
let worktrees = String::from_utf8_lossy(&output.stdout);
assert!(worktrees.contains("to-remove"));
let output = repo.run_twin(&["remove", &worktree_path, "--force"]);
assert!(output.status.success());
let output = repo.exec(&["git", "worktree", "list"]);
let worktrees = String::from_utf8_lossy(&output.stdout);
assert!(!worktrees.contains("to-remove"));
}
#[test]
fn test_complete_workflow() {
let repo = TestRepo::new();
let work1_path_str = repo.worktree_path("work-1");
let work2_path_str = repo.worktree_path("work-2");
let work3_path_str = repo.worktree_path("work-3");
repo.run_twin(&["add", &work1_path_str, "-b", "feature/work-1"]);
repo.run_twin(&["add", &work2_path_str, "-b", "feature/work-2"]);
repo.run_twin(&["add", &work3_path_str, "-b", "feature/work-3"]);
let work1_path = repo.path().parent().unwrap().join(&work1_path_str[3..]);
std::fs::write(work1_path.join("new-file.txt"), "content").unwrap();
Command::new("git")
.args(["add", "."])
.current_dir(&work1_path)
.output()
.unwrap();
Command::new("git")
.args(["commit", "-m", "Work in progress"])
.current_dir(&work1_path)
.output()
.unwrap();
let output = repo.run_twin(&["list"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("work-1"));
assert!(stdout.contains("work-2"));
assert!(stdout.contains("work-3"));
repo.run_twin(&["remove", &work1_path_str, "--force"]);
repo.run_twin(&["remove", &work2_path_str, "--force"]);
repo.run_twin(&["remove", &work3_path_str, "--force"]);
let output = repo.exec(&["git", "worktree", "list"]);
let worktrees = String::from_utf8_lossy(&output.stdout);
assert!(!worktrees.contains("work-1"));
assert!(!worktrees.contains("work-2"));
assert!(!worktrees.contains("work-3"));
}