Files
2026-10-14 22:39:13 -03:00

76 lines
3.8 KiB
Rust

//! `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>, 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<Value> = items.iter().filter(|item| item.get("kind") == Some(&json!("glyphs"))).cloned().collect();
assert_ne!(glyphs, Vec::<Value>::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, &params);
// 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}");
}
}