use hedl_core::{parse, Item, Value};
#[test]
fn test_parse_document_with_list_in_key_value_pair() {
let input = br#"%VERSION: 1.1
---
roles: (admin, editor, viewer)
"#;
let doc = parse(input).unwrap();
let roles = doc.root.get("roles").expect("roles key missing");
match roles {
Item::Scalar(Value::List(items)) => {
assert_eq!(items.len(), 3);
assert!(matches!(&items[0], Value::String(s) if s.as_ref() == "admin"));
assert!(matches!(&items[1], Value::String(s) if s.as_ref() == "editor"));
assert!(matches!(&items[2], Value::String(s) if s.as_ref() == "viewer"));
}
_ => panic!("Expected List, got {:?}", roles),
}
}
#[test]
fn test_parse_document_with_empty_list() {
let input = br#"%VERSION: 1.1
---
tags: ()
"#;
let doc = parse(input).unwrap();
let tags = doc.root.get("tags").expect("tags key missing");
match tags {
Item::Scalar(Value::List(items)) => {
assert!(items.is_empty(), "Expected empty list");
}
_ => panic!("Expected List, got {:?}", tags),
}
}
#[test]
fn test_parse_document_with_bool_list() {
let input = br#"%VERSION: 1.1
---
flags: (true, false, true)
"#;
let doc = parse(input).unwrap();
let flags = doc.root.get("flags").expect("flags key missing");
match flags {
Item::Scalar(Value::List(items)) => {
assert_eq!(items.len(), 3);
assert!(matches!(&items[0], Value::Bool(true)));
assert!(matches!(&items[1], Value::Bool(false)));
assert!(matches!(&items[2], Value::Bool(true)));
}
_ => panic!("Expected List, got {:?}", flags),
}
}
#[test]
fn test_parse_document_with_int_list() {
let input = br#"%VERSION: 1.1
---
numbers: (1, 2, 3, 42)
"#;
let doc = parse(input).unwrap();
let numbers = doc.root.get("numbers").expect("numbers key missing");
match numbers {
Item::Scalar(Value::List(items)) => {
assert_eq!(items.len(), 4);
assert!(matches!(&items[0], Value::Int(1)));
assert!(matches!(&items[1], Value::Int(2)));
assert!(matches!(&items[2], Value::Int(3)));
assert!(matches!(&items[3], Value::Int(42)));
}
_ => panic!("Expected List, got {:?}", numbers),
}
}
#[test]
fn test_parse_document_with_mixed_type_list() {
let input = br#"%VERSION: 1.1
---
mixed: (1, "two", true, ~)
"#;
let doc = parse(input).unwrap();
let mixed = doc.root.get("mixed").expect("mixed key missing");
match mixed {
Item::Scalar(Value::List(items)) => {
assert_eq!(items.len(), 4);
assert!(matches!(&items[0], Value::Int(1)));
assert!(matches!(&items[1], Value::String(s) if s.as_ref() == "two"));
assert!(matches!(&items[2], Value::Bool(true)));
assert!(matches!(&items[3], Value::Null));
}
_ => panic!("Expected List, got {:?}", mixed),
}
}
#[test]
fn test_parse_document_with_nested_object_containing_list() {
let input = br#"%VERSION: 1.1
---
user:
name: Alice
roles: (admin, editor)
"#;
let doc = parse(input).unwrap();
let user = doc.root.get("user").expect("user key missing");
match user {
Item::Object(obj) => {
let name = obj.get("name").expect("name key missing");
assert!(matches!(name, Item::Scalar(Value::String(s)) if s.as_ref() == "Alice"));
let roles = obj.get("roles").expect("roles key missing");
match roles {
Item::Scalar(Value::List(items)) => {
assert_eq!(items.len(), 2);
assert!(matches!(&items[0], Value::String(s) if s.as_ref() == "admin"));
assert!(matches!(&items[1], Value::String(s) if s.as_ref() == "editor"));
}
_ => panic!("Expected List for roles"),
}
}
_ => panic!("Expected Object for user"),
}
}
#[test]
fn test_parse_document_with_list_and_tensor_distinguished() {
let input = br#"%VERSION: 1.1
---
roles: (admin, editor)
weights: [0.5, 0.3, 0.2]
"#;
let doc = parse(input).unwrap();
let roles = doc.root.get("roles").expect("roles key missing");
assert!(matches!(roles, Item::Scalar(Value::List(_))));
let weights = doc.root.get("weights").expect("weights key missing");
assert!(matches!(weights, Item::Scalar(Value::Tensor(_))));
}
#[test]
fn test_parse_matrix_row_with_list_cell() {
let input = br#"%VERSION: 1.1
%STRUCT: User: [id, name, roles]
---
users:@User
|u1, Alice, (admin, editor)
|u2, Bob, (viewer)
"#;
let doc = parse(input).unwrap();
let users = doc.root.get("users").expect("users key missing");
match users {
Item::List(matrix) => {
assert_eq!(matrix.rows.len(), 2);
let alice = &matrix.rows[0];
assert_eq!(alice.fields.len(), 3);
match &alice.fields[2] {
Value::List(roles) => {
assert_eq!(roles.len(), 2);
assert!(matches!(&roles[0], Value::String(s) if s.as_ref() == "admin"));
assert!(matches!(&roles[1], Value::String(s) if s.as_ref() == "editor"));
}
_ => panic!("Expected List for Alice's roles"),
}
let bob = &matrix.rows[1];
assert_eq!(bob.fields.len(), 3);
match &bob.fields[2] {
Value::List(roles) => {
assert_eq!(roles.len(), 1);
assert!(matches!(&roles[0], Value::String(s) if s.as_ref() == "viewer"));
}
_ => panic!("Expected List for Bob's roles"),
}
}
_ => panic!("Expected List for users"),
}
}
#[test]
fn test_parse_list_with_references() {
let input = br#"%VERSION: 1.1
---
assignees: (@user1, @user2, @user3)
"#;
let doc = parse(input).unwrap();
let assignees = doc.root.get("assignees").expect("assignees key missing");
match assignees {
Item::Scalar(Value::List(items)) => {
assert_eq!(items.len(), 3);
for item in items.iter() {
assert!(matches!(item, Value::Reference(_)));
}
}
_ => panic!("Expected List, got {:?}", assignees),
}
}
#[test]
fn test_parse_list_with_enum_directive_rejected() {
let input = br#"%VERSION: 1.1
%ENUM: roles: {a:"admin", e:"editor", v:"viewer"}
---
user_roles: (a, e)
"#;
let result = parse(input);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("removed"));
}
#[test]
fn test_parse_v10_document_still_works() {
let input = br#"%VERSION: 1.0
%STRUCT: User: [id, name]
---
users:@User
|u1, Alice
|u2, Bob
"#;
let doc = parse(input).unwrap();
assert_eq!(doc.version, (1, 0));
let users = doc.root.get("users").expect("users key missing");
match users {
Item::List(matrix) => {
assert_eq!(matrix.rows.len(), 2);
assert_eq!(matrix.rows[0].fields.len(), 2);
assert_eq!(matrix.rows[1].fields.len(), 2);
}
_ => panic!("Expected List for users"),
}
}
#[test]
fn test_parse_list_with_whitespace() {
let input = br#"%VERSION: 1.1
---
items: ( a , b , c )
"#;
let doc = parse(input).unwrap();
let items = doc.root.get("items").expect("items key missing");
match items {
Item::Scalar(Value::List(list)) => {
assert_eq!(list.len(), 3);
assert!(matches!(&list[0], Value::String(s) if s.as_ref() == "a"));
assert!(matches!(&list[1], Value::String(s) if s.as_ref() == "b"));
assert!(matches!(&list[2], Value::String(s) if s.as_ref() == "c"));
}
_ => panic!("Expected List"),
}
}
#[test]
fn test_parse_list_with_quoted_strings() {
let input = br#"%VERSION: 1.1
---
messages: ("hello, world", "foo", "bar")
"#;
let doc = parse(input).unwrap();
let messages = doc.root.get("messages").expect("messages key missing");
match messages {
Item::Scalar(Value::List(list)) => {
assert_eq!(list.len(), 3);
assert!(matches!(&list[0], Value::String(s) if s.as_ref() == "hello, world"));
assert!(matches!(&list[1], Value::String(s) if s.as_ref() == "foo"));
assert!(matches!(&list[2], Value::String(s) if s.as_ref() == "bar"));
}
_ => panic!("Expected List"),
}
}
#[test]
fn test_parse_list_single_element() {
let input = br#"%VERSION: 1.1
---
single: (only)
"#;
let doc = parse(input).unwrap();
let single = doc.root.get("single").expect("single key missing");
match single {
Item::Scalar(Value::List(list)) => {
assert_eq!(list.len(), 1);
assert!(matches!(&list[0], Value::String(s) if s.as_ref() == "only"));
}
_ => panic!("Expected List"),
}
}
#[test]
fn test_parse_list_with_expressions() {
let input = br#"%VERSION: 1.1
---
calcs: ($(now()), $(x), $(concat(a, b)))
"#;
let doc = parse(input).unwrap();
let calcs = doc.root.get("calcs").expect("calcs key missing");
match calcs {
Item::Scalar(Value::List(list)) => {
assert_eq!(list.len(), 3);
assert!(matches!(&list[0], Value::Expression(_)));
assert!(matches!(&list[1], Value::Expression(_)));
assert!(matches!(&list[2], Value::Expression(_)));
}
_ => panic!("Expected List"),
}
}
#[test]
fn test_parse_nested_lists_in_object() {
let input = br#"%VERSION: 1.1
---
config:
allowed: (read, write)
denied: (delete, admin)
"#;
let doc = parse(input).unwrap();
let config = doc.root.get("config").expect("config key missing");
match config {
Item::Object(obj) => {
let allowed = obj.get("allowed").expect("allowed key missing");
assert!(matches!(allowed, Item::Scalar(Value::List(_))));
let denied = obj.get("denied").expect("denied key missing");
assert!(matches!(denied, Item::Scalar(Value::List(_))));
}
_ => panic!("Expected Object"),
}
}
#[test]
fn test_parse_matrix_with_list_and_tensor_cells() {
let input = br#"%VERSION: 1.1
%STRUCT: Record: [id, tags, values]
---
data:@Record
|r1, (tag1, tag2), [1, 2, 3]
|r2, (tag3), [4, 5]
"#;
let doc = parse(input).unwrap();
let data = doc.root.get("data").expect("data key missing");
match data {
Item::List(matrix) => {
assert_eq!(matrix.rows.len(), 2);
let row1 = &matrix.rows[0];
assert!(matches!(&row1.fields[1], Value::List(tags) if tags.len() == 2));
assert!(matches!(&row1.fields[2], Value::Tensor(_)));
let row2 = &matrix.rows[1];
assert!(matches!(&row2.fields[1], Value::List(tags) if tags.len() == 1));
assert!(matches!(&row2.fields[2], Value::Tensor(_)));
}
_ => panic!("Expected List for data"),
}
}
#[test]
fn test_parse_matrix_with_list_containing_tensors() {
let input = br#"%VERSION: 1.1
%STRUCT: Record: [id, matrices]
---
data:@Record
|r1, ([1, 2], [3, 4])
|r2, ([5], [6, 7, 8])
"#;
let doc = parse(input).unwrap();
let data = doc.root.get("data").expect("data key missing");
match data {
Item::List(matrix) => {
assert_eq!(matrix.rows.len(), 2);
match &matrix.rows[0].fields[1] {
Value::List(items) => {
assert_eq!(items.len(), 2);
assert!(matches!(&items[0], Value::Tensor(_)));
assert!(matches!(&items[1], Value::Tensor(_)));
}
_ => panic!("Expected list of tensors in row 1"),
}
match &matrix.rows[1].fields[1] {
Value::List(items) => {
assert_eq!(items.len(), 2);
assert!(matches!(&items[0], Value::Tensor(_)));
assert!(matches!(&items[1], Value::Tensor(_)));
}
_ => panic!("Expected list of tensors in row 2"),
}
}
_ => panic!("Expected List for data"),
}
}
#[test]
fn test_parse_list_with_empty_string_in_document() {
let input = br#"%VERSION: 1.1
---
items: ("", filled, "")
"#;
let doc = parse(input).unwrap();
let items = doc.root.get("items").expect("items key missing");
match items {
Item::Scalar(Value::List(list)) => {
assert_eq!(list.len(), 3);
assert!(matches!(&list[0], Value::String(s) if s.is_empty()));
assert!(matches!(&list[1], Value::String(s) if s.as_ref() == "filled"));
assert!(matches!(&list[2], Value::String(s) if s.is_empty()));
}
_ => panic!("Expected List"),
}
}
#[test]
fn test_parse_list_with_unicode_in_document() {
let input = "%VERSION: 1.1\n---\nlanguages: (日本語, 中文, Русский)\n";
let doc = parse(input.as_bytes()).unwrap();
let languages = doc.root.get("languages").expect("languages key missing");
match languages {
Item::Scalar(Value::List(list)) => {
assert_eq!(list.len(), 3);
assert!(matches!(&list[0], Value::String(s) if s.as_ref() == "日本語"));
assert!(matches!(&list[1], Value::String(s) if s.as_ref() == "中文"));
assert!(matches!(&list[2], Value::String(s) if s.as_ref() == "Русский"));
}
_ => panic!("Expected List"),
}
}
#[test]
fn test_parse_list_with_emoji_in_document() {
let input = "%VERSION: 1.1\n---\nemojis: (😀, 🎉, 🚀)\n";
let doc = parse(input.as_bytes()).unwrap();
let emojis = doc.root.get("emojis").expect("emojis key missing");
match emojis {
Item::Scalar(Value::List(list)) => {
assert_eq!(list.len(), 3);
assert!(matches!(&list[0], Value::String(s) if s.as_ref() == "😀"));
assert!(matches!(&list[1], Value::String(s) if s.as_ref() == "🎉"));
assert!(matches!(&list[2], Value::String(s) if s.as_ref() == "🚀"));
}
_ => panic!("Expected List"),
}
}
#[test]
fn test_parse_list_with_extensive_whitespace() {
let input = br#"%VERSION: 1.1
---
spaced: ( a , b , c )
"#;
let doc = parse(input).unwrap();
let spaced = doc.root.get("spaced").expect("spaced key missing");
match spaced {
Item::Scalar(Value::List(list)) => {
assert_eq!(list.len(), 3);
assert!(matches!(&list[0], Value::String(s) if s.as_ref() == "a"));
assert!(matches!(&list[1], Value::String(s) if s.as_ref() == "b"));
assert!(matches!(&list[2], Value::String(s) if s.as_ref() == "c"));
}
_ => panic!("Expected List"),
}
}
#[test]
fn test_parse_unclosed_list_error() {
let input = br#"%VERSION: 1.1
---
broken: (a, b, c
"#;
let result = parse(input);
assert!(result.is_err(), "Expected error for unclosed list");
let err = result.unwrap_err();
assert!(err.message.contains("unclosed") || err.message.contains("expected ')'"));
}
#[test]
fn test_parse_trailing_comma_in_list_error() {
let input = br#"%VERSION: 1.1
---
bad: (a, b,)
"#;
let result = parse(input);
assert!(result.is_err(), "Expected error for trailing comma");
let err = result.unwrap_err();
assert!(err.message.contains("trailing comma") || err.message.contains("empty element"));
}
#[test]
fn test_parse_consecutive_commas_in_list_error() {
let input = br#"%VERSION: 1.1
---
bad: (a,,b)
"#;
let result = parse(input);
assert!(result.is_err(), "Expected error for consecutive commas");
let err = result.unwrap_err();
assert!(err.message.contains("empty element") || err.message.contains("consecutive commas"));
}
#[test]
fn test_parse_document_with_multiple_lists() {
let input = br#"%VERSION: 1.1
---
permissions: (read, write, delete)
status_codes: (200, 404, 500)
flags: (true, false, true)
"#;
let doc = parse(input).unwrap();
assert!(matches!(
doc.root.get("permissions").unwrap(),
Item::Scalar(Value::List(_))
));
assert!(matches!(
doc.root.get("status_codes").unwrap(),
Item::Scalar(Value::List(_))
));
assert!(matches!(
doc.root.get("flags").unwrap(),
Item::Scalar(Value::List(_))
));
}
#[test]
fn test_parse_list_preserves_value_types() {
let input = br#"%VERSION: 1.1
---
mixed: (42, 3.5, true, false, ~, hello, @ref1)
"#;
let doc = parse(input).unwrap();
let mixed = doc.root.get("mixed").expect("mixed key missing");
match mixed {
Item::Scalar(Value::List(items)) => {
assert_eq!(items.len(), 7);
assert!(matches!(&items[0], Value::Int(42)));
assert!(matches!(&items[1], Value::Float(f) if (*f - 3.5).abs() < 0.001));
assert!(matches!(&items[2], Value::Bool(true)));
assert!(matches!(&items[3], Value::Bool(false)));
assert!(matches!(&items[4], Value::Null));
assert!(matches!(&items[5], Value::String(s) if s.as_ref() == "hello"));
assert!(matches!(&items[6], Value::Reference(_)));
}
_ => panic!("Expected List"),
}
}