use hypen_engine::{
ir::{Element, IRNode, Value},
reactive::{Binding, DependencyGraph},
reconcile::{reconcile_ir, InstanceTree, Patch},
};
use serde_json::json;
#[test]
fn test_create_tree_tracks_single_binding() {
let mut element = Element::new("Text");
element.props.insert(
"text".to_string(),
Value::Binding(Binding::state(vec!["count".to_string()])),
);
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({"count": 42});
let _ = reconcile_ir(
&mut tree,
&IRNode::Element(element.clone()),
None,
&state,
&mut dependencies,
);
let node_id = tree.root().expect("Should have root");
let affected = dependencies.get_affected_nodes("count");
assert!(
!affected.is_empty(),
"Should track dependency for 'count' binding"
);
assert!(
affected.contains(&node_id),
"Should track the created node as dependent on 'count'"
);
}
#[test]
fn test_create_tree_tracks_multiple_bindings_same_node() {
let mut element = Element::new("Text");
element.props.insert(
"text".to_string(),
Value::Binding(Binding::state(vec!["user".to_string(), "name".to_string()])),
);
element.props.insert(
"color".to_string(),
Value::Binding(Binding::state(vec![
"theme".to_string(),
"textColor".to_string(),
])),
);
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({
"user": {"name": "Alice"},
"theme": {"textColor": "blue"}
});
let _ = reconcile_ir(
&mut tree,
&IRNode::Element(element.clone()),
None,
&state,
&mut dependencies,
);
let node_id = tree.root().expect("Should have root");
let affected_user = dependencies.get_affected_nodes("user.name");
assert!(
affected_user.contains(&node_id),
"Should track dependency for 'user.name'"
);
let affected_theme = dependencies.get_affected_nodes("theme.textColor");
assert!(
affected_theme.contains(&node_id),
"Should track dependency for 'theme.textColor'"
);
}
#[test]
fn test_create_tree_tracks_nested_children_separately() {
let mut parent = Element::new("Column");
parent.props.insert(
"width".to_string(),
Value::Binding(Binding::state(vec![
"layout".to_string(),
"width".to_string(),
])),
);
let mut child = Element::new("Text");
child.props.insert(
"text".to_string(),
Value::Binding(Binding::state(vec!["user".to_string(), "name".to_string()])),
);
parent.ir_children.push(IRNode::Element(child));
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({
"layout": {"width": 100},
"user": {"name": "Bob"}
});
let _ = reconcile_ir(
&mut tree,
&IRNode::Element(parent.clone()),
None,
&state,
&mut dependencies,
);
let parent_id = tree.root().expect("Should have root");
let affected_layout = dependencies.get_affected_nodes("layout.width");
assert!(
affected_layout.contains(&parent_id),
"Parent should depend on 'layout.width'"
);
let affected_user = dependencies.get_affected_nodes("user.name");
assert!(
!affected_user.is_empty(),
"Child should depend on 'user.name'"
);
assert!(
affected_user.len() == 1,
"Should have exactly one node depending on user.name"
);
}
#[test]
fn test_create_tree_prefix_tracking() {
let mut element = Element::new("Text");
element.props.insert(
"text".to_string(),
Value::Binding(Binding::state(vec![
"user".to_string(),
"profile".to_string(),
"name".to_string(),
])),
);
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({"user": {"profile": {"name": "Charlie"}}});
let _ = reconcile_ir(
&mut tree,
&IRNode::Element(element.clone()),
None,
&state,
&mut dependencies,
);
let node_id = tree.root().expect("Should have root");
let affected_full = dependencies.get_affected_nodes("user.profile.name");
assert!(
affected_full.contains(&node_id),
"Should track full path 'user.profile.name'"
);
let affected_parent = dependencies.get_affected_nodes("user");
assert!(
affected_parent.contains(&node_id),
"Should be affected when parent 'user' changes"
);
let affected_middle = dependencies.get_affected_nodes("user.profile");
assert!(
affected_middle.contains(&node_id),
"Should be affected when middle path 'user.profile' changes"
);
}
#[test]
fn test_create_tree_generates_correct_patches() {
let mut element = Element::new("Text");
element.props.insert(
"text".to_string(),
Value::Binding(Binding::state(vec!["message".to_string()])),
);
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({"message": "Hello World"});
let patches = reconcile_ir(
&mut tree,
&IRNode::Element(element.clone()),
None,
&state,
&mut dependencies,
);
assert!(
patches.len() >= 2,
"Should generate at least Create and InsertRoot patches"
);
let has_create = patches.iter().any(|p| matches!(p, Patch::Create { .. }));
assert!(has_create, "Should generate Create patch");
let has_insert_root = patches.iter().any(|p| matches!(p, Patch::Insert { .. }));
assert!(has_insert_root, "Should generate Insert patch");
}
#[test]
fn test_create_tree_resolves_bindings_to_values() {
let mut element = Element::new("Text");
element.props.insert(
"text".to_string(),
Value::Binding(Binding::state(vec!["greeting".to_string()])),
);
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({"greeting": "Hello, Test!"});
let _ = reconcile_ir(
&mut tree,
&IRNode::Element(element.clone()),
None,
&state,
&mut dependencies,
);
let node_id = tree.root().expect("Should have root");
let node = tree.get(node_id).expect("Node should exist");
let text_prop = node.props.get("text").expect("Should have 'text' prop");
assert_eq!(
text_prop,
&json!("Hello, Test!"),
"Binding should be resolved to actual state value"
);
}
#[test]
fn test_create_tree_mixed_static_and_bindings() {
let mut element = Element::new("Text");
element.props.insert(
"text".to_string(),
Value::Binding(Binding::state(vec!["dynamic".to_string()])),
);
element
.props
.insert("fontSize".to_string(), Value::Static(json!(16)));
element
.props
.insert("color".to_string(), Value::Static(json!("blue")));
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({"dynamic": "Dynamic Text"});
let _ = reconcile_ir(
&mut tree,
&IRNode::Element(element.clone()),
None,
&state,
&mut dependencies,
);
let node_id = tree.root().expect("Should have root");
let affected = dependencies.get_affected_nodes("dynamic");
assert!(
affected.contains(&node_id),
"Should track 'dynamic' binding"
);
let node = tree.get(node_id).unwrap();
assert_eq!(node.props.get("text"), Some(&json!("Dynamic Text")));
assert_eq!(node.props.get("fontSize"), Some(&json!(16)));
assert_eq!(node.props.get("color"), Some(&json!("blue")));
}
#[test]
fn test_create_tree_no_bindings_no_dependencies() {
let mut element = Element::new("Text");
element
.props
.insert("text".to_string(), Value::Static(json!("Static")));
element
.props
.insert("color".to_string(), Value::Static(json!("red")));
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({});
let _ = reconcile_ir(
&mut tree,
&IRNode::Element(element.clone()),
None,
&state,
&mut dependencies,
);
let affected = dependencies.get_affected_nodes("anything");
assert!(
affected.is_empty(),
"Should not track any dependencies for static-only element"
);
}