280 lines
8.5 KiB
Rust
280 lines
8.5 KiB
Rust
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
use tempfile::TempDir;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct TestEnv {
|
|
home: TempDir,
|
|
themes: TempDir,
|
|
state_file: PathBuf,
|
|
}
|
|
|
|
impl TestEnv {
|
|
fn new() -> Self {
|
|
let home = TempDir::new().unwrap();
|
|
let themes = TempDir::new().unwrap();
|
|
let state_file = home.path().join(".config/theme-manager/state.json");
|
|
TestEnv {
|
|
home,
|
|
themes,
|
|
state_file,
|
|
}
|
|
}
|
|
|
|
fn make_theme_file(&self, theme: &str, rel: &str, content: &str) -> PathBuf {
|
|
let p = self.themes.path().join(theme).join(rel);
|
|
fs::create_dir_all(p.parent().unwrap()).unwrap();
|
|
fs::write(&p, content).unwrap();
|
|
p
|
|
}
|
|
|
|
fn target(&self, rel: &str) -> PathBuf {
|
|
self.home.path().join(rel)
|
|
}
|
|
|
|
fn dm(&self, args: &[&str]) -> std::process::Output {
|
|
let bin = env!("CARGO_BIN_EXE_dm");
|
|
Command::new(bin)
|
|
.args(args)
|
|
.env("DM_HOME", self.home.path())
|
|
.env("DM_THEMES_ROOT", self.themes.path())
|
|
.env("DM_STATE_FILE", &self.state_file)
|
|
.output()
|
|
.unwrap()
|
|
}
|
|
|
|
fn dm_stdout(&self, args: &[&str]) -> String {
|
|
let out = self.dm(args);
|
|
String::from_utf8_lossy(&out.stdout).to_string()
|
|
}
|
|
|
|
fn dm_success(&self, args: &[&str]) {
|
|
let out = self.dm(args);
|
|
if !out.status.success() {
|
|
panic!(
|
|
"dm {:?} failed:\nstdout: {}\nstderr: {}",
|
|
args,
|
|
String::from_utf8_lossy(&out.stdout),
|
|
String::from_utf8_lossy(&out.stderr),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_symlink(p: &Path) -> bool {
|
|
p.symlink_metadata()
|
|
.map(|m| m.file_type().is_symlink())
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// list
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn list_shows_available_themes() {
|
|
let env = TestEnv::new();
|
|
fs::create_dir(env.themes.path().join("dracula")).unwrap();
|
|
fs::create_dir(env.themes.path().join("catppuccin")).unwrap();
|
|
|
|
let out = env.dm_stdout(&["list"]);
|
|
assert!(out.contains("dracula"), "expected dracula in: {out}");
|
|
assert!(out.contains("catppuccin"), "expected catppuccin in: {out}");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// status
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn status_before_apply_reports_no_theme() {
|
|
let env = TestEnv::new();
|
|
let out = env.dm_stdout(&["status"]);
|
|
assert!(out.contains("No theme"), "got: {out}");
|
|
}
|
|
|
|
#[test]
|
|
fn status_after_apply_shows_theme_name() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "# dracula");
|
|
|
|
env.dm_success(&["apply", "dracula"]);
|
|
let out = env.dm_stdout(&["status"]);
|
|
assert!(out.contains("dracula"), "got: {out}");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// apply from empty state
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn apply_creates_symlinks_under_home() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "# zsh");
|
|
env.make_theme_file("dracula", ".config/nvim/init.lua", "-- nvim");
|
|
|
|
env.dm_success(&["apply", "dracula"]);
|
|
|
|
assert!(is_symlink(&env.target(".zshrc")));
|
|
assert!(is_symlink(&env.target(".config/nvim/init.lua")));
|
|
}
|
|
|
|
#[test]
|
|
fn apply_output_shows_summary_counts() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "");
|
|
|
|
let out = env.dm_stdout(&["apply", "dracula"]);
|
|
assert!(out.contains("created=1"), "expected created=1 in: {out}");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// dry-run
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn dry_run_makes_no_changes() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "# zsh");
|
|
|
|
let out = env.dm_stdout(&["apply", "dracula", "--dry-run"]);
|
|
assert!(out.contains("Dry run"), "got: {out}");
|
|
assert!(
|
|
!env.target(".zshrc").exists(),
|
|
"symlink should not exist after dry-run"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn dry_run_shows_create_action() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "");
|
|
|
|
let out = env.dm_stdout(&["apply", "dracula", "--dry-run"]);
|
|
assert!(out.contains("create"), "got: {out}");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// conflict without --force
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn apply_conflicts_on_unmanaged_file() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "# theme");
|
|
fs::write(env.target(".zshrc"), "original").unwrap();
|
|
|
|
let out = env.dm(&["apply", "dracula"]);
|
|
assert!(!out.status.success(), "should fail on conflict");
|
|
|
|
// Original file must be untouched.
|
|
let content = fs::read_to_string(env.target(".zshrc")).unwrap();
|
|
assert_eq!(content, "original");
|
|
}
|
|
|
|
#[test]
|
|
fn apply_force_backs_up_and_replaces() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "# theme");
|
|
fs::write(env.target(".zshrc"), "original").unwrap();
|
|
|
|
env.dm_success(&["apply", "dracula", "--force"]);
|
|
assert!(is_symlink(&env.target(".zshrc")));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// switch dracula → catppuccin
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn switch_themes_replaces_managed_symlinks() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "# dracula");
|
|
env.dm_success(&["apply", "dracula"]);
|
|
|
|
env.make_theme_file("catppuccin", ".zshrc", "# catppuccin");
|
|
env.dm_success(&["apply", "catppuccin"]);
|
|
|
|
let link = fs::read_link(env.target(".zshrc")).unwrap();
|
|
assert!(
|
|
link.to_string_lossy().contains("catppuccin"),
|
|
"link should point to catppuccin, got: {}",
|
|
link.display()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn status_updates_after_theme_switch() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "");
|
|
env.dm_success(&["apply", "dracula"]);
|
|
|
|
env.make_theme_file("catppuccin", ".zshrc", "");
|
|
env.dm_success(&["apply", "catppuccin"]);
|
|
|
|
let out = env.dm_stdout(&["status"]);
|
|
assert!(out.contains("catppuccin"), "got: {out}");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// rollback
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn rollback_restores_backed_up_file() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "# theme");
|
|
fs::write(env.target(".zshrc"), "original content").unwrap();
|
|
|
|
env.dm_success(&["apply", "dracula", "--force"]);
|
|
assert!(is_symlink(&env.target(".zshrc")));
|
|
|
|
env.dm_success(&["rollback"]);
|
|
let content = fs::read_to_string(env.target(".zshrc")).unwrap();
|
|
assert_eq!(content, "original content");
|
|
}
|
|
|
|
#[test]
|
|
fn rollback_removes_newly_created_symlinks() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "# theme");
|
|
|
|
env.dm_success(&["apply", "dracula"]);
|
|
assert!(is_symlink(&env.target(".zshrc")));
|
|
|
|
env.dm_success(&["rollback"]);
|
|
assert!(env.target(".zshrc").symlink_metadata().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn rollback_clears_state() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "");
|
|
|
|
env.dm_success(&["apply", "dracula"]);
|
|
env.dm_success(&["rollback"]);
|
|
|
|
let out = env.dm_stdout(&["status"]);
|
|
assert!(out.contains("No theme"), "got: {out}");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// backup command
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn backup_command_copies_managed_files() {
|
|
let env = TestEnv::new();
|
|
env.make_theme_file("dracula", ".zshrc", "# theme");
|
|
fs::write(env.target(".zshrc"), "original").unwrap();
|
|
|
|
env.dm_success(&["apply", "dracula", "--force"]);
|
|
// Apply already backed up; now apply a second time to switch, creating backup dir
|
|
// Just check the backup command runs without error when state exists.
|
|
let out = env.dm(&["backup"]);
|
|
assert!(out.status.success(), "backup should succeed");
|
|
}
|