use noyalib::cst::{Document, RepairScope, parse_document};
#[track_caller]
fn assert_equivalent_to_full_reparse(doc: &Document) {
let from_full = parse_document(&doc.to_string()).expect("parses");
assert_eq!(doc.to_string(), from_full.to_string(), "to_string mismatch");
assert_eq!(*doc.as_value(), *from_full.as_value(), "as_value mismatch");
}
#[test]
fn scalar_bump_value_matches_full_reparse() {
let mut doc = parse_document("name: foo\nversion: 0.0.1\n").unwrap();
doc.set("version", "0.0.2").unwrap();
assert_eq!(doc.to_string(), "name: foo\nversion: 0.0.2\n");
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn nested_mapping_edit_matches_full_reparse() {
let mut doc = parse_document("outer:\n inner: 1\n next: 2\nother: 3\n").unwrap();
doc.set("outer.inner", "11").unwrap();
assert_eq!(
doc.to_string(),
"outer:\n inner: 11\n next: 2\nother: 3\n",
);
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn sequence_item_edit_matches_full_reparse() {
let mut doc = parse_document("items:\n - a\n - b\n - c\n").unwrap();
doc.set("items[1]", "B").unwrap();
assert_eq!(doc.to_string(), "items:\n - a\n - B\n - c\n");
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn remove_middle_entry_matches_full_reparse() {
let mut doc = parse_document("a: 1\nb: 2\nc: 3\n").unwrap();
doc.remove("b").unwrap();
assert_eq!(doc.to_string(), "a: 1\nc: 3\n");
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn push_back_matches_full_reparse() {
let mut doc = parse_document("items:\n - a\n - b\n").unwrap();
doc.push_back("items", "c").unwrap();
assert_eq!(doc.to_string(), "items:\n - a\n - b\n - c\n");
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn insert_after_matches_full_reparse() {
let mut doc = parse_document("items:\n - a\n - c\n").unwrap();
doc.insert_after("items[0]", "b").unwrap();
assert_eq!(doc.to_string(), "items:\n - a\n - b\n - c\n");
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn scalar_bump_lands_at_entry_scope() {
let mut doc = parse_document("name: foo\nversion: 0.0.1\n").unwrap();
doc.set("version", "0.0.2").unwrap();
assert_eq!(doc.last_repair_scope(), Some(RepairScope::Entry));
}
#[test]
fn push_back_lands_at_collection_scope_or_higher() {
let mut doc = parse_document("items:\n - a\n - b\n").unwrap();
doc.push_back("items", "c").unwrap();
let scope = doc.last_repair_scope().unwrap();
assert!(
matches!(
scope,
RepairScope::Collection | RepairScope::Entry | RepairScope::Document
),
"unexpected scope: {scope:?}",
);
}
#[test]
fn replace_span_over_alias_escalates_to_document() {
let mut doc = parse_document("a: &anc 1\nb: *anc\n").unwrap();
doc.replace_span(13, 17, "different").unwrap();
assert_eq!(doc.last_repair_scope(), Some(RepairScope::Document));
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn replacement_introducing_anchor_escalates_to_document() {
let mut doc = parse_document("a: 1\nb: 2\n").unwrap();
doc.set("a", "&new 1").unwrap();
assert_eq!(doc.last_repair_scope(), Some(RepairScope::Document));
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn replacement_introducing_tag_escalates_to_document() {
let mut doc = parse_document("a: 1\nb: 2\n").unwrap();
doc.set("a", "!!str 1").unwrap();
assert_eq!(doc.last_repair_scope(), Some(RepairScope::Document));
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn replacement_introducing_alias_escalates_to_document() {
let mut doc = parse_document("a: &anc 1\nb: 2\n").unwrap();
doc.set("b", "*anc").unwrap();
assert_eq!(doc.last_repair_scope(), Some(RepairScope::Document));
assert_equivalent_to_full_reparse(&doc);
}
#[test]
fn invalid_replacement_commits_optimistically_and_panics_on_read() {
let mut doc = parse_document("name: foo\n").unwrap();
doc.set("name", "[").unwrap();
assert_eq!(doc.to_string(), "name: [\n");
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = doc.as_value();
}));
assert!(result.is_err(), "as_value must panic on invalid source");
}