rdom_core/
html_collection.rs1use crate::accessor::NodeRef;
12use crate::dom::Dom;
13use crate::node_id::NodeId;
14
15pub struct HtmlCollection<'a, Ext: 'static> {
19 nodes: Vec<NodeId>,
20 dom: &'a Dom<Ext>,
21}
22
23pub type FormControlsCollection<'a, Ext> = HtmlCollection<'a, Ext>;
27
28impl<'a, Ext: 'static> HtmlCollection<'a, Ext> {
29 pub fn from_ids(dom: &'a Dom<Ext>, nodes: impl IntoIterator<Item = NodeId>) -> Self {
32 Self {
33 nodes: nodes.into_iter().collect(),
34 dom,
35 }
36 }
37
38 pub fn len(&self) -> usize {
40 self.nodes.len()
41 }
42
43 pub fn is_empty(&self) -> bool {
45 self.nodes.is_empty()
46 }
47
48 pub fn item(&self, index: usize) -> Option<NodeRef<'a, Ext>> {
52 let id = *self.nodes.get(index)?;
53 if self.dom.contains(id) {
54 Some(self.dom.node(id))
55 } else {
56 None
57 }
58 }
59
60 pub fn named_item(&self, name: &str) -> Option<NodeRef<'a, Ext>> {
66 for &id in &self.nodes {
67 if !self.dom.contains(id) {
68 continue;
69 }
70 let node = self.dom.node(id);
71 if node.get_attribute("name") == Some(name) || node.get_attribute("id") == Some(name) {
72 return Some(node);
73 }
74 }
75 None
76 }
77
78 pub fn iter(&self) -> impl Iterator<Item = NodeRef<'a, Ext>> + '_ {
82 self.nodes
83 .iter()
84 .copied()
85 .filter(|id| self.dom.contains(*id))
86 .map(move |id| self.dom.node(id))
87 }
88
89 pub fn ids(&self) -> &[NodeId] {
91 &self.nodes
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn snapshot_len_item() {
101 let mut dom: Dom = Dom::new();
102 let a = dom.create_element("a");
103 let b = dom.create_element("b");
104 let col = HtmlCollection::from_ids(&dom, [a, b]);
105 assert_eq!(col.len(), 2);
106 assert!(!col.is_empty());
107 assert_eq!(col.item(0).map(|n| n.id()), Some(a));
108 assert_eq!(col.item(1).map(|n| n.id()), Some(b));
109 assert_eq!(col.item(2).map(|n| n.id()), None);
110 }
111
112 #[test]
113 fn named_item_matches_name_attribute_first() {
114 let mut dom: Dom = Dom::new();
115 let input = dom.create_element("input");
116 dom.set_attribute(input, "name", "user").unwrap();
117 let col = HtmlCollection::from_ids(&dom, [input]);
118 assert_eq!(col.named_item("user").map(|n| n.id()), Some(input));
119 assert!(col.named_item("missing").is_none());
120 }
121
122 #[test]
123 fn named_item_falls_back_to_id_attribute() {
124 let mut dom: Dom = Dom::new();
125 let el = dom.create_element("div");
126 dom.set_id(el, "main").unwrap();
127 let col = HtmlCollection::from_ids(&dom, [el]);
128 assert_eq!(col.named_item("main").map(|n| n.id()), Some(el));
129 }
130
131 #[test]
132 fn named_item_name_wins_over_id_on_other_elements() {
133 let mut dom: Dom = Dom::new();
137 let a = dom.create_element("input");
138 dom.set_attribute(a, "name", "foo").unwrap();
139 let b = dom.create_element("div");
140 dom.set_id(b, "foo").unwrap();
141 let col = HtmlCollection::from_ids(&dom, [a, b]);
142 assert_eq!(col.named_item("foo").map(|n| n.id()), Some(a));
143 }
144
145 #[test]
146 fn named_item_skips_removed_elements() {
147 let mut dom: Dom = Dom::new();
148 let root = dom.root();
149 let a = dom.create_element("input");
150 dom.set_attribute(a, "name", "x").unwrap();
151 dom.append_child(root, a).unwrap();
152 let b = dom.create_element("input");
153 dom.set_attribute(b, "name", "y").unwrap();
154 dom.append_child(root, b).unwrap();
155
156 let snapshot = vec![a, b];
157 dom.remove_child(root, a).unwrap();
158 dom.drop_subtree(a).unwrap();
159
160 let col = HtmlCollection::from_ids(&dom, snapshot);
161 assert!(col.named_item("x").is_none());
164 assert_eq!(col.named_item("y").map(|n| n.id()), Some(b));
165 }
166
167 #[test]
168 fn iter_yields_live_elements_in_snapshot_order() {
169 let mut dom: Dom = Dom::new();
170 let a = dom.create_element("a");
171 let b = dom.create_element("b");
172 let col = HtmlCollection::from_ids(&dom, [a, b]);
173 let ids: Vec<NodeId> = col.iter().map(|n| n.id()).collect();
174 assert_eq!(ids, vec![a, b]);
175 }
176
177 #[test]
178 fn ids_returns_raw_snapshot_slice() {
179 let mut dom: Dom = Dom::new();
180 let a = dom.create_element("a");
181 let b = dom.create_element("b");
182 let col = HtmlCollection::from_ids(&dom, [a, b]);
183 assert_eq!(col.ids(), &[a, b][..]);
184 }
185
186 #[test]
187 fn form_controls_collection_is_alias() {
188 let mut dom: Dom = Dom::new();
191 let input = dom.create_element("input");
192 let coll: FormControlsCollection<'_, ()> = HtmlCollection::from_ids(&dom, [input]);
193 assert_eq!(coll.len(), 1);
194 }
195}