Skip to main content

wal_core/virtual_dom/
vlist.rs

1use itertools::{EitherOrBoth, Itertools};
2use web_sys::Node;
3
4use super::VNode;
5
6/// A list of nodes of virtual DOM tree. It undergoes virtual DOM manipulations and patching algorithm optimizations.
7#[derive(PartialEq, Debug)]
8pub struct VList {
9    _key: Option<String>, // TODO: add logic for key attribute
10    pub(crate) nodes: Vec<VNode>,
11}
12
13impl VList {
14    /// Creates a [VList] out of [vector](Vec) of [VNodes](VNode). Optional key works as an easy comparison option, if keys match, old [VList] is used.
15    pub fn new(nodes: Vec<VNode>, key: Option<String>) -> VList {
16        VList { nodes, _key: key }
17    }
18
19    /// Creates an empty [VList]. Optional key works as an easy comparison option, if keys match, old [VList] is used.
20    pub fn new_empty(key: Option<String>) -> VList {
21        VList {
22            nodes: Vec::new(),
23            _key: key,
24        }
25    }
26
27    pub(crate) fn patch(&mut self, last: Option<VNode>, ancestor: &Node) {
28        let mut old_virt: Option<VList> = None;
29
30        match last {
31            None => {}
32            Some(VNode::List(vlist)) => {
33                old_virt = Some(vlist);
34            }
35            Some(VNode::Text(v)) => {
36                v.erase();
37            }
38            Some(VNode::Element(v)) => {
39                v.erase();
40            }
41            Some(VNode::Component(v)) => {
42                v.erase();
43            }
44        }
45
46        self.render(old_virt, ancestor);
47    }
48
49    pub(crate) fn erase(&self) {
50        for node in self.nodes.iter() {
51            node.erase();
52        }
53    }
54
55    pub(crate) fn set_depth(&mut self, depth: u32) {
56        for child in self.nodes.iter_mut() {
57            child.set_depth(depth);
58        }
59    }
60}
61
62impl VList {
63    fn render(&mut self, last: Option<VList>, ancestor: &Node) {
64        for e in self
65            .nodes
66            .iter_mut()
67            .zip_longest(last.map_or_else(Vec::new, |x| x.nodes.into_iter().collect()))
68        {
69            match e {
70                EitherOrBoth::Both(cur, old) => cur.patch(Some(old), ancestor),
71                EitherOrBoth::Left(cur) => cur.patch(None, ancestor),
72                EitherOrBoth::Right(old) => old.erase(),
73            }
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use wasm_bindgen_test::wasm_bindgen_test;
81
82    use crate::{
83        component::{behavior::Behavior, Component},
84        virtual_dom::{dom, VComponent, VElement, VNode, VText},
85    };
86
87    use super::VList;
88    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
89
90    macro_rules! function_name {
91        () => {{
92            fn f() {}
93            fn type_name_of<T>(_: T) -> &'static str {
94                std::any::type_name::<T>()
95            }
96            let name = type_name_of(f);
97            name.strip_suffix("::f").unwrap()
98        }};
99    }
100
101    const VALID_TEXT: &str = "";
102
103    #[wasm_bindgen_test]
104    fn patch_last_none() {
105        let ancestor = dom::create_element("div");
106        dom::set_attribute(&ancestor, "id", function_name!());
107        dom::append_child(&dom::get_root_element(), &ancestor);
108
109        let mut target = VList::new(vec![VText::new(VALID_TEXT).into()], None);
110        target.set_depth(0);
111        target.patch(None, &ancestor);
112    }
113
114    #[wasm_bindgen_test]
115    fn patch_last_text() {
116        let ancestor = dom::create_element("div");
117        dom::set_attribute(&ancestor, "id", function_name!());
118
119        let current = dom::create_text_node("I dont love Rust");
120        dom::append_child(&ancestor, &current);
121
122        dom::append_child(&dom::get_root_element(), &ancestor);
123
124        let text = VNode::Text(VText {
125            text: "I dont love Rust".into(),
126            dom: Some(current),
127        });
128
129        let mut target = VList::new(vec![VText::new(VALID_TEXT).into()], None);
130        target.set_depth(0);
131        target.patch(Some(text), &ancestor);
132    }
133
134    #[wasm_bindgen_test]
135    fn patch_last_elem() {
136        let ancestor = dom::create_element("div");
137        dom::set_attribute(&ancestor, "id", function_name!());
138
139        let current = dom::create_element("div");
140        dom::set_attribute(&current, "id", "I dont love Rust");
141        dom::append_child(&ancestor, &current);
142
143        dom::append_child(&dom::get_root_element(), &ancestor);
144
145        let elem = VNode::Element(VElement {
146            tag_name: "div".into(),
147            attr: [("id".into(), "I dont love Rust".into())].into(),
148            event_handlers: vec![],
149            key: None,
150            children: vec![],
151            dom: Some(current),
152        });
153
154        let mut target = VList::new(vec![VText::new(VALID_TEXT).into()], None);
155        target.set_depth(0);
156        target.patch(Some(elem), &ancestor);
157    }
158
159    struct Comp;
160    impl Component for Comp {
161        type Message = ();
162        type Properties = ();
163
164        fn new(_props: Self::Properties) -> Self {
165            Comp
166        }
167        fn view(&self, _behavior: &mut impl Behavior<Self>) -> VNode {
168            VText::new("I dont love Rust").into()
169        }
170        fn update(&mut self, _message: Self::Message) -> bool {
171            false
172        }
173    }
174
175    #[wasm_bindgen_test]
176    fn patch_last_comp() {
177        let ancestor = dom::create_element("div");
178        dom::set_attribute(&ancestor, "id", function_name!());
179        dom::append_child(&dom::get_root_element(), &ancestor);
180
181        let mut comp = VNode::Component(VComponent::new::<Comp>((), None));
182        comp.set_depth(0);
183        comp.patch(None, &ancestor);
184
185        let mut target = VList::new(vec![VText::new(VALID_TEXT).into()], None);
186        target.set_depth(0);
187        target.patch(Some(comp), &ancestor);
188    }
189
190    #[wasm_bindgen_test]
191    fn patch_last_list() {
192        let ancestor = dom::create_element("div");
193        dom::set_attribute(&ancestor, "id", function_name!());
194        dom::append_child(&dom::get_root_element(), &ancestor);
195
196        let mut list = VNode::List(VList::new(
197            vec![VText::new("I dont love Rust").into()],
198            None,
199        ));
200        list.set_depth(0);
201        list.patch(None, &ancestor);
202
203        let mut target = VList::new(vec![VText::new(VALID_TEXT).into()], None);
204        target.set_depth(0);
205        target.patch(Some(list), &ancestor);
206    }
207}