1use crate::{
7 ir::{NodeId, Value},
8 lifecycle::ModuleInstance,
9 reactive::{DependencyGraph, Scheduler},
10 reconcile::{evaluate_binding, InstanceTree, Patch},
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
45 .get(node_id)
46 .map(|n| {
47 n.raw_props
48 .get("0")
49 .map(|v| matches!(v, Value::Binding(_)))
50 .unwrap_or(false)
51 && n.element_template.is_some()
52 })
53 .unwrap_or(false);
54
55 if is_list_node {
56 render_dirty_list(node_id, tree, state, &mut patches, dependencies);
58 } else {
59 let old_props = tree.get(node_id).map(|n| n.props.clone());
61
62 if let Some(node) = tree.get_mut(node_id) {
64 node.update_props(state);
65 }
66
67 if let (Some(old), Some(node)) = (old_props, tree.get(node_id)) {
69 for (key, new_value) in &node.props {
70 if old.get(key) != Some(new_value) {
71 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
72 }
73 }
74 for key in old.keys() {
76 if !node.props.contains_key(key) {
77 patches.push(Patch::remove_prop(node_id, key.clone()));
78 }
79 }
80 }
81 }
82 }
83
84 patches
85}
86
87fn render_dirty_list(
90 node_id: NodeId,
91 tree: &mut InstanceTree,
92 state: &serde_json::Value,
93 patches: &mut Vec<Patch>,
94 dependencies: &mut DependencyGraph,
95) {
96 use crate::ir::Element;
97 use crate::reconcile::diff::{reconcile_keyed_children, replace_item_bindings};
98
99 let (array_binding, element_template, old_children) = {
101 let node = match tree.get(node_id) {
102 Some(n) => n,
103 None => return,
104 };
105
106 let binding = match node.raw_props.get("0") {
107 Some(Value::Binding(b)) => b.clone(),
108 _ => return,
109 };
110
111 let template = match &node.element_template {
112 Some(t) => t.clone(),
113 None => return, };
115
116 (binding, template, node.children.clone())
117 };
118
119 let array = evaluate_binding(&array_binding, state).unwrap_or(serde_json::Value::Array(vec![]));
121
122 let items = match &array {
123 serde_json::Value::Array(items) => items,
124 _ => return,
125 };
126
127 let mut new_children: Vec<Element> = Vec::new();
130
131 for (index, item) in items.iter().enumerate() {
132 for child_template in &element_template.children {
133 let child_with_item = replace_item_bindings(child_template, item, index);
138 new_children.push(child_with_item);
139 }
140 }
141
142 let old_children_vec: Vec<_> = old_children.iter().copied().collect();
145 let keyed_patches = reconcile_keyed_children(
146 tree,
147 node_id,
148 &old_children_vec,
149 &new_children,
150 state,
151 dependencies,
152 );
153
154 patches.extend(keyed_patches);
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::{ir::Element, lifecycle::Module, reactive::Binding};
161 use serde_json::json;
162
163 #[test]
164 fn test_render_dirty_nodes_no_dirty() {
165 let mut scheduler = Scheduler::new();
166 let mut tree = InstanceTree::new();
167
168 let patches = render_dirty_nodes(&mut scheduler, &mut tree, None);
169 assert_eq!(patches.len(), 0);
170 }
171
172 #[test]
173 fn test_render_dirty_nodes_with_changes() {
174 let mut scheduler = Scheduler::new();
175 let mut tree = InstanceTree::new();
176
177 let module = Module::new("TestModule");
179 let initial_state = json!({"count": 0});
180 let instance = ModuleInstance::new(module, initial_state);
181
182 let element = Element::new("Text");
184 let node_id = tree.create_node(&element, instance.get_state());
185
186 scheduler.mark_dirty(node_id);
188
189 let _patches = render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));
191
192 assert!(
194 !scheduler.has_dirty(),
195 "Scheduler should have no more dirty nodes"
196 );
197 }
198
199 #[test]
200 fn test_render_dirty_nodes_state_change() {
201 use crate::ir::Value;
202
203 let mut scheduler = Scheduler::new();
204 let mut tree = InstanceTree::new();
205
206 let module = Module::new("TestModule");
208 let initial_state = json!({"text": "Hello"});
209 let mut instance = ModuleInstance::new(module, initial_state);
210
211 let mut element = Element::new("Text");
213 element.props.insert(
214 "0".to_string(),
215 Value::Binding(Binding::state(vec!["text".to_string()])),
216 );
217 let node_id = tree.create_node(&element, instance.get_state());
218
219 if let Some(node) = tree.get_mut(node_id) {
221 node.update_props(instance.get_state());
222 }
223
224 instance.update_state(json!({"text": "World"}));
226
227 scheduler.mark_dirty(node_id);
229
230 let patches = render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));
232
233 let set_prop_count = patches
235 .iter()
236 .filter(|p| matches!(p, Patch::SetProp { .. }))
237 .count();
238 assert!(
239 set_prop_count > 0,
240 "Should have SetProp patches for changed state"
241 );
242 }
243
244 #[test]
245 fn test_render_dirty_nodes_multiple_nodes() {
246 let mut scheduler = Scheduler::new();
247 let mut tree = InstanceTree::new();
248
249 let module = Module::new("TestModule");
250 let initial_state = json!({});
251 let instance = ModuleInstance::new(module, initial_state);
252
253 let element1 = Element::new("Text");
255 let element2 = Element::new("Text");
256 let element3 = Element::new("Text");
257
258 let node_id_1 = tree.create_node(&element1, instance.get_state());
259 let _node_id_2 = tree.create_node(&element2, instance.get_state());
260 let node_id_3 = tree.create_node(&element3, instance.get_state());
261
262 scheduler.mark_dirty(node_id_1);
264 scheduler.mark_dirty(node_id_3);
265
266 assert!(scheduler.has_dirty());
268
269 render_dirty_nodes(&mut scheduler, &mut tree, Some(&instance));
270
271 assert!(!scheduler.has_dirty());
273 }
274}