1use rdom_core::{AdjacentPosition, NodeId, NodeMut};
31
32use crate::error::{ParseError, Result};
33use crate::parser::parse_into;
34
35pub trait NodeMutHtml<'a, Ext>
43where
44 Ext: Default + 'static,
45{
46 fn set_inner_html(&mut self, html: &str) -> Result<()>;
56
57 fn set_outer_html(self, html: &str) -> Result<NodeId>;
86
87 fn insert_adjacent_html(&mut self, position: AdjacentPosition, html: &str) -> Result<()>;
99}
100
101impl<'a, Ext> NodeMutHtml<'a, Ext> for NodeMut<'a, Ext>
102where
103 Ext: Default + 'static,
104{
105 fn set_inner_html(&mut self, html: &str) -> Result<()> {
106 let id = self.id();
107 let dom = self.dom_mut();
108 while let Some(first) = dom.node(id).first_child().map(|n| n.id()) {
109 let _ = dom.drop_subtree(first);
110 }
111 parse_into(dom, html, id)?;
112 Ok(())
113 }
114
115 fn set_outer_html(self, html: &str) -> Result<NodeId> {
116 let id = self.id();
117 let dom = self.into_dom_mut();
118
119 let parent = match dom.node(id).parent_node() {
120 Some(p) => p.id(),
121 None => return Err(synth_error("set_outer_html: receiver has no parent")),
122 };
123
124 let staging = dom.create_document_fragment();
128 let parsed = parse_into(dom, html, staging)?;
129 if parsed.is_empty() {
130 let _ = dom.drop_subtree(staging);
131 return Err(synth_error("set_outer_html: parsed fragment is empty"));
132 }
133
134 for &child in &parsed {
137 dom.insert_before(parent, child, Some(id))
138 .map_err(|e| synth_error(&format!("set_outer_html: {e}")))?;
139 }
140
141 let _ = dom.drop_subtree(staging);
144 let _ = dom.drop_subtree(id);
145
146 Ok(parsed[0])
147 }
148
149 fn insert_adjacent_html(&mut self, position: AdjacentPosition, html: &str) -> Result<()> {
150 let id = self.id();
151 let dom = self.dom_mut();
152 let parent_of_id = dom.node(id).parent_node().map(|n| n.id());
153 let needs_parent = matches!(
154 position,
155 AdjacentPosition::BeforeBegin | AdjacentPosition::AfterEnd
156 );
157 if needs_parent && parent_of_id.is_none() {
158 return Err(synth_error(
159 "insert_adjacent_html: BeforeBegin/AfterEnd require a parent",
160 ));
161 }
162
163 let (insertion_parent, reference) = match position {
168 AdjacentPosition::BeforeBegin => (parent_of_id.unwrap(), Some(id)),
169 AdjacentPosition::AfterBegin => (id, dom.node(id).first_child().map(|n| n.id())),
170 AdjacentPosition::BeforeEnd => (id, None),
171 AdjacentPosition::AfterEnd => (
172 parent_of_id.unwrap(),
173 dom.node(id).next_sibling().map(|n| n.id()),
174 ),
175 };
176
177 let staging = dom.create_document_fragment();
178 let parsed = parse_into(dom, html, staging)?;
179
180 for child in parsed {
181 dom.insert_before(insertion_parent, child, reference)
182 .map_err(|e| synth_error(&format!("insert_adjacent_html: {e}")))?;
183 }
184
185 let _ = dom.drop_subtree(staging);
186 Ok(())
187 }
188}
189
190fn synth_error(msg: &str) -> ParseError {
193 ParseError::new(msg.to_string(), 1, 1, 0)
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use rdom_core::{Dom, ListenerOptions};
200
201 fn child_tags(dom: &Dom, parent: NodeId) -> Vec<String> {
202 dom.node(parent)
203 .child_nodes()
204 .map(|n| n.tag_name().unwrap_or(n.node_name()).to_string())
205 .collect()
206 }
207
208 fn s(t: &str) -> String {
209 t.to_string()
210 }
211
212 #[test]
215 fn set_inner_html_replaces_children() {
216 let mut dom: Dom = Dom::new();
217 let parent = dom.create_element("div");
218 let old = dom.create_element("span");
219 dom.append_child(parent, old).unwrap();
220 dom.node_mut(parent)
221 .set_inner_html("<p>hi</p><em>bye</em>")
222 .unwrap();
223 let tags = child_tags(&dom, parent);
224 assert_eq!(tags, vec![s("p"), s("em")]);
225 }
230
231 #[test]
232 fn set_inner_html_clears_when_input_empty() {
233 let mut dom: Dom = Dom::new();
234 let parent = dom.create_element("div");
235 let old = dom.create_element("span");
236 dom.append_child(parent, old).unwrap();
237 dom.node_mut(parent).set_inner_html("").unwrap();
238 assert!(!dom.node(parent).has_child_nodes());
239 assert!(!dom.contains(old));
242 }
243
244 #[test]
245 fn set_inner_html_drops_abortsignal_listeners_on_removed_children() {
246 let mut dom: Dom = Dom::new();
255 let parent = dom.create_element("div");
256 let child = dom.create_element("button");
257 dom.append_child(parent, child).unwrap();
258 dom.add_event_listener(child, "click", ListenerOptions::default(), |_| {})
259 .unwrap();
260 assert_eq!(dom.listener_count(child), 1);
261 dom.node_mut(parent).set_inner_html("<p>new</p>").unwrap();
262 assert_eq!(dom.listener_count(child), 0);
265 }
266
267 #[test]
268 fn set_inner_html_propagates_parse_error_strictly() {
269 let mut dom: Dom = Dom::new();
270 let el = dom.create_element("div");
271 let err = dom
272 .node_mut(el)
273 .set_inner_html("<div><span></p></div>")
274 .unwrap_err();
275 assert!(err.msg.to_lowercase().contains("mismatch"));
277 }
278
279 #[test]
282 fn set_outer_html_replaces_self_in_parent_and_returns_first_id() {
283 let mut dom: Dom = Dom::new();
284 let root = dom.root();
285 let target = dom.create_element("span");
286 let sibling = dom.create_element("span");
287 dom.append_child(root, target).unwrap();
288 dom.append_child(root, sibling).unwrap();
289 let new_id = dom
290 .node_mut(target)
291 .set_outer_html("<p>a</p><em>b</em>")
292 .unwrap();
293 assert_eq!(dom.node(new_id).tag_name(), Some("p"));
295 let tags = child_tags(&dom, root);
296 assert_eq!(
297 tags,
298 vec!["p".to_string(), "em".to_string(), "span".to_string()]
299 );
300 assert!(!dom.contains(target));
301 }
302
303 #[test]
304 fn set_outer_html_errors_when_receiver_has_no_parent() {
305 let mut dom: Dom = Dom::new();
306 let detached = dom.create_element("div");
307 let err = dom
308 .node_mut(detached)
309 .set_outer_html("<p>x</p>")
310 .unwrap_err();
311 assert!(err.msg.contains("no parent"));
312 assert!(dom.contains(detached));
316 }
317
318 #[test]
319 fn set_outer_html_errors_on_empty_parsed_fragment() {
320 let mut dom: Dom = Dom::new();
321 let root = dom.root();
322 let target = dom.create_element("div");
323 dom.append_child(root, target).unwrap();
324 let err = dom.node_mut(target).set_outer_html("").unwrap_err();
325 assert!(err.msg.contains("empty"));
326 assert!(dom.contains(target));
328 assert_eq!(dom.node(target).parent_node().map(|p| p.id()), Some(root));
329 }
330
331 #[test]
332 fn set_outer_html_propagates_parse_error_strictly() {
333 let mut dom: Dom = Dom::new();
334 let root = dom.root();
335 let target = dom.create_element("div");
336 dom.append_child(root, target).unwrap();
337 let err = dom
338 .node_mut(target)
339 .set_outer_html("<a><b></a>")
340 .unwrap_err();
341 assert!(err.msg.to_lowercase().contains("mismatch"));
342 assert!(dom.contains(target));
344 }
345
346 #[test]
349 fn insert_adjacent_html_before_begin() {
350 let mut dom: Dom = Dom::new();
351 let root = dom.root();
352 let target = dom.create_element("span");
353 dom.append_child(root, target).unwrap();
354 dom.node_mut(target)
355 .insert_adjacent_html(AdjacentPosition::BeforeBegin, "<a></a><b></b>")
356 .unwrap();
357 let tags = child_tags(&dom, root);
358 assert_eq!(tags, vec![s("a"), s("b"), s("span")]);
359 }
360
361 #[test]
362 fn insert_adjacent_html_after_begin() {
363 let mut dom: Dom = Dom::new();
364 let parent = dom.create_element("div");
365 let existing = dom.create_element("span");
366 dom.append_child(parent, existing).unwrap();
367 dom.node_mut(parent)
368 .insert_adjacent_html(AdjacentPosition::AfterBegin, "<a></a><b></b>")
369 .unwrap();
370 let tags = child_tags(&dom, parent);
371 assert_eq!(tags, vec![s("a"), s("b"), s("span")]);
372 }
373
374 #[test]
375 fn insert_adjacent_html_before_end() {
376 let mut dom: Dom = Dom::new();
377 let parent = dom.create_element("div");
378 let existing = dom.create_element("span");
379 dom.append_child(parent, existing).unwrap();
380 dom.node_mut(parent)
381 .insert_adjacent_html(AdjacentPosition::BeforeEnd, "<a></a><b></b>")
382 .unwrap();
383 let tags = child_tags(&dom, parent);
384 assert_eq!(tags, vec![s("span"), s("a"), s("b")]);
385 }
386
387 #[test]
388 fn insert_adjacent_html_after_end() {
389 let mut dom: Dom = Dom::new();
390 let root = dom.root();
391 let target = dom.create_element("span");
392 let tail = dom.create_element("span");
393 dom.append_child(root, target).unwrap();
394 dom.append_child(root, tail).unwrap();
395 dom.node_mut(target)
396 .insert_adjacent_html(AdjacentPosition::AfterEnd, "<a></a><b></b>")
397 .unwrap();
398 let tags = child_tags(&dom, root);
399 assert_eq!(tags, vec![s("span"), s("a"), s("b"), s("span")]);
400 }
401
402 #[test]
403 fn insert_adjacent_html_before_begin_errors_without_parent() {
404 let mut dom: Dom = Dom::new();
405 let detached = dom.create_element("div");
406 let err = dom
407 .node_mut(detached)
408 .insert_adjacent_html(AdjacentPosition::BeforeBegin, "<p/>")
409 .unwrap_err();
410 assert!(err.msg.contains("parent"));
411 }
412
413 #[test]
414 fn insert_adjacent_html_after_end_errors_without_parent() {
415 let mut dom: Dom = Dom::new();
416 let detached = dom.create_element("div");
417 let err = dom
418 .node_mut(detached)
419 .insert_adjacent_html(AdjacentPosition::AfterEnd, "<p/>")
420 .unwrap_err();
421 assert!(err.msg.contains("parent"));
422 }
423
424 #[test]
425 fn insert_adjacent_html_propagates_parse_error_strictly() {
426 let mut dom: Dom = Dom::new();
427 let parent = dom.create_element("div");
428 let err = dom
429 .node_mut(parent)
430 .insert_adjacent_html(AdjacentPosition::BeforeEnd, "<a><b></a>")
431 .unwrap_err();
432 assert!(err.msg.to_lowercase().contains("mismatch"));
433 }
434}