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"]);
}