feat: add FlatVDom round-trip conversion tests
This commit is contained in:
@@ -18,6 +18,7 @@ use reader::Reader;
|
|||||||
use rhei::RHEI_PREFIX;
|
use rhei::RHEI_PREFIX;
|
||||||
use style::{AncestorInfo, ComputedStyle, StructuralContext};
|
use style::{AncestorInfo, ComputedStyle, StructuralContext};
|
||||||
pub use types::Value;
|
pub use types::Value;
|
||||||
|
use types::{FlatVDom, VNode};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
pub struct Interpreter;
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ pub struct VNode<'a> {
|
|||||||
pub struct FlatVDom<'a> {
|
pub struct FlatVDom<'a> {
|
||||||
pub nodes: Vec<VNode<'a>>,
|
pub nodes: Vec<VNode<'a>>,
|
||||||
pub properties: Vec<(String, String)>,
|
pub properties: Vec<(String, String)>,
|
||||||
|
root_indices: Vec<NodeIdx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FlatVDom<'a> {
|
impl<'a> FlatVDom<'a> {
|
||||||
@@ -240,19 +241,22 @@ impl<'a> FlatVDom<'a> {
|
|||||||
Self {
|
Self {
|
||||||
nodes: Vec::new(),
|
nodes: Vec::new(),
|
||||||
properties: Vec::new(),
|
properties: Vec::new(),
|
||||||
|
root_indices: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_elements(elements: &[Element<'a>]) -> Self {
|
pub fn from_elements(elements: &[Element<'a>]) -> Self {
|
||||||
let mut fv = FlatVDom::new();
|
let mut fv = FlatVDom::new();
|
||||||
fv.append_elements(elements);
|
fv.append_elements(elements, true);
|
||||||
fv
|
fv
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append_elements(&mut self, elements: &[Element<'a>]) -> Range<NodeIdx> {
|
fn append_elements(&mut self, elements: &[Element<'a>], is_root: bool) {
|
||||||
let start = self.nodes.len();
|
|
||||||
for el in elements {
|
for el in elements {
|
||||||
let node_idx = self.nodes.len();
|
let node_idx = self.nodes.len();
|
||||||
|
if is_root {
|
||||||
|
self.root_indices.push(node_idx);
|
||||||
|
}
|
||||||
let prop_start = self.properties.len();
|
let prop_start = self.properties.len();
|
||||||
for (k, v) in &el.properties {
|
for (k, v) in &el.properties {
|
||||||
self.properties.push((k.clone().into_owned(), v.clone().into_owned()));
|
self.properties.push((k.clone().into_owned(), v.clone().into_owned()));
|
||||||
@@ -265,20 +269,24 @@ impl<'a> FlatVDom<'a> {
|
|||||||
computed_style: el.computed_style,
|
computed_style: el.computed_style,
|
||||||
element_id: el.element_id,
|
element_id: el.element_id,
|
||||||
});
|
});
|
||||||
let child_range = self.append_elements(&el.children);
|
let child_start = self.nodes.len();
|
||||||
self.nodes[node_idx].children_range = child_range;
|
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<Element<'a>> {
|
pub fn into_elements(self) -> Vec<Element<'a>> {
|
||||||
Self::nodes_to_elements(&self.nodes, &self.properties, 0..self.nodes.len())
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nodes_to_elements(nodes: &[VNode<'a>], props: &[(String, String)], range: Range<NodeIdx>) -> Vec<Element<'a>> {
|
fn node_to_element(nodes: &[VNode<'a>], props: &[(String, String)], idx: NodeIdx) -> Option<Element<'a>> {
|
||||||
let mut result = Vec::with_capacity(range.len());
|
let vn = nodes.get(idx)?;
|
||||||
for idx in range {
|
|
||||||
let vn = &nodes[idx];
|
|
||||||
let mut el = Element::new(vn.type_name);
|
let mut el = Element::new(vn.type_name);
|
||||||
el.element_id = vn.element_id;
|
el.element_id = vn.element_id;
|
||||||
el.computed_style = vn.computed_style;
|
el.computed_style = vn.computed_style;
|
||||||
@@ -288,11 +296,12 @@ impl<'a> FlatVDom<'a> {
|
|||||||
el.push_prop(k.clone(), v.clone());
|
el.push_prop(k.clone(), v.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let child_range = vn.children_range.clone();
|
for child_idx in vn.children_range.clone() {
|
||||||
el.children = Self::nodes_to_elements(nodes, props, child_range);
|
if let Some(child) = Self::node_to_element(nodes, props, child_idx) {
|
||||||
result.push(el);
|
el.children.push(child);
|
||||||
}
|
}
|
||||||
result
|
}
|
||||||
|
Some(el)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_node(&self, idx: NodeIdx) -> Option<&VNode<'a>> {
|
pub fn get_node(&self, idx: NodeIdx) -> Option<&VNode<'a>> {
|
||||||
@@ -302,6 +311,14 @@ impl<'a> FlatVDom<'a> {
|
|||||||
pub fn node_count(&self) -> usize {
|
pub fn node_count(&self) -> usize {
|
||||||
self.nodes.len()
|
self.nodes.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn root_count(&self) -> usize {
|
||||||
|
self.root_indices.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root_indices(&self) -> &[NodeIdx] {
|
||||||
|
&self.root_indices
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
Reference in New Issue
Block a user