Skip to content

Commit c6f67a3

Browse files
authored
Add unit tests for pretty_print.rs (#464)
* Add unit tests for pretty_print.rs * Extend unit tests in `pretty_print.rs` to handle non-BMP characters and XPath evaluation * Add unit tests for non-BMP character handling with MathML and XPath
1 parent 84f4767 commit c6f67a3

File tree

1 file changed

+197
-0
lines changed

1 file changed

+197
-0
lines changed

src/pretty_print.rs

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,3 +513,200 @@ fn need_quotes(string: &str) -> bool {
513513
|| string.parse::<i64>().is_ok()
514514
|| string.parse::<f64>().is_ok()
515515
}
516+
517+
#[cfg(test)]
518+
mod tests {
519+
use super::*;
520+
use sxd_document::dom::{ChildOfElement, ChildOfRoot};
521+
use sxd_document::parser;
522+
523+
/// helper function
524+
fn first_element(package: &sxd_document::Package) -> Element<'_> {
525+
let doc = package.as_document();
526+
for child in doc.root().children() {
527+
if let ChildOfRoot::Element(e) = child {
528+
return e;
529+
}
530+
}
531+
panic!("No root element found");
532+
}
533+
534+
#[test]
535+
/// Escapes XML entities and invisible characters for safe display.
536+
/// Tests the method on a few hardcoded characters.
537+
fn handle_special_chars_escapes() {
538+
let input = "& < > \" ' \u{2061} \u{2062} \u{2063} \u{2064} x";
539+
let expected = "&amp; &lt; &gt; &quot; &apos; &#x2061; &#x2062; &#x2063; &#x2064; x";
540+
assert_eq!(handle_special_chars(input), expected);
541+
}
542+
543+
#[test]
544+
/// Formats a leaf element as a single line with escaped text.
545+
fn format_element_leaf_text() {
546+
let package = parser::parse("<math><mi>&amp;</mi></math>").unwrap();
547+
let math = first_element(&package);
548+
let mi = math
549+
.children()
550+
.iter()
551+
.find_map(|c| match c {
552+
ChildOfElement::Element(e) => Some(*e),
553+
_ => None,
554+
})
555+
.unwrap();
556+
assert_eq!(format_element(mi, 0), " <mi>&amp;</mi>\n");
557+
}
558+
559+
#[test]
560+
/// Formats a nested element with indentation and newlines.
561+
fn format_element_nested() {
562+
let package = parser::parse("<math><mi>x</mi><mo>+</mo></math>").unwrap();
563+
let math = first_element(&package);
564+
let rendered = format_element(math, 0);
565+
assert!(rendered.starts_with(" <math>\n"));
566+
assert!(rendered.contains("\n <mi>x</mi>\n"));
567+
assert!(rendered.contains("\n <mo>+</mo>\n"));
568+
assert!(rendered.ends_with("</math>\n"));
569+
}
570+
571+
#[test]
572+
/// Escapes special characters in attribute values.
573+
fn format_attrs_escapes() {
574+
let package = parser::parse("<math a=\"&amp;\" b=\"&lt;\"></math>").unwrap();
575+
let math = first_element(&package);
576+
let rendered = format_attrs(&math.attributes());
577+
assert!(rendered.contains(" a='&amp;'"));
578+
assert!(rendered.contains(" b='&lt;'"));
579+
}
580+
581+
#[test]
582+
/// Preserves non-BMP characters from a literal XML form.
583+
fn format_element_non_bmp_character_literal() {
584+
let package = parser::parse("<math><mi>𝞪</mi></math>").unwrap();
585+
let math = first_element(&package);
586+
let mi = math
587+
.children()
588+
.iter()
589+
.find_map(|c| match c {
590+
ChildOfElement::Element(e) => Some(*e),
591+
_ => None,
592+
})
593+
.unwrap();
594+
let rendered = format_element(mi, 0);
595+
assert!(rendered.contains("𝞪"));
596+
}
597+
598+
#[test]
599+
/// Preserves non-BMP characters from a numeric XML form.
600+
fn format_element_non_bmp_character_numeric() {
601+
let package = parser::parse("<math><mi>&#x1d7aa;</mi></math>").unwrap();
602+
let math = first_element(&package);
603+
let mi = math
604+
.children()
605+
.iter()
606+
.find_map(|c| match c {
607+
ChildOfElement::Element(e) => Some(*e),
608+
_ => None,
609+
})
610+
.unwrap();
611+
let rendered = format_element(mi, 0);
612+
assert!(rendered.contains("𝞪"));
613+
}
614+
615+
#[test]
616+
/// Evaluates non-BMP literal text through sxd_xpath.
617+
fn xpath_non_bmp_literal() {
618+
use sxd_xpath::{Factory, Value};
619+
620+
let package = parser::parse("<math><mi>𝞪</mi></math>").unwrap();
621+
let xpath = Factory::new().build("string(/math/mi)").unwrap().unwrap();
622+
let context = sxd_xpath::Context::new();
623+
624+
let value = xpath.evaluate(&context, first_element(&package)).unwrap();
625+
match value {
626+
Value::String(s) => assert_eq!(s, "𝞪"),
627+
_ => panic!("Expected string value from xpath"),
628+
}
629+
}
630+
631+
#[test]
632+
/// Evaluates non-BMP numeric text through sxd_xpath.
633+
fn xpath_non_bmp_numeric() {
634+
use sxd_xpath::{Factory, Value};
635+
636+
let package = parser::parse("<math><mi>&#x1d7aa;</mi></math>").unwrap();
637+
let xpath = Factory::new().build("string(/math/mi)").unwrap().unwrap();
638+
let context = sxd_xpath::Context::new();
639+
640+
let value = xpath.evaluate(&context, first_element(&package)).unwrap();
641+
match value {
642+
Value::String(s) => assert_eq!(s, "𝞪"),
643+
_ => panic!("Expected string value from xpath"),
644+
}
645+
}
646+
647+
#[test]
648+
/// Evaluates non-BMP literal text with a MathML namespace-qualified XPath.
649+
fn xpath_non_bmp_namespace_literal() {
650+
use sxd_xpath::{Factory, Value};
651+
652+
let xml = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>𝞪</mi></math>";
653+
let package = parser::parse(xml).unwrap();
654+
let xpath = Factory::new()
655+
.build("string(/m:math/m:mi)")
656+
.unwrap()
657+
.unwrap();
658+
let mut context = sxd_xpath::Context::new();
659+
context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
660+
661+
let value = xpath.evaluate(&context, first_element(&package)).unwrap();
662+
match value {
663+
Value::String(s) => assert_eq!(s, "𝞪"),
664+
_ => panic!("Expected string value from xpath"),
665+
}
666+
}
667+
668+
#[test]
669+
/// Evaluates non-BMP numeric text with a MathML namespace-qualified XPath.
670+
fn xpath_non_bmp_namespace_numeric() {
671+
use sxd_xpath::{Factory, Value};
672+
673+
let xml = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>&#120746;</mi></math>";
674+
let package = parser::parse(xml).unwrap();
675+
let xpath = Factory::new()
676+
.build("string(/m:math/m:mi)")
677+
.unwrap()
678+
.unwrap();
679+
let mut context = sxd_xpath::Context::new();
680+
context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
681+
682+
let value = xpath.evaluate(&context, first_element(&package)).unwrap();
683+
match value {
684+
Value::String(s) => assert_eq!(s, "𝞪"),
685+
_ => panic!("Expected string value from xpath"),
686+
}
687+
}
688+
689+
#[test]
690+
/// Extracts a text node via XPath (nodeset result) and verifies the non-BMP character survives.
691+
fn xpath_non_bmp_text_nodeset() {
692+
use sxd_xpath::{Factory, Value};
693+
694+
let xml = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\"><mi>𝞪</mi></math>";
695+
let package = parser::parse(xml).unwrap();
696+
let xpath = Factory::new().build("/m:math/m:mi/text()").unwrap().unwrap();
697+
let mut context = sxd_xpath::Context::new();
698+
context.set_namespace("m", "http://www.w3.org/1998/Math/MathML");
699+
700+
let value = xpath.evaluate(&context, first_element(&package)).unwrap();
701+
match value {
702+
Value::Nodeset(nodes) => {
703+
let ordered = nodes.document_order();
704+
let node = ordered.first().expect("Expected one text node");
705+
let text = node.text().expect("Expected text node");
706+
assert_eq!(text.text(), "𝞪");
707+
assert_eq!(ordered.len(), 1);
708+
}
709+
_ => panic!("Expected nodeset value from xpath"),
710+
}
711+
}
712+
}

0 commit comments

Comments
 (0)