1use crate::{
7 ir::{NodeId, Value},
8 lifecycle::ModuleInstance,
9 reconcile::{InstanceTree, Patch},
10 reactive::{DependencyGraph, Scheduler},
11};
12
13pub fn render_dirty_nodes(
16 scheduler: &mut Scheduler,
17 tree: &mut InstanceTree,
18 module: Option<&ModuleInstance>,
19) -> Vec<Patch> {
20 render_dirty_nodes_with_deps(scheduler, tree, module, &mut DependencyGraph::new())
21}
22
23pub fn render_dirty_nodes_with_deps(
25 scheduler: &mut Scheduler,
26 tree: &mut InstanceTree,
27 module: Option<&ModuleInstance>,
28 dependencies: &mut DependencyGraph,
29) -> Vec<Patch> {
30 if !scheduler.has_dirty() {
31 return Vec::new();
32 }
33
34 let dirty_nodes = scheduler.take_dirty();
35 let state = module
36 .map(|m| m.get_state())
37 .unwrap_or(&serde_json::Value::Null);
38
39 let mut patches = Vec::new();
41 for node_id in dirty_nodes {
42 let is_list_node = tree.get(node_id)
45 .map(|n| {
46 n.raw_props.get("0").map(|v| matches!(v, Value::Binding(_))).unwrap_or(false)
47 && n.element_template.is_some()
48 })
49 .unwrap_or(false);
50
51 if is_list_node {
52 render_dirty_list(node_id, tree, state, &mut patches, dependencies);
54 } else {
55 let old_props = tree.get(node_id).map(|n| n.props.clone());
57
58 if let Some(node) = tree.get_mut(node_id) {
60 node.update_props(state);
61 }
62
63 if let (Some(old), Some(node)) = (old_props, tree.get(node_id)) {
65 for (key, new_value) in &node.props {
66 if old.get(key) != Some(new_value) {
68 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
69 }
70 }
71 }
72 }
73 }
74
75 patches
76}
77
78fn render_dirty_list(
81 node_id: NodeId,
82 tree: &mut InstanceTree,
83 state: &serde_json::Value,
84 patches: &mut Vec<Patch>,
85 dependencies: &mut DependencyGraph,
86) {
87 use crate::reconcile::diff::{replace_item_bindings, reconcile_keyed_children};
88 use crate::ir::Element;
89
90 let (array_binding, element_template, old_children) = {
92 let node = match tree.get(node_id) {
93 Some(n) => n,
94 None => return,
95 };
96
97 let binding = match node.raw_props.get("0") {
98 Some(Value::Binding(b)) => b.clone(),
99 _ => return,
100 };
101
102 let template = match &node.element_template {
103 Some(t) => t.clone(),
104 None => return, };
106
107 (binding, template, node.children.clone())
108 };
109
110 let array = evaluate_binding(&array_binding, state)
112 .unwrap_or(serde_json::Value::Array(vec![]));
113
114 let items = match &array {
115 serde_json::Value::Array(items) => items,
116 _ => return,
117 };
118
119 let mut new_children: Vec<Element> = Vec::new();
122
123 for (index, item) in items.iter().enumerate() {
124 for child_template in &element_template.children {
125 let child_with_item = replace_item_bindings(child_template, item, index);
130 new_children.push(child_with_item);
131 }
132 }
133
134 let old_children_vec: Vec<_> = old_children.iter().copied().collect();
137 let keyed_patches = reconcile_keyed_children(
138 tree,
139 node_id,
140 &old_children_vec,
141 &new_children,
142 state,
143 dependencies,
144 );
145
146 patches.extend(keyed_patches);
147}
148
149fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
151 let mut current = state;
152 for segment in &binding.path {
153 current = current.get(segment)?;
154 }
155 Some(current.clone())
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::{ir::Element, lifecycle::Module, reactive::Binding};
162 use serde_json::json;
163
164 #[test]
165 fn test_render_dirty_nodes_no_dirty() {
166 let mut scheduler = Scheduler::new();
167 let mut tree = InstanceTree::new();
168
169 let patches = render_dirty_nodes(&mut scheduler, &mut tree, None);
170 assert_eq!(patches.len(), 0);
171 }
172
173 #[test]
174 fn test_render_dirty_nodes_with_changes() {
175 let mut scheduler = Scheduler::new();
176 let mut tree = InstanceTree::new();
177
178 let module = Module::new("TestModule");
180 let initial_state = json!({"count": 0});
181 let instance = ModuleInstance::new(module, initial_state);
182
183 let element = Element::new("Text");
185 let node_id = tree.create_node(&element, instance.get_state());
186
187 scheduler.mark_dirty(node_id);
189
190 let patches = render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));
192
193 assert!(!scheduler.has_dirty(), "Scheduler should have no more dirty nodes");
195 }
196
197 #[test]
198 fn test_render_dirty_nodes_state_change() {
199 use crate::ir::Value;
200
201 let mut scheduler = Scheduler::new();
202 let mut tree = InstanceTree::new();
203
204 let module = Module::new("TestModule");
206 let initial_state = json!({"text": "Hello"});
207 let mut instance = ModuleInstance::new(module, initial_state);
208
209 let mut element = Element::new("Text");
211 element
212 .props
213 .insert("0".to_string(), Value::Binding(Binding::state(vec!["text".to_string()])));
214 let node_id = tree.create_node(&element, instance.get_state());
215
216 if let Some(node) = tree.get_mut(node_id) {
218 node.update_props(instance.get_state());
219 }
220
221 instance.update_state(json!({"text": "World"}));
223
224 scheduler.mark_dirty(node_id);
226
227 let patches = render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));
229
230 let set_prop_count = patches
232 .iter()
233 .filter(|p| matches!(p, Patch::SetProp { .. }))
234 .count();
235 assert!(set_prop_count > 0, "Should have SetProp patches for changed state");
236 }
237
238 #[test]
239 fn test_render_dirty_nodes_multiple_nodes() {
240 let mut scheduler = Scheduler::new();
241 let mut tree = InstanceTree::new();
242
243 let module = Module::new("TestModule");
244 let initial_state = json!({});
245 let instance = ModuleInstance::new(module, initial_state);
246
247 let element1 = Element::new("Text");
249 let element2 = Element::new("Text");
250 let element3 = Element::new("Text");
251
252 let node_id_1 = tree.create_node(&element1, instance.get_state());
253 let node_id_2 = tree.create_node(&element2, instance.get_state());
254 let node_id_3 = tree.create_node(&element3, instance.get_state());
255
256 scheduler.mark_dirty(node_id_1);
258 scheduler.mark_dirty(node_id_3);
259
260 assert!(scheduler.has_dirty());
262
263 render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));
264
265 assert!(!scheduler.has_dirty());
267 }
268}