use insta::assert_snapshot;
use math_core::{LatexToMathML, MathCoreConfig, MathDisplay, MaxExpansions, PrettyPrint};
#[test]
fn test_zero_arg() {
let macros = vec![
("half".to_string(), r"\frac{1}{2}".to_string()),
("mycmd".to_string(), r"\sqrt{3}".to_string()),
("withText".to_string(), r"\text{a b}\sum".to_string()),
];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"x = \half, \withText 3";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("custom_cmd_zero_arg", mathml.mathml, latex);
}
#[test]
fn test_one_arg() {
let macros = vec![
("half".to_string(), r"\frac{1}{2}\mspace{3mu}".to_string()),
(
"mycmd".to_string(),
r"\begin{array}{c|c}#1 & 0\end{array}".to_string(),
),
];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"x = \mycmd{3} + \half";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("custom_cmd_one_arg", mathml.mathml, latex);
}
#[test]
fn test_error() {
let macros = vec![
("x".to_string(), "x".to_string()),
("mycmd".to_string(), r"\sqrt{#}".to_string()),
];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let error = LatexToMathML::new(config).unwrap_err();
assert_eq!(error.1, 1);
assert_eq!(error.2, r"\sqrt{#}");
}
#[test]
fn test_spacing() {
let macros = vec![("eq".to_string(), r"=".to_string())];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"x + \eq 3";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("custom_cmd_spacing", mathml.mathml, latex);
}
#[test]
fn test_empty_args() {
let macros = vec![
(
"odv".to_string(),
r"\frac{\mathrm d #1}{\mathrm d #2}".to_string(),
),
("bb".to_string(), r"\mathbb{#1}".to_string()),
("two".to_string(), r"#1+#2".to_string()),
];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
for (name, latex) in [
("empty_first_arg", r"\odv{}{x}"),
("empty_second_arg", r"\odv{y}{}"),
("empty_only_arg", r"\bb{}"),
("empty_arg_at_stream_end", r"\two{a}{}"),
("empty_arg_at_stream_start", r"\two{}{b}"),
] {
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!(name, mathml.mathml, latex);
}
}
#[test]
fn test_literal_args() {
let macros = vec![("hs".to_string(), r"\hspace{#1}".to_string())];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"x \hs{3em} y";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("custom_cmd_literal_args", mathml.mathml, latex);
}
#[test]
fn test_newcommand() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
for (name, latex) in [
(
"newcommand_one_arg",
r"\newcommand{\bb}[1]{\mathbb{#1}}\bb{C}",
),
(
"newcommand_zero_args",
r"\newcommand{\zz}{\mathbb{Z}}x \in \zz",
),
(
"newcommand_two_args",
r"\newcommand{\pair}[2]{(#1, #2)}\pair{x}{y}",
),
(
"newcommand_unbraced_name",
r"\newcommand\half{\frac{1}{2}}\half",
),
(
"newcommand_predefined_cmd_in_body",
r"\newcommand{\imp}{\implies}a \imp b",
),
(
"newcommand_nested_cmd_in_body",
r"\newcommand{\br}[1]{[#1]}\newcommand{\dbr}[1]{\br{\br{#1}}}\dbr{z}",
),
(
"newcommand_arg_after_nested_cmd",
r"\newcommand{\zoo}[1]{\ket{a}#1}\zoo{b}",
),
(
"newcommand_arg_in_and_after_nested_cmd",
r"\newcommand{\zoo}[2]{\ket{#2}#1}\zoo{b}{c}",
),
(
"newcommand_arg_after_predefined_cmd",
r"\newcommand{\zoo}[1]{\pod{a}#1}\zoo{b}",
),
(
"newcommand_arg_in_text",
r"\newcommand{\txt}[1]{\text{a#1b}}\txt{xy}",
),
("newcommand_empty_body", r"\newcommand{\nop}{}a\nop b"),
("newcommand_empty_body_as_arg", r"\newcommand{\nop}{}x^\nop"),
(
"newcommand_empty_arg_as_group",
r"\newcommand{\p}[1]{x^#1}\p{}",
),
("newcommand_spacing", r"\newcommand{\eq}{=}x + \eq 3"),
(
"newcommand_empty_arg",
r"\newcommand{\bb}[1]{\mathbb{#1}}\bb{}",
),
(
"newcommand_mathchoice_in_body",
r"\newcommand{\mc}[1]{\mathchoice{D}{T}{S}{SS}#1\mathchoice{#1}{[#1]}{#1}{#1}}\mc{z}",
),
] {
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{latex}` with error '{e}'"));
assert_snapshot!(name, mathml.mathml, latex);
}
}
#[test]
fn test_providecommand() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
for (name, latex) in [
(
"providecommand_zero_args",
r"\providecommand{\zz}{\mathbb{Z}}x \in \zz",
),
(
"providecommand_one_arg",
r"\providecommand{\bb}[1]{\mathbb{#1}}\bb{C}",
),
(
"providecommand_unbraced_name",
r"\providecommand\half{\frac{1}{2}}\half",
),
(
"providecommand_existing_custom_cmd",
r"\newcommand{\zz}{\mathbb{Z}}\providecommand{\zz}{\mathbb{Q}}\zz",
),
(
"providecommand_existing_builtin",
r"\providecommand{\frac}[2]{#1/#2}\frac{1}{2}",
),
(
"providecommand_then_newcommand",
r"\providecommand{\alpha}{a}\newcommand{\zz}{\mathbb{Z}}\zz",
),
] {
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{latex}` with error '{e}'"));
assert_snapshot!(name, mathml.mathml, latex);
}
}
#[test]
fn test_renewcommand() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
for (name, latex) in [
(
"renewcommand_replaces_newcommand",
r"\newcommand{\zz}{\mathbb{Z}}\renewcommand{\zz}{\mathbb{Q}}\zz",
),
(
"renewcommand_used_immediately",
r"\newcommand{\zz}{\mathbb{Z}}\renewcommand{\zz}{\mathbb{Q}}\zz\zz",
),
(
"renewcommand_changes_num_args",
r"\newcommand{\bb}{\mathbb{Z}}\renewcommand{\bb}[1]{\mathbb{#1}}\bb{C}",
),
(
"renewcommand_unbraced_name",
r"\newcommand\half{\frac{1}{2}}\renewcommand\half{\frac{1}{3}}\half",
),
(
"renewcommand_replaces_builtin",
r"\renewcommand{\epsilon}{\varepsilon}\epsilon",
),
(
"renewcommand_replaces_builtin_with_args",
r"\renewcommand{\frac}[2]{#1/#2}\frac{1}{2}",
),
(
"renewcommand_affects_earlier_body",
r"\newcommand{\xx}{1}\newcommand{\yy}{\xx}\renewcommand{\xx}{2}\yy",
),
(
"renewcommand_then_providecommand",
r"\renewcommand{\epsilon}{a}\providecommand{\epsilon}{b}\epsilon",
),
(
"renewcommand_body_uses_other_command",
r"\newcommand{\zz}{\mathbb{Z}}\renewcommand{\zz}{(\mathbb{Z})}\zz",
),
] {
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{latex}` with error '{e}'"));
assert_snapshot!(name, mathml.mathml, latex);
}
}
#[test]
fn test_late_resolution() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
for (name, latex) in [
(
"late_resolution_of_builtin",
r"\newcommand{\foo}{\epsilon}\renewcommand{\epsilon}{e}\foo",
),
(
"late_resolution_unbraced_name",
r"\newcommand\foo{\epsilon}\renewcommand\epsilon{e}\foo",
),
(
"late_resolution_unbraced_body",
r"\newcommand\foo\epsilon\renewcommand\epsilon{e}\foo",
),
(
"late_resolution_through_argument",
r"\newcommand{\m}[1]{x#1}\newcommand{\a}{1}\newcommand{\z}{0}\m{\renewcommand{\z}{\a}}\renewcommand{\a}{2}\z",
),
(
"late_resolution_env_in_body",
r"\newcommand{\m}{\begin{matrix}a\\b\end{matrix}}\m",
),
(
"late_resolution_empty_bodies",
r"\newcommand{\p}{}\newcommand{\q}{}\newcommand{\pq}{a\p b\q c}\renewcommand{\q}{!}\pq",
),
] {
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{latex}` with error '{e}'"));
assert_snapshot!(name, mathml.mathml, latex);
}
}
#[test]
fn test_late_resolution_across_snippets() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
global_group: true,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(r"\newcommand{\foo}{\epsilon}", MathDisplay::Inline)
.unwrap();
converter
.convert_with_global_state(r"\renewcommand{\epsilon}{e}", MathDisplay::Inline)
.unwrap();
let mathml = converter
.convert_with_global_state(r"\foo", MathDisplay::Inline)
.unwrap();
assert_snapshot!("late_resolution_across_snippets", mathml.mathml, r"\foo");
}
#[test]
fn test_forward_reference() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
for (name, latex) in [
(
"forward_reference",
r"\newcommand{\foo}{x+\baz}\newcommand{\baz}{y}\foo",
),
(
"forward_reference_with_args",
r"\newcommand{\foo}[1]{\baz{#1}}\newcommand{\baz}[1]{[#1]}\foo{z}",
),
(
"forward_reference_resolved_at_use",
r"\newcommand{\foo}{\baz}\newcommand{\baz}{1}\foo\renewcommand{\baz}{2}\foo",
),
] {
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{latex}` with error '{e}'"));
assert_snapshot!(name, mathml.mathml, latex);
}
converter
.convert_with_local_state(r"\newcommand{\foo}{x+\baz}", MathDisplay::Inline)
.unwrap();
assert!(
converter
.convert_with_local_state(r"\newcommand{\foo}{x+\baz}\foo", MathDisplay::Inline)
.is_err()
);
}
#[test]
fn test_forward_reference_across_snippets() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
global_group: true,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(r"\newcommand{\foo}{x+\baz}", MathDisplay::Inline)
.unwrap();
converter
.convert_with_global_state(r"\newcommand{\baz}{y}", MathDisplay::Inline)
.unwrap();
let mathml = converter
.convert_with_global_state(r"\foo", MathDisplay::Inline)
.unwrap();
assert_snapshot!("forward_reference_across_snippets", mathml.mathml, r"\foo");
converter.reset_global_state();
assert!(
converter
.convert_with_global_state(r"\foo", MathDisplay::Inline)
.is_err()
);
}
#[test]
fn test_expansion_limit() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
for latex in [
r"\newcommand{\a}{\b}\newcommand{\b}{\a}\a",
r"\newcommand{\a}{x\a}\a",
r"\newcommand{\zz}{\mathbb{Z}}\renewcommand{\zz}{(\zz)}\zz",
] {
let Err(err) = converter.convert_with_local_state(latex, MathDisplay::Inline) else {
panic!("`{latex}` should not have converted");
};
assert!(
err.to_string().contains("Too many expansions"),
"unexpected error for `{latex}`: {err}"
);
}
}
#[test]
fn test_configurable_expansion_limit() {
let latex = r"\newcommand{\a}{\b\b}\newcommand{\b}{x}\a";
let strict = LatexToMathML::new(MathCoreConfig {
max_expansions: MaxExpansions(2),
..Default::default()
})
.unwrap();
let Err(err) = strict.convert_with_local_state(latex, MathDisplay::Inline) else {
panic!("`{latex}` should not have converted with a limit of 2 expansions");
};
assert!(
err.to_string().contains("Too many expansions"),
"unexpected error: {err}"
);
let lenient = LatexToMathML::new(MathCoreConfig {
max_expansions: MaxExpansions(100),
..Default::default()
})
.unwrap();
assert!(
lenient
.convert_with_local_state(latex, MathDisplay::Inline)
.is_ok()
);
}
#[test]
fn test_config_macros_referring_to_each_other() {
let macros = vec![
("half".to_string(), r"\frac{1}{\two}".to_string()),
("two".to_string(), r"2".to_string()),
];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"\half";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!(
"config_macros_referring_to_each_other",
mathml.mathml,
latex
);
}
#[test]
fn test_config_macro_with_undefined_command() {
let macros = vec![
("half".to_string(), r"\frac{1}{2}".to_string()),
("bad".to_string(), r"\frac{1}{\nosuchcommand}".to_string()),
];
let config = MathCoreConfig {
macros,
..Default::default()
};
let error = LatexToMathML::new(config).unwrap_err();
assert_eq!(error.1, 1);
assert_eq!(error.2, r"\frac{1}{\nosuchcommand}");
}
#[test]
fn test_renewcommand_config_macro() {
let macros = vec![("half".to_string(), r"\frac{1}{2}".to_string())];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"\renewcommand{\half}{\frac{1}{3}}\half";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("renewcommand_replaces_config_macro", mathml.mathml, latex);
let latex = r"\half";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("renewcommand_config_macro_after", mathml.mathml, latex);
}
#[test]
fn test_renewcommand_unreliable_rendering() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
allow_unreliable_rendering: true,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"\renewcommand{\widecheck}{x}\widecheck";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!(
"renewcommand_replaces_builtin_unreliable",
mathml.mathml,
latex
);
}
#[test]
fn test_renewcommand_across_snippets() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
global_group: true,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(r"\newcommand{\zz}{\mathbb{Z}}\zz", MathDisplay::Inline)
.unwrap();
converter
.convert_with_global_state(r"\renewcommand{\zz}{\mathbb{Q}}", MathDisplay::Inline)
.unwrap();
let mathml = converter
.convert_with_global_state(r"\zz", MathDisplay::Inline)
.unwrap();
assert_snapshot!("renewcommand_across_snippets", mathml.mathml, r"\zz");
converter.reset_global_state();
assert!(
converter
.convert_with_global_state(r"\renewcommand{\zz}{\mathbb{R}}", MathDisplay::Inline)
.is_err()
);
}
#[test]
fn test_providecommand_with_config_macro() {
let macros = vec![("half".to_string(), r"\frac{1}{2}".to_string())];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"\providecommand{\half}{\frac{1}{3}}\half";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("providecommand_existing_config_macro", mathml.mathml, latex);
}
#[test]
fn test_newcommand_uses_config_macro() {
let macros = vec![("half".to_string(), r"\frac{1}{2}".to_string())];
let config = MathCoreConfig {
macros,
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"\newcommand{\halves}[1]{#1\half}\halves{3}";
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("newcommand_uses_config_macro", mathml.mathml, latex);
}
#[test]
fn test_newcommand_across_snippets() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
global_group: true,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(
r"\newcommand{\bb}[1]{\mathbb{#1}}\bb{R}",
MathDisplay::Inline,
)
.unwrap();
let mathml = converter
.convert_with_global_state(r"\bb{C}", MathDisplay::Inline)
.unwrap();
assert_snapshot!("newcommand_across_snippets", mathml.mathml, r"\bb{C}");
converter.reset_global_state();
assert!(
converter
.convert_with_global_state(r"\bb{C}", MathDisplay::Inline)
.is_err()
);
}
#[test]
fn test_newcommand_in_convert_all() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
global_group: true,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let results = converter.convert_all(&[
(r"\newcommand{\zz}{\mathbb{Z}}", MathDisplay::Inline),
(r"n \in \zz", MathDisplay::Inline),
]);
let mathml = &results[1].as_ref().unwrap().mathml;
assert_snapshot!("newcommand_in_convert_all", mathml, r"n \in \zz");
}
#[test]
fn test_newcommand_without_global_group() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
let latex = r"\newcommand{\bb}[1]{\mathbb{#1}}\bb{R}";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("newcommand_without_global_group", mathml.mathml, latex);
assert!(
converter
.convert_with_global_state(r"\bb{C}", MathDisplay::Inline)
.is_err()
);
converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
}
#[test]
fn test_renewcommand_without_global_group() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(r"\newcommand{\zz}{\mathbb{Z}}\zz", MathDisplay::Inline)
.unwrap();
assert!(
converter
.convert_with_global_state(r"\renewcommand{\zz}{\mathbb{Q}}", MathDisplay::Inline)
.is_err()
);
}
#[test]
fn test_newcommand_in_convert_all_without_global_group() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let results = converter.convert_all(&[
(r"\newcommand{\zz}{\mathbb{Z}}", MathDisplay::Inline),
(r"n \in \zz", MathDisplay::Inline),
]);
assert!(results[0].is_ok());
assert!(results[1].is_err());
}
#[test]
fn test_newcommand_does_not_leak_into_local_state() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_local_state(r"\newcommand{\zz}{\mathbb{Z}}\zz", MathDisplay::Inline)
.unwrap();
assert!(
converter
.convert_with_local_state(r"\zz", MathDisplay::Inline)
.is_err()
);
}
#[test]
fn test_let() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
for (name, latex) in [
(
"let_keep_old_meaning",
r"\let\originalepsilon\epsilon\renewcommand{\epsilon}{1+\originalepsilon}\epsilon",
),
("let_plain", r"\let\a\alpha\a"),
("let_equals", r"\let\a=\alpha\a"),
("let_equals_spaced", r"\let\a = \alpha \a"),
("let_to_equals", r"\let\eq==x\eq y"),
(
"let_does_not_follow_redefinition",
r"\let\a\alpha\renewcommand{\alpha}{\beta}\a",
),
("let_command_with_args", r"\let\f\frac\f{1}{2}"),
(
"let_to_custom_cmd",
r"\newcommand{\m}[1]{\sqrt{#1}}\let\n\m\n{2}",
),
(
"let_overwrites_newcommand",
r"\newcommand{\a}{x}\let\a\alpha\a",
),
(
"renewcommand_overwrites_let",
r"\let\a\alpha\renewcommand{\a}{x}\a",
),
("let_chain", r"\let\a\alpha\let\b\a\renewcommand{\a}{x}\a\b"),
] {
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{latex}` with error '{e}'"));
assert_snapshot!(name, mathml.mathml, latex);
}
}
#[test]
fn test_let_across_snippets() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
global_group: true,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(r"\newcommand{\zz}{\mathbb{Z}}", MathDisplay::Inline)
.unwrap();
converter
.convert_with_global_state(r"\let\oldzz\zz", MathDisplay::Inline)
.unwrap();
converter
.convert_with_global_state(r"\renewcommand{\zz}{\mathbb{Q}}", MathDisplay::Inline)
.unwrap();
let latex = r"\zz \oldzz";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("let_across_snippets", mathml.mathml, latex);
}
#[test]
fn test_let_without_global_group() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(r"\let\a\alpha\a", MathDisplay::Inline)
.unwrap();
assert!(
converter
.convert_with_global_state(r"\a", MathDisplay::Inline)
.is_err()
);
}
#[test]
fn test_global_let() {
for (name, definition, usage) in [
(
"global_let_to_local_cmd",
r"\newcommand{\zzz}{z}\global\let\zzzz\zzz",
r"\zzzz",
),
(
"global_let_chain",
r"\newcommand{\m}[1]{\sqrt{#1}}\let\n\m\global\let\o\n",
r"\o{2}",
),
("global_let_to_builtin", r"\global\let\a\alpha", r"\a"),
(
"global_let_to_builtin_with_args",
r"\global\let\f\frac",
r"\f{1}{2}",
),
("global_let_to_config_cmd", r"\global\let\c\cfgcmd", r"\c"),
("global_let_doubled", r"\global\global\let\a\alpha", r"\a"),
] {
let config = MathCoreConfig {
macros: vec![("cfgcmd".to_string(), r"\mathbb{C}".to_string())],
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(definition, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{definition}` with error '{e}'"));
let mathml = converter
.convert_with_global_state(usage, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{usage}` with error '{e}'"));
assert_snapshot!(name, mathml.mathml, &format!("{definition}\n{usage}"));
}
}
#[test]
fn test_global_let_keeps_late_binding() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(
r"\newcommand{\inner}{x}\newcommand{\outer}{\inner+1}\global\let\g\outer",
MathDisplay::Inline,
)
.unwrap();
assert!(
converter
.convert_with_global_state(r"\g", MathDisplay::Inline)
.is_err()
);
let latex = r"\newcommand{\inner}{y}\g";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("global_let_late_binding", mathml.mathml, latex);
}
#[test]
fn test_global_let_beats_local() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
let latex = r"\newcommand{\a}{1}\a\global\let\a\alpha\a";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("global_let_beats_local", mathml.mathml, latex);
let latex = r"\a";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("global_let_beats_local_next_snippet", mathml.mathml, latex);
}
#[test]
fn test_global_let_in_global_group() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
global_group: true,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(
r"\newcommand{\zzz}{z}\global\let\zzzz\zzz",
MathDisplay::Inline,
)
.unwrap();
let latex = r"\zzz\zzzz";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("global_let_in_global_group", mathml.mathml, latex);
}
#[test]
fn test_def() {
let config = MathCoreConfig {
macros: vec![("cfgcmd".to_string(), r"\mathbb{C}".to_string())],
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
for (name, latex) in [
("def_zero_args", r"\def\zz{\mathbb{Z}}x \in \zz"),
("def_one_arg", r"\def\bb#1{\mathbb{#1}}\bb{C}"),
("def_two_args", r"\def\add#1#2{#1+#2}\add 2 3"),
(
"def_nine_args",
r"\def\n#1#2#3#4#5#6#7#8#9{#9#8#7#6#5#4#3#2#1}\n123456789",
),
("def_empty_body", r"\def\nop{}a\nop b"),
("def_redefines_builtin", r"\def\frac#1#2{#1/#2}\frac{1}{2}"),
("def_redefines_config_macro", r"\def\cfgcmd{y}\cfgcmd"),
("def_redefines_def", r"\def\zz{1}\def\zz{2}\zz"),
(
"def_redefines_newcommand",
r"\newcommand{\zz}{1}\def\zz{2}\zz",
),
(
"newcommand_redefines_def",
r"\def\zz{1}\renewcommand{\zz}{2}\zz",
),
("def_late_binding", r"\def\a{\b}\def\b{x}\a"),
(
"def_follows_redefinition",
r"\def\a{\b}\def\b{x}\a\def\b{y}\a",
),
("def_repeated_arg", r"\def\sq#1{#1^2}\sq{y}+\sq{y}"),
("def_unused_arg", r"\def\drop#1{z}\drop{y}"),
(
"def_after_let",
r"\let\oldalpha\alpha\def\alpha{1+\oldalpha}\alpha",
),
("def_spaced", r"\def \a #1{[#1]}\a{x}"),
("def_spaced_without_params", r"\def \a {x}\a"),
] {
let mathml = converter
.convert_with_local_state(latex, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{latex}` with error '{e}'"));
assert_snapshot!(name, mathml.mathml, latex);
}
}
#[test]
fn test_gdef() {
for (name, definition, usage) in [
("gdef", r"\gdef\zz{z}", r"\zz"),
("gdef_with_args", r"\gdef\bb#1{\mathbb{#1}}", r"\bb{C}"),
("global_def", r"\global\def\add#1#2{#1+#2}", r"\add 2 3"),
("global_gdef", r"\global\gdef\a{\alpha}", r"\a"),
("global_global_def", r"\global\global\def\a{\alpha}", r"\a"),
(
"gdef_body_refers_to_local_cmd",
r"\newcommand{\zzz}{z}\gdef\g{\zzz}",
r"\newcommand{\zzz}{q}\g",
),
(
"gdef_then_global_let",
r"\gdef\m#1{\sqrt{#1}}\global\let\n\m",
r"\n{2}",
),
] {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(definition, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{definition}` with error '{e}'"));
let mathml = converter
.convert_with_global_state(usage, MathDisplay::Inline)
.unwrap_or_else(|e| panic!("failed to convert `{usage}` with error '{e}'"));
assert_snapshot!(name, mathml.mathml, &format!("{definition}\n{usage}"));
}
}
#[test]
fn test_def_without_global_group() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(r"\def\a{\alpha}\a", MathDisplay::Inline)
.unwrap();
assert!(
converter
.convert_with_global_state(r"\a", MathDisplay::Inline)
.is_err()
);
}
#[test]
fn test_gdef_keeps_late_binding() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(r"\def\inner{x}\gdef\g{\inner+1}", MathDisplay::Inline)
.unwrap();
assert!(
converter
.convert_with_global_state(r"\g", MathDisplay::Inline)
.is_err()
);
let latex = r"\def\inner{y}\g";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("gdef_late_binding", mathml.mathml, latex);
}
#[test]
fn test_gdef_beats_local() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
let latex = r"\def\a{1}\a\gdef\a{\alpha}\a";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("gdef_beats_local", mathml.mathml, latex);
let latex = r"\a";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("gdef_beats_local_next_snippet", mathml.mathml, latex);
}
#[test]
fn test_def_in_global_group() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
global_group: true,
..Default::default()
};
let mut converter = LatexToMathML::new(config).unwrap();
converter
.convert_with_global_state(r"\def\zzz{z}\gdef\zzzz{q}", MathDisplay::Inline)
.unwrap();
let latex = r"\zzz\zzzz";
let mathml = converter
.convert_with_global_state(latex, MathDisplay::Inline)
.unwrap();
assert_snapshot!("def_in_global_group", mathml.mathml, latex);
}
#[test]
fn test_def_recursion() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
assert!(
converter
.convert_with_local_state(r"\def\a{\a}\a", MathDisplay::Inline)
.is_err()
);
}
#[test]
fn test_let_with_ignore_unknown_commands() {
let config = MathCoreConfig {
pretty_print: PrettyPrint::Always,
ignore_unknown_commands: true,
..Default::default()
};
let converter = LatexToMathML::new(config).unwrap();
let latex = r"\let\a\zzzz\newcommand{\zzzz}{x}\a";
assert!(
converter
.convert_with_local_state(latex, MathDisplay::Inline)
.is_err()
);
}