From a798e966f317e63ef83abea00bfe4da95965368e Mon Sep 17 00:00:00 2001 From: Glint Dev Date: Wed, 22 Jul 2026 13:38:03 +0300 Subject: [PATCH] feat: add FlatVDom round-trip conversion tests --- src/interpreter/mod.rs | 67 ++++++++++++++++++++++++++++++++++++++++ src/interpreter/types.rs | 65 ++++++++++++++++++++++++-------------- 2 files changed, 108 insertions(+), 24 deletions(-) diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 9810a0c..4cf2140 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -18,6 +18,7 @@ use reader::Reader; use rhei::RHEI_PREFIX; use style::{AncestorInfo, ComputedStyle, StructuralContext}; pub use types::Value; +use types::{FlatVDom, VNode}; use std::collections::{HashMap, HashSet}; pub struct Interpreter; @@ -662,3 +663,69 @@ impl Interpreter { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_flat_vdom_roundtrip() { + let mut root = Element::new("Panel"); + root.element_id = ElementId(1); + root.push_prop("class", "container"); + root.push_prop("color", "red"); + + let mut child1 = Element::new("Button"); + child1.element_id = ElementId(2); + child1.push_prop("label", "Click"); + + let mut child2 = Element::new("Text"); + child2.element_id = ElementId(3); + child2.push_prop("text", "Hello"); + + root.children.push(child1); + root.children.push(child2); + + let roots = vec![root]; + + let fv = FlatVDom::from_elements(&roots); + assert_eq!(fv.node_count(), 3, "FlatVDom should have 3 nodes"); + + let roundtrip = fv.into_elements(); + assert_eq!(roundtrip.len(), 1, "Should have 1 root"); + assert_eq!(roundtrip[0].type_name, "Panel"); + assert_eq!(roundtrip[0].children.len(), 2); + assert_eq!(roundtrip[0].children[0].type_name, "Button"); + assert_eq!(roundtrip[0].children[0].get_prop("label"), Some("Click")); + assert_eq!(roundtrip[0].children[1].type_name, "Text"); + assert_eq!(roundtrip[0].children[1].get_prop("text"), Some("Hello")); + } + + #[test] + fn test_flat_vdom_empty() { + let fv = FlatVDom::from_elements(&[]); + assert_eq!(fv.node_count(), 0); + let elements = fv.into_elements(); + assert!(elements.is_empty()); + } + + #[test] + fn test_flat_vdom_nested() { + let mut outer = Element::new("Window"); + outer.element_id = ElementId(1); + let mut inner = Element::new("Panel"); + inner.element_id = ElementId(2); + let mut btn = Element::new("Button"); + btn.element_id = ElementId(3); + btn.push_prop("label", "Nested"); + inner.children.push(btn); + outer.children.push(inner); + + let roots = vec![outer]; + let fv = FlatVDom::from_elements(&roots); + assert_eq!(fv.node_count(), 3); + + let elements = fv.into_elements(); + assert_eq!(elements[0].children[0].children[0].get_prop("label"), Some("Nested")); + } +} diff --git a/src/interpreter/types.rs b/src/interpreter/types.rs index d256463..17b3e8b 100644 --- a/src/interpreter/types.rs +++ b/src/interpreter/types.rs @@ -233,6 +233,7 @@ pub struct VNode<'a> { pub struct FlatVDom<'a> { pub nodes: Vec>, pub properties: Vec<(String, String)>, + root_indices: Vec, } impl<'a> FlatVDom<'a> { @@ -240,19 +241,22 @@ impl<'a> FlatVDom<'a> { Self { nodes: Vec::new(), properties: Vec::new(), + root_indices: Vec::new(), } } pub fn from_elements(elements: &[Element<'a>]) -> Self { let mut fv = FlatVDom::new(); - fv.append_elements(elements); + fv.append_elements(elements, true); fv } - fn append_elements(&mut self, elements: &[Element<'a>]) -> Range { - let start = self.nodes.len(); + fn append_elements(&mut self, elements: &[Element<'a>], is_root: bool) { for el in elements { let node_idx = self.nodes.len(); + if is_root { + self.root_indices.push(node_idx); + } let prop_start = self.properties.len(); for (k, v) in &el.properties { self.properties.push((k.clone().into_owned(), v.clone().into_owned())); @@ -265,36 +269,41 @@ impl<'a> FlatVDom<'a> { computed_style: el.computed_style, element_id: el.element_id, }); - let child_range = self.append_elements(&el.children); - self.nodes[node_idx].children_range = child_range; + let child_start = self.nodes.len(); + self.append_elements(&el.children, false); + self.nodes[node_idx].children_range = child_start..self.nodes.len(); } - start..self.nodes.len() } pub fn into_elements(self) -> Vec> { - Self::nodes_to_elements(&self.nodes, &self.properties, 0..self.nodes.len()) - } - - fn nodes_to_elements(nodes: &[VNode<'a>], props: &[(String, String)], range: Range) -> Vec> { - let mut result = Vec::with_capacity(range.len()); - for idx in range { - let vn = &nodes[idx]; - let mut el = Element::new(vn.type_name); - el.element_id = vn.element_id; - el.computed_style = vn.computed_style; - for i in vn.properties.clone() { - if i < props.len() { - let (k, v) = &props[i]; - el.push_prop(k.clone(), v.clone()); - } + let mut result = Vec::with_capacity(self.root_indices.len()); + for &root_idx in &self.root_indices { + if let Some(el) = Self::node_to_element(&self.nodes, &self.properties, root_idx) { + result.push(el); } - let child_range = vn.children_range.clone(); - el.children = Self::nodes_to_elements(nodes, props, child_range); - result.push(el); } result } + fn node_to_element(nodes: &[VNode<'a>], props: &[(String, String)], idx: NodeIdx) -> Option> { + let vn = nodes.get(idx)?; + let mut el = Element::new(vn.type_name); + el.element_id = vn.element_id; + el.computed_style = vn.computed_style; + for i in vn.properties.clone() { + if i < props.len() { + let (k, v) = &props[i]; + el.push_prop(k.clone(), v.clone()); + } + } + for child_idx in vn.children_range.clone() { + if let Some(child) = Self::node_to_element(nodes, props, child_idx) { + el.children.push(child); + } + } + Some(el) + } + pub fn get_node(&self, idx: NodeIdx) -> Option<&VNode<'a>> { self.nodes.get(idx) } @@ -302,6 +311,14 @@ impl<'a> FlatVDom<'a> { pub fn node_count(&self) -> usize { self.nodes.len() } + + pub fn root_count(&self) -> usize { + self.root_indices.len() + } + + pub fn root_indices(&self) -> &[NodeIdx] { + &self.root_indices + } } #[derive(Debug)]