use splice::graph::rename::{
apply_replacements_in_file, create_rename_backup, generate_colored_preview,
generate_preview_diff, group_references_by_file, simulate_replacements,
simulate_replacements_content, RenameBackupManifest, RenameTransaction,
};
use tempfile::TempDir;
fn create_test_file(dir: &TempDir, path: &str, content: &str) -> std::path::PathBuf {
let file_path = dir.path().join(path);
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(&file_path, content).unwrap();
file_path
}
fn create_reference(
file_path: &str,
byte_start: usize,
byte_end: usize,
) -> magellan::references::ReferenceFact {
magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path),
referenced_symbol: "old_name".to_string(),
byte_start,
byte_end,
start_line: 1,
start_col: byte_start,
end_line: 1,
end_col: byte_end,
}
}
#[test]
fn test_rename_single_file() {
let temp_dir = TempDir::new().unwrap();
let content = "fn old_name() {\n old_name();\n old_name();\n}\n";
let file_path = create_test_file(&temp_dir, "test.rs", content);
let references = vec![
create_reference(file_path.to_str().unwrap(), 36, 44),
create_reference(file_path.to_str().unwrap(), 20, 28),
];
let count =
apply_replacements_in_file(&file_path, "old_name", "new_name", &references).unwrap();
assert_eq!(count, 2);
let result = std::fs::read_to_string(&file_path).unwrap();
assert_eq!(
result,
"fn old_name() {\n new_name();\n new_name();\n}\n"
);
}
#[test]
fn test_rename_preview_mode() {
let temp_dir = TempDir::new().unwrap();
let content = "fn foo() {\n println!(\"foo\");\n}\n";
let file_path = create_test_file(&temp_dir, "lib.rs", content);
let references = vec![
create_reference(file_path.to_str().unwrap(), 25, 28),
create_reference(file_path.to_str().unwrap(), 3, 6),
];
let modified = simulate_replacements_content(content, &references, "foo", "bar").unwrap();
assert_eq!(modified, "fn bar() {\n println!(\"bar\");\n}\n");
let original = std::fs::read_to_string(&file_path).unwrap();
assert_eq!(original, content);
}
#[test]
fn test_rename_creates_backup() {
let temp_dir = TempDir::new().unwrap();
let file1 = create_test_file(&temp_dir, "src/main.rs", "fn foo() {}\n");
let file2 = create_test_file(&temp_dir, "src/lib.rs", "fn bar() {}\n");
let backup_dir = create_rename_backup(
temp_dir.path(),
"test_symbol",
&[file1.clone(), file2.clone()],
)
.unwrap();
assert!(backup_dir.exists());
assert!(backup_dir.starts_with(temp_dir.path().join(".splice/backups")));
let dir_name = backup_dir.file_name().unwrap().to_str().unwrap();
assert!(dir_name.starts_with("rename-test_symbol-"));
let manifest_path = backup_dir.join("manifest.json");
assert!(manifest_path.exists());
let manifest: RenameBackupManifest =
serde_json::from_str(&std::fs::read_to_string(&manifest_path).unwrap()).unwrap();
assert_eq!(manifest.files.len(), 2);
assert!(manifest.files.contains_key("src/main.rs"));
assert!(manifest.files.contains_key("src/lib.rs"));
let backup_file1 = backup_dir.join("src/main.rs");
let backup_file2 = backup_dir.join("src/lib.rs");
assert!(backup_file1.exists());
assert!(backup_file2.exists());
let original_content = std::fs::read_to_string(&file1).unwrap();
let backup_content = std::fs::read_to_string(&backup_file1).unwrap();
assert_eq!(original_content, backup_content);
}
#[test]
fn test_generate_preview_diff() {
let original = "fn foo() {\n println!(\"foo\");\n}\n";
let modified = "fn bar() {\n println!(\"bar\");\n}\n";
let file_path = std::path::PathBuf::from("test.rs");
let diff = generate_preview_diff(&file_path, original, modified);
assert!(diff.contains("--- a/test.rs"));
assert!(diff.contains("+++ b/test.rs"));
assert!(diff.contains("-fn foo()"));
assert!(diff.contains("+fn bar()"));
assert!(diff.contains("- println!(\"foo\");"));
assert!(diff.contains("+ println!(\"bar\");"));
}
#[test]
fn test_generate_preview_diff_no_changes() {
let content = "fn foo() {}\n";
let file_path = std::path::PathBuf::from("test.rs");
let diff = generate_preview_diff(&file_path, content, content);
assert!(diff.is_empty());
}
#[test]
fn test_generate_colored_preview() {
let original = "fn foo() {}\n";
let modified = "fn bar() {}\n";
let file_path = std::path::PathBuf::from("test.rs");
let colored = generate_colored_preview(&file_path, original, modified);
assert!(!colored.is_empty());
if !colored.contains('\x1b') {
assert!(colored.contains("-fn foo()"));
assert!(colored.contains("+fn bar()"));
}
}
#[test]
fn test_group_references_by_file() {
let references = vec![
create_reference("/src/a.rs", 100, 103),
create_reference("/src/b.rs", 50, 53),
create_reference("/src/a.rs", 20, 23),
create_reference("/src/b.rs", 10, 13),
];
let grouped = group_references_by_file(&references);
assert_eq!(grouped.len(), 2);
let a_refs = grouped.get(&std::path::PathBuf::from("/src/a.rs")).unwrap();
assert_eq!(a_refs[0].byte_start, 100);
assert_eq!(a_refs[1].byte_start, 20);
let b_refs = grouped.get(&std::path::PathBuf::from("/src/b.rs")).unwrap();
assert_eq!(b_refs[0].byte_start, 50);
assert_eq!(b_refs[1].byte_start, 10);
}
#[test]
fn test_simulate_replacements() {
let references = vec![
create_reference("/src/a.rs", 100, 103),
create_reference("/src/b.rs", 50, 53),
create_reference("/src/a.rs", 20, 23),
];
let simulation = simulate_replacements(&references);
assert_eq!(simulation.len(), 2);
assert_eq!(
simulation.get(&std::path::PathBuf::from("/src/a.rs")),
Some(&2)
);
assert_eq!(
simulation.get(&std::path::PathBuf::from("/src/b.rs")),
Some(&1)
);
}
#[test]
fn test_rename_transaction_rollback() {
let temp_dir = TempDir::new().unwrap();
let file_path = create_test_file(&temp_dir, "test.rs", "fn original() {}\n");
let original_content = "fn original() {}\n";
let backup_dir = temp_dir.path().join(".splice/backups/test-rollback");
std::fs::create_dir_all(&backup_dir).unwrap();
let backup_file = backup_dir.join("test.rs");
std::fs::write(&backup_file, original_content).unwrap();
let manifest = RenameBackupManifest {
operation_id: "test-rollback".to_string(),
timestamp: chrono::Utc::now().to_rfc3339(),
files: std::collections::HashMap::from([(
"test.rs".to_string(),
"dummy_checksum".to_string(),
)]),
};
let manifest_path = backup_dir.join("manifest.json");
std::fs::write(
&manifest_path,
serde_json::to_string_pretty(&manifest).unwrap(),
)
.unwrap();
std::fs::write(&file_path, "fn modified() {}\n").unwrap();
let txn = RenameTransaction::new().with_backup(backup_dir, temp_dir.path().to_path_buf());
txn.rollback().unwrap();
let restored_content = std::fs::read_to_string(&file_path).unwrap();
assert_eq!(restored_content, original_content);
}
#[test]
fn test_rename_transaction_with_multiple_files() {
let temp_dir = TempDir::new().unwrap();
let file1 = create_test_file(&temp_dir, "src/a.rs", "fn foo() {}\n");
let file2 = create_test_file(&temp_dir, "src/b.rs", "fn foo() {}\n");
let refs1 = vec![create_reference(file1.to_str().unwrap(), 3, 6)];
let refs2 = vec![create_reference(file2.to_str().unwrap(), 3, 6)];
apply_replacements_in_file(&file1, "foo", "bar", &refs1).unwrap();
apply_replacements_in_file(&file2, "foo", "bar", &refs2).unwrap();
let content1 = std::fs::read_to_string(&file1).unwrap();
let content2 = std::fs::read_to_string(&file2).unwrap();
assert_eq!(content1, "fn bar() {}\n");
assert_eq!(content2, "fn bar() {}\n");
}
#[test]
fn test_rename_with_multibyte_utf8() {
let temp_dir = TempDir::new().unwrap();
let content = "fn foo() { // 世界\n}\n";
let file_path = create_test_file(&temp_dir, "test.rs", content);
let references = vec![create_reference(file_path.to_str().unwrap(), 3, 6)];
apply_replacements_in_file(&file_path, "foo", "bar", &references).unwrap();
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(result.contains("世界"));
assert!(result.contains("bar()"));
}
#[test]
fn test_rename_preview_with_multiple_files() {
let temp_dir = TempDir::new().unwrap();
let file1 = create_test_file(&temp_dir, "src/a.rs", "fn old_name() {}\n");
let file2 = create_test_file(&temp_dir, "src/b.rs", "fn old_name() {}\n");
let refs1 = vec![create_reference(file1.to_str().unwrap(), 3, 11)];
let refs2 = vec![create_reference(file2.to_str().unwrap(), 3, 11)];
let content1 = std::fs::read_to_string(&file1).unwrap();
let content2 = std::fs::read_to_string(&file2).unwrap();
let modified1 =
simulate_replacements_content(&content1, &refs1, "old_name", "new_name").unwrap();
let modified2 =
simulate_replacements_content(&content2, &refs2, "old_name", "new_name").unwrap();
assert_eq!(modified1, "fn new_name() {}\n");
assert_eq!(modified2, "fn new_name() {}\n");
let original1 = std::fs::read_to_string(&file1).unwrap();
let original2 = std::fs::read_to_string(&file2).unwrap();
assert_eq!(original1, "fn old_name() {}\n");
assert_eq!(original2, "fn old_name() {}\n");
}
#[test]
fn test_backup_preserves_directory_structure() {
let temp_dir = TempDir::new().unwrap();
let file1 = create_test_file(&temp_dir, "src/api/handlers.rs", "pub fn handler() {}\n");
let file2 = create_test_file(
&temp_dir,
"tests/integration_test.rs",
"#[test]\nfn test() {}\n",
);
let backup_dir = create_rename_backup(
temp_dir.path(),
"nested_test",
&[file1.clone(), file2.clone()],
)
.unwrap();
let backup_file1 = backup_dir.join("src/api/handlers.rs");
let backup_file2 = backup_dir.join("tests/integration_test.rs");
assert!(backup_file1.exists());
assert!(backup_file2.exists());
let manifest: RenameBackupManifest =
serde_json::from_str(&std::fs::read_to_string(backup_dir.join("manifest.json")).unwrap())
.unwrap();
assert!(manifest.files.contains_key("src/api/handlers.rs"));
assert!(manifest.files.contains_key("tests/integration_test.rs"));
}
#[test]
fn test_transaction_track_modified() {
let mut txn = RenameTransaction::new();
txn.track_modified(std::path::PathBuf::from("/path/to/file1.rs"));
txn.track_modified(std::path::PathBuf::from("/path/to/file2.rs"));
assert_eq!(txn.modified_count(), 2);
assert_eq!(
txn.modified_files(),
&[
std::path::PathBuf::from("/path/to/file1.rs"),
std::path::PathBuf::from("/path/to/file2.rs")
]
);
}
fn find_symbol_spans(source: &str, symbol_name: &str) -> Vec<(usize, usize)> {
let mut spans = Vec::new();
let mut offset = 0;
while let Some(pos) = source[offset..].find(symbol_name) {
let abs_pos = offset + pos;
let before_ok = abs_pos == 0
|| !source
.chars()
.nth(abs_pos - 1)
.map_or(false, |c| c.is_alphanumeric() || c == '_');
let after_ok = abs_pos + symbol_name.len() >= source.len()
|| !source
.chars()
.nth(abs_pos + symbol_name.len())
.map_or(false, |c| c.is_alphanumeric() || c == '_');
if before_ok && after_ok {
spans.push((abs_pos, abs_pos + symbol_name.len()));
}
offset = abs_pos + symbol_name.len();
}
spans.sort_by(|a, b| b.0.cmp(&a.0));
spans
}
#[test]
fn test_rename_rust_function() {
let temp_dir = TempDir::new().unwrap();
let content = "fn old_name() {\n old_name();\n}\n";
let file_path = create_test_file(&temp_dir, "src/main.rs", content);
let spans = find_symbol_spans(content, "old_name");
assert_eq!(spans.len(), 2, "Should find 2 occurrences of old_name");
let file_path_str = file_path.to_str().unwrap();
let refs: Vec<magellan::references::ReferenceFact> = spans
.into_iter()
.map(|(start, end)| magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path_str),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1, start_col: start,
end_line: 1,
end_col: end,
})
.collect();
let grouped = group_references_by_file(&refs);
for (file_path, refs) in grouped {
apply_replacements_in_file(&file_path, "old_name", "new_name", &refs).unwrap();
}
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(
result.contains("new_name()"),
"Result should contain new_name()"
);
assert!(
!result.contains("old_name()"),
"Result should not contain old_name()"
);
}
#[test]
fn test_rename_python_function() {
let temp_dir = TempDir::new().unwrap();
let content = "def old_name():\n old_name()\n";
let file_path = create_test_file(&temp_dir, "test.py", content);
let spans = find_symbol_spans(content, "old_name");
assert_eq!(spans.len(), 2, "Should find 2 occurrences of old_name");
let file_path_str = file_path.to_str().unwrap();
let refs: Vec<magellan::references::ReferenceFact> = spans
.into_iter()
.map(|(start, end)| magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path_str),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
})
.collect();
let grouped = group_references_by_file(&refs);
for (file_path, refs) in grouped {
apply_replacements_in_file(&file_path, "old_name", "new_name", &refs).unwrap();
}
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(
result.contains("new_name"),
"Result should contain new_name"
);
assert!(
!result.contains("old_name"),
"Result should not contain old_name"
);
}
#[test]
fn test_rename_javascript_function() {
let temp_dir = TempDir::new().unwrap();
let content = "function old_name() {\n old_name();\n}\n";
let file_path = create_test_file(&temp_dir, "test.js", content);
let spans = find_symbol_spans(content, "old_name");
assert_eq!(spans.len(), 2, "Should find 2 occurrences of old_name");
let file_path_str = file_path.to_str().unwrap();
let refs: Vec<magellan::references::ReferenceFact> = spans
.into_iter()
.map(|(start, end)| magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path_str),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
})
.collect();
let grouped = group_references_by_file(&refs);
for (file_path, refs) in grouped {
apply_replacements_in_file(&file_path, "old_name", "new_name", &refs).unwrap();
}
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(
result.contains("new_name"),
"Result should contain new_name"
);
assert!(
!result.contains("old_name"),
"Result should not contain old_name"
);
}
#[test]
fn test_rename_typescript_function() {
let temp_dir = TempDir::new().unwrap();
let content = "function old_name(): void {\n old_name();\n}\n";
let file_path = create_test_file(&temp_dir, "test.ts", content);
let spans = find_symbol_spans(content, "old_name");
assert_eq!(spans.len(), 2, "Should find 2 occurrences of old_name");
let file_path_str = file_path.to_str().unwrap();
let refs: Vec<magellan::references::ReferenceFact> = spans
.into_iter()
.map(|(start, end)| magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path_str),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
})
.collect();
let grouped = group_references_by_file(&refs);
for (file_path, refs) in grouped {
apply_replacements_in_file(&file_path, "old_name", "new_name", &refs).unwrap();
}
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(
result.contains("new_name"),
"Result should contain new_name"
);
assert!(
!result.contains("old_name"),
"Result should not contain old_name"
);
}
#[test]
fn test_rename_c_function() {
let temp_dir = TempDir::new().unwrap();
let content = "void old_name() {\n old_name();\n}\n";
let file_path = create_test_file(&temp_dir, "test.c", content);
let spans = find_symbol_spans(content, "old_name");
assert_eq!(spans.len(), 2, "Should find 2 occurrences of old_name");
let file_path_str = file_path.to_str().unwrap();
let refs: Vec<magellan::references::ReferenceFact> = spans
.into_iter()
.map(|(start, end)| magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path_str),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
})
.collect();
let grouped = group_references_by_file(&refs);
for (file_path, refs) in grouped {
apply_replacements_in_file(&file_path, "old_name", "new_name", &refs).unwrap();
}
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(
result.contains("new_name"),
"Result should contain new_name"
);
assert!(
!result.contains("old_name"),
"Result should not contain old_name"
);
}
#[test]
fn test_rename_cpp_method() {
let temp_dir = TempDir::new().unwrap();
let content = "class MyClass {\npublic:\n void old_name() { old_name(); }\n};\n";
let file_path = create_test_file(&temp_dir, "test.cpp", content);
let spans = find_symbol_spans(content, "old_name");
assert_eq!(spans.len(), 2, "Should find 2 occurrences of old_name");
let file_path_str = file_path.to_str().unwrap();
let refs: Vec<magellan::references::ReferenceFact> = spans
.into_iter()
.map(|(start, end)| magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path_str),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
})
.collect();
let grouped = group_references_by_file(&refs);
for (file_path, refs) in grouped {
apply_replacements_in_file(&file_path, "old_name", "new_name", &refs).unwrap();
}
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(
result.contains("new_name"),
"Result should contain new_name"
);
assert!(
!result.contains("old_name"),
"Result should not contain old_name"
);
}
#[test]
fn test_rename_java_method() {
let temp_dir = TempDir::new().unwrap();
let content = "public class Test {\n void old_name() {\n old_name();\n }\n}\n";
let file_path = create_test_file(&temp_dir, "Test.java", content);
let spans = find_symbol_spans(content, "old_name");
assert_eq!(spans.len(), 2, "Should find 2 occurrences of old_name");
let file_path_str = file_path.to_str().unwrap();
let refs: Vec<magellan::references::ReferenceFact> = spans
.into_iter()
.map(|(start, end)| magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path_str),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
})
.collect();
let grouped = group_references_by_file(&refs);
for (file_path, refs) in grouped {
apply_replacements_in_file(&file_path, "old_name", "new_name", &refs).unwrap();
}
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(
result.contains("new_name"),
"Result should contain new_name"
);
assert!(
!result.contains("old_name"),
"Result should not contain old_name"
);
}
#[test]
fn test_rename_cross_file_rust() {
let temp_dir = TempDir::new().unwrap();
let main_content = "mod lib;\nfn main() {\n lib::old_name();\n}\n";
let main_rs = create_test_file(&temp_dir, "src/main.rs", main_content);
let lib_content = "pub fn old_name() {\n old_name();\n}\n";
let lib_rs = create_test_file(&temp_dir, "src/lib.rs", lib_content);
let main_spans = find_symbol_spans(main_content, "old_name");
let lib_spans = find_symbol_spans(lib_content, "old_name");
assert_eq!(main_spans.len(), 1, "Should find 1 occurrence in main.rs");
assert_eq!(lib_spans.len(), 2, "Should find 2 occurrences in lib.rs");
let mut refs = Vec::new();
for (start, end) in main_spans {
refs.push(magellan::references::ReferenceFact {
file_path: main_rs.clone(),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
});
}
for (start, end) in lib_spans {
refs.push(magellan::references::ReferenceFact {
file_path: lib_rs.clone(),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
});
}
let grouped = group_references_by_file(&refs);
for (file_path, refs) in grouped {
apply_replacements_in_file(&file_path, "old_name", "new_name", &refs).unwrap();
}
let main_content = std::fs::read_to_string(&main_rs).unwrap();
let lib_content = std::fs::read_to_string(&lib_rs).unwrap();
assert!(
main_content.contains("new_name()"),
"main.rs should contain new_name"
);
assert!(
lib_content.contains("new_name()"),
"lib.rs should contain new_name"
);
assert!(
!main_content.contains("old_name"),
"main.rs should not contain old_name"
);
assert!(
!lib_content.contains("old_name"),
"lib.rs should not contain old_name"
);
}
#[test]
fn test_rename_cross_file_python() {
let temp_dir = TempDir::new().unwrap();
let main_content = "from lib import old_name\nold_name()\n";
let main_py = create_test_file(&temp_dir, "main.py", main_content);
let lib_content = "def old_name():\n old_name()\n";
let lib_py = create_test_file(&temp_dir, "lib.py", lib_content);
let main_spans = find_symbol_spans(main_content, "old_name");
let lib_spans = find_symbol_spans(lib_content, "old_name");
assert_eq!(main_spans.len(), 2, "Should find 2 occurrences in main.py");
assert_eq!(lib_spans.len(), 2, "Should find 2 occurrences in lib.py");
let mut refs = Vec::new();
for (start, end) in main_spans {
refs.push(magellan::references::ReferenceFact {
file_path: main_py.clone(),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
});
}
for (start, end) in lib_spans {
refs.push(magellan::references::ReferenceFact {
file_path: lib_py.clone(),
referenced_symbol: "old_name".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
});
}
let grouped = group_references_by_file(&refs);
for (file_path, refs) in grouped {
apply_replacements_in_file(&file_path, "old_name", "new_name", &refs).unwrap();
}
let main_content = std::fs::read_to_string(&main_py).unwrap();
let lib_content = std::fs::read_to_string(&lib_py).unwrap();
assert!(
main_content.contains("new_name"),
"main.py should contain new_name"
);
assert!(
lib_content.contains("new_name"),
"lib.py should contain new_name"
);
assert!(
!main_content.contains("old_name"),
"main.py should not contain old_name"
);
assert!(
!lib_content.contains("old_name"),
"lib.py should not contain old_name"
);
}
#[test]
fn test_rename_byte_accuracy_no_false_positives() {
let temp_dir = TempDir::new().unwrap();
let content = "fn foo() {\n let foo_bar = 1;\n foo();\n}\n";
let file_path = create_test_file(&temp_dir, "test.rs", content);
let spans = find_symbol_spans(content, "foo");
assert_eq!(
spans.len(),
2,
"Should find exactly 2 'foo' occurrences (not foo_bar)"
);
let file_path_str = file_path.to_str().unwrap();
let references: Vec<magellan::references::ReferenceFact> = spans
.into_iter()
.map(|(start, end)| magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path_str),
referenced_symbol: "foo".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
})
.collect();
apply_replacements_in_file(&file_path, "foo", "baz", &references).unwrap();
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(result.contains("fn baz()"), "Should rename foo to baz");
assert!(result.contains("baz();"), "Should rename foo call");
assert!(result.contains("foo_bar"), "Should NOT rename foo_bar");
assert!(
!result.contains("fn foo()"),
"Should not have original foo()"
);
}
#[test]
fn test_rename_byte_accuracy_substring() {
let temp_dir = TempDir::new().unwrap();
let content = "fn bar() {\n let bar_baz = bar();\n}\n";
let file_path = create_test_file(&temp_dir, "test.rs", content);
let spans = find_symbol_spans(content, "bar");
assert_eq!(
spans.len(),
2,
"Should find exactly 2 'bar' occurrences (not bar_baz)"
);
let file_path_str = file_path.to_str().unwrap();
let references: Vec<magellan::references::ReferenceFact> = spans
.into_iter()
.map(|(start, end)| magellan::references::ReferenceFact {
file_path: std::path::PathBuf::from(file_path_str),
referenced_symbol: "bar".to_string(),
byte_start: start,
byte_end: end,
start_line: 1,
start_col: start,
end_line: 1,
end_col: end,
})
.collect();
apply_replacements_in_file(&file_path, "bar", "qux", &references).unwrap();
let result = std::fs::read_to_string(&file_path).unwrap();
assert!(result.contains("fn qux()"), "Should rename bar to qux");
assert!(result.contains("qux();"), "Should rename bar() call");
assert!(
result.contains("bar_baz"),
"Should NOT rename bar_baz identifier"
);
assert!(
!result.contains("fn bar()"),
"Should not have original bar()"
);
}
#[test]
fn test_preview_no_backup_created() {
let temp_dir = TempDir::new().unwrap();
let file_path = create_test_file(&temp_dir, "test.rs", "fn foo() {}\n");
let original_content = "fn foo() {}\n";
let references = vec![create_reference(file_path.to_str().unwrap(), 3, 6)];
let _modified =
simulate_replacements_content(original_content, &references, "foo", "bar").unwrap();
let backups_base = temp_dir.path().join(".splice/backups");
assert!(
!backups_base.exists(),
"Preview mode should not create backup directory"
);
let original = std::fs::read_to_string(&file_path).unwrap();
assert_eq!(original, original_content);
}
#[test]
fn test_preview_no_filesystem_modifications() {
let temp_dir = TempDir::new().unwrap();
let file_path = create_test_file(&temp_dir, "test.rs", "fn foo() {}\n");
let original_content = "fn foo() {}\n";
let original_mtime = std::fs::metadata(&file_path).unwrap().modified().unwrap();
let references = vec![create_reference(file_path.to_str().unwrap(), 3, 6)];
let modified =
simulate_replacements_content(original_content, &references, "foo", "bar").unwrap();
assert_eq!(modified, "fn bar() {}\n");
let current_mtime = std::fs::metadata(&file_path).unwrap().modified().unwrap();
assert_eq!(
original_mtime, current_mtime,
"File mtime should not change in preview mode"
);
let current_content = std::fs::read_to_string(&file_path).unwrap();
assert_eq!(current_content, original_content);
}