Skip to main content

rdom_core/
node_or_string.rs

1//! `NodeOrString` — sum type for variadic tree-mutation helpers.
2//!
3//! JS's variadic `el.append(node1, "text", node2)` doesn't map
4//! cleanly to Rust (no varargs, no string coercion). The rdom
5//! shape takes a slice or iterator of [`NodeOrString`] — built
6//! with `From` impls so authors can write:
7//!
8//! ```
9//! use rdom_core::{Dom, NodeOrString};
10//!
11//! let mut dom: Dom = Dom::new();
12//! let p = dom.create_element("p");
13//! let strong = dom.create_element("strong");
14//!
15//! let items: Vec<NodeOrString> = vec![
16//!     "hello, ".into(),
17//!     strong.into(),
18//!     "!".into(),
19//! ];
20//! # let _ = (p, items);
21//! ```
22//!
23//! The actual `append` / `prepend` / `before` / `after` /
24//! `replace_children` / `replace_with` methods on `NodeMut` come
25//! in M4b step 14. This module ships the carrier type ahead of
26//! time so step 14 has a stable substrate to build on.
27
28use crate::node_id::NodeId;
29
30/// Either an existing node (by id) or a string to be wrapped in a
31/// fresh text node. Built via `From` impls so call sites read
32/// naturally — see module docs for an example.
33///
34/// The variant is named `Text` (not `String`) to keep the carrier
35/// semantics explicit: a `Text` value becomes a `Text` node in
36/// the DOM, never inline markup.
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38pub enum NodeOrString {
39    /// An existing node — will be linked into the destination by
40    /// id. The caller is responsible for ensuring the node id is
41    /// valid for the target Dom.
42    Node(NodeId),
43    /// Text content — the receiver will create a fresh `Text`
44    /// node carrying this string.
45    Text(String),
46}
47
48impl From<NodeId> for NodeOrString {
49    fn from(id: NodeId) -> Self {
50        NodeOrString::Node(id)
51    }
52}
53
54impl From<String> for NodeOrString {
55    fn from(s: String) -> Self {
56        NodeOrString::Text(s)
57    }
58}
59
60impl From<&str> for NodeOrString {
61    fn from(s: &str) -> Self {
62        NodeOrString::Text(s.to_string())
63    }
64}
65
66impl From<&String> for NodeOrString {
67    fn from(s: &String) -> Self {
68        NodeOrString::Text(s.clone())
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::Dom;
76
77    #[test]
78    fn from_node_id_produces_node_variant() {
79        let mut dom: Dom = Dom::new();
80        let n = dom.create_element("p");
81        let v: NodeOrString = n.into();
82        assert_eq!(v, NodeOrString::Node(n));
83    }
84
85    #[test]
86    fn from_str_produces_text_variant() {
87        let v: NodeOrString = "hello".into();
88        assert_eq!(v, NodeOrString::Text("hello".into()));
89    }
90
91    #[test]
92    fn from_string_produces_text_variant() {
93        let v: NodeOrString = String::from("hello").into();
94        assert_eq!(v, NodeOrString::Text("hello".into()));
95    }
96
97    #[test]
98    fn from_string_ref_clones_and_produces_text_variant() {
99        let s = String::from("hello");
100        let v: NodeOrString = (&s).into();
101        assert_eq!(v, NodeOrString::Text("hello".into()));
102        // Original String unaffected — &String → NodeOrString clones.
103        assert_eq!(s, "hello");
104    }
105
106    #[test]
107    fn variadic_ergonomics_via_into_iterator() {
108        // The shape M4b step 14's `append` will take:
109        // `impl IntoIterator<Item = NodeOrString>`. Authors build
110        // a Vec<NodeOrString> via Into::into per element. This
111        // test demonstrates the ergonomic that future helpers will
112        // expose; it's also the canonical step-9 failing test
113        // before the From impls existed.
114        let mut dom: Dom = Dom::new();
115        let strong = dom.create_element("strong");
116        let em = dom.create_element("em");
117
118        let items: Vec<NodeOrString> = vec![
119            "before ".into(),
120            strong.into(),
121            " mid ".into(),
122            em.into(),
123            " after".into(),
124        ];
125
126        assert_eq!(items.len(), 5);
127        match &items[0] {
128            NodeOrString::Text(s) => assert_eq!(s, "before "),
129            _ => panic!("expected Text variant"),
130        }
131        match &items[1] {
132            NodeOrString::Node(n) => assert_eq!(*n, strong),
133            _ => panic!("expected Node variant"),
134        }
135        match &items[4] {
136            NodeOrString::Text(s) => assert_eq!(s, " after"),
137            _ => panic!("expected Text variant"),
138        }
139    }
140
141    #[test]
142    fn distinct_variants_compare_not_equal() {
143        let mut dom: Dom = Dom::new();
144        let n = dom.create_element("p");
145        let from_node = NodeOrString::Node(n);
146        let from_text = NodeOrString::Text("p".into());
147        assert_ne!(from_node, from_text);
148    }
149}