#![cfg(feature = "math")]
use ruvector_scipix::math::{
parse_expression, to_asciimath, to_latex, to_mathml, AsciiMathGenerator, BinaryOp, BracketType,
LaTeXConfig, LaTeXGenerator, LargeOpType, MathExpr, MathNode,
};
#[test]
fn test_quadratic_formula() {
let latex_input = r"\frac{-b + \sqrt{b^2 - 4*a*c}}{2*a}";
let expr = parse_expression(latex_input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains(r"\frac"));
assert!(latex.contains(r"\sqrt"));
assert!(latex.contains("b"));
assert!(latex.contains("a"));
assert!(latex.contains("c"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("<mfrac>"));
assert!(mathml.contains("<msqrt>"));
let asciimath = to_asciimath(&expr);
assert!(asciimath.contains("sqrt"));
assert!(asciimath.contains("/"));
}
#[test]
fn test_pythagorean_theorem() {
let input = "a^2 + b^2 = c^2";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains("a^{2}"));
assert!(latex.contains("b^{2}"));
assert!(latex.contains("c^{2}"));
assert!(latex.contains("="));
let mathml = to_mathml(&expr);
assert!(mathml.contains("<msup>"));
assert!(mathml.contains("<mo>=</mo>"));
}
#[test]
fn test_euler_identity() {
let input = "e^{i*\\pi} + 1 = 0";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains("e^"));
assert!(latex.contains("\\pi"));
}
#[test]
fn test_nested_fractions() {
let input = r"\frac{1}{\frac{2}{\frac{3}{4}}}";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.matches(r"\frac").count() >= 3);
let mathml = to_mathml(&expr);
assert!(mathml.matches("<mfrac>").count() >= 3);
}
#[test]
fn test_matrix_2x2() {
let expr = MathExpr::new(
MathNode::Matrix {
rows: vec![
vec![
MathNode::Number {
value: "1".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "2".to_string(),
is_decimal: false,
},
],
vec![
MathNode::Number {
value: "3".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "4".to_string(),
is_decimal: false,
},
],
],
bracket_type: BracketType::Brackets,
},
1.0,
);
let latex = to_latex(&expr);
assert!(latex.contains(r"\begin{bmatrix}"));
assert!(latex.contains(r"\end{bmatrix}"));
assert!(latex.contains("&"));
assert!(latex.contains(r"\\"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("<mtable>"));
assert!(mathml.contains("<mtr>"));
assert!(mathml.contains("<mtd>"));
let asciimath = to_asciimath(&expr);
assert!(asciimath.contains("["));
assert!(asciimath.contains(";"));
}
#[test]
fn test_matrix_3x3() {
let expr = MathExpr::new(
MathNode::Matrix {
rows: vec![
vec![
MathNode::Number {
value: "1".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "0".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "0".to_string(),
is_decimal: false,
},
],
vec![
MathNode::Number {
value: "0".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "1".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "0".to_string(),
is_decimal: false,
},
],
vec![
MathNode::Number {
value: "0".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "0".to_string(),
is_decimal: false,
},
MathNode::Number {
value: "1".to_string(),
is_decimal: false,
},
],
],
bracket_type: BracketType::Parentheses,
},
1.0,
);
let latex = to_latex(&expr);
assert!(latex.contains(r"\begin{pmatrix}"));
assert!(latex.matches(r"\\").count() >= 2);
}
#[test]
fn test_definite_integral() {
let expr = MathExpr::new(
MathNode::LargeOp {
op_type: LargeOpType::Integral,
lower: Some(Box::new(MathNode::Number {
value: "0".to_string(),
is_decimal: false,
})),
upper: Some(Box::new(MathNode::Number {
value: "1".to_string(),
is_decimal: false,
})),
content: Box::new(MathNode::Binary {
op: BinaryOp::Power,
left: Box::new(MathNode::Symbol {
value: "x".to_string(),
unicode: Some('x'),
}),
right: Box::new(MathNode::Number {
value: "2".to_string(),
is_decimal: false,
}),
}),
},
1.0,
);
let latex = to_latex(&expr);
assert!(latex.contains(r"\int"));
assert!(latex.contains("_{0}"));
assert!(latex.contains("^{1}"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("<munderover>"));
assert!(mathml.contains("∫"));
}
#[test]
fn test_summation() {
let expr = MathExpr::new(
MathNode::LargeOp {
op_type: LargeOpType::Sum,
lower: Some(Box::new(MathNode::Binary {
op: BinaryOp::Equal,
left: Box::new(MathNode::Symbol {
value: "i".to_string(),
unicode: Some('i'),
}),
right: Box::new(MathNode::Number {
value: "1".to_string(),
is_decimal: false,
}),
})),
upper: Some(Box::new(MathNode::Symbol {
value: "n".to_string(),
unicode: Some('n'),
})),
content: Box::new(MathNode::Binary {
op: BinaryOp::Power,
left: Box::new(MathNode::Symbol {
value: "i".to_string(),
unicode: Some('i'),
}),
right: Box::new(MathNode::Number {
value: "2".to_string(),
is_decimal: false,
}),
}),
},
1.0,
);
let latex = to_latex(&expr);
assert!(latex.contains(r"\sum"));
assert!(latex.contains("_{i"));
assert!(latex.contains("^{n}"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("∑"));
assert!(mathml.contains("<munderover>"));
}
#[test]
fn test_product_notation() {
let expr = MathExpr::new(
MathNode::LargeOp {
op_type: LargeOpType::Product,
lower: Some(Box::new(MathNode::Binary {
op: BinaryOp::Equal,
left: Box::new(MathNode::Symbol {
value: "k".to_string(),
unicode: Some('k'),
}),
right: Box::new(MathNode::Number {
value: "1".to_string(),
is_decimal: false,
}),
})),
upper: Some(Box::new(MathNode::Symbol {
value: "n".to_string(),
unicode: Some('n'),
})),
content: Box::new(MathNode::Symbol {
value: "k".to_string(),
unicode: Some('k'),
}),
},
1.0,
);
let latex = to_latex(&expr);
assert!(latex.contains(r"\prod"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("∏"));
}
#[test]
fn test_nth_root() {
let input = r"\sqrt[3]{8}";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains(r"\sqrt[3]"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("<mroot>"));
}
#[test]
fn test_complex_radical() {
let input = r"\sqrt{a^2 + b^2}";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains(r"\sqrt"));
assert!(latex.contains("a^{2}"));
assert!(latex.contains("b^{2}"));
}
#[test]
fn test_binomial_coefficient() {
let input = r"\frac{n}{k}";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains(r"\frac{n}{k}"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("<mfrac>"));
assert!(mathml.contains("<mi>n</mi>"));
assert!(mathml.contains("<mi>k</mi>"));
}
#[test]
fn test_trigonometric_functions() {
let input = r"\sin{x}^2 + \cos{x}^2 = 1";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains(r"\sin"));
assert!(latex.contains(r"\cos"));
}
#[test]
fn test_limits() {
let input = r"\sum_{x=1}^{10} x";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains(r"\sum"));
}
#[test]
fn test_greek_letters() {
let input = r"\alpha + \beta + \gamma = \delta";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains(r"\alpha"));
assert!(latex.contains(r"\beta"));
assert!(latex.contains(r"\gamma"));
assert!(latex.contains(r"\delta"));
}
#[test]
fn test_subscript_and_superscript() {
let input = "a_1^2 + a_2^2 = a_3^2";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains("a_{1}^{2}"));
assert!(latex.contains("a_{2}^{2}"));
assert!(latex.contains("a_{3}^{2}"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("<msubsup>"));
}
#[test]
fn test_operator_precedence() {
let input = "1 + 2 * 3";
let expr = parse_expression(input).unwrap();
match expr.root {
MathNode::Binary {
op: BinaryOp::Add,
right,
..
} => {
assert!(matches!(
*right,
MathNode::Binary {
op: BinaryOp::Multiply,
..
}
));
}
_ => panic!("Expected addition with multiplication on right"),
}
let latex = to_latex(&expr);
assert!(!latex.contains("(2"));
}
#[test]
fn test_parentheses_grouping() {
let input = "(1 + 2) * 3";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains("1 + 2"));
}
#[test]
fn test_complex_nested_expression() {
let input = r"\frac{\sqrt{a + b}}{c^2 - d^2}";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains(r"\frac"));
assert!(latex.contains(r"\sqrt"));
assert!(latex.contains("c^{2}"));
assert!(latex.contains("d^{2}"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("<mfrac>"));
assert!(mathml.contains("<msqrt>"));
assert!(mathml.contains("<msup>"));
}
#[test]
fn test_latex_config_display_style() {
let expr = parse_expression(r"\frac{1}{2}").unwrap();
let config_inline = LaTeXConfig {
display_style: false,
auto_size_delimiters: true,
spacing: true,
};
let config_display = LaTeXConfig {
display_style: true,
auto_size_delimiters: true,
spacing: true,
};
let gen_inline = LaTeXGenerator::with_config(config_inline);
let gen_display = LaTeXGenerator::with_config(config_display);
let latex_inline = gen_inline.generate(&expr);
let latex_display = gen_display.generate(&expr);
assert!(latex_inline.contains(r"\frac"));
assert!(latex_display.contains(r"\frac"));
}
#[test]
fn test_latex_config_no_auto_size() {
let expr = MathExpr::new(
MathNode::Group {
content: Box::new(MathNode::Number {
value: "42".to_string(),
is_decimal: false,
}),
bracket_type: BracketType::Parentheses,
},
1.0,
);
let config_auto = LaTeXConfig {
display_style: false,
auto_size_delimiters: true,
spacing: true,
};
let config_no_auto = LaTeXConfig {
display_style: false,
auto_size_delimiters: false,
spacing: true,
};
let gen_auto = LaTeXGenerator::with_config(config_auto);
let gen_no_auto = LaTeXGenerator::with_config(config_no_auto);
let latex_auto = gen_auto.generate(&expr);
let latex_no_auto = gen_no_auto.generate(&expr);
assert!(latex_auto.contains(r"\left("));
assert!(!latex_no_auto.contains(r"\left("));
}
#[test]
fn test_asciimath_unicode_vs_ascii() {
let expr = parse_expression("2 * 3").unwrap();
let gen_unicode = AsciiMathGenerator::new();
let gen_ascii = AsciiMathGenerator::ascii_only();
let unicode_output = gen_unicode.generate(&expr);
let ascii_output = gen_ascii.generate(&expr);
assert!(unicode_output.contains("×") || unicode_output.contains("*"));
assert!(ascii_output.contains("*"));
}
#[test]
fn test_double_integral() {
let expr = MathExpr::new(
MathNode::LargeOp {
op_type: LargeOpType::DoubleIntegral,
lower: None,
upper: None,
content: Box::new(MathNode::Symbol {
value: "f".to_string(),
unicode: Some('f'),
}),
},
1.0,
);
let latex = to_latex(&expr);
assert!(latex.contains(r"\iint"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("∬"));
}
#[test]
fn test_triple_integral() {
let expr = MathExpr::new(
MathNode::LargeOp {
op_type: LargeOpType::TripleIntegral,
lower: None,
upper: None,
content: Box::new(MathNode::Symbol {
value: "f".to_string(),
unicode: Some('f'),
}),
},
1.0,
);
let latex = to_latex(&expr);
assert!(latex.contains(r"\iiint"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("∭"));
}
#[test]
fn test_decimal_numbers() {
let input = "3.14159";
let expr = parse_expression(input).unwrap();
match expr.root {
MathNode::Number { value, is_decimal } => {
assert_eq!(value, "3.14159");
assert!(is_decimal);
}
_ => panic!("Expected decimal number"),
}
let latex = to_latex(&expr);
assert!(latex.contains("3.14159"));
}
#[test]
fn test_large_expression() {
let input = r"\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}";
let expr = parse_expression(input).unwrap();
let latex = to_latex(&expr);
assert!(latex.contains(r"\frac"));
assert!(latex.contains(r"\sqrt"));
assert!(latex.contains("b^{2}"));
let mathml = to_mathml(&expr);
assert!(mathml.contains("<mfrac>"));
assert!(mathml.contains("<msqrt>"));
let asciimath = to_asciimath(&expr);
assert!(asciimath.contains("sqrt"));
assert!(asciimath.contains("/"));
}
#[test]
fn test_all_bracket_types() {
for bracket_type in &[
BracketType::Parentheses,
BracketType::Brackets,
BracketType::Braces,
BracketType::Vertical,
] {
let expr = MathExpr::new(
MathNode::Group {
content: Box::new(MathNode::Symbol {
value: "x".to_string(),
unicode: Some('x'),
}),
bracket_type: *bracket_type,
},
1.0,
);
let latex = to_latex(&expr);
let mathml = to_mathml(&expr);
assert!(!latex.is_empty());
assert!(!mathml.is_empty());
}
}