use roder_edit_core::{EditApplyError, EditMatchMode, EditOptions, apply_edit};
fn options(fuzzy: EditMatchMode) -> EditOptions {
EditOptions {
fuzzy,
..EditOptions::default()
}
}
#[test]
fn repeated_block_refuses_fuzzy_apply() {
let text = "fn alpha() {\n work();\n}\n\nfn beta() {\n work();\n}\n";
let error = apply_edit(
"src/lib.rs",
text,
" WORK();",
" rest();",
options(EditMatchMode::ApplySafe),
)
.unwrap_err();
let EditApplyError::OldStringNotFound { candidates, .. } = error else {
panic!("expected refusal with diagnostics, got {error:?}");
};
assert!(
!candidates.is_empty(),
"refusal must include candidate snippets"
);
}
#[test]
fn formatter_whitespace_drift_applies_safely_when_unique() {
let text = "const value = 1; \nconst other = 2;\n";
let needle = "const value = 1;\nconst other = 2;";
let diagnose_error = apply_edit(
"src/config.ts",
text,
needle,
"const value = 9;\nconst other = 2;",
options(EditMatchMode::Diagnose),
)
.unwrap_err();
assert!(matches!(
diagnose_error,
EditApplyError::OldStringNotFound { .. }
));
let (updated, outcome) = apply_edit(
"src/config.ts",
text,
needle,
"const value = 9;\nconst other = 2;",
options(EditMatchMode::ApplySafe),
)
.unwrap();
assert!(updated.contains("const value = 9;"));
assert_eq!(outcome.replacements, 1);
}
#[test]
fn line_number_echo_is_stripped_before_matching() {
let text = "alpha\nbeta\ngamma\n";
let (updated, _) = apply_edit(
"notes.txt",
text,
" 2: beta",
"BETA",
EditOptions::default(),
)
.unwrap();
assert_eq!(updated, "alpha\nBETA\ngamma\n");
}
#[test]
fn omitted_indentation_is_restored_when_reindent_is_enabled() {
let text = "fn main() {\n let a = 1;\n let b = 2;\n}\n";
let (updated, _) = apply_edit(
"src/main.rs",
text,
" let a = 1;\n let b = 2;",
"let a = 1;\nif a > 0 {\n use_it(a);\n}",
EditOptions {
reindent_inserted: true,
..EditOptions::default()
},
)
.unwrap();
assert_eq!(
updated, "fn main() {\n let a = 1;\n if a > 0 {\n use_it(a);\n }\n}\n",
"relative indentation inside the inserted block must be preserved"
);
}
#[test]
fn omitted_indentation_is_left_alone_without_opt_in() {
let text = "fn main() {\n let a = 1;\n}\n";
let (updated, _) = apply_edit(
"src/main.rs",
text,
" let a = 1;",
"let a = 2;",
EditOptions::default(),
)
.unwrap();
assert_eq!(updated, "fn main() {\nlet a = 2;\n}\n");
}
#[test]
fn ambiguous_match_refuses_with_candidate_diagnostics() {
let text = "import a\nimport a\nimport b\n";
let error = apply_edit(
"mod.py",
text,
"import a",
"import c",
options(EditMatchMode::ApplySafe),
)
.unwrap_err();
let EditApplyError::OldStringAmbiguous {
occurrences,
candidates,
..
} = error
else {
panic!("expected ambiguity refusal, got {error:?}");
};
assert_eq!(occurrences, 2);
assert!(!candidates.is_empty());
}
#[test]
fn fuzzy_apply_never_fires_below_uniqueness() {
let text = "value() \nvalue()\n";
let error = apply_edit(
"src/x.rs",
text,
"VALUE()",
"other()",
options(EditMatchMode::ApplySafe),
)
.unwrap_err();
assert!(matches!(error, EditApplyError::OldStringNotFound { .. }));
}