wal_core/virtual_dom/
vtext.rs1use web_sys::{Node, Text};
2
3use super::{dom, VNode};
4
5#[derive(PartialEq, Debug)]
7pub struct VText {
8 pub(crate) text: String,
9 pub(crate) dom: Option<Text>,
10}
11
12impl VText {
13 pub fn new<T: ToString>(text: T) -> VText {
23 VText {
24 text: text.to_string(),
25 dom: None,
26 }
27 }
28
29 pub(crate) fn patch(&mut self, last: Option<VNode>, ancestor: &Node) {
30 let mut old_virt: Option<VText> = None;
31 self.dom = None;
32
33 match last {
34 None => {}
35 Some(VNode::Text(vtext)) => {
36 if vtext
37 .dom
38 .as_ref()
39 .is_some_and(|x| x.parent_node().is_some_and(|y| y.eq(ancestor)))
40 {
41 self.dom = vtext.dom.clone();
42 old_virt = Some(vtext);
43 }
44 }
45 Some(VNode::Element(v)) => {
46 v.erase();
47 }
48 Some(VNode::Component(v)) => {
49 v.erase();
50 }
51 Some(VNode::List(v)) => {
52 v.erase();
53 }
54 }
55
56 self.render(old_virt, ancestor);
57 self.check_if_parents_match(ancestor);
58 }
59
60 pub(crate) fn erase(&self) {
61 if let Some(text) = &self.dom {
62 dom::remove_node(text);
63 }
64 }
65}
66
67impl VText {
68 fn render(&mut self, last: Option<VText>, ancestor: &Node) {
69 match last {
70 Some(last) if self.text != last.text => {
72 self.dom
73 .as_ref()
74 .expect("Dom is not created even though it should have been")
75 .set_node_value(Some(self.text.as_str()));
76 }
77 Some(_) => (),
79 None => {
80 let el = dom::create_text_node(&self.text);
81
82 match &self.dom {
83 Some(old_child) => dom::replace_child(ancestor, old_child, &el),
84 None => dom::append_child(ancestor, &el),
85 };
86 self.dom = Some(el);
87 }
88 }
89 }
90
91 fn check_if_parents_match(&mut self, ancestor: &Node) {
92 let parent_node = self.dom.as_ref().unwrap().parent_node().unwrap();
94 if !parent_node.eq(ancestor) {
95 let dom_ref = self.dom.as_ref().unwrap();
96 dom::remove_child(&parent_node, dom_ref);
97 dom::append_child(ancestor, dom_ref);
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use wasm_bindgen_test::wasm_bindgen_test;
105
106 use crate::{
107 component::{behavior::Behavior, Component},
108 virtual_dom::{dom, VComponent, VElement, VList, VNode},
109 };
110
111 use super::VText;
112 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
113
114 #[wasm_bindgen_test]
115 fn new_from_str() {
116 let target = VText::new("I love Rust");
117 assert_eq!(target.text, String::from("I love Rust"));
118 }
119
120 macro_rules! function_name {
121 () => {{
122 fn f() {}
123 fn type_name_of<T>(_: T) -> &'static str {
124 std::any::type_name::<T>()
125 }
126 let name = type_name_of(f);
127 name.strip_suffix("::f").unwrap()
128 }};
129 }
130
131 const VALID_TEXT: &str = "";
132
133 #[wasm_bindgen_test]
134 fn patch_last_none() {
135 let ancestor = dom::create_element("div");
136 dom::set_attribute(&ancestor, "id", function_name!());
137 dom::append_child(&dom::get_root_element(), &ancestor);
138
139 let mut target = VText::new(VALID_TEXT);
140 target.patch(None, &ancestor);
141 }
142
143 #[wasm_bindgen_test]
144 fn patch_last_text() {
145 let ancestor = dom::create_element("div");
146 dom::set_attribute(&ancestor, "id", function_name!());
147
148 let current = dom::create_text_node("I dont love Rust");
149 dom::append_child(&ancestor, ¤t);
150
151 dom::append_child(&dom::get_root_element(), &ancestor);
152
153 let text = VNode::Text(VText {
154 text: "I dont love Rust".into(),
155 dom: Some(current),
156 });
157
158 let mut target = VText::new(VALID_TEXT);
159 target.patch(Some(text), &ancestor);
160 }
161
162 #[wasm_bindgen_test]
163 fn patch_last_elem() {
164 let ancestor = dom::create_element("div");
165 dom::set_attribute(&ancestor, "id", function_name!());
166
167 let current = dom::create_element("div");
168 dom::set_attribute(¤t, "id", "I dont love Rust");
169 dom::append_child(&ancestor, ¤t);
170
171 dom::append_child(&dom::get_root_element(), &ancestor);
172
173 let elem = VNode::Element(VElement {
174 tag_name: "div".into(),
175 attr: [("id".into(), "I dont love Rust".into())].into(),
176 event_handlers: vec![],
177 key: None,
178 children: vec![],
179 dom: Some(current),
180 });
181
182 let mut target = VText::new(VALID_TEXT);
183 target.patch(Some(elem), &ancestor);
184 }
185
186 struct Comp;
187 impl Component for Comp {
188 type Message = ();
189 type Properties = ();
190
191 fn new(_props: Self::Properties) -> Self {
192 Comp
193 }
194 fn view(&self, _behavior: &mut impl Behavior<Self>) -> VNode {
195 VText::new("I dont love Rust").into()
196 }
197 fn update(&mut self, _message: Self::Message) -> bool {
198 false
199 }
200 }
201
202 #[wasm_bindgen_test]
203 fn patch_last_comp() {
204 let ancestor = dom::create_element("div");
205 dom::set_attribute(&ancestor, "id", function_name!());
206 dom::append_child(&dom::get_root_element(), &ancestor);
207
208 let mut comp = VNode::Component(VComponent::new::<Comp>((), None));
209 comp.set_depth(0);
210 comp.patch(None, &ancestor);
211
212 let mut target = VText::new(VALID_TEXT);
213 target.patch(Some(comp), &ancestor);
214 }
215
216 #[wasm_bindgen_test]
217 fn patch_last_list() {
218 let ancestor = dom::create_element("div");
219 dom::set_attribute(&ancestor, "id", function_name!());
220 dom::append_child(&dom::get_root_element(), &ancestor);
221
222 let mut list = VNode::List(VList::new(
223 vec![VText::new("I dont love Rust").into()],
224 None,
225 ));
226 list.patch(None, &ancestor);
227
228 let mut target = VText::new(VALID_TEXT);
229 target.patch(Some(list), &ancestor);
230 }
231}