#![expect(
clippy::panic,
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
reason = "test code"
)]
use rstest::rstest;
use rlsp_yaml_parser::CollectionStyle;
use rlsp_yaml_parser::ScalarStyle;
use rlsp_yaml_parser::loader::{LoadError, LoaderBuilder, load};
use rlsp_yaml_parser::node::Node;
fn load_one(input: &str) -> Node<rlsp_yaml_parser::Span> {
let docs = load(input).expect("load failed");
assert_eq!(docs.len(), 1, "expected 1 document, got {}", docs.len());
docs.into_iter().next().unwrap().root
}
fn load_resolved_one(input: &str) -> Node<rlsp_yaml_parser::Span> {
let docs = LoaderBuilder::new()
.resolved()
.build()
.load(input)
.expect("load failed");
assert_eq!(docs.len(), 1, "expected 1 document, got {}", docs.len());
docs.into_iter().next().unwrap().root
}
fn scalar_value(node: &Node<rlsp_yaml_parser::Span>) -> &str {
match node {
Node::Scalar { value, .. } => value.as_str(),
Node::Mapping { .. } | Node::Sequence { .. } | Node::Alias { .. } => {
panic!("expected Scalar, got: {node:?}")
}
}
}
#[test]
fn spike_plain_scalar_loads() {
let docs = load("hello\n").expect("load failed");
assert_eq!(docs.len(), 1);
assert!(
matches!(&docs[0].root, Node::Scalar { value, .. } if value == "hello"),
"got: {:?}",
&docs[0].root
);
}
#[rstest]
#[case::plain_scalar("hello\n", "hello")]
#[case::integer_scalar("42\n", "42")]
fn plain_scalar_value_is_correct(#[case] input: &str, #[case] expected: &str) {
let node = load_one(input);
assert!(
matches!(&node, Node::Scalar { value, .. } if value == expected),
"got: {node:?}"
);
}
#[test]
fn it_3_empty_document_has_empty_scalar_root() {
let docs = load("---\n...\n").expect("load failed");
assert_eq!(docs.len(), 1);
assert!(
matches!(&docs[0].root, Node::Scalar { value, .. } if value.is_empty()),
"got: {:?}",
&docs[0].root
);
}
#[test]
fn it_4_block_mapping_has_correct_entries() {
let node = load_one("{name: Alice, age: 30}\n");
assert!(
matches!(&node, Node::Mapping { entries, .. } if entries.len() == 2),
"got: {node:?}"
);
}
#[test]
fn it_5_block_sequence_has_correct_items() {
let node = load_one("- alpha\n- beta\n- gamma\n");
assert!(
matches!(&node, Node::Sequence { items, .. } if items.len() == 3),
"got: {node:?}"
);
}
#[test]
fn it_6_nested_mapping_inside_sequence() {
let node = load_one("- {name: Alice, age: 30}\n");
let Node::Sequence { items, .. } = node else {
panic!("expected Sequence");
};
assert!(
matches!(&items[0], Node::Mapping { .. }),
"got: {:?}",
&items[0]
);
}
#[test]
fn it_7_multi_document_produces_two_documents() {
let docs = load("doc1\n---\ndoc2\n").expect("load failed");
assert_eq!(docs.len(), 2);
}
#[test]
fn it_8_leading_comment_is_not_a_node_in_ast() {
let node = load_one("# top comment\nhello\n");
assert!(
matches!(&node, Node::Scalar { value, .. } if value == "hello"),
"expected scalar 'hello', got: {node:?}"
);
}
#[test]
fn it_9_inline_comment_does_not_corrupt_scalar() {
let node = load_one("hello # inline comment\n");
let value = scalar_value(&node);
assert!(
!value.contains("inline comment"),
"comment text should not be in scalar value; got: {value:?}"
);
}
#[test]
fn it_10_comment_between_mapping_entries_is_skipped() {
let node = load_one("a: 1\n# comment\nb: 2\n");
assert!(
matches!(&node, Node::Mapping { entries, .. } if entries.len() == 2),
"expected 2-entry mapping, got: {node:?}"
);
}
#[test]
fn it_11_comment_between_sequence_items_is_skipped() {
let node = load_one("- a\n# comment\n- b\n");
assert!(
matches!(&node, Node::Sequence { items, .. } if items.len() == 2),
"expected 2-item sequence, got: {node:?}"
);
}
#[test]
fn it_12_anchor_on_scalar_is_preserved_in_lossless_mode() {
let node = load_one("- &ref shared\n- *ref\n");
let Node::Sequence { items, .. } = node else {
panic!("expected Sequence");
};
assert_eq!(items.len(), 2);
assert!(
matches!(&items[0], Node::Scalar { anchor: Some(a), value, .. } if a == "ref" && value == "shared"),
"got: {:?}",
&items[0]
);
assert!(
matches!(&items[1], Node::Alias { name, .. } if name == "ref"),
"got: {:?}",
&items[1]
);
}
#[test]
fn it_13_alias_expands_to_anchored_scalar_in_resolved_mode() {
let node = load_resolved_one("- &ref shared\n- *ref\n");
let Node::Sequence { items, .. } = node else {
panic!("expected Sequence");
};
assert_eq!(items.len(), 2);
assert!(
matches!(&items[0], Node::Scalar { value, .. } if value == "shared"),
"got: {:?}",
&items[0]
);
assert!(
matches!(&items[1], Node::Scalar { value, .. } if value == "shared"),
"got: {:?}",
&items[1]
);
}
#[test]
fn it_14_anchor_on_mapping_is_registered() {
let node = load_one("base: &base\n x: 1\nref: *base\n");
let Node::Mapping { entries, .. } = node else {
panic!("expected Mapping");
};
let base_entry = entries.iter().find(|(k, _)| scalar_value(k) == "base");
assert!(base_entry.is_some(), "key 'base' not found");
let (_, val) = base_entry.unwrap();
assert!(
matches!(val, Node::Mapping { anchor: Some(a), .. } if a == "base"),
"got: {val:?}"
);
}
#[test]
fn it_15_alias_expands_to_anchored_mapping_in_resolved_mode() {
let node = LoaderBuilder::new()
.resolved()
.build()
.load("- &b\n x: 1\n- *b\n")
.expect("load failed");
let Node::Sequence { items, .. } = &node[0].root else {
panic!("expected Sequence");
};
assert_eq!(items.len(), 2);
assert!(
matches!(&items[1], Node::Mapping { entries, .. } if entries.len() == 1),
"got: {:?}",
&items[1]
);
}
#[test]
fn it_16_undefined_alias_in_resolved_mode_returns_error() {
let result = LoaderBuilder::new()
.resolved()
.build()
.load("val: *missing\n");
assert!(result.is_err(), "expected Err");
assert!(
matches!(result.unwrap_err(), LoadError::UndefinedAlias { name } if name == "missing"),
"expected UndefinedAlias for 'missing'"
);
}
#[test]
fn it_17_tag_directives_captured_on_document() {
let docs = load("%TAG !foo! tag:example.com,2026:\n---\nhello\n").expect("load failed");
assert_eq!(docs.len(), 1);
assert!(
docs[0]
.tags
.iter()
.any(|(h, p)| h == "!foo!" && p.contains("tag:example.com")),
"expected tag directive pair; tags: {:?}",
docs[0].tags
);
}
#[test]
fn it_18_version_directive_captured_on_document() {
let docs = load("%YAML 1.2\n---\nhello\n").expect("load failed");
assert_eq!(docs.len(), 1);
assert_eq!(docs[0].version, Some((1, 2)));
}
#[test]
fn it_19_parse_error_propagates_as_load_error_parse() {
let result = load("\t invalid\n");
if let Err(e) = result {
assert!(
matches!(e, LoadError::Parse { .. }),
"expected LoadError::Parse, got: {e:?}"
);
}
}
#[test]
fn it_rt_1_scalar_hello() {
let docs = load("hello\n").expect("load");
assert_eq!(docs.len(), 1);
assert!(matches!(&docs[0].root, Node::Scalar { value, .. } if value == "hello"));
}
#[test]
fn it_rt_2_mapping_with_two_pairs() {
let node = load_one("{name: Alice, age: 30}\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping");
};
assert_eq!(entries.len(), 2);
assert!(matches!(&entries[0].0, Node::Scalar { value, .. } if value == "name"));
assert!(matches!(&entries[0].1, Node::Scalar { value, .. } if value == "Alice"));
assert!(matches!(&entries[1].0, Node::Scalar { value, .. } if value == "age"));
assert!(matches!(&entries[1].1, Node::Scalar { value, .. } if value == "30"));
}
#[test]
fn it_rt_3_sequence_with_three_scalars() {
let node = load_one("- alpha\n- beta\n- gamma\n");
let Node::Sequence { items, .. } = &node else {
panic!("expected Sequence");
};
assert_eq!(items.len(), 3);
assert_eq!(scalar_value(&items[0]), "alpha");
assert_eq!(scalar_value(&items[1]), "beta");
assert_eq!(scalar_value(&items[2]), "gamma");
}
#[test]
fn it_rt_4_flow_mapping_two_pairs() {
let node = load_one("{a: 1, b: 2}\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping");
};
assert_eq!(entries.len(), 2);
assert_eq!(scalar_value(&entries[0].0), "a");
assert_eq!(scalar_value(&entries[1].0), "b");
}
#[test]
fn it_rt_5_flow_sequence_three_scalars() {
let node = load_one("[alpha, beta, gamma]\n");
let Node::Sequence { items, .. } = &node else {
panic!("expected Sequence");
};
assert_eq!(items.len(), 3);
assert_eq!(scalar_value(&items[0]), "alpha");
assert_eq!(scalar_value(&items[1]), "beta");
assert_eq!(scalar_value(&items[2]), "gamma");
}
#[test]
fn it_rt_6_empty_flow_mapping() {
let node = load_one("{}\n");
assert!(
matches!(&node, Node::Mapping { entries, .. } if entries.is_empty()),
"got: {node:?}"
);
}
#[test]
fn it_rt_7_empty_flow_sequence() {
let node = load_one("[]\n");
assert!(
matches!(&node, Node::Sequence { items, .. } if items.is_empty()),
"got: {node:?}"
);
}
#[test]
fn it_rt_8_deeply_nested_leaf_scalar() {
let node = load_one("{a: {b: {c: leaf}}}\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping");
};
let (_, b_node) = &entries[0];
let Node::Mapping {
entries: b_entries, ..
} = b_node
else {
panic!("expected nested Mapping for a's value; got: {b_node:?}");
};
let (_, c_node) = &b_entries[0];
let Node::Mapping {
entries: c_entries, ..
} = c_node
else {
panic!("expected nested Mapping for b's value; got: {c_node:?}");
};
let (_, leaf_node) = &c_entries[0];
assert!(
matches!(leaf_node, Node::Scalar { value, .. } if value == "leaf"),
"got: {leaf_node:?}"
);
}
#[test]
fn it_rt_9_anchored_node_preserves_anchor() {
let node = load_one("- &ref shared\n- *ref\n");
let Node::Sequence { items, .. } = &node else {
panic!("expected Sequence");
};
assert!(
matches!(&items[0], Node::Scalar { anchor: Some(a), .. } if a == "ref"),
"got: {:?}",
&items[0]
);
}
#[test]
fn it_rt_10_two_documents_correct_values() {
let docs = load("doc1\n---\ndoc2\n").expect("load");
assert_eq!(docs.len(), 2);
assert!(matches!(&docs[0].root, Node::Scalar { value, .. } if value == "doc1"));
assert!(matches!(&docs[1].root, Node::Scalar { value, .. } if value == "doc2"));
}
#[test]
fn it_rt_11_literal_block_scalar_contains_lines() {
let node = load_one("|\n line1\n line2\n");
let value = scalar_value(&node);
assert!(
value.contains("line1"),
"expected 'line1' in value; got: {value:?}"
);
assert!(
value.contains("line2"),
"expected 'line2' in value; got: {value:?}"
);
}
#[test]
fn it_rt_12_large_mapping_count() {
let mut yaml = String::new();
for i in 0..1000 {
use std::fmt::Write as _;
let _ = writeln!(yaml, "key{i}: val{i}");
}
let node = load_one(&yaml);
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping");
};
assert_eq!(entries.len(), 1000);
assert_eq!(scalar_value(&entries[0].0), "key0");
assert_eq!(scalar_value(&entries[0].1), "val0");
assert_eq!(scalar_value(&entries[999].0), "key999");
assert_eq!(scalar_value(&entries[999].1), "val999");
}
#[test]
fn inline_anchor_before_key_annotates_key_scalar_root() {
let node = load_one("&anchor key: value\n");
let Node::Mapping {
entries, anchor, ..
} = &node
else {
panic!("expected Mapping root, got: {node:?}");
};
assert!(
anchor.is_none(),
"mapping must have no anchor; got: {anchor:?}"
);
assert_eq!(entries.len(), 1, "expected 1 entry; got: {}", entries.len());
let (k, v) = &entries[0];
let Node::Scalar {
value: kv,
anchor: ka,
..
} = k
else {
panic!("key must be Scalar; got: {k:?}");
};
assert_eq!(kv.as_str(), "key");
assert_eq!(
ka.as_deref(),
Some("anchor"),
"anchor must be on key scalar; got: {ka:?}"
);
assert_eq!(scalar_value(v), "value");
}
#[test]
fn inline_anchor_before_key_annotates_key_scalar_indented() {
let node = load_one("outer:\n &anchor inner_key: inner_value\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root");
};
assert_eq!(entries.len(), 1);
let (_, v) = &entries[0];
let Node::Mapping {
entries: inner,
anchor,
..
} = v
else {
panic!("expected inner Mapping; got: {v:?}");
};
assert!(
anchor.is_none(),
"inner mapping must have no anchor; got: {anchor:?}"
);
assert_eq!(
inner.len(),
1,
"expected 1 inner entry; got: {}",
inner.len()
);
let (ik, iv) = &inner[0];
let Node::Scalar {
value: ikv,
anchor: ika,
..
} = ik
else {
panic!("inner key must be Scalar; got: {ik:?}");
};
assert_eq!(ikv.as_str(), "inner_key");
assert_eq!(
ika.as_deref(),
Some("anchor"),
"anchor must be on inner key scalar; got: {ika:?}"
);
assert_eq!(scalar_value(iv), "inner_value");
}
#[test]
fn inline_tag_before_key_annotates_key_scalar() {
let node = load_one("!!str key: value\n");
let Node::Mapping { entries, tag, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert!(tag.is_none(), "mapping must have no tag; got: {tag:?}");
assert_eq!(entries.len(), 1, "expected 1 entry; got: {}", entries.len());
let (k, v) = &entries[0];
let Node::Scalar {
value: kv, tag: kt, ..
} = k
else {
panic!("key must be Scalar; got: {k:?}");
};
assert_eq!(kv.as_str(), "key");
assert!(
kt.as_deref().is_some_and(|t| t.contains("str")),
"tag must be on key scalar; got: {kt:?}"
);
assert_eq!(scalar_value(v), "value");
}
#[test]
fn inline_anchor_and_tag_before_key_annotate_key_scalar() {
let node = load_one("&a !!str key: value\n");
let Node::Mapping {
entries,
anchor,
tag,
..
} = &node
else {
panic!("expected Mapping root; got: {node:?}");
};
assert!(
anchor.is_none(),
"mapping must have no anchor; got: {anchor:?}"
);
assert!(tag.is_none(), "mapping must have no tag; got: {tag:?}");
assert_eq!(entries.len(), 1, "expected 1 entry; got: {}", entries.len());
let (k, v) = &entries[0];
let Node::Scalar {
value: kv,
anchor: ka,
tag: kt,
..
} = k
else {
panic!("key must be Scalar; got: {k:?}");
};
assert_eq!(kv.as_str(), "key");
assert_eq!(
ka.as_deref(),
Some("a"),
"anchor must be on key scalar; got: {ka:?}"
);
assert!(
kt.as_deref().is_some_and(|t| t.contains("str")),
"tag must be on key scalar; got: {kt:?}"
);
assert_eq!(scalar_value(v), "value");
}
#[test]
fn standalone_anchor_before_mapping_annotates_mapping() {
let node = load_one("&anchor\nkey: value\n");
let Node::Mapping {
entries, anchor, ..
} = &node
else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
anchor.as_deref(),
Some("anchor"),
"anchor must be on mapping; got: {anchor:?}"
);
assert_eq!(entries.len(), 1, "expected 1 entry; got: {}", entries.len());
let (k, _) = &entries[0];
let Node::Scalar {
value: kv,
anchor: ka,
..
} = k
else {
panic!("key must be Scalar; got: {k:?}");
};
assert_eq!(kv.as_str(), "key");
assert!(ka.is_none(), "key scalar must have no anchor; got: {ka:?}");
}
#[test]
fn inline_anchor_on_one_key_in_multi_entry_mapping() {
let node = load_one("a: 1\n&anchor b: 2\nc: 3\n");
let Node::Mapping {
entries, anchor, ..
} = &node
else {
panic!("expected Mapping root; got: {node:?}");
};
assert!(
anchor.is_none(),
"mapping must have no anchor; got: {anchor:?}"
);
assert_eq!(
entries.len(),
3,
"expected 3 entries; got: {}",
entries.len()
);
let (k0, _) = &entries[0];
let Node::Scalar { anchor: a0, .. } = k0 else {
panic!("expected Scalar key 0");
};
assert!(a0.is_none(), "key 'a' must have no anchor; got: {a0:?}");
let (k1, v1) = &entries[1];
let Node::Scalar {
value: kv1,
anchor: a1,
..
} = k1
else {
panic!("expected Scalar key 1");
};
assert_eq!(kv1.as_str(), "b");
assert_eq!(
a1.as_deref(),
Some("anchor"),
"anchor must be on key 'b'; got: {a1:?}"
);
assert_eq!(scalar_value(v1), "2");
let (k2, _) = &entries[2];
let Node::Scalar { anchor: a2, .. } = k2 else {
panic!("expected Scalar key 2");
};
assert!(a2.is_none(), "key 'c' must have no anchor; got: {a2:?}");
}
#[test]
fn inline_anchor_produces_no_phantom_nesting() {
let node = load_one("&anchor key: value\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root");
};
assert_eq!(
entries.len(),
1,
"must have exactly 1 entry (no phantom nested mapping); got: {}",
entries.len()
);
let (_, v) = &entries[0];
assert!(
matches!(v, Node::Scalar { .. }),
"value must be a plain scalar, not a nested mapping; got: {v:?}"
);
}
#[test]
fn anchor_before_mapping_key_is_usable_as_alias() {
let node = load_resolved_one("&anchor key: value\nref: *anchor\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
entries.len(),
2,
"expected 2 entries; got: {}",
entries.len()
);
let (k0, v0) = &entries[0];
assert_eq!(scalar_value(k0), "key", "entry 0 key must be 'key'");
assert_eq!(scalar_value(v0), "value", "entry 0 value must be 'value'");
let (k1, v1) = &entries[1];
assert_eq!(scalar_value(k1), "ref", "entry 1 key must be 'ref'");
assert_eq!(
scalar_value(v1),
"key",
"alias *anchor must resolve to the anchored key scalar value 'key'; got: {v1:?}"
);
}
#[test]
fn multiple_anchored_keys_in_same_mapping() {
let node = load_one("&a one: 1\n&b two: 2\n&c three: 3\n");
let Node::Mapping {
entries, anchor, ..
} = &node
else {
panic!("expected Mapping root; got: {node:?}");
};
assert!(
anchor.is_none(),
"mapping must have no anchor; got: {anchor:?}"
);
assert_eq!(
entries.len(),
3,
"expected 3 entries; got: {}",
entries.len()
);
let expected = [("one", "a"), ("two", "b"), ("three", "c")];
for (i, (exp_val, exp_anchor)) in expected.iter().enumerate() {
let (k, _) = &entries[i];
let Node::Scalar {
value: kv,
anchor: ka,
..
} = k
else {
panic!("entry {i} key must be Scalar; got: {k:?}");
};
assert_eq!(kv.as_str(), *exp_val, "entry {i} key value");
assert_eq!(
ka.as_deref(),
Some(*exp_anchor),
"entry {i} key anchor must be '{exp_anchor}'; got: {ka:?}"
);
}
}
#[test]
fn inline_anchor_before_value_scalar_annotates_value() {
let node = load_one("key: &anchor value\n");
let Node::Mapping {
entries, anchor, ..
} = &node
else {
panic!("expected Mapping root; got: {node:?}");
};
assert!(
anchor.is_none(),
"mapping must have no anchor; got: {anchor:?}"
);
assert_eq!(entries.len(), 1);
let (k, v) = &entries[0];
let Node::Scalar {
value: kv,
anchor: ka,
..
} = k
else {
panic!("key must be Scalar; got: {k:?}");
};
assert_eq!(kv.as_str(), "key");
assert!(ka.is_none(), "key scalar must have no anchor; got: {ka:?}");
let Node::Scalar {
value: vv,
anchor: va,
..
} = v
else {
panic!("value must be Scalar; got: {v:?}");
};
assert_eq!(vv.as_str(), "value");
assert_eq!(
va.as_deref(),
Some("anchor"),
"anchor must be on value scalar; got: {va:?}"
);
}
#[test]
fn it_j1_trailing_comment_on_mapping_value_is_attached() {
let node = load_one("key: value # my comment\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 1);
let (_, v) = &entries[0];
let tc = v
.trailing_comment()
.expect("trailing comment must be attached to value node");
assert!(
tc.contains("my comment"),
"trailing comment must contain 'my comment'; got: {tc:?}"
);
}
#[test]
fn it_j2_trailing_comment_text_includes_hash() {
let node = load_one("key: value # my comment\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
let (_, v) = &entries[0];
let tc = v
.trailing_comment()
.expect("trailing comment must be attached");
assert!(
tc.starts_with('#'),
"trailing comment must start with '#'; got: {tc:?}"
);
}
#[test]
fn quoted_key_double_quoted_simple() {
let docs = load("\"key\": value\n").expect("load failed");
assert_eq!(docs.len(), 1);
let Node::Mapping { entries, .. } = &docs[0].root else {
panic!("expected Mapping, got: {:?}", docs[0].root);
};
assert_eq!(entries.len(), 1);
let (k, v) = &entries[0];
assert!(
matches!(k, Node::Scalar { value, style, .. }
if value == "key" && *style == ScalarStyle::DoubleQuoted),
"key must be decoded with DoubleQuoted style, got: {k:?}"
);
assert!(
matches!(v, Node::Scalar { value, .. } if value == "value"),
"val: {v:?}"
);
}
#[test]
fn quoted_key_single_quoted_simple() {
let docs = load("'key': value\n").expect("load failed");
assert_eq!(docs.len(), 1);
let Node::Mapping { entries, .. } = &docs[0].root else {
panic!("expected Mapping, got: {:?}", docs[0].root);
};
assert_eq!(entries.len(), 1);
let (k, v) = &entries[0];
assert!(
matches!(k, Node::Scalar { value, style, .. }
if value == "key" && *style == ScalarStyle::SingleQuoted),
"key must be decoded with SingleQuoted style, got: {k:?}"
);
assert!(
matches!(v, Node::Scalar { value, .. } if value == "value"),
"val: {v:?}"
);
}
#[test]
fn quoted_key_double_quoted_with_escape_sequence() {
let docs = load("\"ke\\ty\": value\n").expect("load failed");
assert_eq!(docs.len(), 1);
let Node::Mapping { entries, .. } = &docs[0].root else {
panic!("expected Mapping, got: {:?}", docs[0].root);
};
assert_eq!(entries.len(), 1);
let (k, _) = &entries[0];
assert!(
matches!(k, Node::Scalar { value, style, .. }
if value == "ke\ty" && *style == ScalarStyle::DoubleQuoted),
"key escape must be decoded and style DoubleQuoted, got: {k:?}"
);
}
#[test]
fn quoted_key_single_quoted_with_escaped_quote() {
let docs = load("'it''s': value\n").expect("load failed");
assert_eq!(docs.len(), 1);
let Node::Mapping { entries, .. } = &docs[0].root else {
panic!("expected Mapping, got: {:?}", docs[0].root);
};
assert_eq!(entries.len(), 1);
let (k, _) = &entries[0];
assert!(
matches!(k, Node::Scalar { value, style, .. }
if value == "it's" && *style == ScalarStyle::SingleQuoted),
"single-quoted key escape must be decoded and style SingleQuoted, got: {k:?}"
);
}
#[test]
fn quoted_key_with_spaces_inside() {
let docs = load("\"hello world\": value\n").expect("load failed");
assert_eq!(docs.len(), 1);
let Node::Mapping { entries, .. } = &docs[0].root else {
panic!("expected Mapping, got: {:?}", docs[0].root);
};
assert_eq!(entries.len(), 1);
let (k, _) = &entries[0];
assert!(
matches!(k, Node::Scalar { value, style, .. }
if value == "hello world" && *style == ScalarStyle::DoubleQuoted),
"spaces inside quoted key must be preserved, got: {k:?}"
);
}
#[test]
fn quoted_key_double_quoted_empty() {
let docs = load("\"\": value\n").expect("load failed");
assert_eq!(docs.len(), 1);
let Node::Mapping { entries, .. } = &docs[0].root else {
panic!("expected Mapping, got: {:?}", docs[0].root);
};
assert_eq!(entries.len(), 1);
let (k, _) = &entries[0];
assert!(
matches!(k, Node::Scalar { value, style, .. }
if value.is_empty() && *style == ScalarStyle::DoubleQuoted),
"empty quoted key must decode to empty string with DoubleQuoted style, got: {k:?}"
);
}
#[test]
fn quoted_key_in_nested_mapping() {
let docs = load("outer:\n \"inner key\": inner value\n").expect("load failed");
assert_eq!(docs.len(), 1);
let Node::Mapping { entries, .. } = &docs[0].root else {
panic!("expected Mapping, got: {:?}", docs[0].root);
};
assert_eq!(entries.len(), 1);
let (k, v) = &entries[0];
assert!(
matches!(k, Node::Scalar { value, .. } if value == "outer"),
"outer key: {k:?}"
);
let Node::Mapping { entries: inner, .. } = v else {
panic!("expected nested Mapping, got: {v:?}");
};
assert_eq!(inner.len(), 1);
let (ik, _) = &inner[0];
assert!(
matches!(ik, Node::Scalar { value, style, .. }
if value == "inner key" && *style == ScalarStyle::DoubleQuoted),
"nested quoted key must be decoded, got: {ik:?}"
);
}
#[test]
fn quoted_key_multiple_entries_mixed() {
let docs = load("plain_key: 1\n\"quoted_key\": 2\n'another': 3\n").expect("load failed");
assert_eq!(docs.len(), 1);
let Node::Mapping { entries, .. } = &docs[0].root else {
panic!("expected Mapping, got: {:?}", docs[0].root);
};
assert_eq!(entries.len(), 3);
let (k0, _) = &entries[0];
assert!(
matches!(k0, Node::Scalar { value, style, .. }
if value == "plain_key" && *style == ScalarStyle::Plain),
"entry 0 key: {k0:?}"
);
let (k1, _) = &entries[1];
assert!(
matches!(k1, Node::Scalar { value, style, .. }
if value == "quoted_key" && *style == ScalarStyle::DoubleQuoted),
"entry 1 key: {k1:?}"
);
let (k2, _) = &entries[2];
assert!(
matches!(k2, Node::Scalar { value, style, .. }
if value == "another" && *style == ScalarStyle::SingleQuoted),
"entry 2 key: {k2:?}"
);
}
#[test]
fn it_j3_trailing_comment_on_sequence_item_is_attached() {
let node = load_one("- item # seq comment\n");
let Node::Sequence { items, .. } = &node else {
panic!("expected Sequence root; got: {node:?}");
};
assert_eq!(items.len(), 1);
let tc = items[0]
.trailing_comment()
.expect("trailing comment must be attached to sequence item");
assert!(
tc.contains("seq comment"),
"trailing comment must contain 'seq comment'; got: {tc:?}"
);
}
#[test]
fn it_j4_no_trailing_comment_when_comment_on_next_line() {
let node = load_one("key: value\n# next line comment\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 1);
let (_, v) = &entries[0];
assert_eq!(
v.trailing_comment(),
None,
"next-line comment must NOT be captured as trailing; got: {:?}",
v.trailing_comment()
);
}
#[test]
fn it_j5_leading_comment_still_works_after_fix() {
let node = load_one("a: 1\n# header\nb: 2\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 2);
let (k, _) = &entries[1];
let lc = k.leading_comments();
assert!(
!lc.is_empty(),
"leading comment must be attached to second mapping key; got empty"
);
assert!(
lc.iter().any(|c| c.contains("header")),
"leading comments must contain 'header'; got: {lc:?}"
);
}
#[test]
fn it_j6_multiple_trailing_comments_separate_entries() {
let node = load_one("a: 1 # first\nb: 2 # second\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 2);
let (_, v0) = &entries[0];
let tc0 = v0
.trailing_comment()
.expect("entry 0 value must have trailing comment");
assert!(
tc0.contains("first"),
"entry 0 trailing comment must contain 'first'; got: {tc0:?}"
);
let (_, v1) = &entries[1];
let tc1 = v1
.trailing_comment()
.expect("entry 1 value must have trailing comment");
assert!(
tc1.contains("second"),
"entry 1 trailing comment must contain 'second'; got: {tc1:?}"
);
}
#[test]
fn it_j7_trailing_comment_with_special_chars() {
let node = load_one("key: value # comment: with: colons\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 1);
let (_, v) = &entries[0];
let tc = v
.trailing_comment()
.expect("trailing comment must be attached");
assert!(
tc.contains("comment: with: colons"),
"trailing comment must contain colons verbatim; got: {tc:?}"
);
}
#[test]
fn it_j8_no_trailing_comment_on_collection_node() {
let node = load_one("map:\n a: 1\n b: 2\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 1);
let (_, v) = &entries[0];
assert!(
matches!(
v,
Node::Mapping {
trailing_comment: None,
..
}
),
"multi-line mapping value must have no trailing comment; got: {v:?}"
);
}
#[test]
fn cs_1_block_sequence_has_block_style() {
let node = load_one("- a\n- b\n");
let Node::Sequence { style, items, .. } = &node else {
panic!("expected Sequence; got: {node:?}");
};
assert_eq!(*style, CollectionStyle::Block);
assert_eq!(items.len(), 2);
}
#[test]
fn cs_2_flow_sequence_has_flow_style() {
let node = load_one("[a, b]");
let Node::Sequence { style, items, .. } = &node else {
panic!("expected Sequence; got: {node:?}");
};
assert_eq!(*style, CollectionStyle::Flow);
assert_eq!(items.len(), 2);
}
#[test]
fn cs_3_block_mapping_has_block_style() {
let node = load_one("a: 1\nb: 2\n");
let Node::Mapping { style, entries, .. } = &node else {
panic!("expected Mapping; got: {node:?}");
};
assert_eq!(*style, CollectionStyle::Block);
assert_eq!(entries.len(), 2);
}
#[test]
fn cs_4_flow_mapping_has_flow_style() {
let node = load_one("{a: 1, b: 2}");
let Node::Mapping { style, entries, .. } = &node else {
panic!("expected Mapping; got: {node:?}");
};
assert_eq!(*style, CollectionStyle::Flow);
assert_eq!(entries.len(), 2);
}
#[test]
fn cs_5_nested_flow_sequence_inside_block_mapping() {
let node = load_one("items: [x, y]\n");
let Node::Mapping { style, entries, .. } = &node else {
panic!("expected Mapping; got: {node:?}");
};
assert_eq!(*style, CollectionStyle::Block);
let (_, val) = &entries[0];
let Node::Sequence {
style: seq_style, ..
} = val
else {
panic!("expected Sequence value; got: {val:?}");
};
assert_eq!(*seq_style, CollectionStyle::Flow);
}
#[test]
fn cs_6_nested_block_mapping_inside_flow_sequence() {
let node = load_one("[{a: 1}]");
let Node::Sequence { style, items, .. } = &node else {
panic!("expected Sequence; got: {node:?}");
};
assert_eq!(*style, CollectionStyle::Flow);
let Node::Mapping {
style: map_style, ..
} = &items[0]
else {
panic!("expected Mapping item; got: {:?}", items[0]);
};
assert_eq!(*map_style, CollectionStyle::Flow);
}
#[test]
fn explicit_key_sequence_item_correct_entry_count() {
let node = load_one("- ? : x\n");
let Node::Sequence { items, .. } = &node else {
panic!("expected Sequence root; got: {node:?}");
};
assert_eq!(items.len(), 1, "expected 1 item; got {}", items.len());
let Node::Mapping { entries, .. } = &items[0] else {
panic!("expected Mapping item; got: {:?}", items[0]);
};
assert_eq!(
entries.len(),
1,
"expected 1 mapping entry; got {}",
entries.len()
);
let (k, _v) = &entries[0];
let Node::Mapping { entries: inner, .. } = k else {
panic!("key must be inner Mapping (complex key); got: {k:?}");
};
assert_eq!(inner.len(), 1, "inner mapping must have 1 entry");
let (ik, iv) = &inner[0];
assert_eq!(
scalar_value(ik),
"",
"inner key must be empty string; got: {ik:?}"
);
assert_eq!(
scalar_value(iv),
"x",
"inner value must be 'x'; got: {iv:?}"
);
}
#[test]
fn explicit_key_sequence_item_inline_key_content() {
let node = load_one("- ? k\n : v\n");
let Node::Sequence { items, .. } = &node else {
panic!("expected Sequence root; got: {node:?}");
};
assert_eq!(items.len(), 1);
let Node::Mapping { entries, .. } = &items[0] else {
panic!("expected Mapping item; got: {:?}", items[0]);
};
assert_eq!(entries.len(), 1);
let (k, v) = &entries[0];
assert_eq!(scalar_value(k), "k");
assert_eq!(scalar_value(v), "v");
}
#[test]
fn explicit_key_two_sequence_items_correct_count() {
let node = load_one("- ? a\n : 1\n- ? b\n : 2\n");
let Node::Sequence { items, .. } = &node else {
panic!("expected Sequence root; got: {node:?}");
};
assert_eq!(
items.len(),
2,
"expected 2 sequence items; got {}",
items.len()
);
for (idx, item) in items.iter().enumerate() {
let Node::Mapping { entries, .. } = item else {
panic!("item {idx} must be Mapping; got: {item:?}");
};
assert_eq!(
entries.len(),
1,
"item {idx} mapping must have 1 entry; got {}",
entries.len()
);
}
}
#[test]
fn explicit_key_sequence_item_no_value() {
let node = load_one("- ?\n");
let Node::Sequence { items, .. } = &node else {
panic!("expected Sequence root; got: {node:?}");
};
assert_eq!(items.len(), 1, "expected 1 sequence item");
assert!(
matches!(&items[0], Node::Mapping { .. }),
"bare ? sequence item must be a Mapping; got: {:?}",
items[0]
);
}
#[test]
fn explicit_key_zero_indented_sequence_as_value() {
let node = load_one("?\n- a\n- b\n:\n- c\n- d\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
entries.len(),
1,
"expected 1 mapping entry; got {}",
entries.len()
);
let (k, v) = &entries[0];
let Node::Sequence { items: k_items, .. } = k else {
panic!("key must be Sequence; got: {k:?}");
};
assert_eq!(
k_items.len(),
2,
"key sequence must have 2 items; got {}",
k_items.len()
);
assert_eq!(scalar_value(&k_items[0]), "a");
assert_eq!(scalar_value(&k_items[1]), "b");
let Node::Sequence { items: v_items, .. } = v else {
panic!("value must be Sequence; got: {v:?}");
};
assert_eq!(
v_items.len(),
2,
"value sequence must have 2 items; got {}",
v_items.len()
);
assert_eq!(scalar_value(&v_items[0]), "c");
assert_eq!(scalar_value(&v_items[1]), "d");
}
#[test]
fn explicit_key_zero_indented_sequence_as_key_no_phantom_empty_key() {
let node = load_one("?\n- seq1\n:\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
entries.len(),
1,
"must be exactly 1 entry — no phantom empty key; got {}",
entries.len()
);
let (k, _) = &entries[0];
let Node::Sequence { items, .. } = k else {
panic!("key must be Sequence; got: {k:?}");
};
assert_eq!(
items.len(),
1,
"key sequence must have 1 item; got {}",
items.len()
);
}
#[test]
fn mapping_entry_following_zero_indented_sequence_key() {
let node = load_one("?\n- seq1\n: v1\nnext: v2\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
entries.len(),
2,
"expected 2 entries; got {}",
entries.len()
);
let (_, v0) = &entries[0];
assert_eq!(
scalar_value(v0),
"v1",
"first value must be 'v1'; got: {v0:?}"
);
let (k1, v1) = &entries[1];
assert_eq!(scalar_value(k1), "next");
assert_eq!(scalar_value(v1), "v2");
}
#[test]
fn explicit_key_two_entries_root_mapping() {
let node = load_one("? a\n: 1\n? b\n: 2\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
entries.len(),
2,
"expected 2 entries; got {}",
entries.len()
);
let (k0, v0) = &entries[0];
assert_eq!(scalar_value(k0), "a");
assert_eq!(scalar_value(v0), "1");
let (k1, v1) = &entries[1];
assert_eq!(scalar_value(k1), "b");
assert_eq!(scalar_value(v1), "2");
}
#[test]
fn explicit_key_three_entries_no_phantom() {
let node = load_one("? x\n: 10\n? y\n: 20\n? z\n: 30\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
entries.len(),
3,
"expected 3 entries; got {}",
entries.len()
);
}
#[test]
fn explicit_key_mixed_with_implicit_keys() {
let node = load_one("plain: 0\n? expl\n: 1\nplain2: 2\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
entries.len(),
3,
"expected 3 entries; got {}",
entries.len()
);
let (k0, v0) = &entries[0];
assert_eq!(scalar_value(k0), "plain");
assert_eq!(scalar_value(v0), "0");
let (k1, v1) = &entries[1];
assert_eq!(scalar_value(k1), "expl");
assert_eq!(scalar_value(v1), "1");
let (k2, v2) = &entries[2];
assert_eq!(scalar_value(k2), "plain2");
assert_eq!(scalar_value(v2), "2");
}
#[test]
fn empty_key_with_comment_on_indicator_line() {
let node = load_one("? # this is a comment\n: value\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 1, "expected 1 entry; got {}", entries.len());
let (k, v) = &entries[0];
assert_eq!(scalar_value(k), "", "key must be empty string; got: {k:?}");
assert_eq!(
scalar_value(v),
"value",
"value must be 'value'; got: {v:?}"
);
}
#[test]
fn empty_key_with_comment_no_value() {
let docs = load("? # comment\n").expect("load failed");
assert_eq!(docs.len(), 1);
assert!(
matches!(&docs[0].root, Node::Mapping { .. }),
"bare ? with comment must produce a Mapping root; got: {:?}",
docs[0].root
);
}
#[test]
fn multiple_empty_keys_with_comments() {
let node = load_one("? # first\n: a\n? # second\n: b\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
entries.len(),
2,
"expected 2 entries; got {}",
entries.len()
);
let (k0, v0) = &entries[0];
assert_eq!(scalar_value(k0), "");
assert_eq!(scalar_value(v0), "a");
let (k1, v1) = &entries[1];
assert_eq!(scalar_value(k1), "");
assert_eq!(scalar_value(v1), "b");
}
#[test]
fn implicit_empty_key_colon_value() {
let node = load_one(": value\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 1, "expected 1 entry; got {}", entries.len());
let (k, v) = &entries[0];
assert_eq!(scalar_value(k), "", "key must be empty string; got: {k:?}");
assert_eq!(scalar_value(v), "value");
}
#[test]
fn multi_doc_explicit_key_each_doc_has_one_entry() {
let docs = load("? a\n: 1\n---\n? b\n: 2\n").expect("load failed");
assert_eq!(docs.len(), 2, "expected 2 documents; got {}", docs.len());
let Node::Mapping { entries: e0, .. } = &docs[0].root else {
panic!("doc 0 root must be Mapping; got: {:?}", docs[0].root);
};
assert_eq!(e0.len(), 1);
assert_eq!(scalar_value(&e0[0].0), "a");
assert_eq!(scalar_value(&e0[0].1), "1");
let Node::Mapping { entries: e1, .. } = &docs[1].root else {
panic!("doc 1 root must be Mapping; got: {:?}", docs[1].root);
};
assert_eq!(e1.len(), 1);
assert_eq!(scalar_value(&e1[0].0), "b");
assert_eq!(scalar_value(&e1[0].1), "2");
}
#[test]
fn multi_doc_empty_key_first_doc_parses_correctly() {
let docs = load("? # comment\n: x\n---\nplain: y\n").expect("load failed");
assert_eq!(docs.len(), 2);
let Node::Mapping { entries: e0, .. } = &docs[0].root else {
panic!("doc 0 root must be Mapping; got: {:?}", docs[0].root);
};
assert_eq!(e0.len(), 1);
assert_eq!(scalar_value(&e0[0].0), "");
assert_eq!(scalar_value(&e0[0].1), "x");
let Node::Mapping { entries: e1, .. } = &docs[1].root else {
panic!("doc 1 root must be Mapping; got: {:?}", docs[1].root);
};
assert_eq!(e1.len(), 1);
assert_eq!(scalar_value(&e1[0].0), "plain");
}
#[test]
fn multi_doc_no_cross_document_state_leakage() {
let docs = load("? p\n: 1\n---\n? q\n: 2\n---\n? r\n: 3\n").expect("load failed");
assert_eq!(docs.len(), 3, "expected 3 documents; got {}", docs.len());
let keys = ["p", "q", "r"];
let vals = ["1", "2", "3"];
for (i, doc) in docs.iter().enumerate() {
let Node::Mapping { entries, .. } = &doc.root else {
panic!("doc {i} root must be Mapping; got: {:?}", doc.root);
};
assert_eq!(
entries.len(),
1,
"doc {i} must have 1 entry; got {}",
entries.len()
);
assert_eq!(scalar_value(&entries[0].0), keys[i], "doc {i} key");
assert_eq!(scalar_value(&entries[0].1), vals[i], "doc {i} value");
}
}
#[test]
fn plain_block_mapping_unaffected_by_explicit_key_changes() {
let node = load_one("key: value\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 1);
assert_eq!(scalar_value(&entries[0].0), "key");
assert_eq!(scalar_value(&entries[0].1), "value");
}
#[test]
fn sequence_of_plain_scalars_unaffected_by_explicit_key_changes() {
let node = load_one("items:\n - a\n - b\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 1);
let (k, v) = &entries[0];
assert_eq!(scalar_value(k), "items");
let Node::Sequence { items, .. } = v else {
panic!("value must be Sequence; got: {v:?}");
};
assert_eq!(items.len(), 2);
}
#[test]
fn explicit_key_followed_by_implicit_key_both_entries_correct() {
let node = load_one("? expl_key\n: expl_val\nimpl_key: impl_val\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(
entries.len(),
2,
"expected 2 entries; got {}",
entries.len()
);
assert_eq!(scalar_value(&entries[0].0), "expl_key");
assert_eq!(scalar_value(&entries[0].1), "expl_val");
assert_eq!(scalar_value(&entries[1].0), "impl_key");
assert_eq!(scalar_value(&entries[1].1), "impl_val");
}
#[test]
fn explicit_key_nested_inside_mapping_value_no_phantom_entries() {
let node = load_one("outer:\n ? inner_key\n : inner_val\n");
let Node::Mapping { entries, .. } = &node else {
panic!("expected Mapping root; got: {node:?}");
};
assert_eq!(entries.len(), 1);
let (k_outer, v_outer) = &entries[0];
assert_eq!(scalar_value(k_outer), "outer");
let Node::Mapping {
entries: inner_entries,
..
} = v_outer
else {
panic!("outer value must be Mapping; got: {v_outer:?}");
};
assert_eq!(
inner_entries.len(),
1,
"inner mapping must have 1 entry; got {}",
inner_entries.len()
);
assert_eq!(scalar_value(&inner_entries[0].0), "inner_key");
assert_eq!(scalar_value(&inner_entries[0].1), "inner_val");
}