#![allow(clippy::unwrap_used, clippy::disallowed_methods)]
use presentar_yaml::{DataContext, ExpressionExecutor, ExpressionParser, Manifest, Value};
use std::collections::HashMap;
const DASHBOARD_YAML: &str = r#"
presentar: "0.1"
name: Sales Dashboard
version: "1.0.0"
description: Real-time sales analytics
data:
transactions:
source: api://transactions
format: json
refresh: 30s
products:
source: file://data/products.json
format: json
layout:
type: dashboard
columns: 12
gap: 16
sections:
- id: header
span: [1, 12]
widgets:
- type: text
content: Sales Dashboard
style: heading
- id: cards
span: [1, 12]
widgets:
- type: data-card
data: "{{ transactions | sum(amount) }}"
- type: data-card
data: "{{ transactions | count }}"
"#;
#[test]
fn test_parse_dashboard_manifest() {
let manifest = Manifest::from_yaml(DASHBOARD_YAML).expect("valid YAML");
assert_eq!(manifest.name, "Sales Dashboard");
assert_eq!(manifest.version, "1.0.0");
assert!(!manifest.description.is_empty());
}
#[test]
fn test_manifest_data_sources() {
let manifest = Manifest::from_yaml(DASHBOARD_YAML).expect("valid YAML");
assert!(manifest.data.contains_key("transactions"));
assert!(manifest.data.contains_key("products"));
let transactions = &manifest.data["transactions"];
assert_eq!(transactions.source, "api://transactions");
assert_eq!(transactions.format, "json");
}
#[test]
fn test_manifest_layout_structure() {
let manifest = Manifest::from_yaml(DASHBOARD_YAML).expect("valid YAML");
assert_eq!(manifest.layout.layout_type, "dashboard");
assert_eq!(manifest.layout.columns, 12);
assert_eq!(manifest.layout.sections.len(), 2);
assert_eq!(manifest.layout.sections[0].id, "header");
assert_eq!(manifest.layout.sections[1].id, "cards");
}
#[test]
fn test_manifest_widgets_in_sections() {
let manifest = Manifest::from_yaml(DASHBOARD_YAML).expect("valid YAML");
let header_section = &manifest.layout.sections[0];
assert_eq!(header_section.widgets.len(), 1);
assert_eq!(header_section.widgets[0].widget_type, "text");
let cards_section = &manifest.layout.sections[1];
assert_eq!(cards_section.widgets.len(), 2);
assert_eq!(cards_section.widgets[0].widget_type, "data-card");
}
#[test]
fn test_manifest_roundtrip() {
let manifest = Manifest::from_yaml(DASHBOARD_YAML).expect("parse");
let yaml_output = manifest.to_yaml().expect("serialize");
let reparsed = Manifest::from_yaml(&yaml_output).expect("reparse");
assert_eq!(manifest.name, reparsed.name);
assert_eq!(manifest.version, reparsed.version);
}
#[test]
fn test_parse_simple_expression() {
let parser = ExpressionParser::new();
let expr = parser.parse("data.transactions").expect("valid expression");
assert_eq!(expr.source, "data.transactions");
assert!(expr.transforms.is_empty());
}
#[test]
fn test_parse_expression_with_transforms() {
let parser = ExpressionParser::new();
let expr = parser
.parse("{{ data.items | filter(active=true) | sort(date) | limit(10) }}")
.expect("valid expression");
assert_eq!(expr.source, "data.items");
assert_eq!(expr.transforms.len(), 3);
}
#[test]
fn test_parse_expression_whitespace_handling() {
let parser = ExpressionParser::new();
let expr1 = parser.parse(" data.items ").expect("with spaces");
let expr2 = parser.parse("{{data.items}}").expect("no spaces");
let expr3 = parser.parse("{{ data.items }}").expect("with spaces");
assert_eq!(expr1.source, "data.items");
assert_eq!(expr2.source, "data.items");
assert_eq!(expr3.source, "data.items");
}
fn sample_context() -> DataContext {
let mut ctx = DataContext::new();
let transactions = Value::Array(vec![
Value::Object({
let mut m = HashMap::new();
m.insert("id".into(), Value::Number(1.0));
m.insert("amount".into(), Value::Number(100.0));
m.insert("status".into(), Value::String("completed".into()));
m
}),
Value::Object({
let mut m = HashMap::new();
m.insert("id".into(), Value::Number(2.0));
m.insert("amount".into(), Value::Number(250.0));
m.insert("status".into(), Value::String("completed".into()));
m
}),
Value::Object({
let mut m = HashMap::new();
m.insert("id".into(), Value::Number(3.0));
m.insert("amount".into(), Value::Number(75.0));
m.insert("status".into(), Value::String("pending".into()));
m
}),
]);
ctx.insert("transactions", transactions);
ctx
}
#[test]
fn test_execute_count_transform() {
let parser = ExpressionParser::new();
let executor = ExpressionExecutor::new();
let ctx = sample_context();
let expr = parser.parse("{{ transactions | count }}").expect("parse");
let result = executor.execute(&expr, &ctx).expect("execute");
assert_eq!(result.as_number(), Some(3.0));
}
#[test]
fn test_execute_sum_transform() {
let parser = ExpressionParser::new();
let executor = ExpressionExecutor::new();
let ctx = sample_context();
let expr = parser
.parse("{{ transactions | sum(amount) }}")
.expect("parse");
let result = executor.execute(&expr, &ctx).expect("execute");
assert_eq!(result.as_number(), Some(425.0)); }
#[test]
fn test_execute_filter_transform() {
let parser = ExpressionParser::new();
let executor = ExpressionExecutor::new();
let ctx = sample_context();
let expr = parser
.parse("{{ transactions | filter(status=completed) }}")
.expect("parse");
let result = executor.execute(&expr, &ctx).expect("execute");
let arr = result.as_array().expect("should be array");
assert_eq!(arr.len(), 2); }
#[test]
fn test_execute_filter_and_sum() {
let parser = ExpressionParser::new();
let executor = ExpressionExecutor::new();
let ctx = sample_context();
let expr = parser
.parse("{{ transactions | filter(status=completed) | sum(amount) }}")
.expect("parse");
let result = executor.execute(&expr, &ctx).expect("execute");
assert_eq!(result.as_number(), Some(350.0)); }
#[test]
fn test_execute_limit_transform() {
let parser = ExpressionParser::new();
let executor = ExpressionExecutor::new();
let ctx = sample_context();
let expr = parser
.parse("{{ transactions | limit(2) }}")
.expect("parse");
let result = executor.execute(&expr, &ctx).expect("execute");
let arr = result.as_array().expect("should be array");
assert_eq!(arr.len(), 2);
}
#[test]
fn test_execute_mean_transform() {
let parser = ExpressionParser::new();
let executor = ExpressionExecutor::new();
let ctx = sample_context();
let expr = parser
.parse("{{ transactions | mean(amount) }}")
.expect("parse");
let result = executor.execute(&expr, &ctx).expect("execute");
let mean = result.as_number().expect("should be number");
let expected = (100.0 + 250.0 + 75.0) / 3.0;
assert!((mean - expected).abs() < 0.01);
}
#[test]
fn test_invalid_yaml_manifest() {
let invalid_yaml = "name: Test\n invalid: indentation";
let result = Manifest::from_yaml(invalid_yaml);
assert!(result.is_err());
}
#[test]
fn test_execute_nonexistent_field() {
let parser = ExpressionParser::new();
let executor = ExpressionExecutor::new();
let ctx = DataContext::new();
let expr = parser.parse("nonexistent").expect("parse");
let result = executor.execute(&expr, &ctx);
assert!(result.is_err());
}
#[test]
fn test_analytics_dashboard_workflow() {
let parser = ExpressionParser::new();
let executor = ExpressionExecutor::new();
let ctx = sample_context();
let total_expr = parser.parse("{{ transactions | sum(amount) }}").unwrap();
let count_expr = parser.parse("{{ transactions | count }}").unwrap();
let avg_expr = parser.parse("{{ transactions | mean(amount) }}").unwrap();
let completed_expr = parser
.parse("{{ transactions | filter(status=completed) | count }}")
.unwrap();
let total = executor.execute(&total_expr, &ctx).unwrap();
let count = executor.execute(&count_expr, &ctx).unwrap();
let avg = executor.execute(&avg_expr, &ctx).unwrap();
let completed = executor.execute(&completed_expr, &ctx).unwrap();
assert_eq!(total.as_number(), Some(425.0));
assert_eq!(count.as_number(), Some(3.0));
assert!((avg.as_number().unwrap() - 141.67).abs() < 0.1);
assert_eq!(completed.as_number(), Some(2.0));
}