1use super::resolve::{evaluate_binding, resolve_props};
2use super::{ControlFlowKind, InstanceTree, Patch};
3use crate::ir::{ConditionalBranch, Element, IRNode, NodeId, Props, Value};
4use crate::reactive::{evaluate_template_string, Binding, DependencyGraph};
5use indexmap::IndexMap;
6
7struct ReconcileCtx<'a> {
12 tree: &'a mut InstanceTree,
13 state: &'a serde_json::Value,
14 patches: &'a mut Vec<Patch>,
15 dependencies: &'a mut DependencyGraph,
16}
17
18pub fn reconcile(
20 tree: &mut InstanceTree,
21 element: &Element,
22 parent_id: Option<NodeId>,
23 state: &serde_json::Value,
24 dependencies: &mut DependencyGraph,
25) -> Vec<Patch> {
26 let mut patches = Vec::new();
27
28 if tree.root().is_none() {
30 let node_id = create_tree(
31 tree,
32 element,
33 parent_id,
34 state,
35 &mut patches,
36 true,
37 dependencies,
38 );
39 tree.set_root(node_id);
40 return patches;
41 }
42
43 if let Some(root_id) = tree.root() {
45 reconcile_node(tree, root_id, element, state, &mut patches, dependencies);
46 }
47
48 patches
49}
50
51pub fn reconcile_ir(
55 tree: &mut InstanceTree,
56 node: &IRNode,
57 parent_id: Option<NodeId>,
58 state: &serde_json::Value,
59 dependencies: &mut DependencyGraph,
60) -> Vec<Patch> {
61 let mut patches = Vec::new();
62
63 if tree.root().is_none() {
65 let node_id = create_ir_node_tree(
66 tree,
67 node,
68 parent_id,
69 state,
70 &mut patches,
71 true,
72 dependencies,
73 );
74 tree.set_root(node_id);
75 return patches;
76 }
77
78 if let Some(root_id) = tree.root() {
80 reconcile_ir_node(tree, root_id, node, state, &mut patches, dependencies);
81 }
82
83 patches
84}
85
86pub fn create_tree(
88 tree: &mut InstanceTree,
89 element: &Element,
90 parent_id: Option<NodeId>,
91 state: &serde_json::Value,
92 patches: &mut Vec<Patch>,
93 is_root: bool,
94 dependencies: &mut DependencyGraph,
95) -> NodeId {
96 if let Some(Value::Binding(_)) = element.props.get("0") {
104 if !element.children.is_empty() {
105 return create_list_tree(
106 tree,
107 element,
108 parent_id,
109 state,
110 patches,
111 is_root,
112 dependencies,
113 );
114 }
115 }
116
117 let node_id = tree.create_node(element, state);
119
120 for value in element.props.values() {
122 match value {
123 Value::Binding(binding) => {
124 dependencies.add_dependency(node_id, binding);
125 }
126 Value::TemplateString { bindings, .. } => {
127 for binding in bindings {
129 dependencies.add_dependency(node_id, binding);
130 }
131 }
132 _ => {}
133 }
134 }
135
136 let node = tree.get(node_id).unwrap();
138
139 let mut props = node.props.clone();
141 if let Some(Value::Static(val)) = element.props.get("__lazy") {
142 if val.as_bool().unwrap_or(false) && !element.children.is_empty() {
143 let child_component = &element.children[0].element_type;
145 props.insert(
146 "__lazy_child".to_string(),
147 serde_json::json!(child_component),
148 );
149 }
150 }
151
152 patches.push(Patch::create(node_id, node.element_type.clone(), props));
153
154 if let Some(parent) = parent_id {
158 tree.add_child(parent, node_id, None);
159 patches.push(Patch::insert(parent, node_id, None));
160 } else if is_root {
161 patches.push(Patch::insert_root(node_id));
163 }
164
165 let is_lazy = element
167 .props
168 .get("__lazy")
169 .and_then(|v| {
170 if let Value::Static(val) = v {
171 val.as_bool()
172 } else {
173 None
174 }
175 })
176 .unwrap_or(false);
177
178 if !is_lazy {
179 for child_element in &element.children {
180 create_tree(
181 tree,
182 child_element,
183 Some(node_id),
184 state,
185 patches,
186 false,
187 dependencies,
188 );
189 }
190 }
191
192 node_id
193}
194
195fn create_list_tree(
198 tree: &mut InstanceTree,
199 element: &Element,
200 parent_id: Option<NodeId>,
201 state: &serde_json::Value,
202 patches: &mut Vec<Patch>,
203 is_root: bool,
204 dependencies: &mut DependencyGraph,
205) -> NodeId {
206 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
208 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
209 } else {
210 serde_json::Value::Array(vec![])
211 };
212
213 let mut list_element = Element::new(&element.element_type);
216 for (key, value) in &element.props {
217 if key != "0" {
219 list_element.props.insert(key.clone(), value.clone());
220 }
221 }
222
223 let node_id = tree.create_node(&list_element, state);
224
225 if let Some(Value::Binding(binding)) = element.props.get("0") {
228 dependencies.add_dependency(node_id, binding);
229 }
230
231 if let Some(node) = tree.get_mut(node_id) {
234 node.raw_props = element.props.clone();
235 node.element_template = Some(std::sync::Arc::new(element.clone()));
236 }
237
238 let node = tree.get(node_id).unwrap();
240 patches.push(Patch::create(
241 node_id,
242 node.element_type.clone(),
243 node.props.clone(),
244 ));
245
246 if let Some(parent) = parent_id {
248 tree.add_child(parent, node_id, None);
249 patches.push(Patch::insert(parent, node_id, None));
250 } else if is_root {
251 patches.push(Patch::insert_root(node_id));
252 }
253
254 if let serde_json::Value::Array(items) = &array {
256 for (index, item) in items.iter().enumerate() {
258 for child_template in &element.children {
259 let child_with_item = replace_item_bindings(child_template, item, index);
261 create_tree(
262 tree,
263 &child_with_item,
264 Some(node_id),
265 state,
266 patches,
267 false,
268 dependencies,
269 );
270 }
271 }
272 }
273
274 node_id
275}
276
277fn navigate_item_path<'a>(
280 item: &'a serde_json::Value,
281 path: &str,
282) -> Option<&'a serde_json::Value> {
283 let mut current = item;
284
285 for segment in path.split('.') {
286 if let Ok(index) = segment.parse::<usize>() {
288 current = current.get(index)?;
289 } else {
290 current = current.get(segment)?;
292 }
293 }
294
295 Some(current)
296}
297
298#[derive(Debug)]
301struct Replacement {
302 start: usize,
303 end: usize,
304 text: String,
305}
306
307fn apply_replacements(s: &str, mut replacements: Vec<Replacement>) -> String {
310 if replacements.is_empty() {
311 return s.to_string();
312 }
313
314 replacements.sort_by(|a, b| a.start.cmp(&b.start));
316
317 let valid_replacements: Vec<_> = replacements
319 .into_iter()
320 .filter(|r| r.start <= s.len() && r.end <= s.len() && r.start <= r.end)
321 .collect();
322
323 if valid_replacements.is_empty() {
324 return s.to_string();
325 }
326
327 let removed: usize = valid_replacements.iter().map(|r| r.end - r.start).sum();
329 let added: usize = valid_replacements.iter().map(|r| r.text.len()).sum();
330 let capacity = s.len() - removed + added;
331
332 let mut result = String::with_capacity(capacity);
333 let mut pos = 0;
334
335 for r in valid_replacements {
336 if r.start > pos {
338 result.push_str(&s[pos..r.start]);
339 }
340 result.push_str(&r.text);
342 pos = r.end;
343 }
344
345 if pos < s.len() {
347 result.push_str(&s[pos..]);
348 }
349
350 result
351}
352
353fn find_path_end(s: &str, start: usize) -> usize {
355 let chars: Vec<char> = s[start..].chars().collect();
356 let mut end = start;
357
358 for (i, ch) in chars.iter().enumerate() {
359 if ch.is_alphanumeric() || *ch == '_' {
360 end = start + i + 1;
361 } else if *ch == '.' {
362 if i + 1 < chars.len() {
364 let next = chars[i + 1];
365 if next.is_alphanumeric() || next == '_' {
366 end = start + i + 1;
367 continue;
368 }
369 }
370 break;
371 } else {
372 break;
373 }
374 }
375
376 end
377}
378
379fn format_value_for_replacement(val: &serde_json::Value, quote_strings: bool) -> String {
381 match val {
382 serde_json::Value::String(s) => {
383 if quote_strings {
384 format!("'{}'", s)
385 } else {
386 s.clone()
387 }
388 }
389 serde_json::Value::Number(n) => n.to_string(),
390 serde_json::Value::Bool(b) => b.to_string(),
391 serde_json::Value::Null => "null".to_string(),
392 _ => serde_json::to_string(val).unwrap_or_default(),
393 }
394}
395
396pub fn replace_item_bindings(element: &Element, item: &serde_json::Value, index: usize) -> Element {
400 replace_item_bindings_with_name(element, item, index, "item")
401}
402
403pub fn reconcile_node(
405 tree: &mut InstanceTree,
406 node_id: NodeId,
407 element: &Element,
408 state: &serde_json::Value,
409 patches: &mut Vec<Patch>,
410 dependencies: &mut DependencyGraph,
411) {
412 let node = tree.get(node_id).cloned();
413 if node.is_none() {
414 return;
415 }
416 let node = node.unwrap();
417
418 let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
422
423 if is_iterable {
424 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
426 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
427 } else {
428 serde_json::Value::Array(vec![])
429 };
430
431 if let serde_json::Value::Array(items) = &array {
433 let old_children = node.children.clone();
434
435 let expected_children_count = items.len() * element.children.len();
437
438 if old_children.len() != expected_children_count {
440 for &old_child_id in &old_children {
441 patches.push(Patch::remove(old_child_id));
442 }
443
444 if let Some(node) = tree.get_mut(node_id) {
446 node.children.clear();
447 }
448
449 for (index, item) in items.iter().enumerate() {
451 for child_template in &element.children {
452 let child_with_item = replace_item_bindings(child_template, item, index);
453 create_tree(
454 tree,
455 &child_with_item,
456 Some(node_id),
457 state,
458 patches,
459 false,
460 dependencies,
461 );
462 }
463 }
464 } else {
465 let mut child_index = 0;
467 for (item_index, item) in items.iter().enumerate() {
468 for child_template in &element.children {
469 if let Some(&old_child_id) = old_children.get(child_index) {
470 let child_with_item =
471 replace_item_bindings(child_template, item, item_index);
472 reconcile_node(
473 tree,
474 old_child_id,
475 &child_with_item,
476 state,
477 patches,
478 dependencies,
479 );
480 }
481 child_index += 1;
482 }
483 }
484 }
485 }
486
487 return; }
489
490 if node.element_type != element.element_type {
492 replace_subtree(tree, node_id, &node, element, state, patches, dependencies);
493 return;
494 }
495
496 for value in element.props.values() {
498 match value {
499 Value::Binding(binding) => {
500 dependencies.add_dependency(node_id, binding);
501 }
502 Value::TemplateString { bindings, .. } => {
503 for binding in bindings {
504 dependencies.add_dependency(node_id, binding);
505 }
506 }
507 _ => {}
508 }
509 }
510
511 let new_props = resolve_props(&element.props, state);
513 let prop_patches = diff_props(node_id, &node.props, &new_props);
514 patches.extend(prop_patches);
515
516 if let Some(node) = tree.get_mut(node_id) {
518 node.props = new_props.clone();
519 node.raw_props = element.props.clone();
520 }
521
522 let is_lazy = element
524 .props
525 .get("__lazy")
526 .and_then(|v| {
527 if let Value::Static(val) = v {
528 val.as_bool()
529 } else {
530 None
531 }
532 })
533 .unwrap_or(false);
534
535 if !is_lazy {
536 let old_children = node.children.clone();
537 let new_children = &element.children;
538
539 for (i, new_child_element) in new_children.iter().enumerate() {
541 if let Some(&old_child_id) = old_children.get(i) {
542 reconcile_node(
544 tree,
545 old_child_id,
546 new_child_element,
547 state,
548 patches,
549 dependencies,
550 );
551 } else {
552 let new_child_id = create_tree(
554 tree,
555 new_child_element,
556 Some(node_id),
557 state,
558 patches,
559 false,
560 dependencies,
561 );
562 if let Some(node) = tree.get_mut(node_id) {
563 node.children.push_back(new_child_id);
564 }
565 }
566 }
567
568 if old_children.len() > new_children.len() {
570 for old_child_id in old_children.iter().skip(new_children.len()).copied() {
571 let subtree_ids = collect_subtree_ids(tree, old_child_id);
573 for &id in &subtree_ids {
574 patches.push(Patch::remove(id));
575 dependencies.remove_node(id);
576 }
577 tree.remove_child(node_id, old_child_id);
579 tree.remove(old_child_id);
580 }
581 }
582 }
583}
584
585fn replace_subtree(
588 tree: &mut InstanceTree,
589 old_node_id: NodeId,
590 old_node: &super::InstanceNode,
591 new_element: &Element,
592 state: &serde_json::Value,
593 patches: &mut Vec<Patch>,
594 dependencies: &mut DependencyGraph,
595) {
596 let parent_id = old_node.parent;
597
598 let old_position = if let Some(pid) = parent_id {
600 tree.get(pid)
601 .and_then(|parent| parent.children.iter().position(|&id| id == old_node_id))
602 } else {
603 None
604 };
605
606 let ids_to_remove = collect_subtree_ids(tree, old_node_id);
608
609 for &id in &ids_to_remove {
611 patches.push(Patch::remove(id));
612 dependencies.remove_node(id);
613 }
614
615 if let Some(pid) = parent_id {
617 if let Some(parent) = tree.get_mut(pid) {
618 parent.children = parent
619 .children
620 .iter()
621 .filter(|&&id| id != old_node_id)
622 .copied()
623 .collect();
624 }
625 }
626
627 tree.remove(old_node_id);
629
630 let is_root = parent_id.is_none();
632 let new_node_id = create_tree(
633 tree,
634 new_element,
635 parent_id,
636 state,
637 patches,
638 is_root,
639 dependencies,
640 );
641
642 if is_root {
644 tree.set_root(new_node_id);
645 } else if let Some(pid) = parent_id {
646 if let Some(pos) = old_position {
647 if let Some(parent) = tree.get_mut(pid) {
648 let current_len = parent.children.len();
649 if pos < current_len - 1 {
652 let new_id = parent.children.pop_back().unwrap();
654 parent.children.insert(pos, new_id);
655
656 let next_sibling = parent.children.get(pos + 1).copied();
658 patches.push(Patch::move_node(pid, new_node_id, next_sibling));
659 }
660 }
662 }
663 }
664}
665
666fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
669 let mut result = Vec::new();
670 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
671
672 while let Some((node_id, children_processed)) = stack.pop() {
673 if children_processed {
674 result.push(node_id);
676 } else {
677 stack.push((node_id, true));
679 if let Some(node) = tree.get(node_id) {
680 for &child_id in node.children.iter().rev() {
682 stack.push((child_id, false));
683 }
684 }
685 }
686 }
687
688 result
689}
690
691fn generate_item_key(
694 item: &serde_json::Value,
695 key_path: Option<&str>,
696 item_name: &str,
697 index: usize,
698) -> String {
699 if let Some(kp) = key_path {
700 item.get(kp)
701 .map(|v| match v {
702 serde_json::Value::String(s) => s.clone(),
703 serde_json::Value::Number(n) => n.to_string(),
704 _ => format!("{}-{}", item_name, index),
705 })
706 .unwrap_or_else(|| format!("{}-{}", item_name, index))
707 } else {
708 format!("{}-{}", item_name, index)
709 }
710}
711
712pub fn reconcile_keyed_children(
715 tree: &mut InstanceTree,
716 parent_id: NodeId,
717 old_children: &[NodeId],
718 new_children: &[Element],
719 state: &serde_json::Value,
720 dependencies: &mut DependencyGraph,
721) -> Vec<Patch> {
722 let mut patches = Vec::new();
723
724 let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
726 let mut old_unkeyed: Vec<NodeId> = Vec::new();
727
728 for &child_id in old_children {
729 if let Some(node) = tree.get(child_id) {
730 if let Some(key) = &node.key {
731 old_keyed.insert(key.clone(), child_id);
732 } else {
733 old_unkeyed.push(child_id);
734 }
735 }
736 }
737
738 let mut new_child_ids: Vec<NodeId> = Vec::new();
739 let mut unkeyed_idx = 0;
740
741 for new_element in new_children {
743 let new_key = new_element.key.as_ref();
744
745 let child_id = if let Some(key) = new_key {
746 if let Some(&old_id) = old_keyed.get(key) {
748 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
750 old_keyed.shift_remove(key); old_id
752 } else {
753 create_tree(
755 tree,
756 new_element,
757 Some(parent_id),
758 state,
759 &mut patches,
760 false,
761 dependencies,
762 )
763 }
764 } else {
765 if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
767 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
768 unkeyed_idx += 1;
769 old_id
770 } else {
771 create_tree(
773 tree,
774 new_element,
775 Some(parent_id),
776 state,
777 &mut patches,
778 false,
779 dependencies,
780 )
781 }
782 };
783
784 new_child_ids.push(child_id);
785 }
786
787 let mut old_positions: Vec<Option<usize>> = Vec::new();
790 for &new_id in &new_child_ids {
791 let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
792 old_positions.push(old_pos);
793 }
794
795 let lis = longest_increasing_subsequence(&old_positions);
797
798 if let Some(parent_node) = tree.get_mut(parent_id) {
800 parent_node.children = new_child_ids.iter().copied().collect();
801 }
802
803 for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
805 if !lis.contains(&new_idx) {
807 let before_id = new_child_ids.get(new_idx + 1).copied();
809 patches.push(Patch::move_node(parent_id, child_id, before_id));
810 }
811 }
812
813 for &old_id in old_keyed.values() {
816 dependencies.remove_node(old_id);
817 tree.remove(old_id);
818 patches.push(Patch::remove(old_id));
819 }
820 for &old_id in &old_unkeyed[unkeyed_idx..] {
821 dependencies.remove_node(old_id);
822 tree.remove(old_id);
823 patches.push(Patch::remove(old_id));
824 }
825
826 patches
827}
828
829fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
834 let valid: Vec<(usize, usize)> = positions
836 .iter()
837 .enumerate()
838 .filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
839 .collect();
840
841 if valid.is_empty() {
842 return Vec::new();
843 }
844
845 let n = valid.len();
846 let mut tails: Vec<usize> = Vec::with_capacity(n);
849 let mut prev: Vec<Option<usize>> = vec![None; n];
851
852 for i in 0..n {
853 let val = valid[i].1;
854 let pos = tails.partition_point(|&t| valid[t].1 < val);
856
857 if pos == tails.len() {
858 tails.push(i);
859 } else {
860 tails[pos] = i;
861 }
862
863 if pos > 0 {
864 prev[i] = Some(tails[pos - 1]);
865 }
866 }
867
868 let mut result = Vec::with_capacity(tails.len());
870 let mut idx = Some(*tails.last().unwrap());
871 while let Some(i) = idx {
872 result.push(valid[i].0);
873 idx = prev[i];
874 }
875 result.reverse();
876
877 result
878}
879
880pub fn diff_props(
882 node_id: NodeId,
883 old_props: &IndexMap<String, serde_json::Value>,
884 new_props: &IndexMap<String, serde_json::Value>,
885) -> Vec<Patch> {
886 let mut patches = Vec::new();
887
888 for (key, new_value) in new_props {
890 if old_props.get(key) != Some(new_value) {
891 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
892 }
893 }
894
895 for key in old_props.keys() {
897 if !new_props.contains_key(key) {
898 patches.push(Patch::remove_prop(node_id, key.clone()));
899 }
900 }
901
902 patches
903}
904
905pub fn create_ir_node_tree(
911 tree: &mut InstanceTree,
912 node: &IRNode,
913 parent_id: Option<NodeId>,
914 state: &serde_json::Value,
915 patches: &mut Vec<Patch>,
916 is_root: bool,
917 dependencies: &mut DependencyGraph,
918) -> NodeId {
919 match node {
920 IRNode::Element(element) => {
921 create_tree(
923 tree,
924 element,
925 parent_id,
926 state,
927 patches,
928 is_root,
929 dependencies,
930 )
931 }
932 IRNode::ForEach {
933 source,
934 item_name,
935 key_path,
936 template,
937 props,
938 } => {
939 let mut ctx = ReconcileCtx {
940 tree,
941 state,
942 patches,
943 dependencies,
944 };
945 create_foreach_ir_tree(
946 &mut ctx,
947 source,
948 item_name,
949 key_path.as_deref(),
950 template,
951 props,
952 node,
953 parent_id,
954 is_root,
955 )
956 }
957 IRNode::Conditional {
958 value,
959 branches,
960 fallback,
961 } => {
962 let mut ctx = ReconcileCtx {
963 tree,
964 state,
965 patches,
966 dependencies,
967 };
968 create_conditional_tree(
969 &mut ctx,
970 value,
971 branches,
972 fallback.as_deref(),
973 node,
974 parent_id,
975 is_root,
976 )
977 }
978 }
979}
980
981fn create_ir_node_tree_with_render_parent(
990 ctx: &mut ReconcileCtx,
991 node: &IRNode,
992 logical_parent: Option<NodeId>,
993 render_parent: Option<NodeId>,
994 is_root: bool,
995) -> NodeId {
996 match node {
997 IRNode::Element(element) => {
998 let node_id = ctx.tree.create_node(element, ctx.state);
1000
1001 if let Some(parent) = logical_parent {
1003 ctx.tree.add_child(parent, node_id, None);
1004 }
1005
1006 ctx.patches.push(Patch::create(
1008 node_id,
1009 element.element_type.clone(),
1010 ctx.tree
1011 .get(node_id)
1012 .map(|n| n.props.clone())
1013 .unwrap_or_default(),
1014 ));
1015
1016 if let Some(render_p) = render_parent {
1017 ctx.patches.push(Patch::insert(render_p, node_id, None));
1018 } else if is_root {
1019 ctx.patches.push(Patch::insert_root(node_id));
1020 }
1021
1022 for (_, value) in &element.props {
1024 match value {
1025 Value::Binding(binding) => {
1026 ctx.dependencies.add_dependency(node_id, binding);
1027 }
1028 Value::TemplateString { bindings, .. } => {
1029 for binding in bindings {
1030 ctx.dependencies.add_dependency(node_id, binding);
1031 }
1032 }
1033 _ => {}
1034 }
1035 }
1036
1037 for child in &element.children {
1039 let child_ir = IRNode::Element((**child).clone());
1040 create_ir_node_tree(
1041 ctx.tree,
1042 &child_ir,
1043 Some(node_id),
1044 ctx.state,
1045 ctx.patches,
1046 false,
1047 ctx.dependencies,
1048 );
1049 }
1050
1051 node_id
1052 }
1053 IRNode::ForEach {
1054 source,
1055 item_name,
1056 key_path,
1057 template,
1058 props,
1059 } => {
1060 create_foreach_ir_tree(
1062 ctx,
1063 source,
1064 item_name,
1065 key_path.as_deref(),
1066 template,
1067 props,
1068 node,
1069 logical_parent, is_root,
1071 )
1072 }
1073 IRNode::Conditional {
1074 value,
1075 branches,
1076 fallback,
1077 } => {
1078 create_conditional_tree(
1080 ctx,
1081 value,
1082 branches,
1083 fallback.as_deref(),
1084 node,
1085 logical_parent, is_root,
1087 )
1088 }
1089 }
1090}
1091
1092#[allow(clippy::too_many_arguments)]
1094fn create_foreach_ir_tree(
1095 ctx: &mut ReconcileCtx,
1096 source: &Binding,
1097 item_name: &str,
1098 key_path: Option<&str>,
1099 template: &[IRNode],
1100 props: &Props,
1101 original_node: &IRNode,
1102 parent_id: Option<NodeId>,
1103 is_root: bool,
1104) -> NodeId {
1105 let array = evaluate_binding(source, ctx.state).unwrap_or(serde_json::Value::Array(vec![]));
1107
1108 let resolved_props = resolve_props(props, ctx.state);
1110
1111 let node_id = ctx.tree.create_control_flow_node(
1113 "__ForEach",
1114 resolved_props.clone(),
1115 props.clone(),
1116 ControlFlowKind::ForEach {
1117 item_name: item_name.to_string(),
1118 key_path: key_path.map(|s| s.to_string()),
1119 },
1120 original_node.clone(),
1121 );
1122
1123 ctx.dependencies.add_dependency(node_id, source);
1125
1126 if let Some(parent) = parent_id {
1129 ctx.tree.add_child(parent, node_id, None);
1130 }
1131
1132 let render_parent = parent_id; if let serde_json::Value::Array(items) = &array {
1137 for (index, item) in items.iter().enumerate() {
1138 let item_key = generate_item_key(item, key_path, item_name, index);
1139
1140 for child_template in template {
1142 let child_with_item = replace_ir_node_item_bindings(
1143 child_template,
1144 item,
1145 index,
1146 item_name,
1147 &item_key,
1148 );
1149 create_ir_node_tree_with_render_parent(
1151 ctx,
1152 &child_with_item,
1153 Some(node_id), render_parent, is_root && render_parent.is_none(),
1156 );
1157 }
1158 }
1159 }
1160
1161 node_id
1162}
1163
1164fn create_conditional_tree(
1166 ctx: &mut ReconcileCtx,
1167 value: &Value,
1168 branches: &[ConditionalBranch],
1169 fallback: Option<&[IRNode]>,
1170 original_node: &IRNode,
1171 parent_id: Option<NodeId>,
1172 is_root: bool,
1173) -> NodeId {
1174 let evaluated_value = evaluate_value(value, ctx.state);
1176
1177 let mut raw_props = Props::new();
1179 raw_props.insert("__condition".to_string(), value.clone());
1180
1181 let node_id = ctx.tree.create_control_flow_node(
1182 "__Conditional",
1183 IndexMap::new(),
1184 raw_props,
1185 ControlFlowKind::Conditional,
1186 original_node.clone(),
1187 );
1188
1189 if let Value::Binding(binding) = value {
1191 ctx.dependencies.add_dependency(node_id, binding);
1192 } else if let Value::TemplateString { bindings, .. } = value {
1193 for binding in bindings {
1194 ctx.dependencies.add_dependency(node_id, binding);
1195 }
1196 }
1197
1198 if let Some(parent) = parent_id {
1201 ctx.tree.add_child(parent, node_id, None);
1202 }
1203
1204 let matched_children = find_matching_branch(&evaluated_value, branches, fallback, ctx.state);
1206
1207 let render_parent = parent_id; if let Some(children) = matched_children {
1211 for child in children {
1212 create_ir_node_tree_with_render_parent(
1214 ctx,
1215 child,
1216 Some(node_id), render_parent, is_root && render_parent.is_none(),
1219 );
1220 }
1221 }
1222
1223 node_id
1224}
1225
1226fn find_matching_branch<'a>(
1228 evaluated_value: &serde_json::Value,
1229 branches: &'a [ConditionalBranch],
1230 fallback: Option<&'a [IRNode]>,
1231 state: &serde_json::Value,
1232) -> Option<&'a [IRNode]> {
1233 for branch in branches {
1234 if pattern_matches(evaluated_value, &branch.pattern, state) {
1235 return Some(&branch.children);
1236 }
1237 }
1238
1239 fallback
1240}
1241
1242fn pattern_matches(
1248 evaluated_value: &serde_json::Value,
1249 pattern: &Value,
1250 state: &serde_json::Value,
1251) -> bool {
1252 match pattern {
1253 Value::Static(pattern_value) => values_match(evaluated_value, pattern_value),
1254 Value::Binding(binding) => {
1255 let resolved = evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null);
1258 is_truthy(&resolved)
1259 }
1260 Value::TemplateString { template, .. } => {
1261 evaluate_expression_pattern(template, evaluated_value)
1264 }
1265 Value::Action(_) => false, }
1267}
1268
1269fn evaluate_expression_pattern(template: &str, value: &serde_json::Value) -> bool {
1272 use crate::reactive::evaluate_expression;
1273 use std::collections::HashMap;
1274
1275 let mut context: HashMap<String, serde_json::Value> = HashMap::new();
1277 context.insert("value".to_string(), value.clone());
1278
1279 let expr = if template.starts_with("${") && template.ends_with("}") {
1281 &template[2..template.len() - 1]
1282 } else if template.contains("${") {
1283 let json_context = serde_json::json!({ "value": value });
1286 match evaluate_template_string(template, &json_context, None) {
1287 Ok(result) => {
1288 return is_truthy_string(&result);
1290 }
1291 Err(_) => return false,
1292 }
1293 } else {
1294 template
1296 };
1297
1298 match evaluate_expression(expr, &context) {
1300 Ok(result) => is_truthy(&result),
1301 Err(_) => false,
1302 }
1303}
1304
1305fn is_truthy_string(s: &str) -> bool {
1307 !s.is_empty() && s != "false" && s != "0" && s != "null" && s != "undefined"
1308}
1309
1310fn is_truthy(value: &serde_json::Value) -> bool {
1312 match value {
1313 serde_json::Value::Null => false,
1314 serde_json::Value::Bool(b) => *b,
1315 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
1316 serde_json::Value::String(s) => is_truthy_string(s),
1317 serde_json::Value::Array(arr) => !arr.is_empty(),
1318 serde_json::Value::Object(obj) => !obj.is_empty(),
1319 }
1320}
1321
1322fn values_match(value: &serde_json::Value, pattern: &serde_json::Value) -> bool {
1329 match pattern {
1330 serde_json::Value::Array(patterns) => patterns.iter().any(|p| values_match(value, p)),
1332
1333 serde_json::Value::String(pattern_str) => {
1335 if pattern_str == "_" || pattern_str == "*" {
1337 return true;
1338 }
1339
1340 if pattern_str.contains("${") {
1342 return evaluate_expression_pattern(pattern_str, value);
1343 }
1344
1345 matches!(value, serde_json::Value::String(s) if s == pattern_str)
1347 }
1348
1349 _ => {
1351 if value == pattern {
1352 return true;
1353 }
1354
1355 match (value, pattern) {
1357 (serde_json::Value::Bool(b), serde_json::Value::Bool(p)) => b == p,
1358 (serde_json::Value::Null, serde_json::Value::Null) => true,
1359 _ => false,
1360 }
1361 }
1362 }
1363}
1364
1365fn evaluate_value(value: &Value, state: &serde_json::Value) -> serde_json::Value {
1367 match value {
1368 Value::Static(v) => v.clone(),
1369 Value::Binding(binding) => {
1370 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
1371 }
1372 Value::TemplateString { template, .. } => {
1373 match evaluate_template_string(template, state, None) {
1374 Ok(result) => serde_json::Value::String(result),
1375 Err(_) => serde_json::Value::String(template.clone()),
1376 }
1377 }
1378 Value::Action(action) => serde_json::Value::String(format!("@{}", action)),
1379 }
1380}
1381
1382fn replace_ir_node_item_bindings(
1385 node: &IRNode,
1386 item: &serde_json::Value,
1387 index: usize,
1388 item_name: &str,
1389 item_key: &str,
1390) -> IRNode {
1391 match node {
1392 IRNode::Element(element) => {
1393 let mut new_element = replace_item_bindings_with_name(element, item, index, item_name);
1394 new_element.key = Some(item_key.to_string());
1396 IRNode::Element(new_element)
1397 }
1398 IRNode::ForEach {
1399 source,
1400 item_name: inner_item_name,
1401 key_path,
1402 template,
1403 props,
1404 } => {
1405 let new_props = replace_props_item_bindings(props, item, item_name);
1408
1409 let new_template: Vec<IRNode> = template
1411 .iter()
1412 .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1413 .collect();
1414
1415 IRNode::ForEach {
1416 source: source.clone(),
1417 item_name: inner_item_name.clone(),
1418 key_path: key_path.clone(),
1419 template: new_template,
1420 props: new_props,
1421 }
1422 }
1423 IRNode::Conditional {
1424 value,
1425 branches,
1426 fallback,
1427 } => {
1428 let new_value = replace_value_item_bindings(value, item, item_name);
1430
1431 let new_branches: Vec<ConditionalBranch> = branches
1433 .iter()
1434 .map(|branch| {
1435 let new_pattern = replace_value_item_bindings(&branch.pattern, item, item_name);
1436 let new_children: Vec<IRNode> = branch
1437 .children
1438 .iter()
1439 .map(|child| {
1440 replace_ir_node_item_bindings(child, item, index, item_name, item_key)
1441 })
1442 .collect();
1443 ConditionalBranch::new(new_pattern, new_children)
1444 })
1445 .collect();
1446
1447 let new_fallback = fallback.as_ref().map(|f| {
1449 f.iter()
1450 .map(|child| {
1451 replace_ir_node_item_bindings(child, item, index, item_name, item_key)
1452 })
1453 .collect()
1454 });
1455
1456 IRNode::Conditional {
1457 value: new_value,
1458 branches: new_branches,
1459 fallback: new_fallback,
1460 }
1461 }
1462 }
1463}
1464
1465fn replace_props_item_bindings(props: &Props, item: &serde_json::Value, item_name: &str) -> Props {
1467 let mut new_props = Props::new();
1468 for (key, value) in props {
1469 new_props.insert(
1470 key.clone(),
1471 replace_value_item_bindings(value, item, item_name),
1472 );
1473 }
1474 new_props
1475}
1476
1477fn replace_value_item_bindings(value: &Value, item: &serde_json::Value, item_name: &str) -> Value {
1481 match value {
1482 Value::Binding(binding) => {
1483 if binding.is_item() {
1484 if binding.path.is_empty() {
1485 Value::Static(item.clone())
1486 } else {
1487 let path = binding.full_path();
1488 if let Some(val) = navigate_item_path(item, &path) {
1489 Value::Static(val.clone())
1490 } else {
1491 value.clone()
1492 }
1493 }
1494 } else {
1495 value.clone()
1496 }
1497 }
1498 Value::TemplateString { template, bindings } => {
1499 let has_item_bindings = bindings.iter().any(|b| b.is_item());
1500 if !has_item_bindings {
1501 return value.clone();
1502 }
1503
1504 let mut replacements = Vec::new();
1506 for binding in bindings {
1507 if binding.is_item() {
1508 let pattern = format!("${{{}}}", binding.full_path_with_source());
1509 if let Some(start) = template.find(&pattern) {
1510 let replacement = if binding.path.is_empty() {
1511 format_value_for_replacement(item, false)
1512 } else if let Some(val) = navigate_item_path(item, &binding.full_path()) {
1513 format_value_for_replacement(val, false)
1514 } else {
1515 continue;
1516 };
1517 replacements.push(Replacement {
1518 start,
1519 end: start + pattern.len(),
1520 text: replacement,
1521 });
1522 }
1523 }
1524 }
1525
1526 let mut result = apply_replacements(template, replacements);
1528
1529 let expr_replacements =
1532 collect_item_replacements_with_name(&result, item, true, item_name);
1533 result = apply_replacements(&result, expr_replacements);
1534
1535 let remaining_bindings: Vec<_> =
1537 bindings.iter().filter(|b| b.is_state()).cloned().collect();
1538
1539 if remaining_bindings.is_empty() {
1540 if result.contains("${") {
1541 match evaluate_template_string(&result, &serde_json::Value::Null, None) {
1542 Ok(evaluated) => Value::Static(serde_json::Value::String(evaluated)),
1543 Err(_) => Value::Static(serde_json::Value::String(result)),
1544 }
1545 } else {
1546 Value::Static(serde_json::Value::String(result))
1547 }
1548 } else {
1549 Value::TemplateString {
1550 template: result,
1551 bindings: remaining_bindings,
1552 }
1553 }
1554 }
1555 Value::Static(serde_json::Value::String(s))
1557 if s.contains(&format!("${{{}.", item_name))
1558 || s.contains(&format!("${{{}}}", item_name)) =>
1559 {
1560 replace_static_item_bindings_with_name(s, item, item_name)
1561 }
1562 _ => value.clone(),
1563 }
1564}
1565
1566fn collect_item_replacements_with_name(
1568 s: &str,
1569 item: &serde_json::Value,
1570 quote_strings: bool,
1571 item_name: &str,
1572) -> Vec<Replacement> {
1573 let mut replacements = Vec::new();
1574 let mut pos = 0;
1575
1576 while pos < s.len() {
1577 if let Some(rel_start) = s[pos..].find(item_name) {
1578 let abs_start = pos + rel_start;
1579 let after_item = abs_start + item_name.len();
1580
1581 if after_item < s.len() && s.as_bytes()[after_item] == b'.' {
1582 let path_start = after_item + 1;
1583 let path_end = find_path_end(s, path_start);
1584
1585 if path_end > path_start {
1586 let path = &s[path_start..path_end];
1587 if let Some(val) = navigate_item_path(item, path) {
1588 let replacement = format_value_for_replacement(val, quote_strings);
1589 replacements.push(Replacement {
1590 start: abs_start,
1591 end: path_end,
1592 text: replacement,
1593 });
1594 }
1595 }
1596 pos = path_end.max(after_item + 1);
1597 } else {
1598 pos = after_item;
1599 }
1600 } else {
1601 break;
1602 }
1603 }
1604
1605 replacements
1606}
1607
1608fn replace_static_item_bindings_with_name(
1610 s: &str,
1611 item: &serde_json::Value,
1612 item_name: &str,
1613) -> Value {
1614 let mut replacements = Vec::new();
1615 let mut pos = 0;
1616
1617 while let Some(start) = s[pos..].find("${") {
1618 let abs_start = pos + start;
1619
1620 if let Some(end) = s[abs_start..].find('}') {
1621 let abs_end = abs_start + end;
1622 let content = &s[abs_start + 2..abs_end];
1623
1624 if content == item_name {
1625 replacements.push(Replacement {
1627 start: abs_start,
1628 end: abs_end + 1,
1629 text: format_value_for_replacement(item, false),
1630 });
1631 pos = abs_end + 1;
1632 } else if content.starts_with(&format!("{}.", item_name))
1633 && is_simple_path_with_name(content, item_name)
1634 {
1635 let path = &content[item_name.len() + 1..];
1637 if let Some(val) = navigate_item_path(item, path) {
1638 replacements.push(Replacement {
1639 start: abs_start,
1640 end: abs_end + 1,
1641 text: format_value_for_replacement(val, false),
1642 });
1643 }
1644 pos = abs_end + 1;
1645 } else if content.contains(&format!("{}.", item_name))
1646 || content.contains(&format!("{} ", item_name))
1647 {
1648 let expr_replacements =
1650 collect_item_replacements_with_name(content, item, true, item_name);
1651 let substituted_content = apply_replacements(content, expr_replacements);
1652 let new_expr = format!("${{{}}}", substituted_content);
1653
1654 if let Ok(evaluated) =
1655 evaluate_template_string(&new_expr, &serde_json::Value::Null, None)
1656 {
1657 replacements.push(Replacement {
1658 start: abs_start,
1659 end: abs_end + 1,
1660 text: evaluated,
1661 });
1662 }
1663 pos = abs_end + 1;
1664 } else {
1665 pos = abs_end + 1;
1666 }
1667 } else {
1668 break;
1669 }
1670 }
1671
1672 let result = apply_replacements(s, replacements);
1673 Value::Static(serde_json::Value::String(result))
1674}
1675
1676fn is_simple_path_with_name(s: &str, item_name: &str) -> bool {
1678 if !s.starts_with(item_name) {
1679 return false;
1680 }
1681
1682 let after_item = &s[item_name.len()..];
1683 if after_item.is_empty() {
1684 return true;
1685 }
1686
1687 if !after_item.starts_with('.') {
1688 return false;
1689 }
1690
1691 after_item[1..]
1692 .chars()
1693 .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
1694}
1695
1696pub fn replace_item_bindings_with_name(
1699 element: &Element,
1700 item: &serde_json::Value,
1701 index: usize,
1702 item_name: &str,
1703) -> Element {
1704 let mut new_element = element.clone();
1705
1706 let mut new_props = Props::new();
1708 for (key, value) in &element.props {
1709 new_props.insert(
1710 key.clone(),
1711 replace_value_item_bindings(value, item, item_name),
1712 );
1713 }
1714 new_element.props = new_props;
1715
1716 let key = item
1718 .get("id")
1719 .and_then(|v| {
1720 v.as_str()
1721 .map(|s| s.to_string())
1722 .or_else(|| v.as_i64().map(|n| n.to_string()))
1723 })
1724 .or_else(|| {
1725 item.get("key")
1726 .and_then(|v| v.as_str().map(|s| s.to_string()))
1727 })
1728 .map(|id| format!("{}-{}", item_name, id))
1729 .unwrap_or_else(|| format!("{}-{}", item_name, index));
1730 new_element.key = Some(key);
1731
1732 new_element.children = element
1734 .children
1735 .iter()
1736 .map(|child| {
1737 std::sync::Arc::new(replace_item_bindings_with_name(
1738 child, item, index, item_name,
1739 ))
1740 })
1741 .collect();
1742
1743 new_element
1744}
1745
1746pub fn reconcile_ir_node(
1748 tree: &mut InstanceTree,
1749 node_id: NodeId,
1750 node: &IRNode,
1751 state: &serde_json::Value,
1752 patches: &mut Vec<Patch>,
1753 dependencies: &mut DependencyGraph,
1754) {
1755 let existing_node = tree.get(node_id).cloned();
1756 if existing_node.is_none() {
1757 return;
1758 }
1759 let existing = existing_node.unwrap();
1760
1761 match node {
1762 IRNode::Element(element) => {
1763 reconcile_node(tree, node_id, element, state, patches, dependencies);
1765 }
1766 IRNode::ForEach {
1767 source,
1768 item_name,
1769 key_path,
1770 template,
1771 props: _,
1772 } => {
1773 if !existing.is_foreach() {
1775 let parent_id = existing.parent;
1777 remove_subtree(tree, node_id, patches, dependencies);
1778 create_ir_node_tree(
1779 tree,
1780 node,
1781 parent_id,
1782 state,
1783 patches,
1784 parent_id.is_none(),
1785 dependencies,
1786 );
1787 return;
1788 }
1789
1790 dependencies.add_dependency(node_id, source);
1792
1793 let array = evaluate_binding(source, state).unwrap_or(serde_json::Value::Array(vec![]));
1795
1796 if let serde_json::Value::Array(items) = &array {
1797 let old_children = existing.children.clone();
1798 let expected_children_count = items.len() * template.len();
1799
1800 if old_children.len() != expected_children_count {
1802 for &old_child_id in &old_children {
1804 patches.push(Patch::remove(old_child_id));
1805 }
1806
1807 if let Some(node) = tree.get_mut(node_id) {
1808 node.children.clear();
1809 }
1810
1811 for (index, item) in items.iter().enumerate() {
1813 let item_key =
1814 generate_item_key(item, key_path.as_deref(), item_name, index);
1815
1816 for child_template in template {
1817 let child_with_item = replace_ir_node_item_bindings(
1818 child_template,
1819 item,
1820 index,
1821 item_name,
1822 &item_key,
1823 );
1824 create_ir_node_tree(
1825 tree,
1826 &child_with_item,
1827 Some(node_id),
1828 state,
1829 patches,
1830 false,
1831 dependencies,
1832 );
1833 }
1834 }
1835 } else {
1836 let mut child_index = 0;
1838 for (item_index, item) in items.iter().enumerate() {
1839 let item_key =
1840 generate_item_key(item, key_path.as_deref(), item_name, item_index);
1841
1842 for child_template in template {
1843 if let Some(&old_child_id) = old_children.get(child_index) {
1844 let child_with_item = replace_ir_node_item_bindings(
1845 child_template,
1846 item,
1847 item_index,
1848 item_name,
1849 &item_key,
1850 );
1851 reconcile_ir_node(
1852 tree,
1853 old_child_id,
1854 &child_with_item,
1855 state,
1856 patches,
1857 dependencies,
1858 );
1859 }
1860 child_index += 1;
1861 }
1862 }
1863 }
1864 }
1865 }
1866 IRNode::Conditional {
1867 value,
1868 branches,
1869 fallback,
1870 } => {
1871 if !existing.is_conditional() {
1873 let parent_id = existing.parent;
1875 remove_subtree(tree, node_id, patches, dependencies);
1876 create_ir_node_tree(
1877 tree,
1878 node,
1879 parent_id,
1880 state,
1881 patches,
1882 parent_id.is_none(),
1883 dependencies,
1884 );
1885 return;
1886 }
1887
1888 if let Value::Binding(binding) = value {
1890 dependencies.add_dependency(node_id, binding);
1891 } else if let Value::TemplateString { bindings, .. } = value {
1892 for binding in bindings {
1893 dependencies.add_dependency(node_id, binding);
1894 }
1895 }
1896
1897 let evaluated_value = evaluate_value(value, state);
1899 let matched_children =
1900 find_matching_branch(&evaluated_value, branches, fallback.as_deref(), state);
1901
1902 let old_children = existing.children.clone();
1903 let old_len = old_children.len();
1904
1905 if let Some(children) = matched_children {
1906 let new_len = children.len();
1907 let common = old_len.min(new_len);
1908
1909 for (i, child) in children.iter().enumerate().take(common) {
1911 if let Some(&old_child_id) = old_children.get(i) {
1912 reconcile_ir_node(tree, old_child_id, child, state, patches, dependencies);
1913 }
1914 }
1915
1916 for i in common..old_len {
1918 if let Some(&old_child_id) = old_children.get(i) {
1919 remove_subtree(tree, old_child_id, patches, dependencies);
1920 if let Some(cond_node) = tree.get_mut(node_id) {
1921 cond_node.children = cond_node
1922 .children
1923 .iter()
1924 .filter(|&&id| id != old_child_id)
1925 .copied()
1926 .collect();
1927 }
1928 }
1929 }
1930
1931 for child in &children[common..] {
1933 create_ir_node_tree(
1934 tree,
1935 child,
1936 Some(node_id),
1937 state,
1938 patches,
1939 false,
1940 dependencies,
1941 );
1942 }
1943 } else {
1944 for &old_child_id in &old_children {
1946 remove_subtree(tree, old_child_id, patches, dependencies);
1947 }
1948
1949 if let Some(cond_node) = tree.get_mut(node_id) {
1950 cond_node.children.clear();
1951 }
1952 }
1953 }
1954 }
1955}
1956
1957fn remove_subtree(
1959 tree: &mut InstanceTree,
1960 node_id: NodeId,
1961 patches: &mut Vec<Patch>,
1962 dependencies: &mut DependencyGraph,
1963) {
1964 let ids = collect_subtree_ids(tree, node_id);
1965 for &id in &ids {
1966 patches.push(Patch::remove(id));
1967 dependencies.remove_node(id);
1968 }
1969 tree.remove(node_id);
1970}
1971
1972#[cfg(test)]
1973mod tests {
1974 use super::*;
1975 use crate::ir::Value;
1976 use serde_json::json;
1977
1978 #[test]
1979 fn test_create_simple_tree() {
1980 use crate::reactive::DependencyGraph;
1981
1982 let mut tree = InstanceTree::new();
1983 let mut patches = Vec::new();
1984 let mut dependencies = DependencyGraph::new();
1985
1986 let element = Element::new("Column")
1987 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
1988
1989 let state = json!({});
1990 create_tree(
1991 &mut tree,
1992 &element,
1993 None,
1994 &state,
1995 &mut patches,
1996 true,
1997 &mut dependencies,
1998 );
1999
2000 assert_eq!(patches.len(), 4);
2003
2004 let root_insert = patches
2006 .iter()
2007 .find(|p| matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root"));
2008 assert!(root_insert.is_some(), "Root insert patch should exist");
2009 }
2010
2011 #[test]
2012 fn test_diff_props() {
2013 let node_id = NodeId::default();
2014 let old = indexmap::indexmap! {
2015 "color".to_string() => json!("red"),
2016 "size".to_string() => json!(16),
2017 };
2018 let new = indexmap::indexmap! {
2019 "color".to_string() => json!("blue"),
2020 "size".to_string() => json!(16),
2021 };
2022
2023 let patches = diff_props(node_id, &old, &new);
2024
2025 assert_eq!(patches.len(), 1);
2027 }
2028
2029 #[test]
2030 fn test_longest_increasing_subsequence_basic() {
2031 let positions = vec![Some(0), Some(1), Some(2), Some(3)];
2033 let lis = longest_increasing_subsequence(&positions);
2034 assert_eq!(lis, vec![0, 1, 2, 3]);
2035 }
2036
2037 #[test]
2038 fn test_longest_increasing_subsequence_reverse() {
2039 let positions = vec![Some(3), Some(2), Some(1), Some(0)];
2041 let lis = longest_increasing_subsequence(&positions);
2042 assert_eq!(lis.len(), 1);
2043 }
2044
2045 #[test]
2046 fn test_longest_increasing_subsequence_mixed() {
2047 let positions = vec![Some(0), Some(3), Some(1), Some(2)];
2049 let lis = longest_increasing_subsequence(&positions);
2050 assert_eq!(lis.len(), 3);
2051 assert!(lis.contains(&0));
2053 assert!(lis.contains(&2));
2054 assert!(lis.contains(&3));
2055 }
2056
2057 #[test]
2058 fn test_longest_increasing_subsequence_with_new_items() {
2059 let positions = vec![None, Some(0), None, Some(1)];
2061 let lis = longest_increasing_subsequence(&positions);
2062 assert_eq!(lis, vec![1, 3]); }
2064
2065 #[test]
2066 fn test_longest_increasing_subsequence_empty() {
2067 let positions: Vec<Option<usize>> = vec![];
2068 let lis = longest_increasing_subsequence(&positions);
2069 assert_eq!(lis, Vec::<usize>::new());
2070 }
2071
2072 #[test]
2073 fn test_longest_increasing_subsequence_all_new() {
2074 let positions = vec![None, None, None];
2076 let lis = longest_increasing_subsequence(&positions);
2077 assert_eq!(lis, Vec::<usize>::new());
2078 }
2079
2080 #[test]
2081 fn test_reconcile_keyed_children_basic() {
2082 let mut tree = InstanceTree::new();
2083 let mut dependencies = DependencyGraph::new();
2084 let state = json!({});
2085
2086 let parent = Element::new("Column");
2088 let parent_id = tree.create_node(&parent, &state);
2089
2090 let mut old_child_1 =
2092 Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
2093 old_child_1.key = Some("item-1".to_string());
2094 let old_id_1 = tree.create_node(&old_child_1, &state);
2095
2096 let mut old_child_2 =
2097 Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
2098 old_child_2.key = Some("item-2".to_string());
2099 let old_id_2 = tree.create_node(&old_child_2, &state);
2100
2101 tree.add_child(parent_id, old_id_1, None);
2102 tree.add_child(parent_id, old_id_2, None);
2103
2104 let mut new_child_2 =
2106 Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
2107 new_child_2.key = Some("item-2".to_string());
2108
2109 let mut new_child_1 =
2110 Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
2111 new_child_1.key = Some("item-1".to_string());
2112
2113 let new_children = vec![new_child_2, new_child_1];
2114 let old_children = vec![old_id_1, old_id_2];
2115
2116 let patches = reconcile_keyed_children(
2117 &mut tree,
2118 parent_id,
2119 &old_children,
2120 &new_children,
2121 &state,
2122 &mut dependencies,
2123 );
2124
2125 let move_count = patches
2127 .iter()
2128 .filter(|p| matches!(p, Patch::Move { .. }))
2129 .count();
2130 let create_count = patches
2131 .iter()
2132 .filter(|p| matches!(p, Patch::Create { .. }))
2133 .count();
2134 let remove_count = patches
2135 .iter()
2136 .filter(|p| matches!(p, Patch::Remove { .. }))
2137 .count();
2138
2139 assert_eq!(create_count, 0, "Should not create new children");
2140 assert_eq!(remove_count, 0, "Should not remove any children");
2141 assert!(move_count > 0, "Should have move patches for reordering");
2142 }
2143
2144 #[test]
2145 fn test_reconcile_keyed_children_add_remove() {
2146 let mut tree = InstanceTree::new();
2147 let mut dependencies = DependencyGraph::new();
2148 let state = json!({});
2149
2150 let parent = Element::new("Column");
2151 let parent_id = tree.create_node(&parent, &state);
2152
2153 let mut old_child_1 = Element::new("Text");
2155 old_child_1.key = Some("item-1".to_string());
2156 let old_id_1 = tree.create_node(&old_child_1, &state);
2157
2158 let mut old_child_2 = Element::new("Text");
2159 old_child_2.key = Some("item-2".to_string());
2160 let old_id_2 = tree.create_node(&old_child_2, &state);
2161
2162 tree.add_child(parent_id, old_id_1, None);
2163 tree.add_child(parent_id, old_id_2, None);
2164
2165 let mut new_child_2 = Element::new("Text");
2167 new_child_2.key = Some("item-2".to_string());
2168
2169 let mut new_child_3 = Element::new("Text");
2170 new_child_3.key = Some("item-3".to_string());
2171
2172 let new_children = vec![new_child_2, new_child_3];
2173 let old_children = vec![old_id_1, old_id_2];
2174
2175 let patches = reconcile_keyed_children(
2176 &mut tree,
2177 parent_id,
2178 &old_children,
2179 &new_children,
2180 &state,
2181 &mut dependencies,
2182 );
2183
2184 let create_count = patches
2185 .iter()
2186 .filter(|p| matches!(p, Patch::Create { .. }))
2187 .count();
2188 let remove_count = patches
2189 .iter()
2190 .filter(|p| matches!(p, Patch::Remove { .. }))
2191 .count();
2192
2193 assert_eq!(create_count, 1, "Should create item-3");
2194 assert_eq!(remove_count, 1, "Should remove item-1");
2195 }
2196
2197 #[test]
2198 fn test_pattern_matches_binding_truthy() {
2199 use crate::reactive::Binding;
2200
2201 let state = json!({"isAdmin": true, "count": 0});
2202 let evaluated = json!("anything"); let pattern = Value::Binding(Binding::state(vec!["isAdmin".to_string()]));
2206 assert!(pattern_matches(&evaluated, &pattern, &state));
2207
2208 let pattern = Value::Binding(Binding::state(vec!["count".to_string()]));
2210 assert!(!pattern_matches(&evaluated, &pattern, &state));
2211
2212 let pattern = Value::Binding(Binding::state(vec!["missing".to_string()]));
2214 assert!(!pattern_matches(&evaluated, &pattern, &state));
2215 }
2216
2217 #[test]
2218 fn test_lis_large_sequence() {
2219 let positions: Vec<Option<usize>> = vec![
2222 Some(3),
2223 Some(1),
2224 Some(4),
2225 Some(1),
2226 Some(5),
2227 Some(9),
2228 Some(2),
2229 Some(6),
2230 ];
2231 let lis = longest_increasing_subsequence(&positions);
2232 assert_eq!(lis.len(), 4);
2233 let values: Vec<usize> = lis.iter().map(|&i| positions[i].unwrap()).collect();
2235 for w in values.windows(2) {
2236 assert!(w[0] < w[1], "LIS must be strictly increasing: {:?}", values);
2237 }
2238 }
2239}