use pushkin_core::edits::{apply_edits, EditError, Replacement};
fn edit(old: &str, new: &str) -> Replacement {
Replacement {
old: old.to_owned(),
new: new.to_owned(),
replace_all: false,
anchor: None,
}
}
fn edit_all(old: &str, new: &str) -> Replacement {
Replacement {
old: old.to_owned(),
new: new.to_owned(),
replace_all: true,
anchor: None,
}
}
#[test]
fn a_unique_match_is_replaced() {
let out = apply_edits(
"let a = 1;\nlet b = 2;\n",
&[edit("let a = 1;", "let a = 9;")],
);
assert_eq!(out.unwrap(), "let a = 9;\nlet b = 2;\n");
}
#[test]
fn a_non_unique_match_without_replace_all_is_an_error() {
let err = apply_edits("x = 1;\nx = 1;\n", &[edit("x = 1;", "x = 2;")]).unwrap_err();
assert!(
matches!(err, EditError::NotUnique { count: 2, .. }),
"expected NotUnique{{count:2}}, got {err:?}"
);
}
#[test]
fn replace_all_replaces_every_occurrence() {
let out = apply_edits("x = 1;\nx = 1;\n", &[edit_all("x = 1;", "x = 2;")]);
assert_eq!(out.unwrap(), "x = 2;\nx = 2;\n");
}
#[test]
fn a_missing_target_is_an_error() {
let err = apply_edits("let a = 1;\n", &[edit("nope", "yes")]).unwrap_err();
assert!(
matches!(err, EditError::NotFound { .. }),
"expected NotFound, got {err:?}"
);
}
#[test]
fn replace_all_with_no_match_is_still_an_error() {
let err = apply_edits("let a = 1;\n", &[edit_all("nope", "yes")]).unwrap_err();
assert!(
matches!(err, EditError::NotFound { .. }),
"expected NotFound, got {err:?}"
);
}
#[test]
fn an_edit_whose_old_and_new_are_identical_is_an_error() {
let err = apply_edits("let a = 1;\n", &[edit("let a = 1;", "let a = 1;")]).unwrap_err();
assert!(matches!(err, EditError::NoOp), "expected NoOp, got {err:?}");
}
#[test]
fn an_empty_target_is_an_error() {
let err = apply_edits("let a = 1;\n", &[edit("", "x")]).unwrap_err();
assert!(
matches!(err, EditError::EmptyTarget),
"expected EmptyTarget, got {err:?}"
);
}
#[test]
fn edits_apply_sequentially_to_the_running_result() {
let out = apply_edits("one\n", &[edit("one", "two"), edit("two", "three")]);
assert_eq!(
out.unwrap(),
"three\n",
"the second edit must see the first edit's output"
);
}
#[test]
fn an_edit_whose_target_a_previous_edit_destroyed_is_an_error() {
let err = apply_edits("one\n", &[edit("one", "two"), edit("one", "three")]).unwrap_err();
assert!(
matches!(err, EditError::NotFound { .. }),
"the second edit's target is gone by the time it runs: {err:?}"
);
}
#[test]
fn a_failure_part_way_through_discards_the_whole_application() {
let err = apply_edits(
"alpha\nbeta\n",
&[edit("alpha", "ALPHA"), edit("missing", "x")],
)
.unwrap_err();
assert!(
matches!(err, EditError::NotFound { .. }),
"expected the second edit to fail: {err:?}"
);
}
#[test]
fn uniqueness_is_evaluated_against_the_running_result_not_the_original() {
let err = apply_edits("a\nb\n", &[edit("a", "b"), edit("b", "c")]).unwrap_err();
assert!(
matches!(err, EditError::NotUnique { count: 2, .. }),
"uniqueness must be judged after prior edits: {err:?}"
);
}
#[test]
fn an_empty_edit_list_is_an_error() {
let err = apply_edits("anything\n", &[]).unwrap_err();
assert!(
matches!(err, EditError::NoEdits),
"a mutation naming no edits is malformed, not a no-op: {err:?}"
);
}
#[test]
fn matching_is_literal_not_regex() {
let out = apply_edits("a.c\nabc\n", &[edit("a.c", "X")]);
assert_eq!(
out.unwrap(),
"X\nabc\n",
"`.` is a literal dot here, so `abc` must be untouched"
);
}
#[test]
fn a_multi_line_target_is_replaced_as_written() {
let out = apply_edits(
"fn main() {\n body();\n}\n",
&[edit("fn main() {\n body();\n}", "fn main() {}")],
);
assert_eq!(out.unwrap(), "fn main() {}\n");
}
#[test]
fn surrounding_bytes_including_the_trailing_newline_are_preserved() {
let out = apply_edits("head\nmid\ntail\n", &[edit("mid", "MID")]).unwrap();
assert_eq!(out, "head\nMID\ntail\n");
assert!(out.ends_with('\n'), "the trailing newline must survive");
}
#[test]
fn a_file_with_no_trailing_newline_keeps_not_having_one() {
let out = apply_edits("head\nmid", &[edit("mid", "MID")]).unwrap();
assert_eq!(out, "head\nMID");
assert!(!out.ends_with('\n'), "no newline must be invented");
}
#[test]
fn an_edit_against_empty_content_reports_not_found() {
let err = apply_edits("", &[edit("x", "y")]).unwrap_err();
assert!(
matches!(err, EditError::NotFound { .. }),
"expected NotFound, got {err:?}"
);
}