use sim_kernel::{Expr, NumberLiteral, Symbol};
use crate::access::{
as_f64, as_i64, entry_field, entry_field_any, entry_required, extra_fields, field, field_any,
field_bool, field_str, remove, required, required_bool, required_map, required_str,
required_sym, set,
};
use crate::build::{entry, float, int, keyword, list, map, num_q, sym, text, vector};
use crate::kind::expr_kind;
use crate::path::{Path, PathError, get, remove_at, set_at};
#[test]
fn field_any_accepts_symbol_and_string_keys() {
let symbol_keyed = map(vec![("role", text("user"))]);
assert_eq!(field_any(&symbol_keyed, "role"), Some(&text("user")));
let string_keyed = Expr::Map(vec![(text("role"), text("user"))]);
assert_eq!(field_any(&string_keyed, "role"), Some(&text("user")));
assert_eq!(field(&string_keyed, "role"), None);
assert_eq!(field_any(&string_keyed, "missing"), None);
}
#[test]
fn required_reports_context_on_missing_field() {
let value = map(vec![("a", int(1))]);
assert_eq!(required(&value, "a", "Record").unwrap(), &int(1));
let err = required(&value, "b", "Record").unwrap_err();
assert_eq!(
err.to_string(),
"evaluation error: Record is missing field b"
);
}
#[test]
fn typed_required_readers_coerce_or_label_the_error() {
let value = Expr::Map(vec![
(sym("name"), text("widget")),
(sym("kind"), sym("gadget")),
(sym("on"), Expr::Bool(true)),
(sym("attrs"), map(vec![("a", int(1))])),
(sym("count"), int(7)),
]);
assert_eq!(required_str(&value, "name", "Item").unwrap(), "widget");
assert_eq!(
required_sym(&value, "kind", "Item").unwrap(),
Symbol::new("gadget")
);
assert!(required_bool(&value, "on", "Item").unwrap());
assert_eq!(required_map(&value, "attrs", "Item").unwrap().len(), 1);
let err = required_str(&value, "count", "Item").unwrap_err();
assert_eq!(
err.to_string(),
"evaluation error: Item field count is not a string"
);
let miss = required_bool(&value, "gone", "Item").unwrap_err();
assert_eq!(
miss.to_string(),
"evaluation error: Item is missing field gone"
);
}
#[test]
fn field_bool_reads_expr_bool_only() {
let value = map(vec![("on", Expr::Bool(true)), ("name", text("x"))]);
assert_eq!(field_bool(&value, "on"), Some(true));
assert_eq!(field_bool(&value, "name"), None); assert_eq!(field_bool(&value, "missing"), None);
let string_keyed = Expr::Map(vec![(text("on"), Expr::Bool(false))]);
assert_eq!(field_bool(&string_keyed, "on"), Some(false));
}
#[test]
fn keyword_builds_a_bare_symbol() {
assert_eq!(keyword("ready"), Symbol::new("ready"));
assert_eq!(sym("ready"), Expr::Symbol(keyword("ready")));
}
#[test]
fn slice_accessors_match_the_map_level_ones() {
let entries = vec![
(sym("role"), text("user")),
(text("name"), text("x")), ];
assert_eq!(entry_field(&entries, "role"), Some(&text("user")));
assert_eq!(entry_field(&entries, "name"), None);
assert_eq!(entry_field_any(&entries, "name"), Some(&text("x")));
assert_eq!(
entry_required(&entries, "role", "Rec").unwrap(),
&text("user")
);
assert_eq!(
entry_required(&entries, "gone", "Rec")
.unwrap_err()
.to_string(),
"evaluation error: Rec is missing field gone"
);
let as_map = Expr::Map(entries.clone());
assert_eq!(field(&as_map, "role"), entry_field(&entries, "role"));
assert_eq!(
field_any(&as_map, "name"),
entry_field_any(&entries, "name")
);
}
#[test]
fn extra_fields_reports_only_unknown_names() {
let value = Expr::Map(vec![
(sym("known"), int(1)),
(text("alsoknown"), int(2)),
(sym("surprise"), int(3)),
]);
assert_eq!(
extra_fields(&value, &["known", "alsoknown"]),
vec!["surprise"]
);
assert!(extra_fields(&value, &["known", "alsoknown", "surprise"]).is_empty());
}
#[test]
fn entry_builds_a_bare_symbol_key() {
assert_eq!(entry("a", int(1)), (sym("a"), int(1)));
assert_eq!(
Expr::Map(vec![entry("a", int(1))]),
map(vec![("a", int(1))])
);
}
#[test]
fn num_q_preserves_qualified_and_unqualified_domains() {
assert_eq!(num_q(None, "f64", "1.5"), crate::build::num("f64", "1.5"));
assert_eq!(
num_q(Some("numbers"), "f64", "1.5"),
Expr::Number(NumberLiteral {
domain: Symbol::qualified("numbers", "f64"),
canonical: "1.5".to_owned(),
})
);
}
#[test]
fn builders_and_readers_round_trip() {
let value = map(vec![
("a", int(1)),
("pi", float(3.5)),
("name", text("x")),
("xs", list(vec![int(1), int(2)])),
]);
assert_eq!(field(&value, "a").and_then(as_i64), Some(1));
assert_eq!(field(&value, "pi").and_then(as_f64), Some(3.5));
assert_eq!(field_str(&value, "name"), Some("x"));
assert!(matches!(field(&value, "xs"), Some(Expr::List(items)) if items.len() == 2));
assert_eq!(field(&value, "missing"), None);
}
#[test]
fn float_canonical_drops_trailing_zero() {
assert_eq!(float(80.0), crate::build::num("f64", "80"));
assert_eq!(float(1.5), crate::build::num("f64", "1.5"));
}
#[test]
fn set_and_remove_preserve_siblings() {
let value = map(vec![("a", int(1)), ("b", int(2)), ("c", int(3))]);
let updated = set(&value, "b", int(9));
assert_eq!(field(&updated, "b"), Some(&int(9)));
assert_eq!(field(&updated, "a"), Some(&int(1)));
assert_eq!(field(&updated, "c"), Some(&int(3)));
assert_eq!(field(&value, "b"), Some(&int(2)));
let inserted = set(&value, "d", int(4));
assert_eq!(field(&inserted, "d"), Some(&int(4)));
let removed = remove(&value, "a");
assert_eq!(field(&removed, "a"), None);
assert_eq!(field(&removed, "b"), Some(&int(2)));
}
#[test]
fn expr_kind_tokens_are_stable() {
assert_eq!(expr_kind(&Expr::Nil), "nil");
assert_eq!(expr_kind(&int(1)), "number");
assert_eq!(expr_kind(&sym("x")), "symbol");
assert_eq!(expr_kind(&text("y")), "string");
assert_eq!(expr_kind(&list(vec![])), "list");
assert_eq!(expr_kind(&vector(vec![])), "vector");
assert_eq!(expr_kind(&map(vec![])), "map");
}
#[test]
fn path_wire_form_round_trips() {
let path = Path::new().key(sym("nodes")).index(0).key(sym("title"));
let wire = path.to_expr();
assert_eq!(Path::from_expr(&wire).unwrap(), path);
}
#[test]
fn get_and_set_at_navigate_nested_maps_and_sequences() {
let root = map(vec![(
"nodes",
list(vec![
map(vec![("title", text("a"))]),
map(vec![("title", text("b"))]),
]),
)]);
let path = Path::new().key(sym("nodes")).index(1).key(sym("title"));
assert_eq!(get(&root, &path), Some(&text("b")));
let updated = set_at(&root, &path, text("z")).unwrap();
assert_eq!(get(&updated, &path), Some(&text("z")));
let sibling = Path::new().key(sym("nodes")).index(0).key(sym("title"));
assert_eq!(get(&updated, &sibling), Some(&text("a")));
assert_eq!(get(&root, &path), Some(&text("b")));
}
#[test]
fn set_at_empty_path_replaces_root() {
let replaced = set_at(&int(1), &Path::new(), text("new")).unwrap();
assert_eq!(replaced, text("new"));
}
#[test]
fn set_at_inserts_a_missing_final_key() {
let root = map(vec![("a", int(1))]);
let updated = set_at(&root, &Path::new().key(sym("b")), int(2)).unwrap();
assert_eq!(field(&updated, "b"), Some(&int(2)));
assert_eq!(field(&updated, "a"), Some(&int(1)));
}
#[test]
fn set_at_errors_on_bad_navigation() {
let root = map(vec![("a", int(1))]);
let deep = Path::new().key(sym("missing")).key(sym("x"));
assert_eq!(set_at(&root, &deep, int(0)), Err(PathError::MissingKey));
let idx = Path::new().key(sym("a")).index(0);
assert_eq!(set_at(&root, &idx, int(0)), Err(PathError::NotASequence));
}
#[test]
fn remove_at_removes_a_nested_key() {
let root = map(vec![("outer", map(vec![("x", int(1)), ("y", int(2))]))]);
let path = Path::new().key(sym("outer")).key(sym("x"));
let removed = remove_at(&root, &path).unwrap();
assert_eq!(get(&removed, &path), None);
assert_eq!(
get(&removed, &Path::new().key(sym("outer")).key(sym("y"))),
Some(&int(2))
);
}
#[test]
fn set_at_matches_the_scene_and_editor_semantics() {
let root = map(vec![
("a", int(1)),
("nested", map(vec![("x", int(10)), ("y", int(20))])),
]);
let path = Path::new().key(sym("nested")).key(sym("x"));
let updated = set_at(&root, &path, int(99)).unwrap();
let expected = map(vec![
("a", int(1)),
("nested", map(vec![("x", int(99)), ("y", int(20))])),
]);
assert_eq!(updated, expected);
}