rd-ast 0.0.1

Canonical, producer-agnostic AST for R's Rd (documentation) format
Documentation
use super::*;

fn delimiter_item() -> RdNode {
    RdNode::tagged(RdTag::Item, None, vec![])
}

#[test]
fn lists_split_delimited_items_without_synthesizing_labels() {
    let base = RdPath::new(vec![RdPathSegment::TopLevel(4), RdPathSegment::Child(2)]);
    let list = RdTagged::new(
        RdTag::Itemize,
        None,
        vec![
            RdNode::Text("\n".into()),
            RdNode::Text("[leading error]".into()),
            delimiter_item(),
            RdNode::Text("[label] body".into()),
            RdNode::tagged(RdTag::Strong, None, vec![RdNode::Text("nested".into())]),
            delimiter_item(),
            delimiter_item(),
            RdNode::Text("last".into()),
        ],
    );
    let view = list.inspect_list(&base).unwrap();
    let mut items = view.items();
    assert!(matches!(items.next().unwrap(), Err(error) if error.path() == &base.with_child(1)));
    let first = match items.next().unwrap().unwrap() {
        RdListItem::Delimited(item) => item,
        _ => unreachable!(),
    };
    assert!(
        matches!(first.body(), [RdNode::Text(label), RdNode::Tagged(_),] if label == "[label] body")
    );
    assert!(
        matches!(items.next().unwrap(), Ok(RdListItem::Delimited(item)) if item.body().is_empty())
    );
    assert!(
        matches!(items.next().unwrap(), Ok(RdListItem::Delimited(item)) if matches!(item.body(), [RdNode::Text(text)] if text == "last"))
    );
    assert!(items.next().is_none());
}

#[test]
fn enumerate_has_the_same_delimiter_semantics() {
    let list = RdTagged::new(
        RdTag::Enumerate,
        None,
        vec![delimiter_item(), RdNode::Text("one".into())],
    );
    let view = list.inspect_list(&RdPath::new(vec![])).unwrap();
    assert_eq!(view.kind(), RdListKind::Enumerate);
    assert!(
        matches!(view.items().next().unwrap(), Ok(RdListItem::Delimited(item)) if item.body().len() == 1)
    );
}

#[test]
fn describe_items_validate_two_groups_and_keep_scanning() {
    let base = RdPath::new(vec![RdPathSegment::TopLevel(3)]);
    let item = |children| RdNode::tagged(RdTag::Item, None, children);
    let list = RdTagged::new(
        RdTag::Describe,
        None,
        vec![
            item(vec![
                RdNode::group(vec![RdNode::Text("a".into())]),
                RdNode::group(vec![RdNode::Text("A".into())]),
            ]),
            item(vec![]),
            item(vec![RdNode::Text("bad".into()), RdNode::group(vec![])]),
            RdNode::Text("content".into()),
            item(vec![RdNode::group(vec![]), RdNode::group(vec![])]),
        ],
    );
    let results: Vec<_> = list.inspect_list(&base).unwrap().items().collect();
    assert!(matches!(&results[0], Ok(RdListItem::Described(item)) if item.label().len() == 1));
    assert!(
        matches!(&results[1], Err(error) if matches!(error.kind(), RdShapeErrorKind::WrongArity { expected: RdArity::Exactly(2), actual: 0 }))
    );
    assert!(matches!(&results[2], Err(error) if error.path() == &base.with_child(2).with_child(0)));
    assert!(matches!(&results[3], Err(error) if error.path() == &base.with_child(3)));
    assert!(matches!(&results[4], Ok(RdListItem::Described(_))));
}

#[test]
fn inspect_list_reports_wrong_tag_and_container_option() {
    let base = RdPath::new(vec![RdPathSegment::TopLevel(1)]);
    let wrong = RdTagged::new(RdTag::Title, None, vec![])
        .inspect_list(&base)
        .unwrap_err();
    assert!(matches!(
        wrong.kind(),
        RdShapeErrorKind::UnexpectedNode {
            expected: RdExpectedNode::List,
            ..
        }
    ));
    assert_eq!(
        wrong.to_string(),
        r"expected list node, found tagged node for \title at top-level[1]"
    );
    let option = RdTagged::new(RdTag::Itemize, Some(vec![]), vec![])
        .inspect_list(&base)
        .unwrap_err();
    assert!(matches!(option.kind(), RdShapeErrorKind::UnexpectedOption));
}