1use super::{InstanceTree, Patch};
2use crate::ir::{Element, NodeId, Value};
3use crate::reactive::{DependencyGraph, evaluate_template_string};
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
215fn is_simple_path(s: &str) -> bool {
219 if !s.starts_with("item") {
221 return false;
222 }
223
224 let after_item = &s[4..]; if after_item.is_empty() {
226 return true; }
228
229 if !after_item.starts_with('.') {
231 return false;
232 }
233
234 after_item[1..].chars().all(|c| c.is_alphanumeric() || c == '_' || c == '.')
236}
237
238pub fn replace_item_bindings(element: &Element, item: &serde_json::Value, index: usize) -> Element {
240 let mut new_element = element.clone();
241
242 let mut new_props = IndexMap::new();
244 for (key, value) in &element.props {
245 let new_value = match value {
246 Value::Binding(binding) => {
247 if binding.is_item() {
249 if binding.path.is_empty() {
251 Value::Static(item.clone())
252 } else {
253 let prop_path = binding.full_path();
255 if let Some(val) = navigate_item_path(item, &prop_path) {
256 Value::Static(val.clone())
257 } else {
258 value.clone()
259 }
260 }
261 } else {
262 value.clone()
263 }
264 }
265 Value::TemplateString { template, bindings } => {
266 let has_item_bindings = bindings.iter().any(|b| b.is_item());
268
269 if has_item_bindings {
270 let mut result = template.clone();
271
272 for binding in bindings {
273 if binding.is_item() {
274 let pattern = format!("${{{}}}", binding.full_path_with_source());
275 let replacement = if binding.path.is_empty() {
276 match item {
278 serde_json::Value::String(s) => s.clone(),
279 serde_json::Value::Number(n) => n.to_string(),
280 serde_json::Value::Bool(b) => b.to_string(),
281 _ => serde_json::to_string(item).unwrap_or_default(),
282 }
283 } else if let Some(val) = navigate_item_path(item, &binding.full_path()) {
284 match val {
285 serde_json::Value::String(s) => s.clone(),
286 serde_json::Value::Number(n) => n.to_string(),
287 serde_json::Value::Bool(b) => b.to_string(),
288 _ => serde_json::to_string(val).unwrap_or_default(),
289 }
290 } else {
291 continue; };
293 result = result.replace(&pattern, &replacement);
294 }
295 }
296
297 let remaining_bindings: Vec<_> = bindings.iter()
299 .filter(|b| b.is_state())
300 .cloned()
301 .collect();
302
303 if remaining_bindings.is_empty() {
304 if result.contains("${") {
306 match evaluate_template_string(&result, &serde_json::Value::Null, None) {
308 Ok(evaluated) => Value::Static(serde_json::Value::String(evaluated)),
309 Err(_) => Value::Static(serde_json::Value::String(result)),
310 }
311 } else {
312 Value::Static(serde_json::Value::String(result))
313 }
314 } else {
315 Value::TemplateString {
316 template: result,
317 bindings: remaining_bindings,
318 }
319 }
320 } else {
321 value.clone()
322 }
323 }
324 Value::Static(serde_json::Value::String(s)) if s.contains("${item.") || s.contains("${item}") => {
328 let mut result = s.clone();
329
330 let mut pos = 0;
335 while let Some(start) = result[pos..].find("${") {
336 let abs_start = pos + start;
337
338 if let Some(end) = result[abs_start..].find('}') {
340 let abs_end = abs_start + end;
341 let content = &result[abs_start + 2..abs_end]; if content == "item" {
345 let replacement = match item {
347 serde_json::Value::String(s) => s.clone(),
348 serde_json::Value::Number(n) => n.to_string(),
349 serde_json::Value::Bool(b) => b.to_string(),
350 _ => serde_json::to_string(item).unwrap_or_default(),
351 };
352 result = format!("{}{}{}", &result[..abs_start], replacement, &result[abs_end + 1..]);
353 pos = 0;
354 continue;
355 } else if content.starts_with("item.") && is_simple_path(content) {
356 let path = &content[5..]; if let Some(val) = navigate_item_path(item, path) {
359 let replacement = match val {
360 serde_json::Value::String(s) => s.clone(),
361 serde_json::Value::Number(n) => n.to_string(),
362 serde_json::Value::Bool(b) => b.to_string(),
363 serde_json::Value::Null => "null".to_string(),
364 _ => serde_json::to_string(val).unwrap_or_default(),
365 };
366 result = format!("{}{}{}", &result[..abs_start], replacement, &result[abs_end + 1..]);
367 pos = 0;
368 continue;
369 }
370 } else if content.contains("item.") || content.contains("item ") {
371 let mut expr_content = content.to_string();
373
374 let mut expr_pos = 0;
376 while let Some(item_start) = expr_content[expr_pos..].find("item.") {
377 let item_abs_start = expr_pos + item_start;
378 let path_start = item_abs_start + 5; let mut path_end = path_start;
382 let chars: Vec<char> = expr_content[path_start..].chars().collect();
383
384 for (i, ch) in chars.iter().enumerate() {
385 if ch.is_alphanumeric() || *ch == '_' || *ch == '.' {
386 if *ch == '.' {
387 if i + 1 >= chars.len() || {
388 let next = chars[i + 1];
389 !next.is_alphanumeric() && next != '_'
390 } {
391 break;
392 }
393 }
394 path_end = path_start + i + 1;
395 } else {
396 break;
397 }
398 }
399
400 if path_end > path_start {
401 let path = &expr_content[path_start..path_end];
402 if let Some(val) = navigate_item_path(item, path) {
403 let replacement = match val {
404 serde_json::Value::String(s) => format!("'{}'", s),
405 serde_json::Value::Number(n) => n.to_string(),
406 serde_json::Value::Bool(b) => b.to_string(),
407 serde_json::Value::Null => "null".to_string(),
408 _ => serde_json::to_string(val).unwrap_or_default(),
409 };
410 expr_content = format!(
411 "{}{}{}",
412 &expr_content[..item_abs_start],
413 replacement,
414 &expr_content[path_end..]
415 );
416 expr_pos = 0;
417 continue;
418 }
419 }
420 expr_pos = item_abs_start + 5;
421 }
422
423 let new_expr = format!("${{{}}}", expr_content);
425 match evaluate_template_string(&new_expr, &serde_json::Value::Null, None) {
426 Ok(evaluated) => {
427 result = format!("{}{}{}", &result[..abs_start], evaluated, &result[abs_end + 1..]);
428 pos = 0;
429 continue;
430 }
431 Err(_) => {
432 pos = abs_end + 1;
434 continue;
435 }
436 }
437 }
438
439 pos = abs_end + 1;
441 } else {
442 break;
443 }
444 }
445
446 Value::Static(serde_json::Value::String(result))
447 }
448 _ => value.clone(),
449 };
450 new_props.insert(key.clone(), new_value);
451 }
452 new_element.props = new_props;
453
454 new_element.key = Some(format!("list-item-{}", index));
456
457 new_element.children = element.children.iter()
459 .map(|child| replace_item_bindings(child, item, index))
460 .collect();
461
462 new_element
463}
464
465pub fn reconcile_node(
467 tree: &mut InstanceTree,
468 node_id: NodeId,
469 element: &Element,
470 state: &serde_json::Value,
471 patches: &mut Vec<Patch>,
472 dependencies: &mut DependencyGraph,
473) {
474 let node = tree.get(node_id).cloned();
475 if node.is_none() {
476 return;
477 }
478 let node = node.unwrap();
479
480 let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
484
485 if is_iterable {
486 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
488 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
489 } else {
490 serde_json::Value::Array(vec![])
491 };
492
493 if let serde_json::Value::Array(items) = &array {
495 let old_children = node.children.clone();
496
497 let expected_children_count = items.len() * element.children.len();
499
500 if old_children.len() != expected_children_count {
502 for &old_child_id in &old_children {
503 patches.push(Patch::remove(old_child_id));
504 }
505
506 if let Some(node) = tree.get_mut(node_id) {
508 node.children.clear();
509 }
510
511 for (index, item) in items.iter().enumerate() {
513 for child_template in &element.children {
514 let child_with_item = replace_item_bindings(child_template, item, index);
515 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
516 }
517 }
518 } else {
519 let mut child_index = 0;
521 for (item_index, item) in items.iter().enumerate() {
522 for child_template in &element.children {
523 if let Some(&old_child_id) = old_children.get(child_index) {
524 let child_with_item = replace_item_bindings(child_template, item, item_index);
525 reconcile_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
526 }
527 child_index += 1;
528 }
529 }
530 }
531 }
532
533 return; }
535
536 if node.element_type != element.element_type {
538 replace_subtree(tree, node_id, &node, element, state, patches, dependencies);
539 return;
540 }
541
542 for value in element.props.values() {
544 if let Value::Binding(binding) = value {
545 dependencies.add_dependency(node_id, binding);
546 }
547 }
548
549 let new_props = resolve_props(&element.props, state);
551 let prop_patches = diff_props(node_id, &node.props, &new_props);
552 patches.extend(prop_patches);
553
554 if let Some(node) = tree.get_mut(node_id) {
556 node.props = new_props.clone();
557 node.raw_props = element.props.clone();
558 }
559
560 let is_lazy = element.props.get("__lazy")
562 .and_then(|v| {
563 if let Value::Static(val) = v {
564 val.as_bool()
565 } else {
566 None
567 }
568 })
569 .unwrap_or(false);
570
571 if !is_lazy {
572 let old_children = node.children.clone();
573 let new_children = &element.children;
574
575 for (i, new_child_element) in new_children.iter().enumerate() {
577 if let Some(&old_child_id) = old_children.get(i) {
578 reconcile_node(tree, old_child_id, new_child_element, state, patches, dependencies);
580 } else {
581 let new_child_id = create_tree(tree, new_child_element, Some(node_id), state, patches, false, dependencies);
583 if let Some(node) = tree.get_mut(node_id) {
584 node.children.push(new_child_id);
585 }
586 }
587 }
588
589 if old_children.len() > new_children.len() {
591 for &old_child_id in &old_children[new_children.len()..] {
592 patches.push(Patch::remove(old_child_id));
593 }
595 }
596 }
597}
598
599fn replace_subtree(
602 tree: &mut InstanceTree,
603 old_node_id: NodeId,
604 old_node: &super::InstanceNode,
605 new_element: &Element,
606 state: &serde_json::Value,
607 patches: &mut Vec<Patch>,
608 dependencies: &mut DependencyGraph,
609) {
610 let parent_id = old_node.parent;
611
612 let old_position = if let Some(pid) = parent_id {
614 tree.get(pid).and_then(|parent| {
615 parent.children.iter().position(|&id| id == old_node_id)
616 })
617 } else {
618 None
619 };
620
621 let ids_to_remove = collect_subtree_ids(tree, old_node_id);
623
624 for &id in &ids_to_remove {
626 patches.push(Patch::remove(id));
627 dependencies.remove_node(id);
628 }
629
630 if let Some(pid) = parent_id {
632 if let Some(parent) = tree.get_mut(pid) {
633 parent.children.retain(|&id| id != old_node_id);
634 }
635 }
636
637 tree.remove(old_node_id);
639
640 let is_root = parent_id.is_none();
642 let new_node_id = create_tree(tree, new_element, parent_id, state, patches, is_root, dependencies);
643
644 if is_root {
646 tree.set_root(new_node_id);
647 } else if let Some(pid) = parent_id {
648 if let Some(pos) = old_position {
649 if let Some(parent) = tree.get_mut(pid) {
650 let current_len = parent.children.len();
651 if pos < current_len - 1 {
654 let new_id = parent.children.pop().unwrap();
656 parent.children.insert(pos, new_id);
657
658 let next_sibling = parent.children.get(pos + 1).copied();
660 patches.push(Patch::move_node(pid, new_node_id, next_sibling));
661 }
662 }
664 }
665 }
666}
667
668fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
671 let mut result = Vec::new();
672 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
673
674 while let Some((node_id, children_processed)) = stack.pop() {
675 if children_processed {
676 result.push(node_id);
678 } else {
679 stack.push((node_id, true));
681 if let Some(node) = tree.get(node_id) {
682 for &child_id in node.children.iter().rev() {
684 stack.push((child_id, false));
685 }
686 }
687 }
688 }
689
690 result
691}
692
693fn resolve_props(props: &IndexMap<String, crate::ir::Value>, state: &serde_json::Value) -> IndexMap<String, serde_json::Value> {
695 resolve_props_with_item(props, state, None)
696}
697
698fn resolve_props_with_item(
700 props: &IndexMap<String, crate::ir::Value>,
701 state: &serde_json::Value,
702 item: Option<&serde_json::Value>,
703) -> IndexMap<String, serde_json::Value> {
704 let mut resolved = IndexMap::new();
705
706 for (key, value) in props {
707 let resolved_value = match value {
708 crate::ir::Value::Static(v) => v.clone(),
709 crate::ir::Value::Binding(binding) => {
710 if binding.is_item() {
711 if let Some(item_value) = item {
713 evaluate_item_binding(binding, item_value).unwrap_or(serde_json::Value::Null)
714 } else {
715 serde_json::Value::Null
716 }
717 } else {
718 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
720 }
721 }
722 crate::ir::Value::TemplateString { template, .. } => {
723 match crate::reactive::evaluate_template_string(template, state, item) {
726 Ok(result) => serde_json::Value::String(result),
727 Err(_) => serde_json::Value::String(template.clone()),
728 }
729 }
730 crate::ir::Value::Action(action) => {
731 serde_json::Value::String(format!("@{}", action))
733 }
734 };
735 resolved.insert(key.clone(), resolved_value);
736 }
737
738 resolved
739}
740
741fn evaluate_item_binding(binding: &crate::reactive::Binding, item: &serde_json::Value) -> Option<serde_json::Value> {
743 if binding.path.is_empty() {
744 return Some(item.clone());
745 }
746
747 let mut current = item;
748 for segment in &binding.path {
749 current = current.get(segment)?;
750 }
751 Some(current.clone())
752}
753
754fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
756 let mut current = state;
757
758 for segment in &binding.path {
759 current = current.get(segment)?;
760 }
761
762 Some(current.clone())
763}
764
765fn reconcile_keyed_children(
767 tree: &mut InstanceTree,
768 parent_id: NodeId,
769 old_children: &[NodeId],
770 new_children: &[Element],
771 state: &serde_json::Value,
772 dependencies: &mut DependencyGraph,
773) -> Vec<Patch> {
774 let mut patches = Vec::new();
775
776 let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
778 let mut old_unkeyed: Vec<NodeId> = Vec::new();
779
780 for &child_id in old_children {
781 if let Some(node) = tree.get(child_id) {
782 if let Some(key) = &node.key {
783 old_keyed.insert(key.clone(), child_id);
784 } else {
785 old_unkeyed.push(child_id);
786 }
787 }
788 }
789
790 let mut new_child_ids: Vec<NodeId> = Vec::new();
791 let mut unkeyed_idx = 0;
792
793 for new_element in new_children {
795 let new_key = new_element.key.as_ref();
796
797 let child_id = if let Some(key) = new_key {
798 if let Some(&old_id) = old_keyed.get(key) {
800 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
802 old_keyed.shift_remove(key); old_id
804 } else {
805 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
807 }
808 } else {
809 if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
811 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
812 unkeyed_idx += 1;
813 old_id
814 } else {
815 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
817 }
818 };
819
820 new_child_ids.push(child_id);
821 }
822
823 let mut old_positions: Vec<Option<usize>> = Vec::new();
826 for &new_id in &new_child_ids {
827 let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
828 old_positions.push(old_pos);
829 }
830
831 let lis = longest_increasing_subsequence(&old_positions);
833
834 if let Some(parent_node) = tree.get_mut(parent_id) {
836 parent_node.children = new_child_ids.clone();
837 }
838
839 for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
841 if !lis.contains(&new_idx) {
843 let before_id = new_child_ids.get(new_idx + 1).copied();
845 patches.push(Patch::move_node(parent_id, child_id, before_id));
846 }
847 }
848
849 for old_id in old_keyed.values() {
851 patches.push(Patch::remove(*old_id));
852 }
853 for &old_id in &old_unkeyed[unkeyed_idx..] {
854 patches.push(Patch::remove(old_id));
855 }
856
857 patches
858}
859
860fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
864 let valid: Vec<(usize, usize)> = positions
866 .iter()
867 .enumerate()
868 .filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
869 .collect();
870
871 if valid.is_empty() {
872 return Vec::new();
873 }
874
875 let n = valid.len();
877 let mut dp: Vec<usize> = vec![1; n];
878 let mut prev: Vec<Option<usize>> = vec![None; n];
879
880 for i in 1..n {
881 for j in 0..i {
882 if valid[j].1 < valid[i].1 && dp[j] + 1 > dp[i] {
883 dp[i] = dp[j] + 1;
884 prev[i] = Some(j);
885 }
886 }
887 }
888
889 let mut max_idx = 0;
891 for i in 1..n {
892 if dp[i] > dp[max_idx] {
893 max_idx = i;
894 }
895 }
896
897 let mut result = Vec::new();
899 let mut idx = Some(max_idx);
900 while let Some(i) = idx {
901 result.push(valid[i].0);
902 idx = prev[i];
903 }
904 result.reverse();
905
906 result
907}
908
909pub fn diff_props(
911 node_id: NodeId,
912 old_props: &IndexMap<String, serde_json::Value>,
913 new_props: &IndexMap<String, serde_json::Value>,
914) -> Vec<Patch> {
915 let mut patches = Vec::new();
916
917 for (key, new_value) in new_props {
919 if old_props.get(key) != Some(new_value) {
920 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
921 }
922 }
923
924 patches
928}
929
930#[cfg(test)]
931mod tests {
932 use super::*;
933 use crate::ir::Value;
934 use serde_json::json;
935
936 #[test]
937 fn test_create_simple_tree() {
938 use crate::reactive::DependencyGraph;
939
940 let mut tree = InstanceTree::new();
941 let mut patches = Vec::new();
942 let mut dependencies = DependencyGraph::new();
943
944 let element = Element::new("Column")
945 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
946
947 let state = json!({});
948 create_tree(&mut tree, &element, None, &state, &mut patches, true, &mut dependencies);
949
950 assert_eq!(patches.len(), 4);
953
954 let root_insert = patches.iter().find(|p| {
956 matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root")
957 });
958 assert!(root_insert.is_some(), "Root insert patch should exist");
959 }
960
961 #[test]
962 fn test_diff_props() {
963 let node_id = NodeId::default();
964 let old = indexmap::indexmap! {
965 "color".to_string() => json!("red"),
966 "size".to_string() => json!(16),
967 };
968 let new = indexmap::indexmap! {
969 "color".to_string() => json!("blue"),
970 "size".to_string() => json!(16),
971 };
972
973 let patches = diff_props(node_id, &old, &new);
974
975 assert_eq!(patches.len(), 1);
977 }
978
979 #[test]
980 fn test_longest_increasing_subsequence_basic() {
981 let positions = vec![Some(0), Some(1), Some(2), Some(3)];
983 let lis = longest_increasing_subsequence(&positions);
984 assert_eq!(lis, vec![0, 1, 2, 3]);
985 }
986
987 #[test]
988 fn test_longest_increasing_subsequence_reverse() {
989 let positions = vec![Some(3), Some(2), Some(1), Some(0)];
991 let lis = longest_increasing_subsequence(&positions);
992 assert_eq!(lis.len(), 1);
993 }
994
995 #[test]
996 fn test_longest_increasing_subsequence_mixed() {
997 let positions = vec![Some(0), Some(3), Some(1), Some(2)];
999 let lis = longest_increasing_subsequence(&positions);
1000 assert_eq!(lis.len(), 3);
1001 assert!(lis.contains(&0));
1003 assert!(lis.contains(&2));
1004 assert!(lis.contains(&3));
1005 }
1006
1007 #[test]
1008 fn test_longest_increasing_subsequence_with_new_items() {
1009 let positions = vec![None, Some(0), None, Some(1)];
1011 let lis = longest_increasing_subsequence(&positions);
1012 assert_eq!(lis, vec![1, 3]); }
1014
1015 #[test]
1016 fn test_longest_increasing_subsequence_empty() {
1017 let positions: Vec<Option<usize>> = vec![];
1018 let lis = longest_increasing_subsequence(&positions);
1019 assert_eq!(lis, Vec::<usize>::new());
1020 }
1021
1022 #[test]
1023 fn test_longest_increasing_subsequence_all_new() {
1024 let positions = vec![None, None, None];
1026 let lis = longest_increasing_subsequence(&positions);
1027 assert_eq!(lis, Vec::<usize>::new());
1028 }
1029
1030 #[test]
1031 fn test_reconcile_keyed_children_basic() {
1032 let mut tree = InstanceTree::new();
1033 let mut dependencies = DependencyGraph::new();
1034 let state = json!({});
1035
1036 let parent = Element::new("Column");
1038 let parent_id = tree.create_node(&parent, &state);
1039
1040 let mut old_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1042 old_child_1.key = Some("item-1".to_string());
1043 let old_id_1 = tree.create_node(&old_child_1, &state);
1044
1045 let mut old_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1046 old_child_2.key = Some("item-2".to_string());
1047 let old_id_2 = tree.create_node(&old_child_2, &state);
1048
1049 tree.add_child(parent_id, old_id_1, None);
1050 tree.add_child(parent_id, old_id_2, None);
1051
1052 let mut new_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1054 new_child_2.key = Some("item-2".to_string());
1055
1056 let mut new_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1057 new_child_1.key = Some("item-1".to_string());
1058
1059 let new_children = vec![new_child_2, new_child_1];
1060 let old_children = vec![old_id_1, old_id_2];
1061
1062 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1063
1064 let move_count = patches.iter().filter(|p| matches!(p, Patch::Move { .. })).count();
1066 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1067 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1068
1069 assert_eq!(create_count, 0, "Should not create new children");
1070 assert_eq!(remove_count, 0, "Should not remove any children");
1071 assert!(move_count > 0, "Should have move patches for reordering");
1072 }
1073
1074 #[test]
1075 fn test_reconcile_keyed_children_add_remove() {
1076 let mut tree = InstanceTree::new();
1077 let mut dependencies = DependencyGraph::new();
1078 let state = json!({});
1079
1080 let parent = Element::new("Column");
1081 let parent_id = tree.create_node(&parent, &state);
1082
1083 let mut old_child_1 = Element::new("Text");
1085 old_child_1.key = Some("item-1".to_string());
1086 let old_id_1 = tree.create_node(&old_child_1, &state);
1087
1088 let mut old_child_2 = Element::new("Text");
1089 old_child_2.key = Some("item-2".to_string());
1090 let old_id_2 = tree.create_node(&old_child_2, &state);
1091
1092 tree.add_child(parent_id, old_id_1, None);
1093 tree.add_child(parent_id, old_id_2, None);
1094
1095 let mut new_child_2 = Element::new("Text");
1097 new_child_2.key = Some("item-2".to_string());
1098
1099 let mut new_child_3 = Element::new("Text");
1100 new_child_3.key = Some("item-3".to_string());
1101
1102 let new_children = vec![new_child_2, new_child_3];
1103 let old_children = vec![old_id_1, old_id_2];
1104
1105 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1106
1107 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1108 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1109
1110 assert_eq!(create_count, 1, "Should create item-3");
1111 assert_eq!(remove_count, 1, "Should remove item-1");
1112 }
1113}