#![allow(
clippy::indexing_slicing,
clippy::panic,
clippy::unwrap_used,
reason = "test code with known-structure parsed data"
)]
use crate::json;
use super::{apply, splice, Edit, Error, Json, Splice};
fn parsed(s: &str) -> json::Document<'_> {
json::parse(s.into()).unwrap()
}
fn field_id(element: &json::Element<'_>, key: &str) -> json::ElemId {
element.find_field(key).unwrap().element().id()
}
fn item_id(element: &json::Element<'_>, index: usize) -> json::ElemId {
element.as_array().unwrap()[index].id()
}
fn replace(elem: json::ElemId, json: &str) -> Edit {
Edit::replace(elem, Json::raw(json))
}
#[test]
fn no_edits_leave_the_source_alone() {
let source = r#"{"a": 1, "b": 2}"#;
let doc = parsed(source);
assert_eq!(apply(&doc, &[]), Ok(source.to_owned()));
}
#[test]
fn remove_a_field() {
let source = r#"{"a": 1, "b": 2}"#;
let doc = parsed(source);
let edits = [Edit::remove(field_id(doc.root(), "a"))];
assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2}"#.to_owned()));
}
#[test]
fn replace_a_value_and_keep_the_key() {
let source = r#"{"min_duration": 1.5, "b": 2}"#;
let doc = parsed(source);
let edits = [replace(field_id(doc.root(), "min_duration"), "2")];
assert_eq!(
apply(&doc, &edits),
Ok(r#"{"min_duration": 2, "b": 2}"#.to_owned())
);
}
#[test]
fn replace_a_scalar_with_an_object() {
let source = r#"{"a": 1, "b": 2}"#;
let doc = parsed(source);
let edits = [replace(field_id(doc.root(), "a"), r#"{"c": [3]}"#)];
assert_eq!(
apply(&doc, &edits),
Ok(r#"{"a": {"c": [3]}, "b": 2}"#.to_owned())
);
}
#[test]
fn replace_an_array_item() {
let source = "[1, 2, 3]";
let doc = parsed(source);
let edits = [replace(item_id(doc.root(), 1), "20")];
assert_eq!(apply(&doc, &edits), Ok("[1, 20, 3]".to_owned()));
}
#[test]
fn a_longer_replacement_does_not_shift_later_edits() {
let source = r#"{"a": 1, "b": 2, "c": 3}"#;
let doc = parsed(source);
let edits = [
replace(
field_id(doc.root(), "a"),
r#""a much longer value than before""#,
),
Edit::remove(field_id(doc.root(), "c")),
];
assert_eq!(
apply(&doc, &edits),
Ok(r#"{"a": "a much longer value than before", "b": 2}"#.to_owned())
);
}
#[test]
fn remove_and_replace_different_elements() {
let source = r#"{"a": 1, "b": 2, "c": 3}"#;
let doc = parsed(source);
let edits = [
Edit::remove(field_id(doc.root(), "a")),
replace(field_id(doc.root(), "c"), "30"),
];
assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2, "c": 30}"#.to_owned()));
}
#[test]
fn removing_the_same_element_twice_removes_it_once() {
let source = r#"{"a": 1, "b": 2}"#;
let doc = parsed(source);
let id = field_id(doc.root(), "a");
let edits = [Edit::remove(id), Edit::remove(id)];
assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2}"#.to_owned()));
}
#[test]
fn a_removal_absorbs_a_replacement_of_the_same_element() {
let source = r#"{"a": 1, "b": 2}"#;
let doc = parsed(source);
let id = field_id(doc.root(), "a");
let edits = [Edit::remove(id), replace(id, "99")];
assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2}"#.to_owned()));
}
#[test]
fn a_removal_absorbs_a_replacement_nested_inside_it() {
let source = r#"{"a": {"max_kwh": 1.5}, "b": 2}"#;
let doc = parsed(source);
let outer = doc.root().find_field("a").unwrap().element();
let edits = [
Edit::remove(outer.id()),
replace(field_id(outer, "max_kwh"), "2"),
];
assert_eq!(apply(&doc, &edits), Ok(r#"{"b": 2}"#.to_owned()));
}
#[test]
fn replacing_the_same_element_twice_is_a_duplicate() {
let source = r#"{"a": 1}"#;
let doc = parsed(source);
let id = field_id(doc.root(), "a");
let edits = [replace(id, "2"), replace(id, "3")];
assert_eq!(apply(&doc, &edits), Err(Error::Duplicate(id)));
}
#[test]
fn replacing_the_same_element_twice_with_the_same_text_is_not() {
let source = r#"{"a": 1}"#;
let doc = parsed(source);
let id = field_id(doc.root(), "a");
let edits = [replace(id, "2"), replace(id, "2")];
assert_eq!(apply(&doc, &edits), Ok(r#"{"a": 2}"#.to_owned()));
}
#[test]
fn a_replacement_nested_in_a_replacement_is_a_conflict() {
let source = r#"{"a": {"b": 1}}"#;
let doc = parsed(source);
let outer = doc.root().find_field("a").unwrap().element();
let edits = [
replace(outer.id(), r#"{"b": 2}"#),
replace(field_id(outer, "b"), "3"),
];
assert_eq!(
apply(&doc, &edits),
Err(Error::Conflict {
outer: outer.id(),
inner: field_id(outer, "b"),
})
);
}
#[test]
fn a_removal_nested_in_a_replacement_is_a_conflict() {
let source = r#"{"a": {"b": 1, "c": 2}}"#;
let doc = parsed(source);
let outer = doc.root().find_field("a").unwrap().element();
let edits = [
replace(outer.id(), r#"{"b": 1}"#),
Edit::remove(field_id(outer, "c")),
];
assert_eq!(
apply(&doc, &edits),
Err(Error::Conflict {
outer: outer.id(),
inner: field_id(outer, "c"),
})
);
}
#[test]
fn an_id_from_beyond_the_document_is_reported() {
let source = r#"{"a": 1}"#;
let doc = parsed(source);
let unknown = json::ElemId::from(99);
let edits = [replace(unknown, "2")];
assert_eq!(apply(&doc, &edits), Err(Error::UnknownElement(unknown)));
}
#[test]
fn removing_the_root_is_reported() {
let source = r#"{"a": 1}"#;
let doc = parsed(source);
let edits = [Edit::remove(doc.root().id())];
assert_eq!(
apply(&doc, &edits),
Err(Error::Removal(json::RemovalError::Root))
);
}
#[test]
fn overlapping_spans_are_reported_rather_than_spliced() {
let source = r#"{"a": 1, "b": 2}"#;
let first = json::Span { start: 1, end: 7 };
let second = json::Span { start: 4, end: 9 };
let splices = [
Splice {
span: first,
text: "",
},
Splice {
span: second,
text: "",
},
];
assert_eq!(
splice(source, &splices),
Err(Error::OverlappingSpans { first, second })
);
}
#[test]
fn edits_apply_to_pretty_printed_json() {
let source = "{\n \"a\": 1,\n \"b\": null,\n \"c\": 3\n}";
let doc = parsed(source);
let edits = [
Edit::remove(field_id(doc.root(), "b")),
replace(field_id(doc.root(), "c"), "30"),
];
assert_eq!(
apply(&doc, &edits),
Ok("{\n \"a\": 1,\n \"c\": 30\n}".to_owned())
);
}