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 if let Value::Binding(binding) = value {
499 dependencies.add_dependency(node_id, binding);
500 }
501 }
502
503 let new_props = resolve_props(&element.props, state);
505 let prop_patches = diff_props(node_id, &node.props, &new_props);
506 patches.extend(prop_patches);
507
508 if let Some(node) = tree.get_mut(node_id) {
510 node.props = new_props.clone();
511 node.raw_props = element.props.clone();
512 }
513
514 let is_lazy = element
516 .props
517 .get("__lazy")
518 .and_then(|v| {
519 if let Value::Static(val) = v {
520 val.as_bool()
521 } else {
522 None
523 }
524 })
525 .unwrap_or(false);
526
527 if !is_lazy {
528 let old_children = node.children.clone();
529 let new_children = &element.children;
530
531 for (i, new_child_element) in new_children.iter().enumerate() {
533 if let Some(&old_child_id) = old_children.get(i) {
534 reconcile_node(
536 tree,
537 old_child_id,
538 new_child_element,
539 state,
540 patches,
541 dependencies,
542 );
543 } else {
544 let new_child_id = create_tree(
546 tree,
547 new_child_element,
548 Some(node_id),
549 state,
550 patches,
551 false,
552 dependencies,
553 );
554 if let Some(node) = tree.get_mut(node_id) {
555 node.children.push_back(new_child_id);
556 }
557 }
558 }
559
560 if old_children.len() > new_children.len() {
562 for old_child_id in old_children.iter().skip(new_children.len()).copied() {
563 let subtree_ids = collect_subtree_ids(tree, old_child_id);
565 for &id in &subtree_ids {
566 patches.push(Patch::remove(id));
567 dependencies.remove_node(id);
568 }
569 tree.remove_child(node_id, old_child_id);
571 tree.remove(old_child_id);
572 }
573 }
574 }
575}
576
577fn replace_subtree(
580 tree: &mut InstanceTree,
581 old_node_id: NodeId,
582 old_node: &super::InstanceNode,
583 new_element: &Element,
584 state: &serde_json::Value,
585 patches: &mut Vec<Patch>,
586 dependencies: &mut DependencyGraph,
587) {
588 let parent_id = old_node.parent;
589
590 let old_position = if let Some(pid) = parent_id {
592 tree.get(pid)
593 .and_then(|parent| parent.children.iter().position(|&id| id == old_node_id))
594 } else {
595 None
596 };
597
598 let ids_to_remove = collect_subtree_ids(tree, old_node_id);
600
601 for &id in &ids_to_remove {
603 patches.push(Patch::remove(id));
604 dependencies.remove_node(id);
605 }
606
607 if let Some(pid) = parent_id {
609 if let Some(parent) = tree.get_mut(pid) {
610 parent.children = parent
611 .children
612 .iter()
613 .filter(|&&id| id != old_node_id)
614 .copied()
615 .collect();
616 }
617 }
618
619 tree.remove(old_node_id);
621
622 let is_root = parent_id.is_none();
624 let new_node_id = create_tree(
625 tree,
626 new_element,
627 parent_id,
628 state,
629 patches,
630 is_root,
631 dependencies,
632 );
633
634 if is_root {
636 tree.set_root(new_node_id);
637 } else if let Some(pid) = parent_id {
638 if let Some(pos) = old_position {
639 if let Some(parent) = tree.get_mut(pid) {
640 let current_len = parent.children.len();
641 if pos < current_len - 1 {
644 let new_id = parent.children.pop_back().unwrap();
646 parent.children.insert(pos, new_id);
647
648 let next_sibling = parent.children.get(pos + 1).copied();
650 patches.push(Patch::move_node(pid, new_node_id, next_sibling));
651 }
652 }
654 }
655 }
656}
657
658fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
661 let mut result = Vec::new();
662 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
663
664 while let Some((node_id, children_processed)) = stack.pop() {
665 if children_processed {
666 result.push(node_id);
668 } else {
669 stack.push((node_id, true));
671 if let Some(node) = tree.get(node_id) {
672 for &child_id in node.children.iter().rev() {
674 stack.push((child_id, false));
675 }
676 }
677 }
678 }
679
680 result
681}
682
683fn generate_item_key(
686 item: &serde_json::Value,
687 key_path: Option<&str>,
688 item_name: &str,
689 index: usize,
690) -> String {
691 if let Some(kp) = key_path {
692 item.get(kp)
693 .map(|v| match v {
694 serde_json::Value::String(s) => s.clone(),
695 serde_json::Value::Number(n) => n.to_string(),
696 _ => format!("{}-{}", item_name, index),
697 })
698 .unwrap_or_else(|| format!("{}-{}", item_name, index))
699 } else {
700 format!("{}-{}", item_name, index)
701 }
702}
703
704pub fn reconcile_keyed_children(
707 tree: &mut InstanceTree,
708 parent_id: NodeId,
709 old_children: &[NodeId],
710 new_children: &[Element],
711 state: &serde_json::Value,
712 dependencies: &mut DependencyGraph,
713) -> Vec<Patch> {
714 let mut patches = Vec::new();
715
716 let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
718 let mut old_unkeyed: Vec<NodeId> = Vec::new();
719
720 for &child_id in old_children {
721 if let Some(node) = tree.get(child_id) {
722 if let Some(key) = &node.key {
723 old_keyed.insert(key.clone(), child_id);
724 } else {
725 old_unkeyed.push(child_id);
726 }
727 }
728 }
729
730 let mut new_child_ids: Vec<NodeId> = Vec::new();
731 let mut unkeyed_idx = 0;
732
733 for new_element in new_children {
735 let new_key = new_element.key.as_ref();
736
737 let child_id = if let Some(key) = new_key {
738 if let Some(&old_id) = old_keyed.get(key) {
740 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
742 old_keyed.shift_remove(key); old_id
744 } else {
745 create_tree(
747 tree,
748 new_element,
749 Some(parent_id),
750 state,
751 &mut patches,
752 false,
753 dependencies,
754 )
755 }
756 } else {
757 if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
759 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
760 unkeyed_idx += 1;
761 old_id
762 } else {
763 create_tree(
765 tree,
766 new_element,
767 Some(parent_id),
768 state,
769 &mut patches,
770 false,
771 dependencies,
772 )
773 }
774 };
775
776 new_child_ids.push(child_id);
777 }
778
779 let mut old_positions: Vec<Option<usize>> = Vec::new();
782 for &new_id in &new_child_ids {
783 let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
784 old_positions.push(old_pos);
785 }
786
787 let lis = longest_increasing_subsequence(&old_positions);
789
790 if let Some(parent_node) = tree.get_mut(parent_id) {
792 parent_node.children = new_child_ids.iter().copied().collect();
793 }
794
795 for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
797 if !lis.contains(&new_idx) {
799 let before_id = new_child_ids.get(new_idx + 1).copied();
801 patches.push(Patch::move_node(parent_id, child_id, before_id));
802 }
803 }
804
805 for &old_id in old_keyed.values() {
808 dependencies.remove_node(old_id);
809 tree.remove(old_id);
810 patches.push(Patch::remove(old_id));
811 }
812 for &old_id in &old_unkeyed[unkeyed_idx..] {
813 dependencies.remove_node(old_id);
814 tree.remove(old_id);
815 patches.push(Patch::remove(old_id));
816 }
817
818 patches
819}
820
821fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
826 let valid: Vec<(usize, usize)> = positions
828 .iter()
829 .enumerate()
830 .filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
831 .collect();
832
833 if valid.is_empty() {
834 return Vec::new();
835 }
836
837 let n = valid.len();
838 let mut tails: Vec<usize> = Vec::with_capacity(n);
841 let mut prev: Vec<Option<usize>> = vec![None; n];
843
844 for i in 0..n {
845 let val = valid[i].1;
846 let pos = tails.partition_point(|&t| valid[t].1 < val);
848
849 if pos == tails.len() {
850 tails.push(i);
851 } else {
852 tails[pos] = i;
853 }
854
855 if pos > 0 {
856 prev[i] = Some(tails[pos - 1]);
857 }
858 }
859
860 let mut result = Vec::with_capacity(tails.len());
862 let mut idx = Some(*tails.last().unwrap());
863 while let Some(i) = idx {
864 result.push(valid[i].0);
865 idx = prev[i];
866 }
867 result.reverse();
868
869 result
870}
871
872pub fn diff_props(
874 node_id: NodeId,
875 old_props: &IndexMap<String, serde_json::Value>,
876 new_props: &IndexMap<String, serde_json::Value>,
877) -> Vec<Patch> {
878 let mut patches = Vec::new();
879
880 for (key, new_value) in new_props {
882 if old_props.get(key) != Some(new_value) {
883 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
884 }
885 }
886
887 for key in old_props.keys() {
889 if !new_props.contains_key(key) {
890 patches.push(Patch::remove_prop(node_id, key.clone()));
891 }
892 }
893
894 patches
895}
896
897pub fn create_ir_node_tree(
903 tree: &mut InstanceTree,
904 node: &IRNode,
905 parent_id: Option<NodeId>,
906 state: &serde_json::Value,
907 patches: &mut Vec<Patch>,
908 is_root: bool,
909 dependencies: &mut DependencyGraph,
910) -> NodeId {
911 match node {
912 IRNode::Element(element) => {
913 create_tree(
915 tree,
916 element,
917 parent_id,
918 state,
919 patches,
920 is_root,
921 dependencies,
922 )
923 }
924 IRNode::ForEach {
925 source,
926 item_name,
927 key_path,
928 template,
929 props,
930 } => {
931 let mut ctx = ReconcileCtx {
932 tree,
933 state,
934 patches,
935 dependencies,
936 };
937 create_foreach_ir_tree(
938 &mut ctx,
939 source,
940 item_name,
941 key_path.as_deref(),
942 template,
943 props,
944 node,
945 parent_id,
946 is_root,
947 )
948 }
949 IRNode::Conditional {
950 value,
951 branches,
952 fallback,
953 } => {
954 let mut ctx = ReconcileCtx {
955 tree,
956 state,
957 patches,
958 dependencies,
959 };
960 create_conditional_tree(
961 &mut ctx,
962 value,
963 branches,
964 fallback.as_deref(),
965 node,
966 parent_id,
967 is_root,
968 )
969 }
970 }
971}
972
973fn create_ir_node_tree_with_render_parent(
982 ctx: &mut ReconcileCtx,
983 node: &IRNode,
984 logical_parent: Option<NodeId>,
985 render_parent: Option<NodeId>,
986 is_root: bool,
987) -> NodeId {
988 match node {
989 IRNode::Element(element) => {
990 let node_id = ctx.tree.create_node(element, ctx.state);
992
993 if let Some(parent) = logical_parent {
995 ctx.tree.add_child(parent, node_id, None);
996 }
997
998 ctx.patches.push(Patch::create(
1000 node_id,
1001 element.element_type.clone(),
1002 ctx.tree
1003 .get(node_id)
1004 .map(|n| n.props.clone())
1005 .unwrap_or_default(),
1006 ));
1007
1008 if let Some(render_p) = render_parent {
1009 ctx.patches.push(Patch::insert(render_p, node_id, None));
1010 } else if is_root {
1011 ctx.patches.push(Patch::insert_root(node_id));
1012 }
1013
1014 for (_, value) in &element.props {
1016 if let Value::Binding(binding) = value {
1017 ctx.dependencies.add_dependency(node_id, binding);
1018 }
1019 }
1020
1021 for child in &element.children {
1023 let child_ir = IRNode::Element((**child).clone());
1024 create_ir_node_tree(
1025 ctx.tree,
1026 &child_ir,
1027 Some(node_id),
1028 ctx.state,
1029 ctx.patches,
1030 false,
1031 ctx.dependencies,
1032 );
1033 }
1034
1035 node_id
1036 }
1037 IRNode::ForEach {
1038 source,
1039 item_name,
1040 key_path,
1041 template,
1042 props,
1043 } => {
1044 create_foreach_ir_tree(
1046 ctx,
1047 source,
1048 item_name,
1049 key_path.as_deref(),
1050 template,
1051 props,
1052 node,
1053 logical_parent, is_root,
1055 )
1056 }
1057 IRNode::Conditional {
1058 value,
1059 branches,
1060 fallback,
1061 } => {
1062 create_conditional_tree(
1064 ctx,
1065 value,
1066 branches,
1067 fallback.as_deref(),
1068 node,
1069 logical_parent, is_root,
1071 )
1072 }
1073 }
1074}
1075
1076#[allow(clippy::too_many_arguments)]
1078fn create_foreach_ir_tree(
1079 ctx: &mut ReconcileCtx,
1080 source: &Binding,
1081 item_name: &str,
1082 key_path: Option<&str>,
1083 template: &[IRNode],
1084 props: &Props,
1085 original_node: &IRNode,
1086 parent_id: Option<NodeId>,
1087 is_root: bool,
1088) -> NodeId {
1089 let array = evaluate_binding(source, ctx.state).unwrap_or(serde_json::Value::Array(vec![]));
1091
1092 let resolved_props = resolve_props(props, ctx.state);
1094
1095 let node_id = ctx.tree.create_control_flow_node(
1097 "__ForEach",
1098 resolved_props.clone(),
1099 props.clone(),
1100 ControlFlowKind::ForEach {
1101 item_name: item_name.to_string(),
1102 key_path: key_path.map(|s| s.to_string()),
1103 },
1104 original_node.clone(),
1105 );
1106
1107 ctx.dependencies.add_dependency(node_id, source);
1109
1110 if let Some(parent) = parent_id {
1113 ctx.tree.add_child(parent, node_id, None);
1114 }
1115
1116 let render_parent = parent_id; if let serde_json::Value::Array(items) = &array {
1121 for (index, item) in items.iter().enumerate() {
1122 let item_key = generate_item_key(item, key_path, item_name, index);
1123
1124 for child_template in template {
1126 let child_with_item = replace_ir_node_item_bindings(
1127 child_template,
1128 item,
1129 index,
1130 item_name,
1131 &item_key,
1132 );
1133 create_ir_node_tree_with_render_parent(
1135 ctx,
1136 &child_with_item,
1137 Some(node_id), render_parent, is_root && render_parent.is_none(),
1140 );
1141 }
1142 }
1143 }
1144
1145 node_id
1146}
1147
1148fn create_conditional_tree(
1150 ctx: &mut ReconcileCtx,
1151 value: &Value,
1152 branches: &[ConditionalBranch],
1153 fallback: Option<&[IRNode]>,
1154 original_node: &IRNode,
1155 parent_id: Option<NodeId>,
1156 is_root: bool,
1157) -> NodeId {
1158 let evaluated_value = evaluate_value(value, ctx.state);
1160
1161 let mut raw_props = Props::new();
1163 raw_props.insert("__condition".to_string(), value.clone());
1164
1165 let node_id = ctx.tree.create_control_flow_node(
1166 "__Conditional",
1167 IndexMap::new(),
1168 raw_props,
1169 ControlFlowKind::Conditional,
1170 original_node.clone(),
1171 );
1172
1173 if let Value::Binding(binding) = value {
1175 ctx.dependencies.add_dependency(node_id, binding);
1176 } else if let Value::TemplateString { bindings, .. } = value {
1177 for binding in bindings {
1178 ctx.dependencies.add_dependency(node_id, binding);
1179 }
1180 }
1181
1182 if let Some(parent) = parent_id {
1185 ctx.tree.add_child(parent, node_id, None);
1186 }
1187
1188 let matched_children = find_matching_branch(&evaluated_value, branches, fallback, ctx.state);
1190
1191 let render_parent = parent_id; if let Some(children) = matched_children {
1195 for child in children {
1196 create_ir_node_tree_with_render_parent(
1198 ctx,
1199 child,
1200 Some(node_id), render_parent, is_root && render_parent.is_none(),
1203 );
1204 }
1205 }
1206
1207 node_id
1208}
1209
1210fn find_matching_branch<'a>(
1212 evaluated_value: &serde_json::Value,
1213 branches: &'a [ConditionalBranch],
1214 fallback: Option<&'a [IRNode]>,
1215 state: &serde_json::Value,
1216) -> Option<&'a [IRNode]> {
1217 for branch in branches {
1218 if pattern_matches(evaluated_value, &branch.pattern, state) {
1219 return Some(&branch.children);
1220 }
1221 }
1222
1223 fallback
1224}
1225
1226fn pattern_matches(
1232 evaluated_value: &serde_json::Value,
1233 pattern: &Value,
1234 state: &serde_json::Value,
1235) -> bool {
1236 match pattern {
1237 Value::Static(pattern_value) => values_match(evaluated_value, pattern_value),
1238 Value::Binding(binding) => {
1239 let resolved = evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null);
1242 is_truthy(&resolved)
1243 }
1244 Value::TemplateString { template, .. } => {
1245 evaluate_expression_pattern(template, evaluated_value)
1248 }
1249 Value::Action(_) => false, }
1251}
1252
1253fn evaluate_expression_pattern(template: &str, value: &serde_json::Value) -> bool {
1256 use crate::reactive::evaluate_expression;
1257 use std::collections::HashMap;
1258
1259 let mut context: HashMap<String, serde_json::Value> = HashMap::new();
1261 context.insert("value".to_string(), value.clone());
1262
1263 let expr = if template.starts_with("${") && template.ends_with("}") {
1265 &template[2..template.len() - 1]
1266 } else if template.contains("${") {
1267 let json_context = serde_json::json!({ "value": value });
1270 match evaluate_template_string(template, &json_context, None) {
1271 Ok(result) => {
1272 return is_truthy_string(&result);
1274 }
1275 Err(_) => return false,
1276 }
1277 } else {
1278 template
1280 };
1281
1282 match evaluate_expression(expr, &context) {
1284 Ok(result) => is_truthy(&result),
1285 Err(_) => false,
1286 }
1287}
1288
1289fn is_truthy_string(s: &str) -> bool {
1291 !s.is_empty() && s != "false" && s != "0" && s != "null" && s != "undefined"
1292}
1293
1294fn is_truthy(value: &serde_json::Value) -> bool {
1296 match value {
1297 serde_json::Value::Null => false,
1298 serde_json::Value::Bool(b) => *b,
1299 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
1300 serde_json::Value::String(s) => is_truthy_string(s),
1301 serde_json::Value::Array(arr) => !arr.is_empty(),
1302 serde_json::Value::Object(obj) => !obj.is_empty(),
1303 }
1304}
1305
1306fn values_match(value: &serde_json::Value, pattern: &serde_json::Value) -> bool {
1313 match pattern {
1314 serde_json::Value::Array(patterns) => patterns.iter().any(|p| values_match(value, p)),
1316
1317 serde_json::Value::String(pattern_str) => {
1319 if pattern_str == "_" || pattern_str == "*" {
1321 return true;
1322 }
1323
1324 if pattern_str.contains("${") {
1326 return evaluate_expression_pattern(pattern_str, value);
1327 }
1328
1329 matches!(value, serde_json::Value::String(s) if s == pattern_str)
1331 }
1332
1333 _ => {
1335 if value == pattern {
1336 return true;
1337 }
1338
1339 match (value, pattern) {
1341 (serde_json::Value::Bool(b), serde_json::Value::Bool(p)) => b == p,
1342 (serde_json::Value::Null, serde_json::Value::Null) => true,
1343 _ => false,
1344 }
1345 }
1346 }
1347}
1348
1349fn evaluate_value(value: &Value, state: &serde_json::Value) -> serde_json::Value {
1351 match value {
1352 Value::Static(v) => v.clone(),
1353 Value::Binding(binding) => {
1354 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
1355 }
1356 Value::TemplateString { template, .. } => {
1357 match evaluate_template_string(template, state, None) {
1358 Ok(result) => serde_json::Value::String(result),
1359 Err(_) => serde_json::Value::String(template.clone()),
1360 }
1361 }
1362 Value::Action(action) => serde_json::Value::String(format!("@{}", action)),
1363 }
1364}
1365
1366fn replace_ir_node_item_bindings(
1369 node: &IRNode,
1370 item: &serde_json::Value,
1371 index: usize,
1372 item_name: &str,
1373 item_key: &str,
1374) -> IRNode {
1375 match node {
1376 IRNode::Element(element) => {
1377 let mut new_element = replace_item_bindings_with_name(element, item, index, item_name);
1378 new_element.key = Some(item_key.to_string());
1380 IRNode::Element(new_element)
1381 }
1382 IRNode::ForEach {
1383 source,
1384 item_name: inner_item_name,
1385 key_path,
1386 template,
1387 props,
1388 } => {
1389 let new_props = replace_props_item_bindings(props, item, item_name);
1392
1393 let new_template: Vec<IRNode> = template
1395 .iter()
1396 .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1397 .collect();
1398
1399 IRNode::ForEach {
1400 source: source.clone(),
1401 item_name: inner_item_name.clone(),
1402 key_path: key_path.clone(),
1403 template: new_template,
1404 props: new_props,
1405 }
1406 }
1407 IRNode::Conditional {
1408 value,
1409 branches,
1410 fallback,
1411 } => {
1412 let new_value = replace_value_item_bindings(value, item, item_name);
1414
1415 let new_branches: Vec<ConditionalBranch> = branches
1417 .iter()
1418 .map(|branch| {
1419 let new_pattern = replace_value_item_bindings(&branch.pattern, item, item_name);
1420 let new_children: Vec<IRNode> = branch
1421 .children
1422 .iter()
1423 .map(|child| {
1424 replace_ir_node_item_bindings(child, item, index, item_name, item_key)
1425 })
1426 .collect();
1427 ConditionalBranch::new(new_pattern, new_children)
1428 })
1429 .collect();
1430
1431 let new_fallback = fallback.as_ref().map(|f| {
1433 f.iter()
1434 .map(|child| {
1435 replace_ir_node_item_bindings(child, item, index, item_name, item_key)
1436 })
1437 .collect()
1438 });
1439
1440 IRNode::Conditional {
1441 value: new_value,
1442 branches: new_branches,
1443 fallback: new_fallback,
1444 }
1445 }
1446 }
1447}
1448
1449fn replace_props_item_bindings(props: &Props, item: &serde_json::Value, item_name: &str) -> Props {
1451 let mut new_props = Props::new();
1452 for (key, value) in props {
1453 new_props.insert(
1454 key.clone(),
1455 replace_value_item_bindings(value, item, item_name),
1456 );
1457 }
1458 new_props
1459}
1460
1461fn replace_value_item_bindings(value: &Value, item: &serde_json::Value, item_name: &str) -> Value {
1465 match value {
1466 Value::Binding(binding) => {
1467 if binding.is_item() {
1468 if binding.path.is_empty() {
1469 Value::Static(item.clone())
1470 } else {
1471 let path = binding.full_path();
1472 if let Some(val) = navigate_item_path(item, &path) {
1473 Value::Static(val.clone())
1474 } else {
1475 value.clone()
1476 }
1477 }
1478 } else {
1479 value.clone()
1480 }
1481 }
1482 Value::TemplateString { template, bindings } => {
1483 let has_item_bindings = bindings.iter().any(|b| b.is_item());
1484 if !has_item_bindings {
1485 return value.clone();
1486 }
1487
1488 let mut replacements = Vec::new();
1490 for binding in bindings {
1491 if binding.is_item() {
1492 let pattern = format!("${{{}}}", binding.full_path_with_source());
1493 if let Some(start) = template.find(&pattern) {
1494 let replacement = if binding.path.is_empty() {
1495 format_value_for_replacement(item, false)
1496 } else if let Some(val) = navigate_item_path(item, &binding.full_path()) {
1497 format_value_for_replacement(val, false)
1498 } else {
1499 continue;
1500 };
1501 replacements.push(Replacement {
1502 start,
1503 end: start + pattern.len(),
1504 text: replacement,
1505 });
1506 }
1507 }
1508 }
1509
1510 let mut result = apply_replacements(template, replacements);
1512
1513 let expr_replacements =
1516 collect_item_replacements_with_name(&result, item, true, item_name);
1517 result = apply_replacements(&result, expr_replacements);
1518
1519 let remaining_bindings: Vec<_> =
1521 bindings.iter().filter(|b| b.is_state()).cloned().collect();
1522
1523 if remaining_bindings.is_empty() {
1524 if result.contains("${") {
1525 match evaluate_template_string(&result, &serde_json::Value::Null, None) {
1526 Ok(evaluated) => Value::Static(serde_json::Value::String(evaluated)),
1527 Err(_) => Value::Static(serde_json::Value::String(result)),
1528 }
1529 } else {
1530 Value::Static(serde_json::Value::String(result))
1531 }
1532 } else {
1533 Value::TemplateString {
1534 template: result,
1535 bindings: remaining_bindings,
1536 }
1537 }
1538 }
1539 Value::Static(serde_json::Value::String(s))
1541 if s.contains(&format!("${{{}.", item_name))
1542 || s.contains(&format!("${{{}}}", item_name)) =>
1543 {
1544 replace_static_item_bindings_with_name(s, item, item_name)
1545 }
1546 _ => value.clone(),
1547 }
1548}
1549
1550fn collect_item_replacements_with_name(
1552 s: &str,
1553 item: &serde_json::Value,
1554 quote_strings: bool,
1555 item_name: &str,
1556) -> Vec<Replacement> {
1557 let mut replacements = Vec::new();
1558 let mut pos = 0;
1559
1560 while pos < s.len() {
1561 if let Some(rel_start) = s[pos..].find(item_name) {
1562 let abs_start = pos + rel_start;
1563 let after_item = abs_start + item_name.len();
1564
1565 if after_item < s.len() && s.as_bytes()[after_item] == b'.' {
1566 let path_start = after_item + 1;
1567 let path_end = find_path_end(s, path_start);
1568
1569 if path_end > path_start {
1570 let path = &s[path_start..path_end];
1571 if let Some(val) = navigate_item_path(item, path) {
1572 let replacement = format_value_for_replacement(val, quote_strings);
1573 replacements.push(Replacement {
1574 start: abs_start,
1575 end: path_end,
1576 text: replacement,
1577 });
1578 }
1579 }
1580 pos = path_end.max(after_item + 1);
1581 } else {
1582 pos = after_item;
1583 }
1584 } else {
1585 break;
1586 }
1587 }
1588
1589 replacements
1590}
1591
1592fn replace_static_item_bindings_with_name(
1594 s: &str,
1595 item: &serde_json::Value,
1596 item_name: &str,
1597) -> Value {
1598 let mut replacements = Vec::new();
1599 let mut pos = 0;
1600
1601 while let Some(start) = s[pos..].find("${") {
1602 let abs_start = pos + start;
1603
1604 if let Some(end) = s[abs_start..].find('}') {
1605 let abs_end = abs_start + end;
1606 let content = &s[abs_start + 2..abs_end];
1607
1608 if content == item_name {
1609 replacements.push(Replacement {
1611 start: abs_start,
1612 end: abs_end + 1,
1613 text: format_value_for_replacement(item, false),
1614 });
1615 pos = abs_end + 1;
1616 } else if content.starts_with(&format!("{}.", item_name))
1617 && is_simple_path_with_name(content, item_name)
1618 {
1619 let path = &content[item_name.len() + 1..];
1621 if let Some(val) = navigate_item_path(item, path) {
1622 replacements.push(Replacement {
1623 start: abs_start,
1624 end: abs_end + 1,
1625 text: format_value_for_replacement(val, false),
1626 });
1627 }
1628 pos = abs_end + 1;
1629 } else if content.contains(&format!("{}.", item_name))
1630 || content.contains(&format!("{} ", item_name))
1631 {
1632 let expr_replacements =
1634 collect_item_replacements_with_name(content, item, true, item_name);
1635 let substituted_content = apply_replacements(content, expr_replacements);
1636 let new_expr = format!("${{{}}}", substituted_content);
1637
1638 if let Ok(evaluated) =
1639 evaluate_template_string(&new_expr, &serde_json::Value::Null, None)
1640 {
1641 replacements.push(Replacement {
1642 start: abs_start,
1643 end: abs_end + 1,
1644 text: evaluated,
1645 });
1646 }
1647 pos = abs_end + 1;
1648 } else {
1649 pos = abs_end + 1;
1650 }
1651 } else {
1652 break;
1653 }
1654 }
1655
1656 let result = apply_replacements(s, replacements);
1657 Value::Static(serde_json::Value::String(result))
1658}
1659
1660fn is_simple_path_with_name(s: &str, item_name: &str) -> bool {
1662 if !s.starts_with(item_name) {
1663 return false;
1664 }
1665
1666 let after_item = &s[item_name.len()..];
1667 if after_item.is_empty() {
1668 return true;
1669 }
1670
1671 if !after_item.starts_with('.') {
1672 return false;
1673 }
1674
1675 after_item[1..]
1676 .chars()
1677 .all(|c| c.is_alphanumeric() || c == '_' || c == '.')
1678}
1679
1680pub fn replace_item_bindings_with_name(
1683 element: &Element,
1684 item: &serde_json::Value,
1685 index: usize,
1686 item_name: &str,
1687) -> Element {
1688 let mut new_element = element.clone();
1689
1690 let mut new_props = Props::new();
1692 for (key, value) in &element.props {
1693 new_props.insert(
1694 key.clone(),
1695 replace_value_item_bindings(value, item, item_name),
1696 );
1697 }
1698 new_element.props = new_props;
1699
1700 let key = item
1702 .get("id")
1703 .and_then(|v| {
1704 v.as_str()
1705 .map(|s| s.to_string())
1706 .or_else(|| v.as_i64().map(|n| n.to_string()))
1707 })
1708 .or_else(|| {
1709 item.get("key")
1710 .and_then(|v| v.as_str().map(|s| s.to_string()))
1711 })
1712 .map(|id| format!("{}-{}", item_name, id))
1713 .unwrap_or_else(|| format!("{}-{}", item_name, index));
1714 new_element.key = Some(key);
1715
1716 new_element.children = element
1718 .children
1719 .iter()
1720 .map(|child| {
1721 std::sync::Arc::new(replace_item_bindings_with_name(
1722 child, item, index, item_name,
1723 ))
1724 })
1725 .collect();
1726
1727 new_element
1728}
1729
1730pub fn reconcile_ir_node(
1732 tree: &mut InstanceTree,
1733 node_id: NodeId,
1734 node: &IRNode,
1735 state: &serde_json::Value,
1736 patches: &mut Vec<Patch>,
1737 dependencies: &mut DependencyGraph,
1738) {
1739 let existing_node = tree.get(node_id).cloned();
1740 if existing_node.is_none() {
1741 return;
1742 }
1743 let existing = existing_node.unwrap();
1744
1745 match node {
1746 IRNode::Element(element) => {
1747 reconcile_node(tree, node_id, element, state, patches, dependencies);
1749 }
1750 IRNode::ForEach {
1751 source,
1752 item_name,
1753 key_path,
1754 template,
1755 props: _,
1756 } => {
1757 if !existing.is_foreach() {
1759 let parent_id = existing.parent;
1761 remove_subtree(tree, node_id, patches, dependencies);
1762 create_ir_node_tree(
1763 tree,
1764 node,
1765 parent_id,
1766 state,
1767 patches,
1768 parent_id.is_none(),
1769 dependencies,
1770 );
1771 return;
1772 }
1773
1774 let array = evaluate_binding(source, state).unwrap_or(serde_json::Value::Array(vec![]));
1776
1777 if let serde_json::Value::Array(items) = &array {
1778 let old_children = existing.children.clone();
1779 let expected_children_count = items.len() * template.len();
1780
1781 if old_children.len() != expected_children_count {
1783 for &old_child_id in &old_children {
1785 patches.push(Patch::remove(old_child_id));
1786 }
1787
1788 if let Some(node) = tree.get_mut(node_id) {
1789 node.children.clear();
1790 }
1791
1792 for (index, item) in items.iter().enumerate() {
1794 let item_key =
1795 generate_item_key(item, key_path.as_deref(), item_name, index);
1796
1797 for child_template in template {
1798 let child_with_item = replace_ir_node_item_bindings(
1799 child_template,
1800 item,
1801 index,
1802 item_name,
1803 &item_key,
1804 );
1805 create_ir_node_tree(
1806 tree,
1807 &child_with_item,
1808 Some(node_id),
1809 state,
1810 patches,
1811 false,
1812 dependencies,
1813 );
1814 }
1815 }
1816 } else {
1817 let mut child_index = 0;
1819 for (item_index, item) in items.iter().enumerate() {
1820 let item_key =
1821 generate_item_key(item, key_path.as_deref(), item_name, item_index);
1822
1823 for child_template in template {
1824 if let Some(&old_child_id) = old_children.get(child_index) {
1825 let child_with_item = replace_ir_node_item_bindings(
1826 child_template,
1827 item,
1828 item_index,
1829 item_name,
1830 &item_key,
1831 );
1832 reconcile_ir_node(
1833 tree,
1834 old_child_id,
1835 &child_with_item,
1836 state,
1837 patches,
1838 dependencies,
1839 );
1840 }
1841 child_index += 1;
1842 }
1843 }
1844 }
1845 }
1846 }
1847 IRNode::Conditional {
1848 value,
1849 branches,
1850 fallback,
1851 } => {
1852 if !existing.is_conditional() {
1854 let parent_id = existing.parent;
1856 remove_subtree(tree, node_id, patches, dependencies);
1857 create_ir_node_tree(
1858 tree,
1859 node,
1860 parent_id,
1861 state,
1862 patches,
1863 parent_id.is_none(),
1864 dependencies,
1865 );
1866 return;
1867 }
1868
1869 let evaluated_value = evaluate_value(value, state);
1871 let matched_children =
1872 find_matching_branch(&evaluated_value, branches, fallback.as_deref(), state);
1873
1874 let old_children = existing.children.clone();
1875 let old_len = old_children.len();
1876
1877 if let Some(children) = matched_children {
1878 let new_len = children.len();
1879 let common = old_len.min(new_len);
1880
1881 for (i, child) in children.iter().enumerate().take(common) {
1883 if let Some(&old_child_id) = old_children.get(i) {
1884 reconcile_ir_node(tree, old_child_id, child, state, patches, dependencies);
1885 }
1886 }
1887
1888 for i in common..old_len {
1890 if let Some(&old_child_id) = old_children.get(i) {
1891 remove_subtree(tree, old_child_id, patches, dependencies);
1892 if let Some(cond_node) = tree.get_mut(node_id) {
1893 cond_node.children = cond_node
1894 .children
1895 .iter()
1896 .filter(|&&id| id != old_child_id)
1897 .copied()
1898 .collect();
1899 }
1900 }
1901 }
1902
1903 for child in &children[common..] {
1905 create_ir_node_tree(
1906 tree,
1907 child,
1908 Some(node_id),
1909 state,
1910 patches,
1911 false,
1912 dependencies,
1913 );
1914 }
1915 } else {
1916 for &old_child_id in &old_children {
1918 remove_subtree(tree, old_child_id, patches, dependencies);
1919 }
1920
1921 if let Some(cond_node) = tree.get_mut(node_id) {
1922 cond_node.children.clear();
1923 }
1924 }
1925 }
1926 }
1927}
1928
1929fn remove_subtree(
1931 tree: &mut InstanceTree,
1932 node_id: NodeId,
1933 patches: &mut Vec<Patch>,
1934 dependencies: &mut DependencyGraph,
1935) {
1936 let ids = collect_subtree_ids(tree, node_id);
1937 for &id in &ids {
1938 patches.push(Patch::remove(id));
1939 dependencies.remove_node(id);
1940 }
1941 tree.remove(node_id);
1942}
1943
1944#[cfg(test)]
1945mod tests {
1946 use super::*;
1947 use crate::ir::Value;
1948 use serde_json::json;
1949
1950 #[test]
1951 fn test_create_simple_tree() {
1952 use crate::reactive::DependencyGraph;
1953
1954 let mut tree = InstanceTree::new();
1955 let mut patches = Vec::new();
1956 let mut dependencies = DependencyGraph::new();
1957
1958 let element = Element::new("Column")
1959 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
1960
1961 let state = json!({});
1962 create_tree(
1963 &mut tree,
1964 &element,
1965 None,
1966 &state,
1967 &mut patches,
1968 true,
1969 &mut dependencies,
1970 );
1971
1972 assert_eq!(patches.len(), 4);
1975
1976 let root_insert = patches
1978 .iter()
1979 .find(|p| matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root"));
1980 assert!(root_insert.is_some(), "Root insert patch should exist");
1981 }
1982
1983 #[test]
1984 fn test_diff_props() {
1985 let node_id = NodeId::default();
1986 let old = indexmap::indexmap! {
1987 "color".to_string() => json!("red"),
1988 "size".to_string() => json!(16),
1989 };
1990 let new = indexmap::indexmap! {
1991 "color".to_string() => json!("blue"),
1992 "size".to_string() => json!(16),
1993 };
1994
1995 let patches = diff_props(node_id, &old, &new);
1996
1997 assert_eq!(patches.len(), 1);
1999 }
2000
2001 #[test]
2002 fn test_longest_increasing_subsequence_basic() {
2003 let positions = vec![Some(0), Some(1), Some(2), Some(3)];
2005 let lis = longest_increasing_subsequence(&positions);
2006 assert_eq!(lis, vec![0, 1, 2, 3]);
2007 }
2008
2009 #[test]
2010 fn test_longest_increasing_subsequence_reverse() {
2011 let positions = vec![Some(3), Some(2), Some(1), Some(0)];
2013 let lis = longest_increasing_subsequence(&positions);
2014 assert_eq!(lis.len(), 1);
2015 }
2016
2017 #[test]
2018 fn test_longest_increasing_subsequence_mixed() {
2019 let positions = vec![Some(0), Some(3), Some(1), Some(2)];
2021 let lis = longest_increasing_subsequence(&positions);
2022 assert_eq!(lis.len(), 3);
2023 assert!(lis.contains(&0));
2025 assert!(lis.contains(&2));
2026 assert!(lis.contains(&3));
2027 }
2028
2029 #[test]
2030 fn test_longest_increasing_subsequence_with_new_items() {
2031 let positions = vec![None, Some(0), None, Some(1)];
2033 let lis = longest_increasing_subsequence(&positions);
2034 assert_eq!(lis, vec![1, 3]); }
2036
2037 #[test]
2038 fn test_longest_increasing_subsequence_empty() {
2039 let positions: Vec<Option<usize>> = vec![];
2040 let lis = longest_increasing_subsequence(&positions);
2041 assert_eq!(lis, Vec::<usize>::new());
2042 }
2043
2044 #[test]
2045 fn test_longest_increasing_subsequence_all_new() {
2046 let positions = vec![None, None, None];
2048 let lis = longest_increasing_subsequence(&positions);
2049 assert_eq!(lis, Vec::<usize>::new());
2050 }
2051
2052 #[test]
2053 fn test_reconcile_keyed_children_basic() {
2054 let mut tree = InstanceTree::new();
2055 let mut dependencies = DependencyGraph::new();
2056 let state = json!({});
2057
2058 let parent = Element::new("Column");
2060 let parent_id = tree.create_node(&parent, &state);
2061
2062 let mut old_child_1 =
2064 Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
2065 old_child_1.key = Some("item-1".to_string());
2066 let old_id_1 = tree.create_node(&old_child_1, &state);
2067
2068 let mut old_child_2 =
2069 Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
2070 old_child_2.key = Some("item-2".to_string());
2071 let old_id_2 = tree.create_node(&old_child_2, &state);
2072
2073 tree.add_child(parent_id, old_id_1, None);
2074 tree.add_child(parent_id, old_id_2, None);
2075
2076 let mut new_child_2 =
2078 Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
2079 new_child_2.key = Some("item-2".to_string());
2080
2081 let mut new_child_1 =
2082 Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
2083 new_child_1.key = Some("item-1".to_string());
2084
2085 let new_children = vec![new_child_2, new_child_1];
2086 let old_children = vec![old_id_1, old_id_2];
2087
2088 let patches = reconcile_keyed_children(
2089 &mut tree,
2090 parent_id,
2091 &old_children,
2092 &new_children,
2093 &state,
2094 &mut dependencies,
2095 );
2096
2097 let move_count = patches
2099 .iter()
2100 .filter(|p| matches!(p, Patch::Move { .. }))
2101 .count();
2102 let create_count = patches
2103 .iter()
2104 .filter(|p| matches!(p, Patch::Create { .. }))
2105 .count();
2106 let remove_count = patches
2107 .iter()
2108 .filter(|p| matches!(p, Patch::Remove { .. }))
2109 .count();
2110
2111 assert_eq!(create_count, 0, "Should not create new children");
2112 assert_eq!(remove_count, 0, "Should not remove any children");
2113 assert!(move_count > 0, "Should have move patches for reordering");
2114 }
2115
2116 #[test]
2117 fn test_reconcile_keyed_children_add_remove() {
2118 let mut tree = InstanceTree::new();
2119 let mut dependencies = DependencyGraph::new();
2120 let state = json!({});
2121
2122 let parent = Element::new("Column");
2123 let parent_id = tree.create_node(&parent, &state);
2124
2125 let mut old_child_1 = Element::new("Text");
2127 old_child_1.key = Some("item-1".to_string());
2128 let old_id_1 = tree.create_node(&old_child_1, &state);
2129
2130 let mut old_child_2 = Element::new("Text");
2131 old_child_2.key = Some("item-2".to_string());
2132 let old_id_2 = tree.create_node(&old_child_2, &state);
2133
2134 tree.add_child(parent_id, old_id_1, None);
2135 tree.add_child(parent_id, old_id_2, None);
2136
2137 let mut new_child_2 = Element::new("Text");
2139 new_child_2.key = Some("item-2".to_string());
2140
2141 let mut new_child_3 = Element::new("Text");
2142 new_child_3.key = Some("item-3".to_string());
2143
2144 let new_children = vec![new_child_2, new_child_3];
2145 let old_children = vec![old_id_1, old_id_2];
2146
2147 let patches = reconcile_keyed_children(
2148 &mut tree,
2149 parent_id,
2150 &old_children,
2151 &new_children,
2152 &state,
2153 &mut dependencies,
2154 );
2155
2156 let create_count = patches
2157 .iter()
2158 .filter(|p| matches!(p, Patch::Create { .. }))
2159 .count();
2160 let remove_count = patches
2161 .iter()
2162 .filter(|p| matches!(p, Patch::Remove { .. }))
2163 .count();
2164
2165 assert_eq!(create_count, 1, "Should create item-3");
2166 assert_eq!(remove_count, 1, "Should remove item-1");
2167 }
2168
2169 #[test]
2170 fn test_pattern_matches_binding_truthy() {
2171 use crate::reactive::Binding;
2172
2173 let state = json!({"isAdmin": true, "count": 0});
2174 let evaluated = json!("anything"); let pattern = Value::Binding(Binding::state(vec!["isAdmin".to_string()]));
2178 assert!(pattern_matches(&evaluated, &pattern, &state));
2179
2180 let pattern = Value::Binding(Binding::state(vec!["count".to_string()]));
2182 assert!(!pattern_matches(&evaluated, &pattern, &state));
2183
2184 let pattern = Value::Binding(Binding::state(vec!["missing".to_string()]));
2186 assert!(!pattern_matches(&evaluated, &pattern, &state));
2187 }
2188
2189 #[test]
2190 fn test_lis_large_sequence() {
2191 let positions: Vec<Option<usize>> = vec![
2194 Some(3),
2195 Some(1),
2196 Some(4),
2197 Some(1),
2198 Some(5),
2199 Some(9),
2200 Some(2),
2201 Some(6),
2202 ];
2203 let lis = longest_increasing_subsequence(&positions);
2204 assert_eq!(lis.len(), 4);
2205 let values: Vec<usize> = lis.iter().map(|&i| positions[i].unwrap()).collect();
2207 for w in values.windows(2) {
2208 assert!(w[0] < w[1], "LIS must be strictly increasing: {:?}", values);
2209 }
2210 }
2211}