#![cfg(feature = "mutation")]
use hyprlang::Config;
use std::fs;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
fn create_test_dir() -> PathBuf {
let counter = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let dir = std::env::temp_dir().join(format!(
"hyprlang_multi_file_test_{}_{}",
timestamp,
counter
));
fs::create_dir_all(&dir).unwrap();
dir
}
fn cleanup_test_dir(dir: &PathBuf) {
let _ = fs::remove_dir_all(dir);
}
#[test]
fn test_multi_file_source_tracking() {
let test_dir = create_test_dir();
let subconfig1_path = test_dir.join("subconfig1.conf");
fs::write(
&subconfig1_path,
r#"$GAPS = 10
$BORDER = 2
"#,
)
.unwrap();
let subconfig2_path = test_dir.join("subconfig2.conf");
fs::write(
&subconfig2_path,
r#"decoration {
rounding = 5
active_opacity = 0.95
}
"#,
)
.unwrap();
let master_path = test_dir.join("master.conf");
fs::write(
&master_path,
format!(
r#"# Master configuration
source = {}
source = {}
general {{
border_size = $BORDER
}}
"#,
subconfig1_path.display(),
subconfig2_path.display()
),
)
.unwrap();
let mut config = Config::new();
config.parse_file(&master_path).unwrap();
assert_eq!(config.get_variable("GAPS"), Some("10"));
assert_eq!(config.get_variable("BORDER"), Some("2"));
assert_eq!(config.get_int("decoration:rounding").unwrap(), 5);
assert_eq!(config.get_float("decoration:active_opacity").unwrap(), 0.95);
let source_files = config.get_source_files();
assert!(source_files.len() >= 3, "Expected at least 3 source files, got {}", source_files.len());
let var_source = config.get_key_source_file("$GAPS");
assert!(var_source.is_some(), "Expected to find source file for $GAPS");
let rounding_source = config.get_key_source_file("decoration:rounding");
assert!(rounding_source.is_some(), "Expected to find source file for decoration:rounding");
cleanup_test_dir(&test_dir);
}
#[test]
fn test_multi_file_mutation_updates_correct_file() {
let test_dir = create_test_dir();
let subconfig1_path = test_dir.join("subconfig1.conf");
fs::write(&subconfig1_path, "$MY_VAR = original\n").unwrap();
let subconfig2_path = test_dir.join("subconfig2.conf");
fs::write(
&subconfig2_path,
r#"decoration {
rounding = 5
}
"#,
)
.unwrap();
let master_path = test_dir.join("master.conf");
fs::write(
&master_path,
format!(
r#"source = {}
source = {}
border_size = 3
"#,
subconfig1_path.display(),
subconfig2_path.display()
),
)
.unwrap();
let mut config = Config::new();
config.parse_file(&master_path).unwrap();
assert_eq!(config.get_int("decoration:rounding").unwrap(), 5);
assert_eq!(config.get_int("border_size").unwrap(), 3);
config.set_int("decoration:rounding", 15);
let modified = config.get_modified_files();
assert!(!modified.is_empty(), "Expected at least one modified file");
let saved = config.save_all().unwrap();
assert!(!saved.is_empty(), "Expected at least one file to be saved");
let subconfig2_content = fs::read_to_string(&subconfig2_path).unwrap();
assert!(
subconfig2_content.contains("rounding = 15"),
"Expected subconfig2 to contain 'rounding = 15', got:\n{}",
subconfig2_content
);
let subconfig1_content = fs::read_to_string(&subconfig1_path).unwrap();
assert!(
subconfig1_content.contains("$MY_VAR = original"),
"Expected subconfig1 to still contain original value, got:\n{}",
subconfig1_content
);
let master_content = fs::read_to_string(&master_path).unwrap();
assert!(
master_content.contains("source ="),
"Expected master to still contain source directive, got:\n{}",
master_content
);
cleanup_test_dir(&test_dir);
}
#[test]
fn test_multi_file_variable_mutation() {
let test_dir = create_test_dir();
let vars_path = test_dir.join("vars.conf");
fs::write(
&vars_path,
r#"$GAPS = 10
$OPACITY = 0.9
"#,
)
.unwrap();
let master_path = test_dir.join("master.conf");
fs::write(
&master_path,
format!(
r#"source = {}
border_size = $GAPS
"#,
vars_path.display()
),
)
.unwrap();
let mut config = Config::new();
config.parse_file(&master_path).unwrap();
assert_eq!(config.get_variable("GAPS"), Some("10"));
config.set_variable("GAPS".to_string(), "20".to_string());
let saved = config.save_all().unwrap();
assert!(!saved.is_empty(), "Expected at least one file to be saved");
let vars_content = fs::read_to_string(&vars_path).unwrap();
assert!(
vars_content.contains("$GAPS = 20"),
"Expected vars.conf to contain '$GAPS = 20', got:\n{}",
vars_content
);
cleanup_test_dir(&test_dir);
}
#[test]
fn test_new_key_goes_to_primary_file() {
let test_dir = create_test_dir();
let subconfig_path = test_dir.join("subconfig.conf");
fs::write(&subconfig_path, "existing_key = 123\n").unwrap();
let master_path = test_dir.join("master.conf");
fs::write(
&master_path,
format!(
r#"source = {}
master_key = 456
"#,
subconfig_path.display()
),
)
.unwrap();
let mut config = Config::new();
config.parse_file(&master_path).unwrap();
config.set_int("brand_new_key", 789);
config.save_all().unwrap();
let master_content = fs::read_to_string(&master_path).unwrap();
assert!(
master_content.contains("brand_new_key = 789"),
"Expected new key to be added to master.conf, got:\n{}",
master_content
);
cleanup_test_dir(&test_dir);
}
#[test]
fn test_serialize_specific_file() {
let test_dir = create_test_dir();
let subconfig_path = test_dir.join("subconfig.conf");
fs::write(&subconfig_path, "sub_key = 100\n").unwrap();
let master_path = test_dir.join("master.conf");
fs::write(
&master_path,
format!(
r#"source = {}
master_key = 200
"#,
subconfig_path.display()
),
)
.unwrap();
let config = Config::new();
let mut config = config;
config.parse_file(&master_path).unwrap();
let canonical_subconfig = subconfig_path.canonicalize().unwrap();
let serialized = config.serialize_file(&canonical_subconfig).unwrap();
assert!(
serialized.contains("sub_key = 100"),
"Expected serialized subconfig to contain 'sub_key = 100', got:\n{}",
serialized
);
cleanup_test_dir(&test_dir);
}
#[test]
fn test_round_trip_with_multi_file_mutation() {
let test_dir = create_test_dir();
let appearance_path = test_dir.join("appearance.conf");
fs::write(
&appearance_path,
r#"decoration {
rounding = 10
blur = true
}
"#,
)
.unwrap();
let master_path = test_dir.join("master.conf");
fs::write(
&master_path,
format!(
r#"source = {}
border_size = 2
"#,
appearance_path.display()
),
)
.unwrap();
let mut config1 = Config::new();
config1.parse_file(&master_path).unwrap();
config1.set_int("decoration:rounding", 25);
config1.save_all().unwrap();
let mut config2 = Config::new();
config2.parse_file(&master_path).unwrap();
assert_eq!(
config2.get_int("decoration:rounding").unwrap(),
25,
"Expected rounding to be 25 after round-trip"
);
assert_eq!(config2.get_int("border_size").unwrap(), 2);
cleanup_test_dir(&test_dir);
}