1use super::{InstanceTree, Patch, ControlFlowKind};
2use super::resolve::{evaluate_binding, resolve_props};
3use crate::ir::{Element, NodeId, Value, IRNode, ConditionalBranch, Props};
4use crate::reactive::{DependencyGraph, evaluate_template_string, Binding};
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(tree, element, parent_id, state, &mut patches, true, dependencies);
31 tree.set_root(node_id);
32 return patches;
33 }
34
35 if let Some(root_id) = tree.root() {
37 reconcile_node(tree, root_id, element, state, &mut patches, dependencies);
38 }
39
40 patches
41}
42
43pub fn reconcile_ir(
47 tree: &mut InstanceTree,
48 node: &IRNode,
49 parent_id: Option<NodeId>,
50 state: &serde_json::Value,
51 dependencies: &mut DependencyGraph,
52) -> Vec<Patch> {
53 let mut patches = Vec::new();
54
55 if tree.root().is_none() {
57 let node_id = create_ir_node_tree(tree, node, parent_id, state, &mut patches, true, dependencies);
58 tree.set_root(node_id);
59 return patches;
60 }
61
62 if let Some(root_id) = tree.root() {
64 reconcile_ir_node(tree, root_id, node, state, &mut patches, dependencies);
65 }
66
67 patches
68}
69
70pub fn create_tree(
72 tree: &mut InstanceTree,
73 element: &Element,
74 parent_id: Option<NodeId>,
75 state: &serde_json::Value,
76 patches: &mut Vec<Patch>,
77 is_root: bool,
78 dependencies: &mut DependencyGraph,
79) -> NodeId {
80 if let Some(Value::Binding(_)) = element.props.get("0") {
88 if !element.children.is_empty() {
89 return create_list_tree(tree, element, parent_id, state, patches, is_root, dependencies);
90 }
91 }
92
93 let node_id = tree.create_node(element, state);
95
96 for value in element.props.values() {
98 match value {
99 Value::Binding(binding) => {
100 dependencies.add_dependency(node_id, binding);
101 }
102 Value::TemplateString { bindings, .. } => {
103 for binding in bindings {
105 dependencies.add_dependency(node_id, binding);
106 }
107 }
108 _ => {}
109 }
110 }
111
112 let node = tree.get(node_id).unwrap();
114
115 let mut props = node.props.clone();
117 if let Some(Value::Static(val)) = element.props.get("__lazy") {
118 if val.as_bool().unwrap_or(false) && !element.children.is_empty() {
119 let child_component = &element.children[0].element_type;
121 props.insert("__lazy_child".to_string(), serde_json::json!(child_component));
122 }
123 }
124
125 patches.push(Patch::create(
126 node_id,
127 node.element_type.clone(),
128 props,
129 ));
130
131 if let Some(parent) = parent_id {
135 tree.add_child(parent, node_id, None);
136 patches.push(Patch::insert(parent, node_id, None));
137 } else if is_root {
138 patches.push(Patch::insert_root(node_id));
140 }
141
142 let is_lazy = element.props.get("__lazy")
144 .and_then(|v| {
145 if let Value::Static(val) = v {
146 val.as_bool()
147 } else {
148 None
149 }
150 })
151 .unwrap_or(false);
152
153 if !is_lazy {
154 for child_element in &element.children {
155 create_tree(tree, child_element, Some(node_id), state, patches, false, dependencies);
156 }
157 }
158
159 node_id
160}
161
162fn create_list_tree(
165 tree: &mut InstanceTree,
166 element: &Element,
167 parent_id: Option<NodeId>,
168 state: &serde_json::Value,
169 patches: &mut Vec<Patch>,
170 is_root: bool,
171 dependencies: &mut DependencyGraph,
172) -> NodeId {
173 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
175 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
176 } else {
177 serde_json::Value::Array(vec![])
178 };
179
180 let mut list_element = Element::new(&element.element_type);
183 for (key, value) in &element.props {
184 if key != "0" {
186 list_element.props.insert(key.clone(), value.clone());
187 }
188 }
189
190 let node_id = tree.create_node(&list_element, state);
191
192 if let Some(Value::Binding(binding)) = element.props.get("0") {
195 dependencies.add_dependency(node_id, binding);
196 }
197
198 if let Some(node) = tree.get_mut(node_id) {
201 node.raw_props = element.props.clone();
202 node.element_template = Some(std::sync::Arc::new(element.clone()));
203 }
204
205 let node = tree.get(node_id).unwrap();
207 patches.push(Patch::create(
208 node_id,
209 node.element_type.clone(),
210 node.props.clone(),
211 ));
212
213 if let Some(parent) = parent_id {
215 tree.add_child(parent, node_id, None);
216 patches.push(Patch::insert(parent, node_id, None));
217 } else if is_root {
218 patches.push(Patch::insert_root(node_id));
219 }
220
221 if let serde_json::Value::Array(items) = &array {
223 for (index, item) in items.iter().enumerate() {
225 for child_template in &element.children {
226 let child_with_item = replace_item_bindings(child_template, item, index);
228 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
229 }
230 }
231 }
232
233 node_id
234}
235
236fn navigate_item_path<'a>(item: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> {
239 let mut current = item;
240
241 for segment in path.split('.') {
242 if let Ok(index) = segment.parse::<usize>() {
244 current = current.get(index)?;
245 } else {
246 current = current.get(segment)?;
248 }
249 }
250
251 Some(current)
252}
253
254#[derive(Debug)]
257struct Replacement {
258 start: usize,
259 end: usize,
260 text: String,
261}
262
263fn apply_replacements(s: &str, mut replacements: Vec<Replacement>) -> String {
266 if replacements.is_empty() {
267 return s.to_string();
268 }
269
270 replacements.sort_by(|a, b| a.start.cmp(&b.start));
272
273 let valid_replacements: Vec<_> = replacements
275 .into_iter()
276 .filter(|r| r.start <= s.len() && r.end <= s.len() && r.start <= r.end)
277 .collect();
278
279 if valid_replacements.is_empty() {
280 return s.to_string();
281 }
282
283 let removed: usize = valid_replacements.iter().map(|r| r.end - r.start).sum();
285 let added: usize = valid_replacements.iter().map(|r| r.text.len()).sum();
286 let capacity = s.len() - removed + added;
287
288 let mut result = String::with_capacity(capacity);
289 let mut pos = 0;
290
291 for r in valid_replacements {
292 if r.start > pos {
294 result.push_str(&s[pos..r.start]);
295 }
296 result.push_str(&r.text);
298 pos = r.end;
299 }
300
301 if pos < s.len() {
303 result.push_str(&s[pos..]);
304 }
305
306 result
307}
308
309fn find_path_end(s: &str, start: usize) -> usize {
311 let chars: Vec<char> = s[start..].chars().collect();
312 let mut end = start;
313
314 for (i, ch) in chars.iter().enumerate() {
315 if ch.is_alphanumeric() || *ch == '_' {
316 end = start + i + 1;
317 } else if *ch == '.' {
318 if i + 1 < chars.len() {
320 let next = chars[i + 1];
321 if next.is_alphanumeric() || next == '_' {
322 end = start + i + 1;
323 continue;
324 }
325 }
326 break;
327 } else {
328 break;
329 }
330 }
331
332 end
333}
334
335fn format_value_for_replacement(val: &serde_json::Value, quote_strings: bool) -> String {
337 match val {
338 serde_json::Value::String(s) => {
339 if quote_strings {
340 format!("'{}'", s)
341 } else {
342 s.clone()
343 }
344 }
345 serde_json::Value::Number(n) => n.to_string(),
346 serde_json::Value::Bool(b) => b.to_string(),
347 serde_json::Value::Null => "null".to_string(),
348 _ => serde_json::to_string(val).unwrap_or_default(),
349 }
350}
351
352pub fn replace_item_bindings(element: &Element, item: &serde_json::Value, index: usize) -> Element {
356 replace_item_bindings_with_name(element, item, index, "item")
357}
358
359pub fn reconcile_node(
361 tree: &mut InstanceTree,
362 node_id: NodeId,
363 element: &Element,
364 state: &serde_json::Value,
365 patches: &mut Vec<Patch>,
366 dependencies: &mut DependencyGraph,
367) {
368 let node = tree.get(node_id).cloned();
369 if node.is_none() {
370 return;
371 }
372 let node = node.unwrap();
373
374 let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
378
379 if is_iterable {
380 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
382 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
383 } else {
384 serde_json::Value::Array(vec![])
385 };
386
387 if let serde_json::Value::Array(items) = &array {
389 let old_children = node.children.clone();
390
391 let expected_children_count = items.len() * element.children.len();
393
394 if old_children.len() != expected_children_count {
396 for &old_child_id in &old_children {
397 patches.push(Patch::remove(old_child_id));
398 }
399
400 if let Some(node) = tree.get_mut(node_id) {
402 node.children.clear();
403 }
404
405 for (index, item) in items.iter().enumerate() {
407 for child_template in &element.children {
408 let child_with_item = replace_item_bindings(child_template, item, index);
409 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
410 }
411 }
412 } else {
413 let mut child_index = 0;
415 for (item_index, item) in items.iter().enumerate() {
416 for child_template in &element.children {
417 if let Some(&old_child_id) = old_children.get(child_index) {
418 let child_with_item = replace_item_bindings(child_template, item, item_index);
419 reconcile_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
420 }
421 child_index += 1;
422 }
423 }
424 }
425 }
426
427 return; }
429
430 if node.element_type != element.element_type {
432 replace_subtree(tree, node_id, &node, element, state, patches, dependencies);
433 return;
434 }
435
436 for value in element.props.values() {
438 if let Value::Binding(binding) = value {
439 dependencies.add_dependency(node_id, binding);
440 }
441 }
442
443 let new_props = resolve_props(&element.props, state);
445 let prop_patches = diff_props(node_id, &node.props, &new_props);
446 patches.extend(prop_patches);
447
448 if let Some(node) = tree.get_mut(node_id) {
450 node.props = new_props.clone();
451 node.raw_props = element.props.clone();
452 }
453
454 let is_lazy = element.props.get("__lazy")
456 .and_then(|v| {
457 if let Value::Static(val) = v {
458 val.as_bool()
459 } else {
460 None
461 }
462 })
463 .unwrap_or(false);
464
465 if !is_lazy {
466 let old_children = node.children.clone();
467 let new_children = &element.children;
468
469 for (i, new_child_element) in new_children.iter().enumerate() {
471 if let Some(&old_child_id) = old_children.get(i) {
472 reconcile_node(tree, old_child_id, new_child_element, state, patches, dependencies);
474 } else {
475 let new_child_id = create_tree(tree, new_child_element, Some(node_id), state, patches, false, dependencies);
477 if let Some(node) = tree.get_mut(node_id) {
478 node.children.push_back(new_child_id);
479 }
480 }
481 }
482
483 if old_children.len() > new_children.len() {
485 for old_child_id in old_children.iter().skip(new_children.len()).copied() {
486 let subtree_ids = collect_subtree_ids(tree, old_child_id);
488 for &id in &subtree_ids {
489 patches.push(Patch::remove(id));
490 dependencies.remove_node(id);
491 }
492 tree.remove_child(node_id, old_child_id);
494 tree.remove(old_child_id);
495 }
496 }
497 }
498}
499
500fn replace_subtree(
503 tree: &mut InstanceTree,
504 old_node_id: NodeId,
505 old_node: &super::InstanceNode,
506 new_element: &Element,
507 state: &serde_json::Value,
508 patches: &mut Vec<Patch>,
509 dependencies: &mut DependencyGraph,
510) {
511 let parent_id = old_node.parent;
512
513 let old_position = if let Some(pid) = parent_id {
515 tree.get(pid).and_then(|parent| {
516 parent.children.iter().position(|&id| id == old_node_id)
517 })
518 } else {
519 None
520 };
521
522 let ids_to_remove = collect_subtree_ids(tree, old_node_id);
524
525 for &id in &ids_to_remove {
527 patches.push(Patch::remove(id));
528 dependencies.remove_node(id);
529 }
530
531 if let Some(pid) = parent_id {
533 if let Some(parent) = tree.get_mut(pid) {
534 parent.children = parent.children.iter().filter(|&&id| id != old_node_id).copied().collect();
535 }
536 }
537
538 tree.remove(old_node_id);
540
541 let is_root = parent_id.is_none();
543 let new_node_id = create_tree(tree, new_element, parent_id, state, patches, is_root, dependencies);
544
545 if is_root {
547 tree.set_root(new_node_id);
548 } else if let Some(pid) = parent_id {
549 if let Some(pos) = old_position {
550 if let Some(parent) = tree.get_mut(pid) {
551 let current_len = parent.children.len();
552 if pos < current_len - 1 {
555 let new_id = parent.children.pop_back().unwrap();
557 parent.children.insert(pos, new_id);
558
559 let next_sibling = parent.children.get(pos + 1).copied();
561 patches.push(Patch::move_node(pid, new_node_id, next_sibling));
562 }
563 }
565 }
566 }
567}
568
569fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
572 let mut result = Vec::new();
573 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
574
575 while let Some((node_id, children_processed)) = stack.pop() {
576 if children_processed {
577 result.push(node_id);
579 } else {
580 stack.push((node_id, true));
582 if let Some(node) = tree.get(node_id) {
583 for &child_id in node.children.iter().rev() {
585 stack.push((child_id, false));
586 }
587 }
588 }
589 }
590
591 result
592}
593
594fn generate_item_key(item: &serde_json::Value, key_path: Option<&str>, item_name: &str, index: usize) -> String {
597 if let Some(kp) = key_path {
598 item.get(kp)
599 .map(|v| match v {
600 serde_json::Value::String(s) => s.clone(),
601 serde_json::Value::Number(n) => n.to_string(),
602 _ => format!("{}-{}", item_name, index),
603 })
604 .unwrap_or_else(|| format!("{}-{}", item_name, index))
605 } else {
606 format!("{}-{}", item_name, index)
607 }
608}
609
610pub fn reconcile_keyed_children(
613 tree: &mut InstanceTree,
614 parent_id: NodeId,
615 old_children: &[NodeId],
616 new_children: &[Element],
617 state: &serde_json::Value,
618 dependencies: &mut DependencyGraph,
619) -> Vec<Patch> {
620 let mut patches = Vec::new();
621
622 let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
624 let mut old_unkeyed: Vec<NodeId> = Vec::new();
625
626 for &child_id in old_children {
627 if let Some(node) = tree.get(child_id) {
628 if let Some(key) = &node.key {
629 old_keyed.insert(key.clone(), child_id);
630 } else {
631 old_unkeyed.push(child_id);
632 }
633 }
634 }
635
636 let mut new_child_ids: Vec<NodeId> = Vec::new();
637 let mut unkeyed_idx = 0;
638
639 for new_element in new_children {
641 let new_key = new_element.key.as_ref();
642
643 let child_id = if let Some(key) = new_key {
644 if let Some(&old_id) = old_keyed.get(key) {
646 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
648 old_keyed.shift_remove(key); old_id
650 } else {
651 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
653 }
654 } else {
655 if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
657 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
658 unkeyed_idx += 1;
659 old_id
660 } else {
661 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
663 }
664 };
665
666 new_child_ids.push(child_id);
667 }
668
669 let mut old_positions: Vec<Option<usize>> = Vec::new();
672 for &new_id in &new_child_ids {
673 let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
674 old_positions.push(old_pos);
675 }
676
677 let lis = longest_increasing_subsequence(&old_positions);
679
680 if let Some(parent_node) = tree.get_mut(parent_id) {
682 parent_node.children = new_child_ids.iter().copied().collect();
683 }
684
685 for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
687 if !lis.contains(&new_idx) {
689 let before_id = new_child_ids.get(new_idx + 1).copied();
691 patches.push(Patch::move_node(parent_id, child_id, before_id));
692 }
693 }
694
695 for &old_id in old_keyed.values() {
698 dependencies.remove_node(old_id);
699 tree.remove(old_id);
700 patches.push(Patch::remove(old_id));
701 }
702 for &old_id in &old_unkeyed[unkeyed_idx..] {
703 dependencies.remove_node(old_id);
704 tree.remove(old_id);
705 patches.push(Patch::remove(old_id));
706 }
707
708 patches
709}
710
711fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
716 let valid: Vec<(usize, usize)> = positions
718 .iter()
719 .enumerate()
720 .filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
721 .collect();
722
723 if valid.is_empty() {
724 return Vec::new();
725 }
726
727 let n = valid.len();
728 let mut tails: Vec<usize> = Vec::with_capacity(n);
731 let mut prev: Vec<Option<usize>> = vec![None; n];
733
734 for i in 0..n {
735 let val = valid[i].1;
736 let pos = tails.partition_point(|&t| valid[t].1 < val);
738
739 if pos == tails.len() {
740 tails.push(i);
741 } else {
742 tails[pos] = i;
743 }
744
745 if pos > 0 {
746 prev[i] = Some(tails[pos - 1]);
747 }
748 }
749
750 let mut result = Vec::with_capacity(tails.len());
752 let mut idx = Some(*tails.last().unwrap());
753 while let Some(i) = idx {
754 result.push(valid[i].0);
755 idx = prev[i];
756 }
757 result.reverse();
758
759 result
760}
761
762pub fn diff_props(
764 node_id: NodeId,
765 old_props: &IndexMap<String, serde_json::Value>,
766 new_props: &IndexMap<String, serde_json::Value>,
767) -> Vec<Patch> {
768 let mut patches = Vec::new();
769
770 for (key, new_value) in new_props {
772 if old_props.get(key) != Some(new_value) {
773 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
774 }
775 }
776
777 for key in old_props.keys() {
779 if !new_props.contains_key(key) {
780 patches.push(Patch::remove_prop(node_id, key.clone()));
781 }
782 }
783
784 patches
785}
786
787pub fn create_ir_node_tree(
793 tree: &mut InstanceTree,
794 node: &IRNode,
795 parent_id: Option<NodeId>,
796 state: &serde_json::Value,
797 patches: &mut Vec<Patch>,
798 is_root: bool,
799 dependencies: &mut DependencyGraph,
800) -> NodeId {
801 match node {
802 IRNode::Element(element) => {
803 create_tree(tree, element, parent_id, state, patches, is_root, dependencies)
805 }
806 IRNode::ForEach { source, item_name, key_path, template, props } => {
807 let mut ctx = ReconcileCtx { tree, state, patches, dependencies };
808 create_foreach_ir_tree(
809 &mut ctx,
810 source,
811 item_name,
812 key_path.as_deref(),
813 template,
814 props,
815 node,
816 parent_id,
817 is_root,
818 )
819 }
820 IRNode::Conditional { value, branches, fallback } => {
821 let mut ctx = ReconcileCtx { tree, state, patches, dependencies };
822 create_conditional_tree(
823 &mut ctx,
824 value,
825 branches,
826 fallback.as_deref(),
827 node,
828 parent_id,
829 is_root,
830 )
831 }
832 }
833}
834
835fn create_ir_node_tree_with_render_parent(
844 ctx: &mut ReconcileCtx,
845 node: &IRNode,
846 logical_parent: Option<NodeId>,
847 render_parent: Option<NodeId>,
848 is_root: bool,
849) -> NodeId {
850 match node {
851 IRNode::Element(element) => {
852 let node_id = ctx.tree.create_node(element, ctx.state);
854
855 if let Some(parent) = logical_parent {
857 ctx.tree.add_child(parent, node_id, None);
858 }
859
860 ctx.patches.push(Patch::create(
862 node_id,
863 element.element_type.clone(),
864 ctx.tree.get(node_id).map(|n| n.props.clone()).unwrap_or_default(),
865 ));
866
867 if let Some(render_p) = render_parent {
868 ctx.patches.push(Patch::insert(render_p, node_id, None));
869 } else if is_root {
870 ctx.patches.push(Patch::insert_root(node_id));
871 }
872
873 for (_, value) in &element.props {
875 if let Value::Binding(binding) = value {
876 ctx.dependencies.add_dependency(node_id, binding);
877 }
878 }
879
880 for child in &element.children {
882 let child_ir = IRNode::Element((**child).clone());
883 create_ir_node_tree(ctx.tree, &child_ir, Some(node_id), ctx.state, ctx.patches, false, ctx.dependencies);
884 }
885
886 node_id
887 }
888 IRNode::ForEach { source, item_name, key_path, template, props } => {
889 create_foreach_ir_tree(
891 ctx,
892 source,
893 item_name,
894 key_path.as_deref(),
895 template,
896 props,
897 node,
898 logical_parent, is_root,
900 )
901 }
902 IRNode::Conditional { value, branches, fallback } => {
903 create_conditional_tree(
905 ctx,
906 value,
907 branches,
908 fallback.as_deref(),
909 node,
910 logical_parent, is_root,
912 )
913 }
914 }
915}
916
917#[allow(clippy::too_many_arguments)]
919fn create_foreach_ir_tree(
920 ctx: &mut ReconcileCtx,
921 source: &Binding,
922 item_name: &str,
923 key_path: Option<&str>,
924 template: &[IRNode],
925 props: &Props,
926 original_node: &IRNode,
927 parent_id: Option<NodeId>,
928 is_root: bool,
929) -> NodeId {
930 let array = evaluate_binding(source, ctx.state).unwrap_or(serde_json::Value::Array(vec![]));
932
933 let resolved_props = resolve_props(props, ctx.state);
935
936 let node_id = ctx.tree.create_control_flow_node(
938 "__ForEach",
939 resolved_props.clone(),
940 props.clone(),
941 ControlFlowKind::ForEach {
942 item_name: item_name.to_string(),
943 key_path: key_path.map(|s| s.to_string()),
944 },
945 original_node.clone(),
946 );
947
948 ctx.dependencies.add_dependency(node_id, source);
950
951 if let Some(parent) = parent_id {
954 ctx.tree.add_child(parent, node_id, None);
955 }
956
957 let render_parent = parent_id; if let serde_json::Value::Array(items) = &array {
962 for (index, item) in items.iter().enumerate() {
963 let item_key = generate_item_key(item, key_path, item_name, index);
964
965 for child_template in template {
967 let child_with_item = replace_ir_node_item_bindings(
968 child_template,
969 item,
970 index,
971 item_name,
972 &item_key,
973 );
974 create_ir_node_tree_with_render_parent(
976 ctx,
977 &child_with_item,
978 Some(node_id), render_parent, is_root && render_parent.is_none(),
981 );
982 }
983 }
984 }
985
986 node_id
987}
988
989fn create_conditional_tree(
991 ctx: &mut ReconcileCtx,
992 value: &Value,
993 branches: &[ConditionalBranch],
994 fallback: Option<&[IRNode]>,
995 original_node: &IRNode,
996 parent_id: Option<NodeId>,
997 is_root: bool,
998) -> NodeId {
999 let evaluated_value = evaluate_value(value, ctx.state);
1001
1002 let mut raw_props = Props::new();
1004 raw_props.insert("__condition".to_string(), value.clone());
1005
1006 let node_id = ctx.tree.create_control_flow_node(
1007 "__Conditional",
1008 IndexMap::new(),
1009 raw_props,
1010 ControlFlowKind::Conditional,
1011 original_node.clone(),
1012 );
1013
1014 if let Value::Binding(binding) = value {
1016 ctx.dependencies.add_dependency(node_id, binding);
1017 } else if let Value::TemplateString { bindings, .. } = value {
1018 for binding in bindings {
1019 ctx.dependencies.add_dependency(node_id, binding);
1020 }
1021 }
1022
1023 if let Some(parent) = parent_id {
1026 ctx.tree.add_child(parent, node_id, None);
1027 }
1028
1029 let matched_children = find_matching_branch(&evaluated_value, branches, fallback, ctx.state);
1031
1032 let render_parent = parent_id; if let Some(children) = matched_children {
1036 for child in children {
1037 create_ir_node_tree_with_render_parent(
1039 ctx,
1040 child,
1041 Some(node_id), render_parent, is_root && render_parent.is_none(),
1044 );
1045 }
1046 }
1047
1048 node_id
1049}
1050
1051fn find_matching_branch<'a>(
1053 evaluated_value: &serde_json::Value,
1054 branches: &'a [ConditionalBranch],
1055 fallback: Option<&'a [IRNode]>,
1056 state: &serde_json::Value,
1057) -> Option<&'a [IRNode]> {
1058 for branch in branches {
1059 if pattern_matches(evaluated_value, &branch.pattern, state) {
1060 return Some(&branch.children);
1061 }
1062 }
1063
1064 fallback
1065}
1066
1067fn pattern_matches(evaluated_value: &serde_json::Value, pattern: &Value, state: &serde_json::Value) -> bool {
1073 match pattern {
1074 Value::Static(pattern_value) => {
1075 values_match(evaluated_value, pattern_value)
1076 }
1077 Value::Binding(binding) => {
1078 let resolved = evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null);
1081 is_truthy(&resolved)
1082 }
1083 Value::TemplateString { template, .. } => {
1084 evaluate_expression_pattern(template, evaluated_value)
1087 }
1088 Value::Action(_) => false, }
1090}
1091
1092fn evaluate_expression_pattern(template: &str, value: &serde_json::Value) -> bool {
1095 use crate::reactive::evaluate_expression;
1096 use std::collections::HashMap;
1097
1098 let mut context: HashMap<String, serde_json::Value> = HashMap::new();
1100 context.insert("value".to_string(), value.clone());
1101
1102 let expr = if template.starts_with("${") && template.ends_with("}") {
1104 &template[2..template.len()-1]
1105 } else if template.contains("${") {
1106 let json_context = serde_json::json!({ "value": value });
1109 match evaluate_template_string(template, &json_context, None) {
1110 Ok(result) => {
1111 return is_truthy_string(&result);
1113 }
1114 Err(_) => return false,
1115 }
1116 } else {
1117 template
1119 };
1120
1121 match evaluate_expression(expr, &context) {
1123 Ok(result) => is_truthy(&result),
1124 Err(_) => false,
1125 }
1126}
1127
1128fn is_truthy_string(s: &str) -> bool {
1130 !s.is_empty() && s != "false" && s != "0" && s != "null" && s != "undefined"
1131}
1132
1133fn is_truthy(value: &serde_json::Value) -> bool {
1135 match value {
1136 serde_json::Value::Null => false,
1137 serde_json::Value::Bool(b) => *b,
1138 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
1139 serde_json::Value::String(s) => is_truthy_string(s),
1140 serde_json::Value::Array(arr) => !arr.is_empty(),
1141 serde_json::Value::Object(obj) => !obj.is_empty(),
1142 }
1143}
1144
1145fn values_match(value: &serde_json::Value, pattern: &serde_json::Value) -> bool {
1152 match pattern {
1153 serde_json::Value::Array(patterns) => {
1155 patterns.iter().any(|p| values_match(value, p))
1156 }
1157
1158 serde_json::Value::String(pattern_str) => {
1160 if pattern_str == "_" || pattern_str == "*" {
1162 return true;
1163 }
1164
1165 if pattern_str.contains("${") {
1167 return evaluate_expression_pattern(pattern_str, value);
1168 }
1169
1170 matches!(value, serde_json::Value::String(s) if s == pattern_str)
1172 }
1173
1174 _ => {
1176 if value == pattern {
1177 return true;
1178 }
1179
1180 match (value, pattern) {
1182 (serde_json::Value::Bool(b), serde_json::Value::Bool(p)) => b == p,
1183 (serde_json::Value::Null, serde_json::Value::Null) => true,
1184 _ => false,
1185 }
1186 }
1187 }
1188}
1189
1190fn evaluate_value(value: &Value, state: &serde_json::Value) -> serde_json::Value {
1192 match value {
1193 Value::Static(v) => v.clone(),
1194 Value::Binding(binding) => {
1195 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
1196 }
1197 Value::TemplateString { template, .. } => {
1198 match evaluate_template_string(template, state, None) {
1199 Ok(result) => serde_json::Value::String(result),
1200 Err(_) => serde_json::Value::String(template.clone()),
1201 }
1202 }
1203 Value::Action(action) => serde_json::Value::String(format!("@{}", action)),
1204 }
1205}
1206
1207fn replace_ir_node_item_bindings(
1210 node: &IRNode,
1211 item: &serde_json::Value,
1212 index: usize,
1213 item_name: &str,
1214 item_key: &str,
1215) -> IRNode {
1216 match node {
1217 IRNode::Element(element) => {
1218 let mut new_element = replace_item_bindings_with_name(element, item, index, item_name);
1219 new_element.key = Some(item_key.to_string());
1221 IRNode::Element(new_element)
1222 }
1223 IRNode::ForEach { source, item_name: inner_item_name, key_path, template, props } => {
1224 let new_props = replace_props_item_bindings(props, item, item_name);
1227
1228 let new_template: Vec<IRNode> = template.iter()
1230 .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1231 .collect();
1232
1233 IRNode::ForEach {
1234 source: source.clone(),
1235 item_name: inner_item_name.clone(),
1236 key_path: key_path.clone(),
1237 template: new_template,
1238 props: new_props,
1239 }
1240 }
1241 IRNode::Conditional { value, branches, fallback } => {
1242 let new_value = replace_value_item_bindings(value, item, item_name);
1244
1245 let new_branches: Vec<ConditionalBranch> = branches.iter()
1247 .map(|branch| {
1248 let new_pattern = replace_value_item_bindings(&branch.pattern, item, item_name);
1249 let new_children: Vec<IRNode> = branch.children.iter()
1250 .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1251 .collect();
1252 ConditionalBranch::new(new_pattern, new_children)
1253 })
1254 .collect();
1255
1256 let new_fallback = fallback.as_ref().map(|f| {
1258 f.iter()
1259 .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1260 .collect()
1261 });
1262
1263 IRNode::Conditional {
1264 value: new_value,
1265 branches: new_branches,
1266 fallback: new_fallback,
1267 }
1268 }
1269 }
1270}
1271
1272fn replace_props_item_bindings(props: &Props, item: &serde_json::Value, item_name: &str) -> Props {
1274 let mut new_props = Props::new();
1275 for (key, value) in props {
1276 new_props.insert(key.clone(), replace_value_item_bindings(value, item, item_name));
1277 }
1278 new_props
1279}
1280
1281fn replace_value_item_bindings(value: &Value, item: &serde_json::Value, item_name: &str) -> Value {
1285 match value {
1286 Value::Binding(binding) => {
1287 if binding.is_item() {
1288 if binding.path.is_empty() {
1289 Value::Static(item.clone())
1290 } else {
1291 let path = binding.full_path();
1292 if let Some(val) = navigate_item_path(item, &path) {
1293 Value::Static(val.clone())
1294 } else {
1295 value.clone()
1296 }
1297 }
1298 } else {
1299 value.clone()
1300 }
1301 }
1302 Value::TemplateString { template, bindings } => {
1303 let has_item_bindings = bindings.iter().any(|b| b.is_item());
1304 if !has_item_bindings {
1305 return value.clone();
1306 }
1307
1308 let mut replacements = Vec::new();
1310 for binding in bindings {
1311 if binding.is_item() {
1312 let pattern = format!("${{{}}}", binding.full_path_with_source());
1313 if let Some(start) = template.find(&pattern) {
1314 let replacement = if binding.path.is_empty() {
1315 format_value_for_replacement(item, false)
1316 } else if let Some(val) = navigate_item_path(item, &binding.full_path()) {
1317 format_value_for_replacement(val, false)
1318 } else {
1319 continue;
1320 };
1321 replacements.push(Replacement {
1322 start,
1323 end: start + pattern.len(),
1324 text: replacement,
1325 });
1326 }
1327 }
1328 }
1329
1330 let mut result = apply_replacements(template, replacements);
1332
1333 let expr_replacements = collect_item_replacements_with_name(&result, item, true, item_name);
1336 result = apply_replacements(&result, expr_replacements);
1337
1338 let remaining_bindings: Vec<_> = bindings.iter()
1340 .filter(|b| b.is_state())
1341 .cloned()
1342 .collect();
1343
1344 if remaining_bindings.is_empty() {
1345 if result.contains("${") {
1346 match evaluate_template_string(&result, &serde_json::Value::Null, None) {
1347 Ok(evaluated) => Value::Static(serde_json::Value::String(evaluated)),
1348 Err(_) => Value::Static(serde_json::Value::String(result)),
1349 }
1350 } else {
1351 Value::Static(serde_json::Value::String(result))
1352 }
1353 } else {
1354 Value::TemplateString {
1355 template: result,
1356 bindings: remaining_bindings,
1357 }
1358 }
1359 }
1360 Value::Static(serde_json::Value::String(s)) if s.contains(&format!("${{{}.", item_name)) || s.contains(&format!("${{{}}}", item_name)) => {
1362 replace_static_item_bindings_with_name(s, item, item_name)
1363 }
1364 _ => value.clone(),
1365 }
1366}
1367
1368fn collect_item_replacements_with_name(s: &str, item: &serde_json::Value, quote_strings: bool, item_name: &str) -> Vec<Replacement> {
1370 let mut replacements = Vec::new();
1371 let mut pos = 0;
1372
1373 while pos < s.len() {
1374 if let Some(rel_start) = s[pos..].find(item_name) {
1375 let abs_start = pos + rel_start;
1376 let after_item = abs_start + item_name.len();
1377
1378 if after_item < s.len() && s.as_bytes()[after_item] == b'.' {
1379 let path_start = after_item + 1;
1380 let path_end = find_path_end(s, path_start);
1381
1382 if path_end > path_start {
1383 let path = &s[path_start..path_end];
1384 if let Some(val) = navigate_item_path(item, path) {
1385 let replacement = format_value_for_replacement(val, quote_strings);
1386 replacements.push(Replacement {
1387 start: abs_start,
1388 end: path_end,
1389 text: replacement,
1390 });
1391 }
1392 }
1393 pos = path_end.max(after_item + 1);
1394 } else {
1395 pos = after_item;
1396 }
1397 } else {
1398 break;
1399 }
1400 }
1401
1402 replacements
1403}
1404
1405fn replace_static_item_bindings_with_name(s: &str, item: &serde_json::Value, item_name: &str) -> Value {
1407 let mut replacements = Vec::new();
1408 let mut pos = 0;
1409
1410 while let Some(start) = s[pos..].find("${") {
1411 let abs_start = pos + start;
1412
1413 if let Some(end) = s[abs_start..].find('}') {
1414 let abs_end = abs_start + end;
1415 let content = &s[abs_start + 2..abs_end];
1416
1417 if content == item_name {
1418 replacements.push(Replacement {
1420 start: abs_start,
1421 end: abs_end + 1,
1422 text: format_value_for_replacement(item, false),
1423 });
1424 pos = abs_end + 1;
1425 } else if content.starts_with(&format!("{}.", item_name)) && is_simple_path_with_name(content, item_name) {
1426 let path = &content[item_name.len() + 1..];
1428 if let Some(val) = navigate_item_path(item, path) {
1429 replacements.push(Replacement {
1430 start: abs_start,
1431 end: abs_end + 1,
1432 text: format_value_for_replacement(val, false),
1433 });
1434 }
1435 pos = abs_end + 1;
1436 } else if content.contains(&format!("{}.", item_name)) || content.contains(&format!("{} ", item_name)) {
1437 let expr_replacements = collect_item_replacements_with_name(content, item, true, item_name);
1439 let substituted_content = apply_replacements(content, expr_replacements);
1440 let new_expr = format!("${{{}}}", substituted_content);
1441
1442 if let Ok(evaluated) = evaluate_template_string(&new_expr, &serde_json::Value::Null, None) {
1443 replacements.push(Replacement {
1444 start: abs_start,
1445 end: abs_end + 1,
1446 text: evaluated,
1447 });
1448 }
1449 pos = abs_end + 1;
1450 } else {
1451 pos = abs_end + 1;
1452 }
1453 } else {
1454 break;
1455 }
1456 }
1457
1458 let result = apply_replacements(s, replacements);
1459 Value::Static(serde_json::Value::String(result))
1460}
1461
1462fn is_simple_path_with_name(s: &str, item_name: &str) -> bool {
1464 if !s.starts_with(item_name) {
1465 return false;
1466 }
1467
1468 let after_item = &s[item_name.len()..];
1469 if after_item.is_empty() {
1470 return true;
1471 }
1472
1473 if !after_item.starts_with('.') {
1474 return false;
1475 }
1476
1477 after_item[1..].chars().all(|c| c.is_alphanumeric() || c == '_' || c == '.')
1478}
1479
1480pub fn replace_item_bindings_with_name(
1483 element: &Element,
1484 item: &serde_json::Value,
1485 index: usize,
1486 item_name: &str,
1487) -> Element {
1488 let mut new_element = element.clone();
1489
1490 let mut new_props = Props::new();
1492 for (key, value) in &element.props {
1493 new_props.insert(key.clone(), replace_value_item_bindings(value, item, item_name));
1494 }
1495 new_element.props = new_props;
1496
1497 let key = item.get("id")
1499 .and_then(|v| v.as_str().map(|s| s.to_string()).or_else(|| v.as_i64().map(|n| n.to_string())))
1500 .or_else(|| item.get("key").and_then(|v| v.as_str().map(|s| s.to_string())))
1501 .map(|id| format!("{}-{}", item_name, id))
1502 .unwrap_or_else(|| format!("{}-{}", item_name, index));
1503 new_element.key = Some(key);
1504
1505 new_element.children = element.children.iter()
1507 .map(|child| std::sync::Arc::new(replace_item_bindings_with_name(child, item, index, item_name)))
1508 .collect();
1509
1510 new_element
1511}
1512
1513pub fn reconcile_ir_node(
1515 tree: &mut InstanceTree,
1516 node_id: NodeId,
1517 node: &IRNode,
1518 state: &serde_json::Value,
1519 patches: &mut Vec<Patch>,
1520 dependencies: &mut DependencyGraph,
1521) {
1522 let existing_node = tree.get(node_id).cloned();
1523 if existing_node.is_none() {
1524 return;
1525 }
1526 let existing = existing_node.unwrap();
1527
1528 match node {
1529 IRNode::Element(element) => {
1530 reconcile_node(tree, node_id, element, state, patches, dependencies);
1532 }
1533 IRNode::ForEach { source, item_name, key_path, template, props: _ } => {
1534 if !existing.is_foreach() {
1536 let parent_id = existing.parent;
1538 remove_subtree(tree, node_id, patches, dependencies);
1539 create_ir_node_tree(tree, node, parent_id, state, patches, parent_id.is_none(), dependencies);
1540 return;
1541 }
1542
1543 let array = evaluate_binding(source, state).unwrap_or(serde_json::Value::Array(vec![]));
1545
1546 if let serde_json::Value::Array(items) = &array {
1547 let old_children = existing.children.clone();
1548 let expected_children_count = items.len() * template.len();
1549
1550 if old_children.len() != expected_children_count {
1552 for &old_child_id in &old_children {
1554 patches.push(Patch::remove(old_child_id));
1555 }
1556
1557 if let Some(node) = tree.get_mut(node_id) {
1558 node.children.clear();
1559 }
1560
1561 for (index, item) in items.iter().enumerate() {
1563 let item_key = generate_item_key(item, key_path.as_deref(), item_name, index);
1564
1565 for child_template in template {
1566 let child_with_item = replace_ir_node_item_bindings(
1567 child_template,
1568 item,
1569 index,
1570 item_name,
1571 &item_key,
1572 );
1573 create_ir_node_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
1574 }
1575 }
1576 } else {
1577 let mut child_index = 0;
1579 for (item_index, item) in items.iter().enumerate() {
1580 let item_key = generate_item_key(item, key_path.as_deref(), item_name, item_index);
1581
1582 for child_template in template {
1583 if let Some(&old_child_id) = old_children.get(child_index) {
1584 let child_with_item = replace_ir_node_item_bindings(
1585 child_template,
1586 item,
1587 item_index,
1588 item_name,
1589 &item_key,
1590 );
1591 reconcile_ir_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
1592 }
1593 child_index += 1;
1594 }
1595 }
1596 }
1597 }
1598 }
1599 IRNode::Conditional { value, branches, fallback } => {
1600 if !existing.is_conditional() {
1602 let parent_id = existing.parent;
1604 remove_subtree(tree, node_id, patches, dependencies);
1605 create_ir_node_tree(tree, node, parent_id, state, patches, parent_id.is_none(), dependencies);
1606 return;
1607 }
1608
1609 let evaluated_value = evaluate_value(value, state);
1611 let matched_children = find_matching_branch(&evaluated_value, branches, fallback.as_deref(), state);
1612
1613 let old_children = existing.children.clone();
1614 let old_len = old_children.len();
1615
1616 if let Some(children) = matched_children {
1617 let new_len = children.len();
1618 let common = old_len.min(new_len);
1619
1620 for (i, child) in children.iter().enumerate().take(common) {
1622 if let Some(&old_child_id) = old_children.get(i) {
1623 reconcile_ir_node(tree, old_child_id, child, state, patches, dependencies);
1624 }
1625 }
1626
1627 for i in common..old_len {
1629 if let Some(&old_child_id) = old_children.get(i) {
1630 remove_subtree(tree, old_child_id, patches, dependencies);
1631 if let Some(cond_node) = tree.get_mut(node_id) {
1632 cond_node.children = cond_node.children.iter()
1633 .filter(|&&id| id != old_child_id)
1634 .copied()
1635 .collect();
1636 }
1637 }
1638 }
1639
1640 for child in &children[common..] {
1642 create_ir_node_tree(tree, child, Some(node_id), state, patches, false, dependencies);
1643 }
1644 } else {
1645 for &old_child_id in &old_children {
1647 remove_subtree(tree, old_child_id, patches, dependencies);
1648 }
1649
1650 if let Some(cond_node) = tree.get_mut(node_id) {
1651 cond_node.children.clear();
1652 }
1653 }
1654 }
1655 }
1656}
1657
1658fn remove_subtree(
1660 tree: &mut InstanceTree,
1661 node_id: NodeId,
1662 patches: &mut Vec<Patch>,
1663 dependencies: &mut DependencyGraph,
1664) {
1665 let ids = collect_subtree_ids(tree, node_id);
1666 for &id in &ids {
1667 patches.push(Patch::remove(id));
1668 dependencies.remove_node(id);
1669 }
1670 tree.remove(node_id);
1671}
1672
1673#[cfg(test)]
1674mod tests {
1675 use super::*;
1676 use crate::ir::Value;
1677 use serde_json::json;
1678
1679 #[test]
1680 fn test_create_simple_tree() {
1681 use crate::reactive::DependencyGraph;
1682
1683 let mut tree = InstanceTree::new();
1684 let mut patches = Vec::new();
1685 let mut dependencies = DependencyGraph::new();
1686
1687 let element = Element::new("Column")
1688 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
1689
1690 let state = json!({});
1691 create_tree(&mut tree, &element, None, &state, &mut patches, true, &mut dependencies);
1692
1693 assert_eq!(patches.len(), 4);
1696
1697 let root_insert = patches.iter().find(|p| {
1699 matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root")
1700 });
1701 assert!(root_insert.is_some(), "Root insert patch should exist");
1702 }
1703
1704 #[test]
1705 fn test_diff_props() {
1706 let node_id = NodeId::default();
1707 let old = indexmap::indexmap! {
1708 "color".to_string() => json!("red"),
1709 "size".to_string() => json!(16),
1710 };
1711 let new = indexmap::indexmap! {
1712 "color".to_string() => json!("blue"),
1713 "size".to_string() => json!(16),
1714 };
1715
1716 let patches = diff_props(node_id, &old, &new);
1717
1718 assert_eq!(patches.len(), 1);
1720 }
1721
1722 #[test]
1723 fn test_longest_increasing_subsequence_basic() {
1724 let positions = vec![Some(0), Some(1), Some(2), Some(3)];
1726 let lis = longest_increasing_subsequence(&positions);
1727 assert_eq!(lis, vec![0, 1, 2, 3]);
1728 }
1729
1730 #[test]
1731 fn test_longest_increasing_subsequence_reverse() {
1732 let positions = vec![Some(3), Some(2), Some(1), Some(0)];
1734 let lis = longest_increasing_subsequence(&positions);
1735 assert_eq!(lis.len(), 1);
1736 }
1737
1738 #[test]
1739 fn test_longest_increasing_subsequence_mixed() {
1740 let positions = vec![Some(0), Some(3), Some(1), Some(2)];
1742 let lis = longest_increasing_subsequence(&positions);
1743 assert_eq!(lis.len(), 3);
1744 assert!(lis.contains(&0));
1746 assert!(lis.contains(&2));
1747 assert!(lis.contains(&3));
1748 }
1749
1750 #[test]
1751 fn test_longest_increasing_subsequence_with_new_items() {
1752 let positions = vec![None, Some(0), None, Some(1)];
1754 let lis = longest_increasing_subsequence(&positions);
1755 assert_eq!(lis, vec![1, 3]); }
1757
1758 #[test]
1759 fn test_longest_increasing_subsequence_empty() {
1760 let positions: Vec<Option<usize>> = vec![];
1761 let lis = longest_increasing_subsequence(&positions);
1762 assert_eq!(lis, Vec::<usize>::new());
1763 }
1764
1765 #[test]
1766 fn test_longest_increasing_subsequence_all_new() {
1767 let positions = vec![None, None, None];
1769 let lis = longest_increasing_subsequence(&positions);
1770 assert_eq!(lis, Vec::<usize>::new());
1771 }
1772
1773 #[test]
1774 fn test_reconcile_keyed_children_basic() {
1775 let mut tree = InstanceTree::new();
1776 let mut dependencies = DependencyGraph::new();
1777 let state = json!({});
1778
1779 let parent = Element::new("Column");
1781 let parent_id = tree.create_node(&parent, &state);
1782
1783 let mut old_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1785 old_child_1.key = Some("item-1".to_string());
1786 let old_id_1 = tree.create_node(&old_child_1, &state);
1787
1788 let mut old_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1789 old_child_2.key = Some("item-2".to_string());
1790 let old_id_2 = tree.create_node(&old_child_2, &state);
1791
1792 tree.add_child(parent_id, old_id_1, None);
1793 tree.add_child(parent_id, old_id_2, None);
1794
1795 let mut new_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1797 new_child_2.key = Some("item-2".to_string());
1798
1799 let mut new_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1800 new_child_1.key = Some("item-1".to_string());
1801
1802 let new_children = vec![new_child_2, new_child_1];
1803 let old_children = vec![old_id_1, old_id_2];
1804
1805 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1806
1807 let move_count = patches.iter().filter(|p| matches!(p, Patch::Move { .. })).count();
1809 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1810 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1811
1812 assert_eq!(create_count, 0, "Should not create new children");
1813 assert_eq!(remove_count, 0, "Should not remove any children");
1814 assert!(move_count > 0, "Should have move patches for reordering");
1815 }
1816
1817 #[test]
1818 fn test_reconcile_keyed_children_add_remove() {
1819 let mut tree = InstanceTree::new();
1820 let mut dependencies = DependencyGraph::new();
1821 let state = json!({});
1822
1823 let parent = Element::new("Column");
1824 let parent_id = tree.create_node(&parent, &state);
1825
1826 let mut old_child_1 = Element::new("Text");
1828 old_child_1.key = Some("item-1".to_string());
1829 let old_id_1 = tree.create_node(&old_child_1, &state);
1830
1831 let mut old_child_2 = Element::new("Text");
1832 old_child_2.key = Some("item-2".to_string());
1833 let old_id_2 = tree.create_node(&old_child_2, &state);
1834
1835 tree.add_child(parent_id, old_id_1, None);
1836 tree.add_child(parent_id, old_id_2, None);
1837
1838 let mut new_child_2 = Element::new("Text");
1840 new_child_2.key = Some("item-2".to_string());
1841
1842 let mut new_child_3 = Element::new("Text");
1843 new_child_3.key = Some("item-3".to_string());
1844
1845 let new_children = vec![new_child_2, new_child_3];
1846 let old_children = vec![old_id_1, old_id_2];
1847
1848 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1849
1850 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1851 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1852
1853 assert_eq!(create_count, 1, "Should create item-3");
1854 assert_eq!(remove_count, 1, "Should remove item-1");
1855 }
1856
1857 #[test]
1858 fn test_pattern_matches_binding_truthy() {
1859 use crate::reactive::Binding;
1860
1861 let state = json!({"isAdmin": true, "count": 0});
1862 let evaluated = json!("anything"); let pattern = Value::Binding(Binding::state(vec!["isAdmin".to_string()]));
1866 assert!(pattern_matches(&evaluated, &pattern, &state));
1867
1868 let pattern = Value::Binding(Binding::state(vec!["count".to_string()]));
1870 assert!(!pattern_matches(&evaluated, &pattern, &state));
1871
1872 let pattern = Value::Binding(Binding::state(vec!["missing".to_string()]));
1874 assert!(!pattern_matches(&evaluated, &pattern, &state));
1875 }
1876
1877 #[test]
1878 fn test_lis_large_sequence() {
1879 let positions: Vec<Option<usize>> = vec![
1882 Some(3), Some(1), Some(4), Some(1), Some(5), Some(9), Some(2), Some(6),
1883 ];
1884 let lis = longest_increasing_subsequence(&positions);
1885 assert_eq!(lis.len(), 4);
1886 let values: Vec<usize> = lis.iter().map(|&i| positions[i].unwrap()).collect();
1888 for w in values.windows(2) {
1889 assert!(w[0] < w[1], "LIS must be strictly increasing: {:?}", values);
1890 }
1891 }
1892}