tests.rs raw

use super::*;
use crate::sexpr;

fn parse(source: &str) -> Expr {
    let doc = sexpr::read(source).expect("reads");
    from_sexpr(&doc.values[0], source).expect("valid expression")
}

fn parse_err(source: &str) -> Diagnostic {
    let doc = sexpr::read(source).expect("reads");
    from_sexpr(&doc.values[0], source).expect_err("expected an error")
}

#[test]
fn literals_and_paths() {
    assert!(matches!(parse("42"), Expr::Int(42, _)));
    assert!(matches!(parse("true"), Expr::Bool(true, _)));
    let Expr::Path(segments, _) = parse("form.busy") else {
        panic!()
    };
    assert_eq!(segments, ["form", "busy"]);
}

#[test]
fn nary_folds_left() {
    let Expr::Binary(BinOp::Add, lhs, _, _) = parse("(+ 1 2 3)") else {
        panic!()
    };
    assert!(matches!(*lhs, Expr::Binary(BinOp::Add, ..)));
}

#[test]
fn unary_minus_vs_binary() {
    assert!(matches!(parse("(- x)"), Expr::Unary(UnOp::Neg, ..)));
    assert!(matches!(parse("(- x 1)"), Expr::Binary(BinOp::Sub, ..)));
}

#[test]
fn if_and_not() {
    assert!(matches!(parse("(if hovered 1 0)"), Expr::If(..)));
    assert!(matches!(parse("(not busy)"), Expr::Unary(UnOp::Not, ..)));
    let err = parse_err("(if x 1)");
    assert!(err.message.contains("exactly 3"));
}

#[test]
fn unknown_head_parses_as_a_call() {
    // Non-operator heads are handler invocations; whether the name is a
    // declared handler — and whether a call is legal in this position —
    // is validation's decision, with spans intact.
    let Expr::Call(name, args, _) = parse("(frobnicate x)") else {
        panic!("expected a call");
    };
    assert_eq!(name, "frobnicate");
    assert_eq!(args.len(), 1);
}

#[test]
fn templates_scan_refs_with_file_spans() {
    let source = r#""Count: {count}!""#;
    let Expr::Str(template, _) = parse(source) else {
        panic!()
    };
    assert_eq!(
        template.segments,
        vec![
            Segment::Literal("Count: ".into()),
            Segment::Ref("count".into(), sexpr::Span::new(9, 14)),
            Segment::Literal("!".into()),
        ]
    );
    // The span points exactly at `count` in the source.
    assert_eq!(&source[9..14], "count");
}

#[test]
fn template_escapes() {
    let Expr::Str(template, _) = parse(r#""{{literal}} \{also} {x}""#) else {
        panic!()
    };
    assert_eq!(
        template.segments,
        vec![
            Segment::Literal("{literal} {also} ".into()),
            Segment::Ref("x".into(), sexpr::Span::new(22, 23)),
        ]
    );
}

#[test]
fn template_errors() {
    assert!(parse_err(r#""{unclosed""#).message.contains("unclosed"));
    assert!(parse_err(r#""{}""#).message.contains("empty"));
    assert!(
        parse_err(r#""{a b}""#)
            .message
            .contains("interpolations hold a name")
    );
}

#[test]
fn literalness() {
    assert!(parse("(+ 1 (* 2 3))").is_literal());
    assert!(parse(r#""plain""#).is_literal());
    assert!(!parse(r#""{count}""#).is_literal());
    assert!(!parse("(if hovered 1 0)").is_literal());
}

#[test]
fn referenced_names_are_collected() {
    let mut names = Vec::new();
    parse(r#"(if pressed "{a}" "{b}")"#).referenced_names(&mut names);
    let just_names: Vec<_> = names.iter().map(|(n, _)| n.as_str()).collect();
    assert_eq!(just_names, ["pressed", "a", "b"]);
}