use crate::compat::HashMap;
use crate::widget::capability::CapabilityValue;
#[derive(Debug, Clone, PartialEq)]
pub struct Node {
pub widget: String,
pub key: Option<String>,
pub props: HashMap<String, CapabilityValue>,
pub children: Vec<Node>,
}
impl Node {
pub fn new(widget: impl Into<String>) -> Self {
Self { widget: widget.into(), key: None, props: HashMap::new(), children: Vec::new() }
}
pub fn key(mut self, key: impl Into<String>) -> Self {
self.key = Some(key.into());
self
}
pub fn prop(mut self, name: impl Into<String>, value: CapabilityValue) -> Self {
self.props.insert(name.into(), value);
self
}
pub fn child(mut self, child: Node) -> Self {
self.children.push(child);
self
}
pub fn children_of(mut self, children: impl IntoIterator<Item = Node>) -> Self {
self.children.extend(children);
self
}
pub fn prop_value(&self, name: &str) -> Option<&CapabilityValue> {
self.props.get(name)
}
pub fn key_str(&self) -> Option<&str> {
self.key.as_deref()
}
pub fn node_count(&self) -> usize {
let mut count = 0usize;
let mut stack = vec![self];
while let Some(node) = stack.pop() {
count += 1;
stack.extend(node.children.iter());
}
count
}
pub fn duplicate_sibling_keys(&self) -> Vec<(&str, usize)> {
let mut counts: HashMap<&str, usize> = HashMap::new();
for child in &self.children {
if let Some(k) = child.key_str() {
*counts.entry(k).or_insert(0) += 1;
}
}
let mut dups: Vec<(&str, usize)> = counts.into_iter().filter(|(_, n)| *n > 1).collect();
dups.sort_by(|a, b| a.0.cmp(b.0));
dups
}
pub fn walk(&self) -> Vec<(&Node, usize)> {
let mut out = Vec::new();
let mut stack = vec![(self, 0usize)];
while let Some((node, depth)) = stack.pop() {
out.push((node, depth));
for child in node.children.iter().rev() {
stack.push((child, depth + 1));
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(v: &str) -> CapabilityValue {
CapabilityValue::String(v.to_string())
}
#[test]
fn new_node_is_a_keyless_leaf() {
let n = Node::new("label");
assert_eq!(n.widget, "label");
assert_eq!(n.key, None);
assert!(n.props.is_empty());
assert!(n.children.is_empty());
assert_eq!(n.node_count(), 1);
}
#[test]
fn builder_chain_sets_key_and_props() {
let n = Node::new("label").key("title").prop("text", s("Hi"));
assert_eq!(n.key_str(), Some("title"));
assert_eq!(n.prop_value("text"), Some(&s("Hi")));
}
#[test]
fn setting_a_property_twice_keeps_the_last_write() {
let n = Node::new("label").prop("text", s("first")).prop("text", s("second"));
assert_eq!(n.prop_value("text"), Some(&s("second")));
}
#[test]
fn children_keep_insertion_order() {
let n = Node::new("vbox").child(Node::new("a")).child(Node::new("b")).child(Node::new("c"));
let names: Vec<&str> = n.children.iter().map(|c| c.widget.as_str()).collect();
assert_eq!(names, ["a", "b", "c"]);
}
#[test]
fn children_of_appends_rather_than_replaces() {
let n =
Node::new("vbox").child(Node::new("a")).children_of([Node::new("b"), Node::new("c")]);
assert_eq!(n.children.len(), 3);
assert_eq!(n.children[2].widget, "c");
}
#[test]
fn node_count_covers_the_whole_subtree() {
let n = Node::new("vbox")
.child(Node::new("a").child(Node::new("a1")).child(Node::new("a2")))
.child(Node::new("b"));
assert_eq!(n.node_count(), 5);
}
#[test]
fn walk_is_depth_first_and_reports_depth() {
let n =
Node::new("vbox").child(Node::new("a").child(Node::new("a1"))).child(Node::new("b"));
let seen: Vec<(&str, usize)> =
n.walk().into_iter().map(|(node, d)| (node.widget.as_str(), d)).collect();
assert_eq!(seen, [("vbox", 0), ("a", 1), ("a1", 2), ("b", 1)]);
}
#[test]
fn duplicate_sibling_keys_are_reported() {
let n = Node::new("vbox")
.child(Node::new("a").key("dup"))
.child(Node::new("b").key("dup"))
.child(Node::new("c").key("unique"));
assert_eq!(n.duplicate_sibling_keys(), [("dup", 2)]);
}
#[test]
fn duplicate_report_ignores_keyless_siblings() {
let n = Node::new("vbox").child(Node::new("a")).child(Node::new("b"));
assert!(n.duplicate_sibling_keys().is_empty());
}
#[test]
fn duplicate_report_covers_only_direct_children() {
let n = Node::new("vbox")
.child(Node::new("list").key("left").child(Node::new("row").key("total")))
.child(Node::new("list").key("right").child(Node::new("row").key("total")));
assert!(n.duplicate_sibling_keys().is_empty());
}
#[test]
fn duplicate_report_is_not_confused_by_grandchildren() {
let n = Node::new("vbox")
.child(Node::new("a").key("k").child(Node::new("a1").key("k")))
.child(Node::new("b").key("k"));
assert_eq!(n.duplicate_sibling_keys(), [("k", 2)]);
}
#[test]
fn props_are_compared_by_exact_value() {
let a = Node::new("x").prop("v", CapabilityValue::Float(1.0));
let b = Node::new("x").prop("v", CapabilityValue::Float(1.000_000_1));
assert_ne!(a.props, b.props);
}
#[test]
fn a_node_is_a_value_that_can_be_cloned_and_compared() {
let a = Node::new("label").key("k").prop("text", s("t"));
let b = a.clone();
assert_eq!(a, b);
}
}