1use super::{InstanceTree, Patch};
2use crate::ir::{Element, NodeId, Value};
3use crate::reactive::DependencyGraph;
4use indexmap::IndexMap;
5
6pub fn reconcile(
8 tree: &mut InstanceTree,
9 element: &Element,
10 parent_id: Option<NodeId>,
11 state: &serde_json::Value,
12 dependencies: &mut DependencyGraph,
13) -> Vec<Patch> {
14 let mut patches = Vec::new();
15
16 if tree.root().is_none() {
18 let node_id = create_tree(tree, element, parent_id, state, &mut patches, true, dependencies);
19 tree.set_root(node_id);
20 return patches;
21 }
22
23 if let Some(root_id) = tree.root() {
25 reconcile_node(tree, root_id, element, state, &mut patches, dependencies);
26 }
27
28 patches
29}
30
31pub fn create_tree(
33 tree: &mut InstanceTree,
34 element: &Element,
35 parent_id: Option<NodeId>,
36 state: &serde_json::Value,
37 patches: &mut Vec<Patch>,
38 is_root: bool,
39 dependencies: &mut DependencyGraph,
40) -> NodeId {
41 if let Some(Value::Binding(_)) = element.props.get("0") {
49 if !element.children.is_empty() {
50 return create_list_tree(tree, element, parent_id, state, patches, is_root, dependencies);
51 }
52 }
53
54 let node_id = tree.create_node(element, state);
56
57 for value in element.props.values() {
59 match value {
60 Value::Binding(binding) => {
61 dependencies.add_dependency(node_id, binding);
62 }
63 Value::TemplateString { bindings, .. } => {
64 for binding in bindings {
66 dependencies.add_dependency(node_id, binding);
67 }
68 }
69 _ => {}
70 }
71 }
72
73 let node = tree.get(node_id).unwrap();
75
76 let mut props = node.props.clone();
78 if let Some(Value::Static(val)) = element.props.get("__lazy") {
79 if val.as_bool().unwrap_or(false) && !element.children.is_empty() {
80 let child_component = &element.children[0].element_type;
82 props.insert("__lazy_child".to_string(), serde_json::json!(child_component));
83 }
84 }
85
86 patches.push(Patch::create(
87 node_id,
88 node.element_type.clone(),
89 props,
90 ));
91
92 if let Some(parent) = parent_id {
96 tree.add_child(parent, node_id, None);
97 patches.push(Patch::insert(parent, node_id, None));
98 } else if is_root {
99 patches.push(Patch::insert_root(node_id));
101 }
102
103 let is_lazy = element.props.get("__lazy")
105 .and_then(|v| {
106 if let Value::Static(val) = v {
107 val.as_bool()
108 } else {
109 None
110 }
111 })
112 .unwrap_or(false);
113
114 if !is_lazy {
115 for child_element in &element.children {
116 create_tree(tree, child_element, Some(node_id), state, patches, false, dependencies);
117 }
118 }
119
120 node_id
121}
122
123fn create_list_tree(
126 tree: &mut InstanceTree,
127 element: &Element,
128 parent_id: Option<NodeId>,
129 state: &serde_json::Value,
130 patches: &mut Vec<Patch>,
131 is_root: bool,
132 dependencies: &mut DependencyGraph,
133) -> NodeId {
134 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
136 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
137 } else {
138 serde_json::Value::Array(vec![])
139 };
140
141 let mut list_element = Element::new(&element.element_type);
144 for (key, value) in &element.props {
145 if key != "0" {
147 list_element.props.insert(key.clone(), value.clone());
148 }
149 }
150
151 let node_id = tree.create_node(&list_element, state);
152
153 if let Some(Value::Binding(binding)) = element.props.get("0") {
156 dependencies.add_dependency(node_id, binding);
157 }
158
159 if let Some(node) = tree.get_mut(node_id) {
162 node.raw_props = element.props.clone();
163 node.element_template = Some(element.clone());
164 }
165
166 let node = tree.get(node_id).unwrap();
168 patches.push(Patch::create(
169 node_id,
170 node.element_type.clone(),
171 node.props.clone(),
172 ));
173
174 if let Some(parent) = parent_id {
176 tree.add_child(parent, node_id, None);
177 patches.push(Patch::insert(parent, node_id, None));
178 } else if is_root {
179 patches.push(Patch::insert_root(node_id));
180 }
181
182 if let serde_json::Value::Array(items) = &array {
184 for (index, item) in items.iter().enumerate() {
186 for child_template in &element.children {
187 let child_with_item = replace_item_bindings(child_template, item, index);
189 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
190 }
191 }
192 }
193
194 node_id
195}
196
197fn navigate_item_path<'a>(item: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> {
200 let mut current = item;
201
202 for segment in path.split('.') {
203 if let Ok(index) = segment.parse::<usize>() {
205 current = current.get(index)?;
206 } else {
207 current = current.get(segment)?;
209 }
210 }
211
212 Some(current)
213}
214
215pub fn replace_item_bindings(element: &Element, item: &serde_json::Value, index: usize) -> Element {
217 let mut new_element = element.clone();
218
219 let mut new_props = IndexMap::new();
221 for (key, value) in &element.props {
222 let new_value = match value {
223 Value::Binding(binding) => {
224 if binding.is_item() {
226 if binding.path.is_empty() {
228 Value::Static(item.clone())
229 } else {
230 let prop_path = binding.full_path();
232 if let Some(val) = navigate_item_path(item, &prop_path) {
233 Value::Static(val.clone())
234 } else {
235 value.clone()
236 }
237 }
238 } else {
239 value.clone()
240 }
241 }
242 Value::TemplateString { template, bindings } => {
243 let has_item_bindings = bindings.iter().any(|b| b.is_item());
245
246 if has_item_bindings {
247 let mut result = template.clone();
248
249 for binding in bindings {
250 if binding.is_item() {
251 let pattern = format!("${{{}}}", binding.full_path_with_source());
252 let replacement = if binding.path.is_empty() {
253 match item {
255 serde_json::Value::String(s) => s.clone(),
256 serde_json::Value::Number(n) => n.to_string(),
257 serde_json::Value::Bool(b) => b.to_string(),
258 _ => serde_json::to_string(item).unwrap_or_default(),
259 }
260 } else if let Some(val) = navigate_item_path(item, &binding.full_path()) {
261 match val {
262 serde_json::Value::String(s) => s.clone(),
263 serde_json::Value::Number(n) => n.to_string(),
264 serde_json::Value::Bool(b) => b.to_string(),
265 _ => serde_json::to_string(val).unwrap_or_default(),
266 }
267 } else {
268 continue; };
270 result = result.replace(&pattern, &replacement);
271 }
272 }
273
274 let remaining_bindings: Vec<_> = bindings.iter()
276 .filter(|b| b.is_state())
277 .cloned()
278 .collect();
279
280 if remaining_bindings.is_empty() {
281 Value::Static(serde_json::Value::String(result))
282 } else {
283 Value::TemplateString {
284 template: result,
285 bindings: remaining_bindings,
286 }
287 }
288 } else {
289 value.clone()
290 }
291 }
292 Value::Static(serde_json::Value::String(s)) if s.contains("${item.") || s.contains("${item}") => {
294 let mut result = s.clone();
295
296 let mut pos = 0;
298 while let Some(start) = result[pos..].find("${item") {
299 let abs_start = pos + start;
300 if let Some(end) = result[abs_start..].find('}') {
301 let abs_end = abs_start + end;
302 let content = &result[abs_start + 2..abs_end]; let replacement = if content == "item" {
306 match item {
308 serde_json::Value::String(s) => s.clone(),
309 serde_json::Value::Number(n) => n.to_string(),
310 serde_json::Value::Bool(b) => b.to_string(),
311 _ => serde_json::to_string(item).unwrap_or_default(),
312 }
313 } else if content.starts_with("item.") {
314 let path = &content[5..]; if let Some(val) = navigate_item_path(item, path) {
317 match val {
318 serde_json::Value::String(s) => s.clone(),
319 serde_json::Value::Number(n) => n.to_string(),
320 serde_json::Value::Bool(b) => b.to_string(),
321 _ => serde_json::to_string(val).unwrap_or_default(),
322 }
323 } else {
324 pos = abs_end + 1;
325 continue;
326 }
327 } else {
328 pos = abs_end + 1;
329 continue;
330 };
331
332 let pattern = format!("${{{}}}", content);
333 result = result.replace(&pattern, &replacement);
334 pos = 0; } else {
336 break;
337 }
338 }
339
340 Value::Static(serde_json::Value::String(result))
341 }
342 _ => value.clone(),
343 };
344 new_props.insert(key.clone(), new_value);
345 }
346 new_element.props = new_props;
347
348 new_element.key = Some(format!("list-item-{}", index));
350
351 new_element.children = element.children.iter()
353 .map(|child| replace_item_bindings(child, item, index))
354 .collect();
355
356 new_element
357}
358
359pub fn reconcile_node(
361 tree: &mut InstanceTree,
362 node_id: NodeId,
363 element: &Element,
364 state: &serde_json::Value,
365 patches: &mut Vec<Patch>,
366 dependencies: &mut DependencyGraph,
367) {
368 let node = tree.get(node_id).cloned();
369 if node.is_none() {
370 return;
371 }
372 let node = node.unwrap();
373
374 let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
378
379 if is_iterable {
380 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
382 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
383 } else {
384 serde_json::Value::Array(vec![])
385 };
386
387 if let serde_json::Value::Array(items) = &array {
389 let old_children = node.children.clone();
390
391 let expected_children_count = items.len() * element.children.len();
393
394 if old_children.len() != expected_children_count {
396 for &old_child_id in &old_children {
397 patches.push(Patch::remove(old_child_id));
398 }
399
400 if let Some(node) = tree.get_mut(node_id) {
402 node.children.clear();
403 }
404
405 for (index, item) in items.iter().enumerate() {
407 for child_template in &element.children {
408 let child_with_item = replace_item_bindings(child_template, item, index);
409 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
410 }
411 }
412 } else {
413 let mut child_index = 0;
415 for (item_index, item) in items.iter().enumerate() {
416 for child_template in &element.children {
417 if let Some(&old_child_id) = old_children.get(child_index) {
418 let child_with_item = replace_item_bindings(child_template, item, item_index);
419 reconcile_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
420 }
421 child_index += 1;
422 }
423 }
424 }
425 }
426
427 return; }
429
430 if node.element_type != element.element_type {
432 replace_subtree(tree, node_id, &node, element, state, patches, dependencies);
433 return;
434 }
435
436 for value in element.props.values() {
438 if let Value::Binding(binding) = value {
439 dependencies.add_dependency(node_id, binding);
440 }
441 }
442
443 let new_props = resolve_props(&element.props, state);
445 let prop_patches = diff_props(node_id, &node.props, &new_props);
446 patches.extend(prop_patches);
447
448 if let Some(node) = tree.get_mut(node_id) {
450 node.props = new_props.clone();
451 node.raw_props = element.props.clone();
452 }
453
454 let is_lazy = element.props.get("__lazy")
456 .and_then(|v| {
457 if let Value::Static(val) = v {
458 val.as_bool()
459 } else {
460 None
461 }
462 })
463 .unwrap_or(false);
464
465 if !is_lazy {
466 let old_children = node.children.clone();
467 let new_children = &element.children;
468
469 for (i, new_child_element) in new_children.iter().enumerate() {
471 if let Some(&old_child_id) = old_children.get(i) {
472 reconcile_node(tree, old_child_id, new_child_element, state, patches, dependencies);
474 } else {
475 let new_child_id = create_tree(tree, new_child_element, Some(node_id), state, patches, false, dependencies);
477 if let Some(node) = tree.get_mut(node_id) {
478 node.children.push(new_child_id);
479 }
480 }
481 }
482
483 if old_children.len() > new_children.len() {
485 for &old_child_id in &old_children[new_children.len()..] {
486 patches.push(Patch::remove(old_child_id));
487 }
489 }
490 }
491}
492
493fn replace_subtree(
496 tree: &mut InstanceTree,
497 old_node_id: NodeId,
498 old_node: &super::InstanceNode,
499 new_element: &Element,
500 state: &serde_json::Value,
501 patches: &mut Vec<Patch>,
502 dependencies: &mut DependencyGraph,
503) {
504 let parent_id = old_node.parent;
505
506 let old_position = if let Some(pid) = parent_id {
508 tree.get(pid).and_then(|parent| {
509 parent.children.iter().position(|&id| id == old_node_id)
510 })
511 } else {
512 None
513 };
514
515 let ids_to_remove = collect_subtree_ids(tree, old_node_id);
517
518 for &id in &ids_to_remove {
520 patches.push(Patch::remove(id));
521 dependencies.remove_node(id);
522 }
523
524 if let Some(pid) = parent_id {
526 if let Some(parent) = tree.get_mut(pid) {
527 parent.children.retain(|&id| id != old_node_id);
528 }
529 }
530
531 tree.remove(old_node_id);
533
534 let is_root = parent_id.is_none();
536 let new_node_id = create_tree(tree, new_element, parent_id, state, patches, is_root, dependencies);
537
538 if is_root {
540 tree.set_root(new_node_id);
541 } else if let Some(pid) = parent_id {
542 if let Some(pos) = old_position {
543 if let Some(parent) = tree.get_mut(pid) {
544 let current_len = parent.children.len();
545 if pos < current_len - 1 {
548 let new_id = parent.children.pop().unwrap();
550 parent.children.insert(pos, new_id);
551
552 let next_sibling = parent.children.get(pos + 1).copied();
554 patches.push(Patch::move_node(pid, new_node_id, next_sibling));
555 }
556 }
558 }
559 }
560}
561
562fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
565 let mut result = Vec::new();
566 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
567
568 while let Some((node_id, children_processed)) = stack.pop() {
569 if children_processed {
570 result.push(node_id);
572 } else {
573 stack.push((node_id, true));
575 if let Some(node) = tree.get(node_id) {
576 for &child_id in node.children.iter().rev() {
578 stack.push((child_id, false));
579 }
580 }
581 }
582 }
583
584 result
585}
586
587fn resolve_props(props: &IndexMap<String, crate::ir::Value>, state: &serde_json::Value) -> IndexMap<String, serde_json::Value> {
589 resolve_props_with_item(props, state, None)
590}
591
592fn resolve_props_with_item(
594 props: &IndexMap<String, crate::ir::Value>,
595 state: &serde_json::Value,
596 item: Option<&serde_json::Value>,
597) -> IndexMap<String, serde_json::Value> {
598 let mut resolved = IndexMap::new();
599
600 for (key, value) in props {
601 let resolved_value = match value {
602 crate::ir::Value::Static(v) => v.clone(),
603 crate::ir::Value::Binding(binding) => {
604 if binding.is_item() {
605 if let Some(item_value) = item {
607 evaluate_item_binding(binding, item_value).unwrap_or(serde_json::Value::Null)
608 } else {
609 serde_json::Value::Null
610 }
611 } else {
612 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
614 }
615 }
616 crate::ir::Value::TemplateString { template, .. } => {
617 match crate::reactive::evaluate_template_string(template, state, item) {
620 Ok(result) => serde_json::Value::String(result),
621 Err(_) => serde_json::Value::String(template.clone()),
622 }
623 }
624 crate::ir::Value::Action(action) => {
625 serde_json::Value::String(format!("@{}", action))
627 }
628 };
629 resolved.insert(key.clone(), resolved_value);
630 }
631
632 resolved
633}
634
635fn evaluate_item_binding(binding: &crate::reactive::Binding, item: &serde_json::Value) -> Option<serde_json::Value> {
637 if binding.path.is_empty() {
638 return Some(item.clone());
639 }
640
641 let mut current = item;
642 for segment in &binding.path {
643 current = current.get(segment)?;
644 }
645 Some(current.clone())
646}
647
648fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
650 let mut current = state;
651
652 for segment in &binding.path {
653 current = current.get(segment)?;
654 }
655
656 Some(current.clone())
657}
658
659fn reconcile_keyed_children(
661 tree: &mut InstanceTree,
662 parent_id: NodeId,
663 old_children: &[NodeId],
664 new_children: &[Element],
665 state: &serde_json::Value,
666 dependencies: &mut DependencyGraph,
667) -> Vec<Patch> {
668 let mut patches = Vec::new();
669
670 let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
672 let mut old_unkeyed: Vec<NodeId> = Vec::new();
673
674 for &child_id in old_children {
675 if let Some(node) = tree.get(child_id) {
676 if let Some(key) = &node.key {
677 old_keyed.insert(key.clone(), child_id);
678 } else {
679 old_unkeyed.push(child_id);
680 }
681 }
682 }
683
684 let mut new_child_ids: Vec<NodeId> = Vec::new();
685 let mut unkeyed_idx = 0;
686
687 for new_element in new_children {
689 let new_key = new_element.key.as_ref();
690
691 let child_id = if let Some(key) = new_key {
692 if let Some(&old_id) = old_keyed.get(key) {
694 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
696 old_keyed.shift_remove(key); old_id
698 } else {
699 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
701 }
702 } else {
703 if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
705 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
706 unkeyed_idx += 1;
707 old_id
708 } else {
709 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
711 }
712 };
713
714 new_child_ids.push(child_id);
715 }
716
717 let mut old_positions: Vec<Option<usize>> = Vec::new();
720 for &new_id in &new_child_ids {
721 let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
722 old_positions.push(old_pos);
723 }
724
725 let lis = longest_increasing_subsequence(&old_positions);
727
728 if let Some(parent_node) = tree.get_mut(parent_id) {
730 parent_node.children = new_child_ids.clone();
731 }
732
733 for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
735 if !lis.contains(&new_idx) {
737 let before_id = new_child_ids.get(new_idx + 1).copied();
739 patches.push(Patch::move_node(parent_id, child_id, before_id));
740 }
741 }
742
743 for old_id in old_keyed.values() {
745 patches.push(Patch::remove(*old_id));
746 }
747 for &old_id in &old_unkeyed[unkeyed_idx..] {
748 patches.push(Patch::remove(old_id));
749 }
750
751 patches
752}
753
754fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
758 let valid: Vec<(usize, usize)> = positions
760 .iter()
761 .enumerate()
762 .filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
763 .collect();
764
765 if valid.is_empty() {
766 return Vec::new();
767 }
768
769 let n = valid.len();
771 let mut dp: Vec<usize> = vec![1; n];
772 let mut prev: Vec<Option<usize>> = vec![None; n];
773
774 for i in 1..n {
775 for j in 0..i {
776 if valid[j].1 < valid[i].1 && dp[j] + 1 > dp[i] {
777 dp[i] = dp[j] + 1;
778 prev[i] = Some(j);
779 }
780 }
781 }
782
783 let mut max_idx = 0;
785 for i in 1..n {
786 if dp[i] > dp[max_idx] {
787 max_idx = i;
788 }
789 }
790
791 let mut result = Vec::new();
793 let mut idx = Some(max_idx);
794 while let Some(i) = idx {
795 result.push(valid[i].0);
796 idx = prev[i];
797 }
798 result.reverse();
799
800 result
801}
802
803pub fn diff_props(
805 node_id: NodeId,
806 old_props: &IndexMap<String, serde_json::Value>,
807 new_props: &IndexMap<String, serde_json::Value>,
808) -> Vec<Patch> {
809 let mut patches = Vec::new();
810
811 for (key, new_value) in new_props {
813 if old_props.get(key) != Some(new_value) {
814 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
815 }
816 }
817
818 patches
822}
823
824#[cfg(test)]
825mod tests {
826 use super::*;
827 use crate::ir::Value;
828 use serde_json::json;
829
830 #[test]
831 fn test_create_simple_tree() {
832 use crate::reactive::DependencyGraph;
833
834 let mut tree = InstanceTree::new();
835 let mut patches = Vec::new();
836 let mut dependencies = DependencyGraph::new();
837
838 let element = Element::new("Column")
839 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
840
841 let state = json!({});
842 create_tree(&mut tree, &element, None, &state, &mut patches, true, &mut dependencies);
843
844 assert_eq!(patches.len(), 4);
847
848 let root_insert = patches.iter().find(|p| {
850 matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root")
851 });
852 assert!(root_insert.is_some(), "Root insert patch should exist");
853 }
854
855 #[test]
856 fn test_diff_props() {
857 let node_id = NodeId::default();
858 let old = indexmap::indexmap! {
859 "color".to_string() => json!("red"),
860 "size".to_string() => json!(16),
861 };
862 let new = indexmap::indexmap! {
863 "color".to_string() => json!("blue"),
864 "size".to_string() => json!(16),
865 };
866
867 let patches = diff_props(node_id, &old, &new);
868
869 assert_eq!(patches.len(), 1);
871 }
872
873 #[test]
874 fn test_longest_increasing_subsequence_basic() {
875 let positions = vec![Some(0), Some(1), Some(2), Some(3)];
877 let lis = longest_increasing_subsequence(&positions);
878 assert_eq!(lis, vec![0, 1, 2, 3]);
879 }
880
881 #[test]
882 fn test_longest_increasing_subsequence_reverse() {
883 let positions = vec![Some(3), Some(2), Some(1), Some(0)];
885 let lis = longest_increasing_subsequence(&positions);
886 assert_eq!(lis.len(), 1);
887 }
888
889 #[test]
890 fn test_longest_increasing_subsequence_mixed() {
891 let positions = vec![Some(0), Some(3), Some(1), Some(2)];
893 let lis = longest_increasing_subsequence(&positions);
894 assert_eq!(lis.len(), 3);
895 assert!(lis.contains(&0));
897 assert!(lis.contains(&2));
898 assert!(lis.contains(&3));
899 }
900
901 #[test]
902 fn test_longest_increasing_subsequence_with_new_items() {
903 let positions = vec![None, Some(0), None, Some(1)];
905 let lis = longest_increasing_subsequence(&positions);
906 assert_eq!(lis, vec![1, 3]); }
908
909 #[test]
910 fn test_longest_increasing_subsequence_empty() {
911 let positions: Vec<Option<usize>> = vec![];
912 let lis = longest_increasing_subsequence(&positions);
913 assert_eq!(lis, Vec::<usize>::new());
914 }
915
916 #[test]
917 fn test_longest_increasing_subsequence_all_new() {
918 let positions = vec![None, None, None];
920 let lis = longest_increasing_subsequence(&positions);
921 assert_eq!(lis, Vec::<usize>::new());
922 }
923
924 #[test]
925 fn test_reconcile_keyed_children_basic() {
926 let mut tree = InstanceTree::new();
927 let mut dependencies = DependencyGraph::new();
928 let state = json!({});
929
930 let parent = Element::new("Column");
932 let parent_id = tree.create_node(&parent, &state);
933
934 let mut old_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
936 old_child_1.key = Some("item-1".to_string());
937 let old_id_1 = tree.create_node(&old_child_1, &state);
938
939 let mut old_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
940 old_child_2.key = Some("item-2".to_string());
941 let old_id_2 = tree.create_node(&old_child_2, &state);
942
943 tree.add_child(parent_id, old_id_1, None);
944 tree.add_child(parent_id, old_id_2, None);
945
946 let mut new_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
948 new_child_2.key = Some("item-2".to_string());
949
950 let mut new_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
951 new_child_1.key = Some("item-1".to_string());
952
953 let new_children = vec![new_child_2, new_child_1];
954 let old_children = vec![old_id_1, old_id_2];
955
956 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
957
958 let move_count = patches.iter().filter(|p| matches!(p, Patch::Move { .. })).count();
960 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
961 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
962
963 assert_eq!(create_count, 0, "Should not create new children");
964 assert_eq!(remove_count, 0, "Should not remove any children");
965 assert!(move_count > 0, "Should have move patches for reordering");
966 }
967
968 #[test]
969 fn test_reconcile_keyed_children_add_remove() {
970 let mut tree = InstanceTree::new();
971 let mut dependencies = DependencyGraph::new();
972 let state = json!({});
973
974 let parent = Element::new("Column");
975 let parent_id = tree.create_node(&parent, &state);
976
977 let mut old_child_1 = Element::new("Text");
979 old_child_1.key = Some("item-1".to_string());
980 let old_id_1 = tree.create_node(&old_child_1, &state);
981
982 let mut old_child_2 = Element::new("Text");
983 old_child_2.key = Some("item-2".to_string());
984 let old_id_2 = tree.create_node(&old_child_2, &state);
985
986 tree.add_child(parent_id, old_id_1, None);
987 tree.add_child(parent_id, old_id_2, None);
988
989 let mut new_child_2 = Element::new("Text");
991 new_child_2.key = Some("item-2".to_string());
992
993 let mut new_child_3 = Element::new("Text");
994 new_child_3.key = Some("item-3".to_string());
995
996 let new_children = vec![new_child_2, new_child_3];
997 let old_children = vec![old_id_1, old_id_2];
998
999 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1000
1001 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1002 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1003
1004 assert_eq!(create_count, 1, "Should create item-3");
1005 assert_eq!(remove_count, 1, "Should remove item-1");
1006 }
1007}