Add a centralized ui module with Arc's visual identity: colored commit IDs (magenta), bookmarks (cyan), tags (yellow), status symbols, and diff highlighting. Update all command output and tests accordingly.
614 lines
16 KiB
Rust
614 lines
16 KiB
Rust
use std::process::Command;
|
|
use tempfile::TempDir;
|
|
|
|
fn arc_cmd() -> Command {
|
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_arc"));
|
|
cmd.env("NO_COLOR", "1");
|
|
cmd
|
|
}
|
|
|
|
fn init_repo(dir: &TempDir) {
|
|
arc_cmd()
|
|
.arg("init")
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.expect("failed to run arc init");
|
|
}
|
|
|
|
#[test]
|
|
fn config_yml_is_valid_yaml() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
let config_path = dir.path().join(".arc").join("config.yml");
|
|
let contents = std::fs::read_to_string(&config_path).unwrap();
|
|
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
|
|
assert!(value.is_mapping());
|
|
}
|
|
|
|
#[test]
|
|
fn config_has_default_section() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
let config_path = dir.path().join(".arc").join("config.yml");
|
|
let contents = std::fs::read_to_string(&config_path).unwrap();
|
|
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
|
|
|
|
let defaults = &value["default"];
|
|
assert_eq!(defaults["bookmark"].as_str(), Some("main"));
|
|
assert_eq!(defaults["remote"].as_str(), Some("origin"));
|
|
}
|
|
|
|
#[test]
|
|
fn head_is_valid_yaml() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
let head_path = dir.path().join(".arc").join("HEAD");
|
|
let contents = std::fs::read_to_string(&head_path).unwrap();
|
|
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
|
|
assert!(value.is_mapping());
|
|
assert_eq!(value["state"].as_str(), Some("unborn"));
|
|
assert_eq!(value["bookmark"].as_str(), Some("main"));
|
|
}
|
|
|
|
#[test]
|
|
fn bookmark_ref_is_valid_yaml() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
let bookmark_path = dir.path().join(".arc").join("bookmarks").join("main");
|
|
let contents = std::fs::read_to_string(&bookmark_path).unwrap();
|
|
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
|
|
assert!(value["commit"].is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn config_set_and_get_user_name() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "set", "user.name", "alice"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(stdout.contains("user.name = alice"));
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "user.name"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert_eq!(stdout.trim(), "alice");
|
|
}
|
|
|
|
#[test]
|
|
fn config_set_and_get_user_email() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "user.email", "alice@example.com"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "user.email"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&out.stdout).trim(),
|
|
"alice@example.com"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn config_set_and_get_user_key() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "user.key", "~/.ssh/id_ed25519"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "user.key"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&out.stdout).trim(),
|
|
"~/.ssh/id_ed25519"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn config_set_and_get_default_bookmark() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "default.bookmark", "develop"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "default.bookmark"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "develop");
|
|
}
|
|
|
|
#[test]
|
|
fn config_set_and_get_default_remote() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "default.remote", "upstream"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "default.remote"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "upstream");
|
|
}
|
|
|
|
#[test]
|
|
fn config_set_and_get_alias() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "aliases.c", "commit"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "aliases.c"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "commit");
|
|
}
|
|
|
|
#[test]
|
|
fn config_get_unset_key_returns_not_set() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "user.name"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
assert!(String::from_utf8_lossy(&out.stdout).contains("is not set"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_unset_key() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "user.name", "alice"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "unset", "user.name"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
assert!(String::from_utf8_lossy(&out.stdout).contains("unset user.name"));
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "user.name"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(String::from_utf8_lossy(&out.stdout).contains("is not set"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_unset_alias() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "aliases.c", "commit"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
arc_cmd()
|
|
.args(["config", "unset", "aliases.c"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "aliases.c"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(String::from_utf8_lossy(&out.stdout).contains("is not set"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_show_displays_yaml() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "user.name", "bob"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "show"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(stdout.contains("bob"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_invalid_key_fails() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "set", "invalid", "value"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(!out.status.success());
|
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
assert!(stderr.contains("invalid config key"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_invalid_section_fails() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "set", "bogus.field", "value"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(!out.status.success());
|
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
assert!(stderr.contains("invalid config key"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_invalid_field_fails() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "set", "user.bogus", "value"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(!out.status.success());
|
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
assert!(stderr.contains("invalid config key"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_set_overwrites_existing() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "user.name", "alice"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "user.name", "bob"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "user.name"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "bob");
|
|
}
|
|
|
|
#[test]
|
|
fn config_global_set_and_get() {
|
|
let dir = TempDir::new().unwrap();
|
|
let global_dir = TempDir::new().unwrap();
|
|
|
|
init_repo(&dir);
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "set", "-g", "user.name", "global-user"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "-g", "user.name"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "global-user");
|
|
}
|
|
|
|
#[test]
|
|
fn config_local_overrides_global() {
|
|
let dir = TempDir::new().unwrap();
|
|
let global_dir = TempDir::new().unwrap();
|
|
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "-g", "user.name", "global-user"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "user.name", "local-user"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "user.name"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "local-user");
|
|
}
|
|
|
|
#[test]
|
|
fn config_get_falls_back_to_global() {
|
|
let dir = TempDir::new().unwrap();
|
|
let global_dir = TempDir::new().unwrap();
|
|
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "-g", "user.email", "global@example.com"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "user.email"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&out.stdout).trim(),
|
|
"global@example.com"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn config_global_unset() {
|
|
let dir = TempDir::new().unwrap();
|
|
let global_dir = TempDir::new().unwrap();
|
|
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "-g", "user.name", "global-user"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
arc_cmd()
|
|
.args(["config", "unset", "-g", "user.name"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "-g", "user.name"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(String::from_utf8_lossy(&out.stdout).contains("is not set"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_global_show() {
|
|
let dir = TempDir::new().unwrap();
|
|
let global_dir = TempDir::new().unwrap();
|
|
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "-g", "user.name", "global-user"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "show", "-g"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(stdout.contains("global-user"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_show_effective_merges_local_and_global() {
|
|
let dir = TempDir::new().unwrap();
|
|
let global_dir = TempDir::new().unwrap();
|
|
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "-g", "user.name", "global-user"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "user.email", "local@example.com"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "show"])
|
|
.current_dir(dir.path())
|
|
.env("XDG_CONFIG_HOME", global_dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(stdout.contains("global-user"));
|
|
assert!(stdout.contains("local@example.com"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_persisted_to_yaml_file() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "user.name", "alice"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let config_path = dir.path().join(".arc").join("config.yml");
|
|
let contents = std::fs::read_to_string(&config_path).unwrap();
|
|
let value: serde_yaml::Value = serde_yaml::from_str(&contents).unwrap();
|
|
assert_eq!(value["user"]["name"].as_str(), Some("alice"));
|
|
}
|
|
|
|
#[test]
|
|
fn alias_expansion_via_config() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "aliases.s", "status"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd().arg("s").current_dir(dir.path()).output().unwrap();
|
|
assert!(out.status.success());
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(
|
|
stdout.contains("clean") || stdout.contains("no changes") || stdout.is_empty(),
|
|
"unexpected status output: {stdout}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn alias_expansion_passes_arguments() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
std::fs::write(dir.path().join("hello.txt"), "hello world").unwrap();
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "aliases.c", "commit"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["c", "test commit via alias"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert!(out.status.success());
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(stdout.contains("committed"));
|
|
}
|
|
|
|
#[test]
|
|
fn config_multiple_aliases() {
|
|
let dir = TempDir::new().unwrap();
|
|
init_repo(&dir);
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "aliases.c", "commit"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
arc_cmd()
|
|
.args(["config", "set", "aliases.s", "status"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "aliases.c"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "commit");
|
|
|
|
let out = arc_cmd()
|
|
.args(["config", "get", "aliases.s"])
|
|
.current_dir(dir.path())
|
|
.output()
|
|
.unwrap();
|
|
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "status");
|
|
}
|