use crate::accessor::NodeRef;
use crate::dom::Dom;
use crate::node_id::NodeId;
pub struct HtmlCollection<'a, Ext: 'static> {
nodes: Vec<NodeId>,
dom: &'a Dom<Ext>,
}
pub type FormControlsCollection<'a, Ext> = HtmlCollection<'a, Ext>;
impl<'a, Ext: 'static> HtmlCollection<'a, Ext> {
pub fn from_ids(dom: &'a Dom<Ext>, nodes: impl IntoIterator<Item = NodeId>) -> Self {
Self {
nodes: nodes.into_iter().collect(),
dom,
}
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn item(&self, index: usize) -> Option<NodeRef<'a, Ext>> {
let id = *self.nodes.get(index)?;
if self.dom.contains(id) {
Some(self.dom.node(id))
} else {
None
}
}
pub fn named_item(&self, name: &str) -> Option<NodeRef<'a, Ext>> {
for &id in &self.nodes {
if !self.dom.contains(id) {
continue;
}
let node = self.dom.node(id);
if node.get_attribute("name") == Some(name) || node.get_attribute("id") == Some(name) {
return Some(node);
}
}
None
}
pub fn iter(&self) -> impl Iterator<Item = NodeRef<'a, Ext>> + '_ {
self.nodes
.iter()
.copied()
.filter(|id| self.dom.contains(*id))
.map(move |id| self.dom.node(id))
}
pub fn ids(&self) -> &[NodeId] {
&self.nodes
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snapshot_len_item() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("a");
let b = dom.create_element("b");
let col = HtmlCollection::from_ids(&dom, [a, b]);
assert_eq!(col.len(), 2);
assert!(!col.is_empty());
assert_eq!(col.item(0).map(|n| n.id()), Some(a));
assert_eq!(col.item(1).map(|n| n.id()), Some(b));
assert_eq!(col.item(2).map(|n| n.id()), None);
}
#[test]
fn named_item_matches_name_attribute_first() {
let mut dom: Dom = Dom::new();
let input = dom.create_element("input");
dom.set_attribute(input, "name", "user").unwrap();
let col = HtmlCollection::from_ids(&dom, [input]);
assert_eq!(col.named_item("user").map(|n| n.id()), Some(input));
assert!(col.named_item("missing").is_none());
}
#[test]
fn named_item_falls_back_to_id_attribute() {
let mut dom: Dom = Dom::new();
let el = dom.create_element("div");
dom.set_id(el, "main").unwrap();
let col = HtmlCollection::from_ids(&dom, [el]);
assert_eq!(col.named_item("main").map(|n| n.id()), Some(el));
}
#[test]
fn named_item_name_wins_over_id_on_other_elements() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("input");
dom.set_attribute(a, "name", "foo").unwrap();
let b = dom.create_element("div");
dom.set_id(b, "foo").unwrap();
let col = HtmlCollection::from_ids(&dom, [a, b]);
assert_eq!(col.named_item("foo").map(|n| n.id()), Some(a));
}
#[test]
fn named_item_skips_removed_elements() {
let mut dom: Dom = Dom::new();
let root = dom.root();
let a = dom.create_element("input");
dom.set_attribute(a, "name", "x").unwrap();
dom.append_child(root, a).unwrap();
let b = dom.create_element("input");
dom.set_attribute(b, "name", "y").unwrap();
dom.append_child(root, b).unwrap();
let snapshot = vec![a, b];
dom.remove_child(root, a).unwrap();
dom.drop_subtree(a).unwrap();
let col = HtmlCollection::from_ids(&dom, snapshot);
assert!(col.named_item("x").is_none());
assert_eq!(col.named_item("y").map(|n| n.id()), Some(b));
}
#[test]
fn iter_yields_live_elements_in_snapshot_order() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("a");
let b = dom.create_element("b");
let col = HtmlCollection::from_ids(&dom, [a, b]);
let ids: Vec<NodeId> = col.iter().map(|n| n.id()).collect();
assert_eq!(ids, vec![a, b]);
}
#[test]
fn ids_returns_raw_snapshot_slice() {
let mut dom: Dom = Dom::new();
let a = dom.create_element("a");
let b = dom.create_element("b");
let col = HtmlCollection::from_ids(&dom, [a, b]);
assert_eq!(col.ids(), &[a, b][..]);
}
#[test]
fn form_controls_collection_is_alias() {
let mut dom: Dom = Dom::new();
let input = dom.create_element("input");
let coll: FormControlsCollection<'_, ()> = HtmlCollection::from_ids(&dom, [input]);
assert_eq!(coll.len(), 1);
}
}