1use crate::{
7 ir::{NodeId, Value},
8 lifecycle::ModuleInstance,
9 reconcile::{InstanceTree, Patch, evaluate_binding},
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) {
67 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
68 }
69 }
70 for key in old.keys() {
72 if !node.props.contains_key(key) {
73 patches.push(Patch::remove_prop(node_id, key.clone()));
74 }
75 }
76 }
77 }
78 }
79
80 patches
81}
82
83fn render_dirty_list(
86 node_id: NodeId,
87 tree: &mut InstanceTree,
88 state: &serde_json::Value,
89 patches: &mut Vec<Patch>,
90 dependencies: &mut DependencyGraph,
91) {
92 use crate::reconcile::diff::{replace_item_bindings, reconcile_keyed_children};
93 use crate::ir::Element;
94
95 let (array_binding, element_template, old_children) = {
97 let node = match tree.get(node_id) {
98 Some(n) => n,
99 None => return,
100 };
101
102 let binding = match node.raw_props.get("0") {
103 Some(Value::Binding(b)) => b.clone(),
104 _ => return,
105 };
106
107 let template = match &node.element_template {
108 Some(t) => t.clone(),
109 None => return, };
111
112 (binding, template, node.children.clone())
113 };
114
115 let array = evaluate_binding(&array_binding, state)
117 .unwrap_or(serde_json::Value::Array(vec![]));
118
119 let items = match &array {
120 serde_json::Value::Array(items) => items,
121 _ => return,
122 };
123
124 let mut new_children: Vec<Element> = Vec::new();
127
128 for (index, item) in items.iter().enumerate() {
129 for child_template in &element_template.children {
130 let child_with_item = replace_item_bindings(child_template, item, index);
135 new_children.push(child_with_item);
136 }
137 }
138
139 let old_children_vec: Vec<_> = old_children.iter().copied().collect();
142 let keyed_patches = reconcile_keyed_children(
143 tree,
144 node_id,
145 &old_children_vec,
146 &new_children,
147 state,
148 dependencies,
149 );
150
151 patches.extend(keyed_patches);
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::{ir::Element, lifecycle::Module, reactive::Binding};
158 use serde_json::json;
159
160 #[test]
161 fn test_render_dirty_nodes_no_dirty() {
162 let mut scheduler = Scheduler::new();
163 let mut tree = InstanceTree::new();
164
165 let patches = render_dirty_nodes(&mut scheduler, &mut tree, None);
166 assert_eq!(patches.len(), 0);
167 }
168
169 #[test]
170 fn test_render_dirty_nodes_with_changes() {
171 let mut scheduler = Scheduler::new();
172 let mut tree = InstanceTree::new();
173
174 let module = Module::new("TestModule");
176 let initial_state = json!({"count": 0});
177 let instance = ModuleInstance::new(module, initial_state);
178
179 let element = Element::new("Text");
181 let node_id = tree.create_node(&element, instance.get_state());
182
183 scheduler.mark_dirty(node_id);
185
186 let _patches = render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));
188
189 assert!(!scheduler.has_dirty(), "Scheduler should have no more dirty nodes");
191 }
192
193 #[test]
194 fn test_render_dirty_nodes_state_change() {
195 use crate::ir::Value;
196
197 let mut scheduler = Scheduler::new();
198 let mut tree = InstanceTree::new();
199
200 let module = Module::new("TestModule");
202 let initial_state = json!({"text": "Hello"});
203 let mut instance = ModuleInstance::new(module, initial_state);
204
205 let mut element = Element::new("Text");
207 element
208 .props
209 .insert("0".to_string(), Value::Binding(Binding::state(vec!["text".to_string()])));
210 let node_id = tree.create_node(&element, instance.get_state());
211
212 if let Some(node) = tree.get_mut(node_id) {
214 node.update_props(instance.get_state());
215 }
216
217 instance.update_state(json!({"text": "World"}));
219
220 scheduler.mark_dirty(node_id);
222
223 let patches = render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));
225
226 let set_prop_count = patches
228 .iter()
229 .filter(|p| matches!(p, Patch::SetProp { .. }))
230 .count();
231 assert!(set_prop_count > 0, "Should have SetProp patches for changed state");
232 }
233
234 #[test]
235 fn test_render_dirty_nodes_multiple_nodes() {
236 let mut scheduler = Scheduler::new();
237 let mut tree = InstanceTree::new();
238
239 let module = Module::new("TestModule");
240 let initial_state = json!({});
241 let instance = ModuleInstance::new(module, initial_state);
242
243 let element1 = Element::new("Text");
245 let element2 = Element::new("Text");
246 let element3 = Element::new("Text");
247
248 let node_id_1 = tree.create_node(&element1, instance.get_state());
249 let _node_id_2 = tree.create_node(&element2, instance.get_state());
250 let node_id_3 = tree.create_node(&element3, instance.get_state());
251
252 scheduler.mark_dirty(node_id_1);
254 scheduler.mark_dirty(node_id_3);
255
256 assert!(scheduler.has_dirty());
258
259 render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));
260
261 assert!(!scheduler.has_dirty());
263 }
264}