rd-ast 0.0.1

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

fn base_path() -> RdPath {
    RdPath::new(vec![RdPathSegment::TopLevel(3)])
}

#[test]
fn all_example_control_tags_have_lossy_and_strict_views() {
    let cases = [
        (RdTag::DontRun, RdExampleControlKind::DontRun),
        (RdTag::DontTest, RdExampleControlKind::DontTest),
        (RdTag::DontShow, RdExampleControlKind::DontShow),
        (RdTag::DontDiff, RdExampleControlKind::DontDiff),
        (RdTag::TestOnly, RdExampleControlKind::TestOnly),
    ];
    let path = base_path();
    for (tag, kind) in cases {
        let children = vec![RdNode::RCode("x()".into()), RdNode::Comment("% c".into())];
        let node = RdNode::tagged(tag, None, children.clone());
        let lossy = node.example_control(&path).unwrap();
        assert_eq!(lossy.path(), &path);
        assert_eq!(lossy.kind(), kind);
        assert_eq!(lossy.body(), children.as_slice());
        let strict = node.inspect_example_control(&path).unwrap().unwrap();
        assert_eq!(strict.path(), &path);
        assert_eq!(strict.kind(), kind);
        assert_eq!(strict.body(), children.as_slice());
    }
    assert_ne!(
        RdExampleControlKind::DontShow,
        RdExampleControlKind::TestOnly
    );
}

#[test]
fn example_control_preserves_leaf_kinds_nested_markup_and_empty_bodies() {
    let path = base_path();
    let verb_body = vec![RdNode::Verb("shown".into())];
    let verb = RdNode::tagged(RdTag::DontRun, None, verb_body.clone());
    assert_eq!(
        verb.example_control(&path).unwrap().body(),
        verb_body.as_slice()
    );

    let nested_body = vec![
        RdNode::RCode("before(".into()),
        RdNode::tagged(RdTag::Link, None, vec![RdNode::Text("topic".into())]),
        RdNode::Comment("% retained".into()),
        RdNode::RCode(")".into()),
    ];
    let nested = RdNode::tagged(RdTag::DontTest, None, nested_body.clone());
    assert_eq!(
        nested
            .inspect_example_control(&path)
            .unwrap()
            .unwrap()
            .body(),
        nested_body.as_slice()
    );

    let code_body = vec![RdNode::RCode("code".into())];
    let code = RdNode::tagged(RdTag::DontShow, None, code_body.clone());
    assert_eq!(
        code.example_control(&path).unwrap().body(),
        code_body.as_slice()
    );

    let empty = RdNode::tagged(RdTag::TestOnly, None, vec![]);
    assert!(empty.example_control(&path).unwrap().body().is_empty());
    assert!(
        empty
            .inspect_example_control(&path)
            .unwrap()
            .unwrap()
            .body()
            .is_empty()
    );
}

#[test]
fn example_control_options_are_lossy_none_and_strict_errors() {
    let path = base_path();
    for option in [Some(vec![]), Some(vec![RdNode::Text("opt".into())])] {
        let node = RdNode::tagged(RdTag::DontDiff, option, vec![]);
        assert!(node.example_control(&path).is_none());
        let error = node.inspect_example_control(&path).unwrap_err();
        assert_eq!(error.path(), &path);
        assert_eq!(error.tag(), Some(&RdTag::DontDiff));
        assert!(matches!(error.kind(), RdShapeErrorKind::UnexpectedOption));
    }
}

#[test]
fn unrelated_nodes_and_raw_nodes_follow_example_control_boundary() {
    let path = base_path();
    for node in [
        RdNode::RCode("leaf".into()),
        RdNode::Comment("% leaf".into()),
        RdNode::tagged(RdTag::Code, None, vec![]),
    ] {
        assert!(node.example_control(&path).is_none());
        assert!(node.inspect_example_control(&path).unwrap().is_none());
    }

    for spelling in [r"\dontrun", r"\testonly"] {
        let node = RdNode::Raw(crate::producer::raw_node(
            Some(spelling.into()),
            None,
            vec![RdNode::RCode("opaque".into())],
            Some(crate::RawRdValue::Character(vec![Some("payload".into())])),
            vec![],
        ));
        assert!(node.example_control(&path).is_none());
        let error = node.inspect_example_control(&path).unwrap_err();
        assert_eq!(error.path(), &path);
        assert_eq!(error.tag(), Some(&RdTag::from_rd_tag(spelling)));
        assert!(matches!(
            error.kind(),
            RdShapeErrorKind::UnexpectedNode {
                expected: RdExpectedNode::Tagged,
                actual: RdNodeKind::Raw
            }
        ));
    }

    for raw in [None, Some(r"\other")] {
        let node = RdNode::Raw(crate::producer::raw_node(
            raw.map(str::to_owned),
            None,
            vec![],
            None,
            vec![],
        ));
        assert!(node.example_control(&path).is_none());
        assert!(node.inspect_example_control(&path).unwrap().is_none());
    }
}

#[test]
fn raw_children_are_preserved_and_errors_propagate_full_paths() {
    let path = RdPath::new(vec![RdPathSegment::TopLevel(7), RdPathSegment::Child(2)]);
    let raw_child = RdNode::Raw(crate::producer::raw_node(
        Some(r"\opaque".into()),
        None,
        vec![],
        None,
        vec![],
    ));
    let wrapper = RdNode::tagged(RdTag::DontRun, None, vec![raw_child.clone()]);
    let view = wrapper.inspect_example_control(&path).unwrap().unwrap();
    assert_eq!(view.body(), &[raw_child]);

    let option = RdNode::tagged(RdTag::DontRun, Some(vec![]), vec![]);
    assert_eq!(
        option.inspect_example_control(&path).unwrap_err().path(),
        &path
    );
    let raw = RdNode::Raw(crate::producer::raw_node(
        Some(r"\dontrun".into()),
        None,
        vec![],
        None,
        vec![],
    ));
    assert_eq!(
        raw.inspect_example_control(&path).unwrap_err().path(),
        &path
    );
}