use dom_cat::{Document, Error, Node, query_selector, query_selector_all};
fn build(source: &str) -> Result<Document, Error> {
let html_doc = html_cat::parse(source)?;
Ok(Document::from_html_doc(&html_doc))
}
fn fail(msg: &'static str) -> Error {
Error::InvalidSelector {
selector: msg.to_owned(),
}
}
#[test]
fn query_type_selector() -> Result<(), Error> {
let doc = build("<html><body><p>hi</p></body></html>")?;
query_selector(&doc, "p")?
.map(|_| ())
.ok_or_else(|| fail("expected p"))
}
#[test]
fn query_class_selector() -> Result<(), Error> {
let doc = build("<html><body><p class=\"hi\">x</p><p>y</p></body></html>")?;
let id = query_selector(&doc, ".hi")?.ok_or_else(|| fail("expected .hi"))?;
let class = doc
.get(id)
.and_then(Node::as_element)
.and_then(|e| e.attribute("class"))
.ok_or_else(|| fail("expected class attr"))?;
(class == "hi")
.then_some(())
.ok_or_else(|| fail("class mismatch"))
}
#[test]
fn query_id_selector() -> Result<(), Error> {
let doc = build("<html><body><p id=\"main\">x</p></body></html>")?;
query_selector(&doc, "#main")?
.map(|_| ())
.ok_or_else(|| fail("expected #main"))
}
#[test]
fn query_attribute_selector() -> Result<(), Error> {
let doc = build("<html><body><input type=\"text\"><input type=\"radio\"></body></html>")?;
let matches = query_selector_all(&doc, "input[type=\"text\"]")?;
(matches.len() == 1)
.then_some(())
.ok_or_else(|| fail("expected one text input"))
}
#[test]
fn query_descendant_combinator() -> Result<(), Error> {
let doc = build("<html><body><div><p>x</p></div><p>y</p></body></html>")?;
let matches = query_selector_all(&doc, "div p")?;
(matches.len() == 1)
.then_some(())
.ok_or_else(|| fail("expected one p under div"))
}
#[test]
fn query_child_combinator() -> Result<(), Error> {
let doc =
build("<html><body><div><p>x</p></div><div><span><p>y</p></span></div></body></html>")?;
let matches = query_selector_all(&doc, "div > p")?;
(matches.len() == 1)
.then_some(())
.ok_or_else(|| fail("expected one direct child p"))
}
#[test]
fn query_selector_list() -> Result<(), Error> {
let doc = build("<html><body><h1>a</h1><h2>b</h2><p>c</p></body></html>")?;
let matches = query_selector_all(&doc, "h1, h2")?;
(matches.len() == 2)
.then_some(())
.ok_or_else(|| fail("expected h1 and h2"))
}
#[test]
fn query_first_child() -> Result<(), Error> {
let doc = build("<html><body><p>a</p><p>b</p><p>c</p></body></html>")?;
let matches = query_selector_all(&doc, "p:first-child")?;
(matches.len() == 1)
.then_some(())
.ok_or_else(|| fail("expected one first-child p"))
}
#[test]
fn set_attribute_round_trip() -> Result<(), Error> {
let doc = build("<html><body><p>x</p></body></html>")?;
let p_id = query_selector(&doc, "p")?.ok_or_else(|| fail("expected p"))?;
let updated = dom_cat::set_attribute(doc, p_id, "data-x", "42")?;
let value = updated
.get(p_id)
.and_then(Node::as_element)
.and_then(|e| e.attribute("data-x"))
.ok_or_else(|| fail("attribute missing after set"))?;
(value == "42")
.then_some(())
.ok_or_else(|| fail("attribute value mismatch"))
}
#[test]
fn remove_attribute_round_trip() -> Result<(), Error> {
let doc = build("<html><body><p class=\"hi\">x</p></body></html>")?;
let p_id = query_selector(&doc, "p")?.ok_or_else(|| fail("expected p"))?;
let updated = dom_cat::remove_attribute(doc, p_id, "class")?;
let missing = updated
.get(p_id)
.and_then(Node::as_element)
.map(|e| e.attribute("class").is_none())
.ok_or_else(|| fail("element missing"))?;
missing
.then_some(())
.ok_or_else(|| fail("attribute still present after remove"))
}
#[test]
fn remove_child_drops_subtree() -> Result<(), Error> {
let doc = build("<html><body><p>x</p><span>y</span></body></html>")?;
let body_id = query_selector(&doc, "body")?.ok_or_else(|| fail("expected body"))?;
let p_id = query_selector(&doc, "p")?.ok_or_else(|| fail("expected p"))?;
let updated = dom_cat::remove_child(doc, body_id, p_id)?;
let still_there = query_selector(&updated, "p")?;
still_there
.is_none()
.then_some(())
.ok_or_else(|| fail("p still present after remove"))
}