use crate::common::{
TestRepo, repo, set_temp_home_env, setup_snapshot_settings_with_home, temp_home, wt_command,
};
use insta_cmd::assert_cmd_snapshot;
use rstest::rstest;
use std::fs;
use tempfile::TempDir;
#[rstest]
fn test_list_config_full_enabled(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
[projects."repo".list]
full = true
"#,
)
.unwrap();
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.arg("list").current_dir(repo.root_path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_list_config_branches_enabled(repo: TestRepo, temp_home: TempDir) {
repo.run_git(&["branch", "feature"]);
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
[projects."repo".list]
branches = true
"#,
)
.unwrap();
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.arg("list").current_dir(repo.root_path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_list_config_cli_override(repo: TestRepo, temp_home: TempDir) {
repo.run_git(&["branch", "feature"]);
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
[projects."repo".list]
branches = false
"#,
)
.unwrap();
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.arg("list")
.arg("--branches")
.current_dir(repo.root_path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_list_config_full_and_branches(repo: TestRepo, temp_home: TempDir) {
repo.run_git(&["branch", "feature"]);
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
[projects."repo".list]
full = true
branches = true
"#,
)
.unwrap();
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.arg("list").current_dir(repo.root_path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_list_no_config(repo: TestRepo, temp_home: TempDir) {
repo.run_git(&["branch", "feature"]);
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.arg("list").current_dir(repo.root_path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_list_project_url_column(repo: TestRepo, temp_home: TempDir) {
repo.write_project_config(
r#"[list]
url = "http://localhost:{{ branch | hash_port }}"
"#,
);
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
let settings = setup_snapshot_settings_with_home(&repo, &temp_home);
settings.bind(|| {
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.arg("list").current_dir(repo.root_path());
assert_cmd_snapshot!(cmd);
});
}
#[rstest]
fn test_list_json_url_fields(repo: TestRepo, temp_home: TempDir) {
repo.write_project_config(
r#"[list]
url = "http://localhost:{{ branch | hash_port }}"
"#,
);
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.args(["list", "--format=json"])
.current_dir(repo.root_path());
let output = cmd.output().unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let items = json.as_array().unwrap();
assert!(!items.is_empty());
let first = &items[0];
let url = first["url"].as_str().unwrap();
assert!(url.starts_with("http://localhost:"));
let port: u16 = url.split(':').next_back().unwrap().parse().unwrap();
assert!((10000..=19999).contains(&port));
assert!(first["url_active"].is_boolean());
}
#[rstest]
fn test_list_json_no_url_without_template(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.args(["list", "--format=json"])
.current_dir(repo.root_path());
let output = cmd.output().unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let items = json.as_array().unwrap();
assert!(!items.is_empty());
let first = &items[0];
assert!(first["url"].is_null());
assert!(first["url_active"].is_null());
}
#[rstest]
fn test_list_url_with_branches_flag(repo: TestRepo, temp_home: TempDir) {
for branch in &["feature-a", "feature-b", "feature-c"] {
let worktree_path = repo
.root_path()
.parent()
.unwrap()
.join(format!("repo.{}", branch));
if worktree_path.exists() {
let _ = repo
.git_command()
.args([
"worktree",
"remove",
"--force",
worktree_path.to_str().unwrap(),
])
.run();
}
let _ = repo.git_command().args(["branch", "-D", branch]).run();
}
repo.run_git(&["branch", "feature"]);
repo.write_project_config(
r#"[list]
url = "http://localhost:{{ branch | hash_port }}"
"#,
);
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.args(["list", "--branches", "--format=json"])
.current_dir(repo.root_path());
let output = cmd.output().unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let items = json.as_array().unwrap();
assert_eq!(items.len(), 2);
let worktree = items.iter().find(|i| i["kind"] == "worktree").unwrap();
let branch = items.iter().find(|i| i["kind"] == "branch").unwrap();
assert!(
worktree["url"]
.as_str()
.unwrap()
.starts_with("http://localhost:"),
"Worktree should have URL"
);
assert!(
branch["url"].is_null(),
"Branch without worktree should not have URL"
);
assert!(
branch["url_active"].is_null(),
"Branch without worktree should not have url_active"
);
}
#[rstest]
fn test_list_url_with_branch_variable(repo: TestRepo, temp_home: TempDir) {
repo.write_project_config(
r#"[list]
url = "http://localhost:8080/{{ branch }}"
"#,
);
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
"#,
)
.unwrap();
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.args(["list", "--format=json"])
.current_dir(repo.root_path());
let output = cmd.output().unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let items = json.as_array().unwrap();
let first = &items[0];
let url = first["url"].as_str().unwrap();
assert_eq!(url, "http://localhost:8080/main");
}
#[rstest]
fn test_list_config_timeout_triggers_timeouts(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
[projects."repo".list]
task-timeout-ms = 1
"#,
)
.unwrap();
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.arg("list").current_dir(repo.root_path());
let output = cmd.output().unwrap();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("timed out") || output.status.success(),
"Expected either timeout message in footer or success (if git was fast enough)"
);
}
#[rstest]
fn test_list_config_timeout_zero_means_no_timeout(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
[projects."repo".list]
task-timeout-ms = 0
"#,
)
.unwrap();
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.arg("list").current_dir(repo.root_path());
let output = cmd.output().unwrap();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("timed out"),
"Expected no timeout message with task-timeout-ms = 0, but got: {}",
stderr
);
}
#[rstest]
fn test_list_config_timeout_disabled_with_full(repo: TestRepo, temp_home: TempDir) {
let global_config_dir = temp_home.path().join(".config").join("worktrunk");
fs::create_dir_all(&global_config_dir).unwrap();
fs::write(
global_config_dir.join("config.toml"),
r#"worktree-path = "../{{ repo }}.{{ branch }}"
[projects."repo".list]
task-timeout-ms = 1
"#,
)
.unwrap();
let mut cmd = wt_command();
repo.configure_wt_cmd(&mut cmd);
set_temp_home_env(&mut cmd, temp_home.path());
cmd.args(["list", "--full"]).current_dir(repo.root_path());
let output = cmd.output().unwrap();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("timed out"),
"Expected no timeout message with --full flag, but got: {}",
stderr
);
}