#![cfg(feature = "std")]
use noyalib::Value;
use noyalib::cst::parse_document;
fn set(src: &str, path: &str, value: &Value) -> Result<String, (noyalib::Error, String)> {
let mut doc = parse_document(src).unwrap();
match doc.set_path(path, value) {
Ok(()) => Ok(doc.to_string()),
Err(e) => Err((e, doc.to_string())),
}
}
#[test]
fn creates_one_missing_level() {
let out = set("title: x\n", "menu.visible", &Value::Bool(true)).unwrap();
assert_eq!(out, "title: x\nmenu:\n visible: true\n");
}
#[test]
fn creates_three_missing_levels() {
let out = set("title: x\n", "a.b.c", &Value::Bool(true)).unwrap();
assert_eq!(out, "title: x\na:\n b:\n c: true\n");
}
#[test]
fn empty_document_receives_its_first_key() {
let out = set("", "menu.visible", &Value::Bool(true)).unwrap();
assert_eq!(out, "menu:\n visible: true\n");
}
#[test]
fn comment_only_document_keeps_its_header() {
let out = set(
"# generated by tooling\n",
"menu.visible",
&Value::Bool(true),
)
.unwrap();
assert_eq!(out, "# generated by tooling\nmenu:\n visible: true\n");
}
#[test]
fn bare_document_marker_is_preserved() {
let out = set("---\n", "menu.visible", &Value::Bool(true)).unwrap();
assert_eq!(out, "---\nmenu:\n visible: true\n");
}
#[test]
fn missing_trailing_newline_gains_one_before_the_new_entry() {
let out = set("title: x", "menu.visible", &Value::Bool(true)).unwrap();
assert_eq!(out, "title: x\nmenu:\n visible: true\n");
}
#[test]
fn existing_leaf_is_an_ordinary_upsert() {
let out = set(
"menu:\n visible: false\n",
"menu.visible",
&Value::Bool(true),
)
.unwrap();
assert_eq!(out, "menu:\n visible: true\n");
}
#[test]
fn equal_value_leaves_the_bytes_alone() {
let src = "menu:\n visible: true\n";
let out = set(src, "menu.visible", &Value::Bool(true)).unwrap();
assert_eq!(out, src);
}
#[test]
fn new_levels_follow_the_document_indent_unit() {
let out = set("top:\n inner: 1\n", "menu.deep.flag", &Value::Bool(true)).unwrap();
assert_eq!(
out,
"top:\n inner: 1\nmenu:\n deep:\n flag: true\n"
);
}
#[test]
fn collection_value_for_a_new_key_is_created() {
let tags: Value = noyalib::from_str("[a, b]").unwrap();
let out = set("title: x\n", "tags", &tags).unwrap();
assert_eq!(out, "title: x\ntags:\n - a\n - b\n");
let loaded: Value = noyalib::from_str(&out).unwrap();
assert_eq!(loaded["tags"], tags);
}
#[test]
fn round_trip_loads_back_as_expected() {
let out = set("title: x\n", "a.b.c", &Value::from(7_i64)).unwrap();
let loaded: Value = noyalib::from_str(&out).unwrap();
assert_eq!(loaded["a"]["b"]["c"], Value::from(7_i64));
assert_eq!(loaded["title"].as_str(), Some("x"));
}
#[test]
fn scalar_prefix_refuses_byte_identical() {
let src = "title: x\n";
let (err, out) = set(src, "title.x", &Value::Bool(true)).unwrap_err();
assert_eq!(out, src);
assert!(err.to_string().contains("non-mapping"), "got: {err}");
}
#[test]
fn explicit_null_root_refuses_byte_identical() {
let src = "null\n";
let (err, out) = set(src, "menu.visible", &Value::Bool(true)).unwrap_err();
assert_eq!(out, src);
assert!(
err.to_string().contains("cannot be created here"),
"got: {err}"
);
}
#[test]
fn implicit_null_parent_refuses_with_guidance() {
let src = "menu:\n";
let (err, out) = set(src, "menu.visible", &Value::Bool(true)).unwrap_err();
assert_eq!(out, src);
assert!(err.to_string().contains("null value"), "got: {err}");
assert!(err.to_string().contains("`set`"), "got: {err}");
}
#[test]
fn missing_sequence_index_refuses() {
let src = "items:\n - a\n";
let (err, out) = set(src, "items[3].x", &Value::Bool(true)).unwrap_err();
assert_eq!(out, src);
assert!(err.to_string().contains("mappings only"), "got: {err}");
}
#[test]
fn flow_parent_creates_flow_members() {
let src = "a: {x: 1}\n";
let out = set(src, "a.b.c", &Value::Bool(true)).unwrap();
assert_eq!(out, "a: {x: 1, b: {c: true}}\n");
}
#[test]
fn wildcard_segment_refuses() {
let src = "a: 1\n";
let (err, out) = set(src, "a.*", &Value::Bool(true)).unwrap_err();
assert_eq!(out, src);
assert!(err.to_string().contains("wildcard"), "got: {err}");
}
#[test]
fn empty_path_refuses() {
let src = "a: 1\n";
let (err, out) = set(src, "", &Value::Bool(true)).unwrap_err();
assert_eq!(out, src);
assert!(err.to_string().contains("non-empty path"), "got: {err}");
}
#[test]
fn collection_value_over_an_existing_scalar_refuses() {
let src = "tags: plain\n";
let seq: Value = noyalib::from_str("[a]").unwrap();
let (err, out) = set(src, "tags", &seq).unwrap_err();
assert_eq!(out, src);
assert!(err.to_string().contains("collection"), "got: {err}");
}
#[test]
fn descends_through_existing_sequence_items() {
let src = "items:\n - name: a\n";
match set(src, "items[0].extra", &Value::Bool(true)) {
Ok(out) => {
let loaded: Value = noyalib::from_str(&out).unwrap();
assert_eq!(loaded["items"][0]["extra"], Value::Bool(true));
}
Err((_, out)) => assert_eq!(out, src),
}
}