//! Comprehensive integration tests for mathematical expression parsing and conversion //! //! These tests cover complex mathematical expressions including: //! - Fractions and nested fractions //! - Radicals (square roots, nth roots) //! - Powers and exponents //! - Matrices and vectors //! - Integrals and summations //! - Trigonometric functions //! - Greek letters and special symbols //! - Complex nested expressions //! //! NOTE: These tests require the `math` feature to be enabled. //! Run with: cargo test --features math #![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() { // The famous quadratic formula: x = (-b ± √(b² - 4ac)) / 2a 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("")); assert!(mathml.contains("")); let asciimath = to_asciimath(&expr); assert!(asciimath.contains("sqrt")); assert!(asciimath.contains("/")); } #[test] fn test_pythagorean_theorem() { // a² + b² = c² 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("")); assert!(mathml.contains("=")); } #[test] fn test_euler_identity() { // e^(iπ) + 1 = 0 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() { // Complex continued fraction let input = r"\frac{1}{\frac{2}{\frac{3}{4}}}"; let expr = parse_expression(input).unwrap(); let latex = to_latex(&expr); // Should contain multiple \frac commands assert!(latex.matches(r"\frac").count() >= 3); let mathml = to_mathml(&expr); assert!(mathml.matches("").count() >= 3); } #[test] fn test_matrix_2x2() { // 2x2 matrix 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("")); assert!(mathml.contains("")); assert!(mathml.contains("")); let asciimath = to_asciimath(&expr); assert!(asciimath.contains("[")); assert!(asciimath.contains(";")); } #[test] fn test_matrix_3x3() { // 3x3 identity matrix 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() { // ∫₀¹ x² dx 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("")); assert!(mathml.contains("∫")); } #[test] fn test_summation() { // ∑_{i=1}^{n} i² 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("")); } #[test] fn test_product_notation() { // ∏_{k=1}^{n} k 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() { // ∛8 (cube 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("")); } #[test] fn test_complex_radical() { // √(a² + b²) 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() { // (n choose k) represented as fraction 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("")); assert!(mathml.contains("n")); assert!(mathml.contains("k")); } #[test] fn test_trigonometric_functions() { // sin²(x) + cos²(x) = 1 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() { // lim_{x→∞} (1 + 1/x)^x = e // Simplified: testing basic limit structure 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() { // a₁² + a₂² = a₃² 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("")); } #[test] fn test_operator_precedence() { // 1 + 2 * 3 should parse as 1 + (2 * 3) 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); // Should not have unnecessary parentheses around 2 * 3 assert!(!latex.contains("(2")); } #[test] fn test_parentheses_grouping() { // (1 + 2) * 3 should parse as (1 + 2) * 3 let input = "(1 + 2) * 3"; let expr = parse_expression(input).unwrap(); let latex = to_latex(&expr); // The addition should be grouped assert!(latex.contains("1 + 2")); } #[test] fn test_complex_nested_expression() { // Complex expression with multiple levels of nesting 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("")); assert!(mathml.contains("")); assert!(mathml.contains("")); } #[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); // Both should contain \frac 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() { // Test a realistically complex 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("")); assert!(mathml.contains("")); 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); // All should produce valid output assert!(!latex.is_empty()); assert!(!mathml.is_empty()); } }