use azul_layout::xml::{parse_xml_string, XmlNodeChild};
fn as_element(child: &XmlNodeChild) -> &azul_core::xml::XmlNode {
match child {
XmlNodeChild::Element(node) => node,
_ => panic!("Expected element node, got text node"),
}
}
fn element_children(children: &[XmlNodeChild]) -> Vec<&azul_core::xml::XmlNode> {
children
.iter()
.filter_map(|c| match c {
XmlNodeChild::Element(node) => Some(node),
_ => None,
})
.collect()
}
#[test]
fn test_self_closing_tags() {
let xml = r#"
<html>
<body>
<header/>
<div>Content</div>
<footer/>
</body>
</html>
"#;
let result = parse_xml_string(xml).unwrap();
let html = as_element(&result[0]);
assert_eq!(html.node_type.as_str(), "html");
let html_elems = element_children(html.children.as_ref());
assert_eq!(html_elems.len(), 1);
let body = html_elems[0];
assert_eq!(body.node_type.as_str(), "body");
let body_elems = element_children(body.children.as_ref());
assert_eq!(body_elems.len(), 3);
assert_eq!(body_elems[0].node_type.as_str(), "header");
assert_eq!(body_elems[1].node_type.as_str(), "div");
assert_eq!(body_elems[2].node_type.as_str(), "footer");
}
#[test]
fn test_self_closing_with_attributes() {
let xml = r#"
<html>
<body>
<header exclude-pages="1"/>
<div class="content">Text</div>
</body>
</html>
"#;
let result = parse_xml_string(xml).unwrap();
let html = as_element(&result[0]);
let body = element_children(html.children.as_ref())[0];
let header = element_children(body.children.as_ref())[0];
assert_eq!(header.node_type.as_str(), "header");
assert_eq!(header.attributes.as_ref().len(), 1);
assert_eq!(header.attributes.as_ref()[0].key.as_str(), "exclude-pages");
assert_eq!(header.attributes.as_ref()[0].value.as_str(), "1");
assert_eq!(header.children.as_ref().len(), 0);
}
#[test]
fn test_mixed_self_closing_and_regular() {
let xml = r#"
<html>
<head>
<style>/* CSS */</style>
</head>
<body>
<header/>
<div>
<p>Text</p>
<br/>
<span>More text</span>
</div>
<footer/>
</body>
</html>
"#;
let result = parse_xml_string(xml).unwrap();
let html = as_element(&result[0]);
let html_elems = element_children(html.children.as_ref());
assert_eq!(html_elems.len(), 2);
let head = html_elems[0];
let body = html_elems[1];
assert_eq!(head.node_type.as_str(), "head");
assert_eq!(body.node_type.as_str(), "body");
let body_elems = element_children(body.children.as_ref());
assert_eq!(body_elems.len(), 3);
assert_eq!(body_elems[0].node_type.as_str(), "header");
assert_eq!(body_elems[1].node_type.as_str(), "div");
assert_eq!(body_elems[2].node_type.as_str(), "footer");
}
#[test]
fn test_html5_auto_close_list_items() {
let xml = r#"
<html>
<body>
<ul>
<li>Item 1
<li>Item 2
<li>Item 3
</ul>
</body>
</html>
"#;
let result = parse_xml_string(xml).unwrap();
let html = as_element(&result[0]);
let body = element_children(html.children.as_ref())[0];
let ul = element_children(body.children.as_ref())[0];
let li_items = element_children(ul.children.as_ref());
assert_eq!(li_items.len(), 3, "Should have 3 list items");
assert_eq!(li_items[0].node_type.as_str(), "li");
assert_eq!(li_items[1].node_type.as_str(), "li");
assert_eq!(li_items[2].node_type.as_str(), "li");
assert!(li_items[0].children.as_ref().len() > 0, "First li should have text");
}
#[test]
fn test_html5_paragraph_auto_close() {
let xml = r#"
<html>
<body>
<p>First paragraph
<div>This div auto-closes the paragraph</div>
<p>Second paragraph
<h1>This heading auto-closes the paragraph</h1>
</body>
</html>
"#;
let result = parse_xml_string(xml).unwrap();
let html = as_element(&result[0]);
let body = element_children(html.children.as_ref())[0];
let body_elems = element_children(body.children.as_ref());
assert_eq!(body_elems.len(), 4, "body should have p, div, p, h1");
assert_eq!(body_elems[0].node_type.as_str(), "p");
assert_eq!(body_elems[1].node_type.as_str(), "div");
assert_eq!(body_elems[2].node_type.as_str(), "p");
assert_eq!(body_elems[3].node_type.as_str(), "h1");
}
#[test]
fn test_html5_optional_closing_tags() {
let xml = r#"
<html>
<body>
<div>
<p>Paragraph 1
<p>Paragraph 2
</div>
</body>
</html>
"#;
let result = parse_xml_string(xml).unwrap();
let html = as_element(&result[0]);
let body = element_children(html.children.as_ref())[0];
let div = element_children(body.children.as_ref())[0];
let div_elems = element_children(div.children.as_ref());
assert_eq!(div_elems.len(), 2, "div should have 2 paragraphs");
assert_eq!(div_elems[0].node_type.as_str(), "p");
assert_eq!(div_elems[1].node_type.as_str(), "p");
}
#[test]
fn test_html5_table_auto_close() {
let xml = r#"
<html>
<body>
<table>
<tr>
<td>Cell 1
<td>Cell 2
</tr>
<tr>
<td>Cell 3
<td>Cell 4
</tr>
</table>
</body>
</html>
"#;
let result = parse_xml_string(xml).unwrap();
let html = as_element(&result[0]);
let body = element_children(html.children.as_ref())[0];
let table = element_children(body.children.as_ref())[0];
let rows = element_children(table.children.as_ref());
assert_eq!(rows.len(), 2, "Table should have 2 rows");
let row1_cells = element_children(rows[0].children.as_ref());
let row2_cells = element_children(rows[1].children.as_ref());
assert_eq!(row1_cells.len(), 2, "First row should have 2 cells");
assert_eq!(row2_cells.len(), 2, "Second row should have 2 cells");
}
#[test]
fn test_html5_void_elements_with_wrong_closing() {
let xml = r#"
<html>
<body>
<img src="test.png"></img>
<br></br>
<hr></hr>
<input type="text"></input>
</body>
</html>
"#;
let result = parse_xml_string(xml).unwrap();
let html = as_element(&result[0]);
let body = element_children(html.children.as_ref())[0];
let body_elems = element_children(body.children.as_ref());
assert_eq!(body_elems.len(), 4);
assert_eq!(body_elems[0].node_type.as_str(), "img");
assert_eq!(body_elems[1].node_type.as_str(), "br");
assert_eq!(body_elems[2].node_type.as_str(), "hr");
assert_eq!(body_elems[3].node_type.as_str(), "input");
}
#[test]
fn test_header_without_closing_tag_lenient() {
let xml = r#"
<html>
<body>
<header>
<div>Page number</div>
<hr/>
<footer>Bottom</footer>
</body>
</html>
"#;
let result = parse_xml_string(xml);
assert!(result.is_ok(), "Should succeed with lenient HTML5 parsing");
let nodes = result.unwrap();
let html = as_element(&nodes[0]);
let body = element_children(html.children.as_ref())[0];
let body_elems = element_children(body.children.as_ref());
assert!(body_elems.len() >= 1, "Should have at least one element child");
let header = body_elems[0];
assert_eq!(header.node_type.as_str(), "header");
let header_elems = element_children(header.children.as_ref());
assert!(header_elems.len() >= 2, "Header should contain div, hr (and footer as child)");
}
#[test]
fn test_auto_close_void_tags() {
let xml = r#"
<html>
<body>
<img src="test.png">
<br>
<hr>
<input type="text">
<div>Content</div>
</body>
</html>
"#;
let result = parse_xml_string(xml).unwrap();
let html = as_element(&result[0]);
let body = element_children(html.children.as_ref())[0];
let body_elems = element_children(body.children.as_ref());
assert_eq!(body_elems.len(), 5, "body should have img, br, hr, input, div");
assert_eq!(body_elems[0].node_type.as_str(), "img");
assert_eq!(body_elems[1].node_type.as_str(), "br");
assert_eq!(body_elems[2].node_type.as_str(), "hr");
assert_eq!(body_elems[3].node_type.as_str(), "input");
assert_eq!(body_elems[4].node_type.as_str(), "div");
}
#[test]
fn test_inline_span_text_node_structure() {
let xml = r#"
<html>
<body>
<p>Text before <span class="highlight">inline text</span> text after.</p>
</body>
</html>
"#;
let result = parse_xml_string(xml).expect("Should parse XML successfully");
assert_eq!(result.len(), 1, "Should have one root node");
let html = as_element(&result[0]);
assert_eq!(html.node_type.as_str(), "html");
let body = element_children(html.children.as_ref())[0];
assert_eq!(body.node_type.as_str(), "body");
let body_elems = element_children(body.children.as_ref());
assert_eq!(body_elems.len(), 1, "Body should have 1 element child (p)");
let p = body_elems[0];
assert_eq!(p.node_type.as_str(), "p");
assert_eq!(
p.children.as_ref().len(),
3,
"Paragraph should have [TextNode, Span, TextNode], found {} children",
p.children.as_ref().len()
);
match &p.children.as_ref()[0] {
XmlNodeChild::Text(text) => {
assert_eq!(text.as_str(), "Text before ");
}
XmlNodeChild::Element(_) => panic!("First child should be a text node"),
}
match &p.children.as_ref()[1] {
XmlNodeChild::Element(span) => {
assert_eq!(span.node_type.as_str(), "span");
assert_eq!(span.children.as_ref().len(), 1);
match &span.children.as_ref()[0] {
XmlNodeChild::Text(text) => {
assert_eq!(text.as_str(), "inline text");
}
XmlNodeChild::Element(_) => panic!("Span's child should be a text node"),
}
}
XmlNodeChild::Text(_) => panic!("Second child should be an element (span)"),
}
match &p.children.as_ref()[2] {
XmlNodeChild::Text(text) => {
assert_eq!(text.as_str(), " text after.");
}
XmlNodeChild::Element(_) => panic!("Third child should be a text node"),
}
}