//! `math.render` end to end through [`Registry::run`]: the draw list, its fixed //! metrics and the parameter validation. mod common; use common::{TempVault, temp_vault}; use notescraft_engine::{Error, Registry, Session, register_core}; use serde_json::{Value, json}; fn fixture(name: &str) -> (Registry, Session, TempVault) { let vault = temp_vault(name); let mut reg = Registry::new(); register_core(&mut reg).expect("register the core commands"); (reg, Session::open(vault.path()).expect("open the vault session"), vault) } #[test] fn renders_a_formula_into_a_draw_list() { let (reg, mut session, _vault) = fixture("math_render"); let out = reg.run("math.render", &mut session, &json!({ "src": "\\frac{a}{b} + x^2", "display": true, "size": 12.0 })).expect("math.render"); let width = out.get("width").and_then(Value::as_f64).unwrap_or_default(); assert!(width > 0.0, "the box has a width: {out}"); let height = out.get("height").and_then(Value::as_f64).unwrap_or_default(); let depth = out.get("depth").and_then(Value::as_f64).unwrap_or_default(); assert!(height > 0.0 && depth > 0.0, "the box spans the baseline: {out}"); let items = out.get("items").and_then(Value::as_array).unwrap_or(&Vec::new()).clone(); assert!(!items.is_empty(), "there are draw items"); let glyphs: Vec = items.iter().filter(|item| item.get("kind") == Some(&json!("glyphs"))).cloned().collect(); assert_ne!(glyphs, Vec::::new(), "the glyphs are listed"); let has_x = glyphs.iter().any(|item| item.get("text") == Some(&json!("x"))); assert!(has_x, "the variable x appears as a glyph: {items:?}"); let has_rule = items.iter().any(|item| item.get("kind") == Some(&json!("rule"))); assert!(has_rule, "the fraction bar is a rule item"); } #[test] fn display_fractions_are_wider_than_inline_ones() { let (reg, mut session, _vault) = fixture("math_render_inline"); let inline = reg.run("math.render", &mut session, &json!({ "src": "\\frac{a}{b}" })).expect("inline"); let display = reg.run("math.render", &mut session, &json!({ "src": "\\frac{a}{b}", "display": true })).expect("display"); let inline_w = inline.get("width").and_then(Value::as_f64).unwrap_or_default(); let display_w = display.get("width").and_then(Value::as_f64).unwrap_or_default(); // Display fractions keep the children at full size; inline ones scale them down. assert!(display_w > inline_w, "display style keeps the children at full size: {display_w} > {inline_w}"); } #[test] fn a_bad_source_is_an_invalid_math_command_error() { let (reg, mut session, _vault) = fixture("math_render_error"); let outcome = reg.run("math.render", &mut session, &json!({ "src": "\\frac{1}{2" })); let Err(Error::Command { source, .. }) = outcome else { panic!("{outcome:?}") }; assert_eq!(source.code, "invalid_math"); assert_ne!(source.message, "", "the error carries a message"); } #[test] fn params_are_validated() { let (reg, mut session, _vault) = fixture("math_render_params"); let outcomes = [ ("math.render", json!({ "size": 1.0 }), "missing `src`"), ("math.render", json!({ "src": "x", "size": -1.0 }), "negative size"), ("math.render", json!({ "src": "x", "size": "big" }), "string size"), ("math.render", json!({ "src": "x", "display": "yes" }), "string display"), ]; for (id, params, why) in outcomes { let outcome = reg.run(id, &mut session, ¶ms); // The registry rejects bad params itself; body-level checks become command errors. let code = match outcome { Err(Error::InvalidParams { .. }) => "invalid_params".to_owned(), Err(Error::Command { source, .. }) => source.code, other => panic!("{why}: {other:?}"), }; assert_eq!(code, "invalid_params", "{why}"); } }