#![allow(clippy::field_reassign_with_default, clippy::doc_markdown)]
use ox_mf2_parser::{
parse_source, MessageMode, ParseOptions, SemanticMessageKind, SourceFileInput, SourceStore,
};
fn parse(source: &str, parse_semantic: bool) -> ox_mf2_parser::ParseResult {
let mut sources = SourceStore::new();
let id = sources.add(SourceFileInput {
source,
..Default::default()
});
let mut options = ParseOptions::default();
options.parse_semantic = parse_semantic;
parse_source(&sources, id, options)
}
#[test]
fn parse_semantic_false_omits_model() {
let result = parse("Hello", false);
assert!(result.semantic.is_none());
}
#[test]
fn simple_message_is_pattern() {
let result = parse("Hello, {$name}!", true);
let semantic = result.semantic.unwrap();
assert_eq!(semantic.mode, MessageMode::Simple);
assert_eq!(semantic.kind, SemanticMessageKind::Pattern);
assert!(!semantic.patterns.is_empty());
assert!(!semantic.expressions.is_empty());
assert_eq!(semantic.references.len(), 1);
assert_eq!(
semantic.references[0].name_span.start,
semantic.references[0].semantic_ref.span.start
);
}
#[test]
fn complex_quoted_pattern_is_pattern() {
let result = parse("{{Hello, {$name}!}}", true);
let semantic = result.semantic.unwrap();
assert_eq!(semantic.mode, MessageMode::Complex);
assert_eq!(semantic.kind, SemanticMessageKind::Pattern);
assert!(semantic.patterns.iter().any(|p| p.is_quoted));
}
#[test]
fn matcher_body_is_select() {
let result = parse(".match $x\n* {{fallback}}", true);
let semantic = result.semantic.unwrap();
assert_eq!(semantic.mode, MessageMode::Complex);
assert_eq!(semantic.kind, SemanticMessageKind::Select);
assert_eq!(semantic.selectors.len(), 1);
assert!(semantic.variants.iter().any(|v| v.has_catch_all));
}
#[test]
fn declarations_collect_with_variable_back_reference() {
let result = parse(".local $x = {$y}\n{{Hi}}", true);
let semantic = result.semantic.unwrap();
assert_eq!(semantic.declarations.len(), 1);
let decl = &semantic.declarations[0];
assert!(decl.variable.is_some(), "declared variable link expected");
assert!(!semantic.references.is_empty());
}
#[test]
fn markup_options_and_attributes_are_lowered() {
let result = parse("{{Click {#a opt=|x| @id=|me|/} now}}", true);
let semantic = result.semantic.unwrap();
assert_eq!(semantic.markups.len(), 1, "expected one markup");
assert!(
!semantic.options.is_empty(),
"expected markup options to be lowered"
);
assert!(
!semantic.attributes.is_empty(),
"expected markup attributes to be lowered"
);
}
#[test]
fn input_declaration_body_is_lowered() {
let result = parse(
".input {$count :number minimumFractionDigits=2}\n{{Hi {$count}}}",
true,
);
let semantic = result.semantic.unwrap();
assert_eq!(semantic.declarations.len(), 1);
assert!(semantic.declarations[0].variable.is_some());
assert!(
!semantic.functions.is_empty(),
"expected .input body's function to be recorded"
);
assert!(
!semantic.options.is_empty(),
"expected .input body's option to be recorded"
);
let refs_to_count = semantic
.references
.iter()
.filter(|r| !r.semantic_ref.span.is_empty())
.count();
assert!(
refs_to_count >= 1,
"expected at least one variable reference in the message body"
);
}
#[test]
fn every_record_links_back_to_node_id() {
let result = parse(".local $x = {$y}\n.match $x\n* {{fallback}}", true);
let semantic = result.semantic.unwrap();
for r in &semantic.declarations {
assert_ne!(r.semantic_ref.node.raw(), u32::MAX);
}
for r in &semantic.selectors {
assert_ne!(r.semantic_ref.node.raw(), u32::MAX);
}
for r in &semantic.variants {
assert_ne!(r.semantic_ref.node.raw(), u32::MAX);
}
for r in &semantic.patterns {
assert_ne!(r.semantic_ref.node.raw(), u32::MAX);
}
}