#[macro_export]
macro_rules! wasm_face_tests {
() => {
#[wasm_bindgen_test]
fn parse_xpath_roundtrip() {
let doc = leptris::wasmface::WasmDocument::parse(
"<root><item id=\"1\">alpha</item>\
<item id=\"2\">beta</item></root>",
)
.expect("parse");
assert_eq!(doc.root_name().as_deref(), Some("root"));
assert_eq!(doc.xpath_count("//item").unwrap(), 2);
assert_eq!(doc.xpath("//item[1]").unwrap(), "alpha");
assert_eq!(doc.xpath("count(//item)").unwrap(), "2");
assert_eq!(doc.xpath("1+1").unwrap(), "2");
}
#[wasm_bindgen_test]
fn serialize_roundtrip_keeps_attributes() {
let doc = leptris::wasmface::WasmDocument::parse(
"<w:document xmlns:w=\"u\"><w:r><w:sz w:val=\"24\"/></w:r>\
</w:document>",
)
.expect("parse");
let xml = doc.to_xml().unwrap();
assert!(xml.contains("<w:sz w:val=\"24\"/>"), "xml: {xml}");
}
#[wasm_bindgen_test]
fn parse_error_surfaces_status() {
assert!(leptris::wasmface::WasmDocument::parse("<r>").is_err());
}
#[wasm_bindgen_test]
fn node_handles_walk_and_write() {
let doc =
leptris::wasmface::WasmDocument::parse("<root><a x='1'>t1</a><b/></root>").unwrap();
let root = doc.root().expect("root");
assert_eq!(root.name().as_deref(), Some("root"));
let kids = root.children();
assert_eq!(kids.len(), 2, "a + b in document order");
assert_eq!(kids[0].name().as_deref(), Some("a"));
assert_eq!(kids[0].text().as_deref(), Some("t1"));
assert_eq!(kids[0].kind(), leptris::wasmface::WasmNodeKind::Element);
let built = leptris::wasmface::WasmDocument::parse("<w/>").unwrap();
let top = built.root().expect("w");
let child = built.create_element("kid").expect("kid");
child.set_attribute("k", "v").expect("set attr");
top.add_child(child).expect("attach");
let text = built.create_text("hello").expect("text");
top.add_child(text).expect("attach text");
let xml = built.to_xml().unwrap();
assert!(xml.contains("<kid k=\"v\"/>"), "xml: {xml}");
assert!(xml.contains("hello"), "xml: {xml}");
let res = doc.xpath_full("//a").unwrap();
assert_eq!(res.kind(), 0, "nodeset");
assert_eq!(res.nodes().len(), 1);
assert_eq!(res.nodes()[0].name().as_deref(), Some("a"));
let count = doc.xpath_full("count(//*)").unwrap();
assert_eq!(count.kind(), 2, "number");
assert!((count.number() - 3.0).abs() < 1e-9);
let flag = doc.xpath_full("count(//a) = 1").unwrap();
assert_eq!(flag.kind(), 1, "boolean");
assert!(flag.boolean());
}
#[wasm_bindgen_test]
fn xsd_validate_roundtrip() {
let schema = leptris::wasmface::compile_xsd(
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\
<xs:element name=\"r\"><xs:complexType><xs:sequence>\
<xs:element name=\"e\" type=\"xs:string\"/></xs:sequence>\
</xs:complexType></xs:element></xs:schema>",
)
.expect("compile");
let good = leptris::wasmface::WasmDocument::parse("<r><e>x</e></r>").unwrap();
assert!(schema.validate_doc(&good).unwrap(), "valid instance");
let bad = leptris::wasmface::WasmDocument::parse("<r><e>x</e><e>y</e></r>").unwrap();
assert!(!schema.validate_doc(&bad).unwrap(), "invalid instance");
let rows = schema.last_errors();
assert!(!rows.is_empty(), "error rows must enumerate");
}
#[wasm_bindgen_test]
fn namespace_reads() {
let doc = leptris::wasmface::WasmDocument::parse(
"<root xmlns:ns=\"urn:n\"><ns:child>t</ns:child><plain/></root>",
)
.unwrap();
let root = doc.root().expect("root");
assert!(root.namespace_uri().is_none(), "no-ns root");
let kids = root.children();
assert_eq!(kids[0].namespace_uri().as_deref(), Some("urn:n"));
assert_eq!(kids[0].prefix().as_deref(), Some("ns"));
assert_eq!(kids[1].namespace_uri(), None);
assert_eq!(kids[1].prefix(), None);
}
#[wasm_bindgen_test]
fn attribute_pairs_lanes() {
let doc = leptris::wasmface::WasmDocument::parse(
"<root a=\"1\" ns:b=\"2\" xmlns:ns=\"urn:n\"/>",
)
.unwrap();
let pairs = doc.root().expect("root").attribute_pairs().expect("pairs");
assert_eq!(pairs.len(), 2);
assert_eq!(pairs[0].name(), "a");
assert_eq!(pairs[0].value(), "1");
assert_eq!(pairs[0].uri(), None, "no-namespace attr has no uri lane");
assert_eq!(pairs[1].name(), "ns:b");
assert_eq!(pairs[1].prefix().as_deref(), Some("ns"));
assert_eq!(pairs[1].uri().as_deref(), Some("urn:n"));
}
#[wasm_bindgen_test]
fn attribute_pairs_no_ns_after_namespaced() {
for xml in [
"<a xmlns:p=\"urn:p\" p:x=\"1\" y=\"2\"/>",
"<a y=\"2\" xmlns:p=\"urn:p\" p:x=\"1\"/>",
] {
let doc = leptris::wasmface::WasmDocument::parse(xml).unwrap();
let pairs = doc.root().expect("root").attribute_pairs().expect("pairs");
let x = pairs.iter().find(|a| a.name() == "p:x").expect("p:x");
let y = pairs.iter().find(|a| a.name() == "y").expect("y");
assert_eq!(x.value(), "1", "{xml}");
assert_eq!(x.prefix().as_deref(), Some("p"), "{xml}");
assert_eq!(x.uri().as_deref(), Some("urn:p"), "{xml}");
assert_eq!(y.value(), "2", "{xml}");
assert_eq!(y.prefix(), None, "{xml}");
assert_eq!(y.uri(), None, "{xml}");
}
}
#[wasm_bindgen_test]
fn attribute_pairs_mixed_three() {
let doc = leptris::wasmface::WasmDocument::parse(
"<a xmlns:p=\"urn:p\" xmlns:q=\"urn:q\" p:x=\"1\" y=\"2\" q:z=\"3\"/>",
)
.unwrap();
let pairs = doc.root().expect("root").attribute_pairs().expect("pairs");
let got: Vec<_> = pairs
.iter()
.filter(|a| !a.name().starts_with("xmlns"))
.map(|a| (a.name(), a.value(), a.prefix(), a.uri()))
.collect();
let s = |v: &str| Some(v.to_string());
assert_eq!(
got,
vec![
("p:x".to_string(), "1".to_string(), s("p"), s("urn:p")),
("y".to_string(), "2".to_string(), None, None),
("q:z".to_string(), "3".to_string(), s("q"), s("urn:q")),
]
);
}
#[wasm_bindgen_test]
fn capabilities_describe_engine() {
let caps = leptris::wasmface::engine_capabilities();
assert!(caps.caps() > 0.0, "engine advertises bits");
assert_eq!(caps.descriptor_version(), 1);
assert!(caps.serves(1.0), "bit 0 must be served by any engine");
}
#[wasm_bindgen_test]
fn parse_html_whatwg() {
let doc = leptris::wasmface::parse_html("<p>hello<br>world</p>").unwrap();
let xml = doc.to_xml().unwrap();
assert!(xml.to_lowercase().contains("hello"), "text kept: {xml}");
assert!(doc.root_name().is_some());
}
#[wasm_bindgen_test]
fn schema_set_through_vfs() {
const A: &str = "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" \
targetNamespace=\"urn:a\" xmlns=\"urn:a\">\
<xs:element name=\"ea\" type=\"xs:string\"/></xs:schema>";
const B: &str = "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" \
targetNamespace=\"urn:b\" xmlns:b=\"urn:b\" xmlns:a=\"urn:a\">\
<xs:import namespace=\"urn:a\" schemaLocation=\"set-a.xsd\"/>\
<xs:element name=\"eb\"><xs:complexType><xs:sequence>\
<xs:element ref=\"a:ea\"/></xs:sequence></xs:complexType>\
</xs:element></xs:schema>";
leptris::wasmface::register_file(
"/virtual/set-a.xsd".to_string(),
A.as_bytes().to_vec(),
)
.expect("register a");
leptris::wasmface::register_file(
"/virtual/set-b.xsd".to_string(),
B.as_bytes().to_vec(),
)
.expect("register b");
let schema = leptris::wasmface::compile_xsd_set(vec![
"/virtual/set-b.xsd".to_string(),
"/virtual/set-a.xsd".to_string(),
])
.expect("set compile through vfs");
let good = leptris::wasmface::WasmDocument::parse(
"<b:eb xmlns:b=\"urn:b\" xmlns:a=\"urn:a\"><a:ea>x</a:ea></b:eb>",
)
.unwrap();
assert!(
schema.validate_doc(&good).unwrap(),
"imported set validates"
);
}
#[wasm_bindgen_test]
fn loud_failures() {
assert!(
leptris::wasmface::compile_xsd_set(vec!["/virtual/nope.xsd".to_string()]).is_err(),
"unregistered path must fail"
);
assert!(leptris::wasmface::compile_xsd(
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\
<xs:element name=\"x\"><xs:complexType><xs:sequence>\
<xs:element ref=\"nope\"/></xs:sequence></xs:complexType>\
</xs:element></xs:schema>",
)
.is_err());
}
#[wasm_bindgen_test]
fn attribute_single_read() {
let doc = leptris::wasmface::WasmDocument::parse(
"<root id=\"r1\" w:val=\"7\" xmlns:w=\"u\"/>",
)
.expect("parse");
let root = doc.root().expect("root");
assert_eq!(root.attribute("id").as_deref(), Some("r1"));
assert_eq!(root.attribute("w:val").as_deref(), Some("7"));
assert_eq!(root.attribute("absent"), None);
}
#[wasm_bindgen_test]
fn element_rows_preorder_batches_and_resume() {
let doc =
leptris::wasmface::WasmDocument::parse("<a id=\"top\"><b><c/></b><d>text</d></a>")
.expect("parse");
let root = doc.root().expect("root");
let all = root.element_rows(0, 10).expect("batch");
assert!(all.is_done());
assert_eq!(all.rows().len(), 4); assert_eq!(all.rows()[0].name(), "a");
assert_eq!(all.rows()[0].depth(), 0);
assert_eq!(all.rows()[0].attrs().len(), 1);
assert_eq!(all.rows()[0].attrs()[0].name(), "id");
assert_eq!(all.rows()[3].name(), "d");
assert_eq!(all.rows()[3].text(), "text");
let b1 = root.element_rows(0, 2).expect("b1");
assert_eq!(b1.rows().len(), 2);
assert!(!b1.is_done());
assert_eq!(b1.next_cursor(), 2);
let b2 = root.element_rows(b1.next_cursor(), 2).expect("b2");
assert_eq!(b2.rows().len(), 2);
assert!(b2.is_done());
let b3 = root.element_rows(99, 2).expect("b3");
assert!(b3.rows().is_empty());
assert!(b3.is_done());
}
#[wasm_bindgen_test]
fn create_comment_cdata_pi_and_add_child() {
use leptris::wasmface::{WasmDocument, WasmNodeKind};
let doc = WasmDocument::parse("<r/>").unwrap();
let root = doc.root().expect("root");
let c = root
.add_child(doc.create_comment(" note ").unwrap())
.unwrap();
let d = root.add_child(doc.create_cdata("a<b&c").unwrap()).unwrap();
let p = root
.add_child(doc.create_pi("xml-stylesheet", "href=\"s.css\"").unwrap())
.unwrap();
assert_eq!(c.kind(), WasmNodeKind::Comment);
assert_eq!(d.kind(), WasmNodeKind::Cdata);
assert_eq!(p.kind(), WasmNodeKind::Pi);
assert_eq!(root.children().len(), 3);
let xml = doc.to_xml().unwrap();
assert!(xml.contains("<!-- note -->"), "xml: {xml}");
assert!(xml.contains("<![CDATA[a<b&c]]>"), "xml: {xml}");
assert!(
xml.contains("<?xml-stylesheet href=\"s.css\"?>"),
"xml: {xml}"
);
}
#[wasm_bindgen_test]
fn remove_attribute_reports_presence() {
let doc = leptris::wasmface::WasmDocument::parse(
"<r a=\"1\" b=\"2\" xmlns:n=\"urn:n\" n:c=\"3\"/>",
)
.unwrap();
let root = doc.root().expect("root");
assert!(root.remove_attribute("a").unwrap());
assert!(!root.remove_attribute("a").unwrap(), "already gone");
assert!(!root.remove_attribute("nope").unwrap());
assert!(root.remove_attribute("n:c").unwrap(), "spelled name");
let names: Vec<String> = root
.attribute_pairs()
.unwrap()
.iter()
.map(|a| a.name())
.collect();
assert_eq!(names, vec!["b".to_string()]);
let xml = doc.to_xml().unwrap();
assert!(
!xml.contains("a=\"1\"") && !xml.contains("n:c"),
"xml: {xml}"
);
}
#[wasm_bindgen_test]
fn parse_error_carries_position() {
use leptris::wasmface::{last_parse_error, WasmDocument};
assert!(WasmDocument::parse("<r>\n <a>\n </b></r>").is_err());
let e = last_parse_error().expect("engine recorded the failure");
assert_eq!((e.line(), e.column()), (3, 7), "{}", e.message());
assert!(e.message().contains("mismatch"), "{}", e.message());
}
#[wasm_bindgen_test]
fn parse_with_options_applies_max_depth() {
use leptris::wasmface::{WasmDocument, WasmParseOptions};
let deep = format!("{}{}", "<a>".repeat(20), "</a>".repeat(20));
assert!(WasmDocument::parse(&deep).is_ok());
let mut opts = WasmParseOptions::new();
opts.max_depth = 5;
assert!(WasmDocument::parse_with_options(&deep, &opts).is_err());
opts.max_depth = 0;
let doc = WasmDocument::parse_with_options("<r><x/></r>", &opts).unwrap();
assert_eq!(doc.xpath_count("//x").unwrap(), 1);
}
#[wasm_bindgen_test]
fn node_xpath_uses_the_element_as_context() {
let doc = leptris::wasmface::WasmDocument::parse(
"<r xmlns:n=\"urn:n\"><n:a x=\"1\"/><n:a x=\"2\"/><b/></r>",
)
.unwrap();
let kids = doc.root().expect("root").children();
assert_eq!(kids[1].xpath("string(@x)").unwrap(), "2");
assert_eq!(kids[1].xpath("namespace-uri(.)").unwrap(), "urn:n");
assert_eq!(kids[2].xpath("name(.)").unwrap(), "b");
}
};
}