1use super::{InstanceTree, Patch, ControlFlowKind};
2use crate::ir::{Element, NodeId, Value, IRNode, ConditionalBranch, Props};
3use crate::reactive::{DependencyGraph, evaluate_template_string, Binding};
4use indexmap::IndexMap;
5
6pub fn reconcile(
8 tree: &mut InstanceTree,
9 element: &Element,
10 parent_id: Option<NodeId>,
11 state: &serde_json::Value,
12 dependencies: &mut DependencyGraph,
13) -> Vec<Patch> {
14 let mut patches = Vec::new();
15
16 if tree.root().is_none() {
18 let node_id = create_tree(tree, element, parent_id, state, &mut patches, true, dependencies);
19 tree.set_root(node_id);
20 return patches;
21 }
22
23 if let Some(root_id) = tree.root() {
25 reconcile_node(tree, root_id, element, state, &mut patches, dependencies);
26 }
27
28 patches
29}
30
31pub fn reconcile_ir(
35 tree: &mut InstanceTree,
36 node: &IRNode,
37 parent_id: Option<NodeId>,
38 state: &serde_json::Value,
39 dependencies: &mut DependencyGraph,
40) -> Vec<Patch> {
41 let mut patches = Vec::new();
42
43 if tree.root().is_none() {
45 let node_id = create_ir_node_tree(tree, node, parent_id, state, &mut patches, true, dependencies);
46 tree.set_root(node_id);
47 return patches;
48 }
49
50 if let Some(root_id) = tree.root() {
52 reconcile_ir_node(tree, root_id, node, state, &mut patches, dependencies);
53 }
54
55 patches
56}
57
58pub fn create_tree(
60 tree: &mut InstanceTree,
61 element: &Element,
62 parent_id: Option<NodeId>,
63 state: &serde_json::Value,
64 patches: &mut Vec<Patch>,
65 is_root: bool,
66 dependencies: &mut DependencyGraph,
67) -> NodeId {
68 if let Some(Value::Binding(_)) = element.props.get("0") {
76 if !element.children.is_empty() {
77 return create_list_tree(tree, element, parent_id, state, patches, is_root, dependencies);
78 }
79 }
80
81 let node_id = tree.create_node(element, state);
83
84 for value in element.props.values() {
86 match value {
87 Value::Binding(binding) => {
88 dependencies.add_dependency(node_id, binding);
89 }
90 Value::TemplateString { bindings, .. } => {
91 for binding in bindings {
93 dependencies.add_dependency(node_id, binding);
94 }
95 }
96 _ => {}
97 }
98 }
99
100 let node = tree.get(node_id).unwrap();
102
103 let mut props = node.props.clone();
105 if let Some(Value::Static(val)) = element.props.get("__lazy") {
106 if val.as_bool().unwrap_or(false) && !element.children.is_empty() {
107 let child_component = &element.children[0].element_type;
109 props.insert("__lazy_child".to_string(), serde_json::json!(child_component));
110 }
111 }
112
113 patches.push(Patch::create(
114 node_id,
115 node.element_type.clone(),
116 props,
117 ));
118
119 if let Some(parent) = parent_id {
123 tree.add_child(parent, node_id, None);
124 patches.push(Patch::insert(parent, node_id, None));
125 } else if is_root {
126 patches.push(Patch::insert_root(node_id));
128 }
129
130 let is_lazy = element.props.get("__lazy")
132 .and_then(|v| {
133 if let Value::Static(val) = v {
134 val.as_bool()
135 } else {
136 None
137 }
138 })
139 .unwrap_or(false);
140
141 if !is_lazy {
142 for child_element in &element.children {
143 create_tree(tree, child_element, Some(node_id), state, patches, false, dependencies);
144 }
145 }
146
147 node_id
148}
149
150fn create_list_tree(
153 tree: &mut InstanceTree,
154 element: &Element,
155 parent_id: Option<NodeId>,
156 state: &serde_json::Value,
157 patches: &mut Vec<Patch>,
158 is_root: bool,
159 dependencies: &mut DependencyGraph,
160) -> NodeId {
161 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
163 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
164 } else {
165 serde_json::Value::Array(vec![])
166 };
167
168 let mut list_element = Element::new(&element.element_type);
171 for (key, value) in &element.props {
172 if key != "0" {
174 list_element.props.insert(key.clone(), value.clone());
175 }
176 }
177
178 let node_id = tree.create_node(&list_element, state);
179
180 if let Some(Value::Binding(binding)) = element.props.get("0") {
183 dependencies.add_dependency(node_id, binding);
184 }
185
186 if let Some(node) = tree.get_mut(node_id) {
189 node.raw_props = element.props.clone();
190 node.element_template = Some(std::sync::Arc::new(element.clone()));
191 }
192
193 let node = tree.get(node_id).unwrap();
195 patches.push(Patch::create(
196 node_id,
197 node.element_type.clone(),
198 node.props.clone(),
199 ));
200
201 if let Some(parent) = parent_id {
203 tree.add_child(parent, node_id, None);
204 patches.push(Patch::insert(parent, node_id, None));
205 } else if is_root {
206 patches.push(Patch::insert_root(node_id));
207 }
208
209 if let serde_json::Value::Array(items) = &array {
211 for (index, item) in items.iter().enumerate() {
213 for child_template in &element.children {
214 let child_with_item = replace_item_bindings(child_template, item, index);
216 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
217 }
218 }
219 }
220
221 node_id
222}
223
224fn navigate_item_path<'a>(item: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> {
227 let mut current = item;
228
229 for segment in path.split('.') {
230 if let Ok(index) = segment.parse::<usize>() {
232 current = current.get(index)?;
233 } else {
234 current = current.get(segment)?;
236 }
237 }
238
239 Some(current)
240}
241
242#[derive(Debug)]
245struct Replacement {
246 start: usize,
247 end: usize,
248 text: String,
249}
250
251fn apply_replacements(s: &str, mut replacements: Vec<Replacement>) -> String {
254 if replacements.is_empty() {
255 return s.to_string();
256 }
257
258 replacements.sort_by(|a, b| a.start.cmp(&b.start));
260
261 let valid_replacements: Vec<_> = replacements
263 .into_iter()
264 .filter(|r| r.start <= s.len() && r.end <= s.len() && r.start <= r.end)
265 .collect();
266
267 if valid_replacements.is_empty() {
268 return s.to_string();
269 }
270
271 let removed: usize = valid_replacements.iter().map(|r| r.end - r.start).sum();
273 let added: usize = valid_replacements.iter().map(|r| r.text.len()).sum();
274 let capacity = s.len() - removed + added;
275
276 let mut result = String::with_capacity(capacity);
277 let mut pos = 0;
278
279 for r in valid_replacements {
280 if r.start > pos {
282 result.push_str(&s[pos..r.start]);
283 }
284 result.push_str(&r.text);
286 pos = r.end;
287 }
288
289 if pos < s.len() {
291 result.push_str(&s[pos..]);
292 }
293
294 result
295}
296
297fn find_path_end(s: &str, start: usize) -> usize {
299 let chars: Vec<char> = s[start..].chars().collect();
300 let mut end = start;
301
302 for (i, ch) in chars.iter().enumerate() {
303 if ch.is_alphanumeric() || *ch == '_' {
304 end = start + i + 1;
305 } else if *ch == '.' {
306 if i + 1 < chars.len() {
308 let next = chars[i + 1];
309 if next.is_alphanumeric() || next == '_' {
310 end = start + i + 1;
311 continue;
312 }
313 }
314 break;
315 } else {
316 break;
317 }
318 }
319
320 end
321}
322
323fn format_value_for_replacement(val: &serde_json::Value, quote_strings: bool) -> String {
325 match val {
326 serde_json::Value::String(s) => {
327 if quote_strings {
328 format!("'{}'", s)
329 } else {
330 s.clone()
331 }
332 }
333 serde_json::Value::Number(n) => n.to_string(),
334 serde_json::Value::Bool(b) => b.to_string(),
335 serde_json::Value::Null => "null".to_string(),
336 _ => serde_json::to_string(val).unwrap_or_default(),
337 }
338}
339
340pub fn replace_item_bindings(element: &Element, item: &serde_json::Value, index: usize) -> Element {
344 replace_item_bindings_with_name(element, item, index, "item")
345}
346
347pub fn reconcile_node(
349 tree: &mut InstanceTree,
350 node_id: NodeId,
351 element: &Element,
352 state: &serde_json::Value,
353 patches: &mut Vec<Patch>,
354 dependencies: &mut DependencyGraph,
355) {
356 let node = tree.get(node_id).cloned();
357 if node.is_none() {
358 return;
359 }
360 let node = node.unwrap();
361
362 let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
366
367 if is_iterable {
368 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
370 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
371 } else {
372 serde_json::Value::Array(vec![])
373 };
374
375 if let serde_json::Value::Array(items) = &array {
377 let old_children = node.children.clone();
378
379 let expected_children_count = items.len() * element.children.len();
381
382 if old_children.len() != expected_children_count {
384 for &old_child_id in &old_children {
385 patches.push(Patch::remove(old_child_id));
386 }
387
388 if let Some(node) = tree.get_mut(node_id) {
390 node.children.clear();
391 }
392
393 for (index, item) in items.iter().enumerate() {
395 for child_template in &element.children {
396 let child_with_item = replace_item_bindings(child_template, item, index);
397 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
398 }
399 }
400 } else {
401 let mut child_index = 0;
403 for (item_index, item) in items.iter().enumerate() {
404 for child_template in &element.children {
405 if let Some(&old_child_id) = old_children.get(child_index) {
406 let child_with_item = replace_item_bindings(child_template, item, item_index);
407 reconcile_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
408 }
409 child_index += 1;
410 }
411 }
412 }
413 }
414
415 return; }
417
418 if node.element_type != element.element_type {
420 replace_subtree(tree, node_id, &node, element, state, patches, dependencies);
421 return;
422 }
423
424 for value in element.props.values() {
426 if let Value::Binding(binding) = value {
427 dependencies.add_dependency(node_id, binding);
428 }
429 }
430
431 let new_props = resolve_props(&element.props, state);
433 let prop_patches = diff_props(node_id, &node.props, &new_props);
434 patches.extend(prop_patches);
435
436 if let Some(node) = tree.get_mut(node_id) {
438 node.props = new_props.clone();
439 node.raw_props = element.props.clone();
440 }
441
442 let is_lazy = element.props.get("__lazy")
444 .and_then(|v| {
445 if let Value::Static(val) = v {
446 val.as_bool()
447 } else {
448 None
449 }
450 })
451 .unwrap_or(false);
452
453 if !is_lazy {
454 let old_children = node.children.clone();
455 let new_children = &element.children;
456
457 for (i, new_child_element) in new_children.iter().enumerate() {
459 if let Some(&old_child_id) = old_children.get(i) {
460 reconcile_node(tree, old_child_id, new_child_element, state, patches, dependencies);
462 } else {
463 let new_child_id = create_tree(tree, new_child_element, Some(node_id), state, patches, false, dependencies);
465 if let Some(node) = tree.get_mut(node_id) {
466 node.children.push_back(new_child_id);
467 }
468 }
469 }
470
471 if old_children.len() > new_children.len() {
473 for old_child_id in old_children.iter().skip(new_children.len()).copied() {
474 let subtree_ids = collect_subtree_ids(tree, old_child_id);
476 for &id in &subtree_ids {
477 patches.push(Patch::remove(id));
478 dependencies.remove_node(id);
479 }
480 tree.remove_child(node_id, old_child_id);
482 tree.remove(old_child_id);
483 }
484 }
485 }
486}
487
488fn replace_subtree(
491 tree: &mut InstanceTree,
492 old_node_id: NodeId,
493 old_node: &super::InstanceNode,
494 new_element: &Element,
495 state: &serde_json::Value,
496 patches: &mut Vec<Patch>,
497 dependencies: &mut DependencyGraph,
498) {
499 let parent_id = old_node.parent;
500
501 let old_position = if let Some(pid) = parent_id {
503 tree.get(pid).and_then(|parent| {
504 parent.children.iter().position(|&id| id == old_node_id)
505 })
506 } else {
507 None
508 };
509
510 let ids_to_remove = collect_subtree_ids(tree, old_node_id);
512
513 for &id in &ids_to_remove {
515 patches.push(Patch::remove(id));
516 dependencies.remove_node(id);
517 }
518
519 if let Some(pid) = parent_id {
521 if let Some(parent) = tree.get_mut(pid) {
522 parent.children = parent.children.iter().filter(|&&id| id != old_node_id).copied().collect();
523 }
524 }
525
526 tree.remove(old_node_id);
528
529 let is_root = parent_id.is_none();
531 let new_node_id = create_tree(tree, new_element, parent_id, state, patches, is_root, dependencies);
532
533 if is_root {
535 tree.set_root(new_node_id);
536 } else if let Some(pid) = parent_id {
537 if let Some(pos) = old_position {
538 if let Some(parent) = tree.get_mut(pid) {
539 let current_len = parent.children.len();
540 if pos < current_len - 1 {
543 let new_id = parent.children.pop_back().unwrap();
545 parent.children.insert(pos, new_id);
546
547 let next_sibling = parent.children.get(pos + 1).copied();
549 patches.push(Patch::move_node(pid, new_node_id, next_sibling));
550 }
551 }
553 }
554 }
555}
556
557fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
560 let mut result = Vec::new();
561 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
562
563 while let Some((node_id, children_processed)) = stack.pop() {
564 if children_processed {
565 result.push(node_id);
567 } else {
568 stack.push((node_id, true));
570 if let Some(node) = tree.get(node_id) {
571 for &child_id in node.children.iter().rev() {
573 stack.push((child_id, false));
574 }
575 }
576 }
577 }
578
579 result
580}
581
582fn resolve_props(props: &IndexMap<String, crate::ir::Value>, state: &serde_json::Value) -> IndexMap<String, serde_json::Value> {
584 resolve_props_with_item(props, state, None)
585}
586
587fn resolve_props_with_item(
589 props: &IndexMap<String, crate::ir::Value>,
590 state: &serde_json::Value,
591 item: Option<&serde_json::Value>,
592) -> IndexMap<String, serde_json::Value> {
593 let mut resolved = IndexMap::new();
594
595 for (key, value) in props {
596 let resolved_value = match value {
597 crate::ir::Value::Static(v) => v.clone(),
598 crate::ir::Value::Binding(binding) => {
599 if binding.is_item() {
600 if let Some(item_value) = item {
602 evaluate_item_binding(binding, item_value).unwrap_or(serde_json::Value::Null)
603 } else {
604 serde_json::Value::Null
605 }
606 } else {
607 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
609 }
610 }
611 crate::ir::Value::TemplateString { template, .. } => {
612 match crate::reactive::evaluate_template_string(template, state, item) {
615 Ok(result) => serde_json::Value::String(result),
616 Err(_) => serde_json::Value::String(template.clone()),
617 }
618 }
619 crate::ir::Value::Action(action) => {
620 serde_json::Value::String(format!("@{}", action))
622 }
623 };
624 resolved.insert(key.clone(), resolved_value);
625 }
626
627 resolved
628}
629
630fn evaluate_binding_path(binding: &crate::reactive::Binding, root: &serde_json::Value) -> Option<serde_json::Value> {
633 if binding.path.is_empty() {
634 return Some(root.clone());
635 }
636
637 let mut current = root;
638 for segment in &binding.path {
639 current = current.get(segment)?;
640 }
641 Some(current.clone())
642}
643
644fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
646 evaluate_binding_path(binding, state)
647}
648
649fn evaluate_item_binding(binding: &crate::reactive::Binding, item: &serde_json::Value) -> Option<serde_json::Value> {
651 evaluate_binding_path(binding, item)
652}
653
654fn generate_item_key(item: &serde_json::Value, key_path: Option<&str>, item_name: &str, index: usize) -> String {
657 if let Some(kp) = key_path {
658 item.get(kp)
659 .map(|v| match v {
660 serde_json::Value::String(s) => s.clone(),
661 serde_json::Value::Number(n) => n.to_string(),
662 _ => format!("{}-{}", item_name, index),
663 })
664 .unwrap_or_else(|| format!("{}-{}", item_name, index))
665 } else {
666 format!("{}-{}", item_name, index)
667 }
668}
669
670pub fn reconcile_keyed_children(
673 tree: &mut InstanceTree,
674 parent_id: NodeId,
675 old_children: &[NodeId],
676 new_children: &[Element],
677 state: &serde_json::Value,
678 dependencies: &mut DependencyGraph,
679) -> Vec<Patch> {
680 let mut patches = Vec::new();
681
682 let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
684 let mut old_unkeyed: Vec<NodeId> = Vec::new();
685
686 for &child_id in old_children {
687 if let Some(node) = tree.get(child_id) {
688 if let Some(key) = &node.key {
689 old_keyed.insert(key.clone(), child_id);
690 } else {
691 old_unkeyed.push(child_id);
692 }
693 }
694 }
695
696 let mut new_child_ids: Vec<NodeId> = Vec::new();
697 let mut unkeyed_idx = 0;
698
699 for new_element in new_children {
701 let new_key = new_element.key.as_ref();
702
703 let child_id = if let Some(key) = new_key {
704 if let Some(&old_id) = old_keyed.get(key) {
706 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
708 old_keyed.shift_remove(key); old_id
710 } else {
711 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
713 }
714 } else {
715 if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
717 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
718 unkeyed_idx += 1;
719 old_id
720 } else {
721 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
723 }
724 };
725
726 new_child_ids.push(child_id);
727 }
728
729 let mut old_positions: Vec<Option<usize>> = Vec::new();
732 for &new_id in &new_child_ids {
733 let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
734 old_positions.push(old_pos);
735 }
736
737 let lis = longest_increasing_subsequence(&old_positions);
739
740 if let Some(parent_node) = tree.get_mut(parent_id) {
742 parent_node.children = new_child_ids.iter().copied().collect();
743 }
744
745 for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
747 if !lis.contains(&new_idx) {
749 let before_id = new_child_ids.get(new_idx + 1).copied();
751 patches.push(Patch::move_node(parent_id, child_id, before_id));
752 }
753 }
754
755 for &old_id in old_keyed.values() {
758 dependencies.remove_node(old_id);
759 tree.remove(old_id);
760 patches.push(Patch::remove(old_id));
761 }
762 for &old_id in &old_unkeyed[unkeyed_idx..] {
763 dependencies.remove_node(old_id);
764 tree.remove(old_id);
765 patches.push(Patch::remove(old_id));
766 }
767
768 patches
769}
770
771fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
775 let valid: Vec<(usize, usize)> = positions
777 .iter()
778 .enumerate()
779 .filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
780 .collect();
781
782 if valid.is_empty() {
783 return Vec::new();
784 }
785
786 let n = valid.len();
788 let mut dp: Vec<usize> = vec![1; n];
789 let mut prev: Vec<Option<usize>> = vec![None; n];
790
791 for i in 1..n {
792 for j in 0..i {
793 if valid[j].1 < valid[i].1 && dp[j] + 1 > dp[i] {
794 dp[i] = dp[j] + 1;
795 prev[i] = Some(j);
796 }
797 }
798 }
799
800 let mut max_idx = 0;
802 for i in 1..n {
803 if dp[i] > dp[max_idx] {
804 max_idx = i;
805 }
806 }
807
808 let mut result = Vec::new();
810 let mut idx = Some(max_idx);
811 while let Some(i) = idx {
812 result.push(valid[i].0);
813 idx = prev[i];
814 }
815 result.reverse();
816
817 result
818}
819
820pub fn diff_props(
822 node_id: NodeId,
823 old_props: &IndexMap<String, serde_json::Value>,
824 new_props: &IndexMap<String, serde_json::Value>,
825) -> Vec<Patch> {
826 let mut patches = Vec::new();
827
828 for (key, new_value) in new_props {
830 if old_props.get(key) != Some(new_value) {
831 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
832 }
833 }
834
835 patches
839}
840
841pub fn create_ir_node_tree(
847 tree: &mut InstanceTree,
848 node: &IRNode,
849 parent_id: Option<NodeId>,
850 state: &serde_json::Value,
851 patches: &mut Vec<Patch>,
852 is_root: bool,
853 dependencies: &mut DependencyGraph,
854) -> NodeId {
855 match node {
856 IRNode::Element(element) => {
857 create_tree(tree, element, parent_id, state, patches, is_root, dependencies)
859 }
860 IRNode::ForEach { source, item_name, key_path, template, props } => {
861 create_foreach_ir_tree(
862 tree,
863 source,
864 item_name,
865 key_path.as_deref(),
866 template,
867 props,
868 node,
869 parent_id,
870 state,
871 patches,
872 is_root,
873 dependencies,
874 )
875 }
876 IRNode::Conditional { value, branches, fallback } => {
877 create_conditional_tree(
878 tree,
879 value,
880 branches,
881 fallback.as_deref(),
882 node,
883 parent_id,
884 state,
885 patches,
886 is_root,
887 dependencies,
888 )
889 }
890 }
891}
892
893fn create_ir_node_tree_with_render_parent(
902 tree: &mut InstanceTree,
903 node: &IRNode,
904 logical_parent: Option<NodeId>,
905 render_parent: 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 let node_id = tree.create_node(element, state);
915
916 if let Some(parent) = logical_parent {
918 tree.add_child(parent, node_id, None);
919 }
920
921 patches.push(Patch::create(
923 node_id,
924 element.element_type.clone(),
925 tree.get(node_id).map(|n| n.props.clone()).unwrap_or_default(),
926 ));
927
928 if let Some(render_p) = render_parent {
929 patches.push(Patch::insert(render_p, node_id, None));
930 } else if is_root {
931 patches.push(Patch::insert_root(node_id));
932 }
933
934 for (_, value) in &element.props {
936 if let Value::Binding(binding) = value {
937 dependencies.add_dependency(node_id, binding);
938 }
939 }
940
941 for child in &element.children {
943 let child_ir = IRNode::Element((**child).clone());
944 create_ir_node_tree(tree, &child_ir, Some(node_id), state, patches, false, dependencies);
945 }
946
947 node_id
948 }
949 IRNode::ForEach { source, item_name, key_path, template, props } => {
950 create_foreach_ir_tree(
952 tree,
953 source,
954 item_name,
955 key_path.as_deref(),
956 template,
957 props,
958 node,
959 logical_parent, state,
961 patches,
962 is_root,
963 dependencies,
964 )
965 }
966 IRNode::Conditional { value, branches, fallback } => {
967 create_conditional_tree(
969 tree,
970 value,
971 branches,
972 fallback.as_deref(),
973 node,
974 logical_parent, state,
976 patches,
977 is_root,
978 dependencies,
979 )
980 }
981 }
982}
983
984fn create_foreach_ir_tree(
986 tree: &mut InstanceTree,
987 source: &Binding,
988 item_name: &str,
989 key_path: Option<&str>,
990 template: &[IRNode],
991 props: &Props,
992 original_node: &IRNode,
993 parent_id: Option<NodeId>,
994 state: &serde_json::Value,
995 patches: &mut Vec<Patch>,
996 is_root: bool,
997 dependencies: &mut DependencyGraph,
998) -> NodeId {
999 let array = evaluate_binding(source, state).unwrap_or(serde_json::Value::Array(vec![]));
1001
1002 let resolved_props = resolve_props(props, state);
1004
1005 let node_id = tree.create_control_flow_node(
1007 "__ForEach",
1008 resolved_props.clone(),
1009 props.clone(),
1010 ControlFlowKind::ForEach {
1011 item_name: item_name.to_string(),
1012 key_path: key_path.map(|s| s.to_string()),
1013 },
1014 original_node.clone(),
1015 );
1016
1017 dependencies.add_dependency(node_id, source);
1019
1020 if let Some(parent) = parent_id {
1023 tree.add_child(parent, node_id, None);
1024 }
1025
1026 let render_parent = parent_id; if let serde_json::Value::Array(items) = &array {
1031 for (index, item) in items.iter().enumerate() {
1032 let item_key = generate_item_key(item, key_path, item_name, index);
1033
1034 for child_template in template {
1036 let child_with_item = replace_ir_node_item_bindings(
1037 child_template,
1038 item,
1039 index,
1040 item_name,
1041 &item_key,
1042 );
1043 let child_id = create_ir_node_tree_with_render_parent(
1045 tree,
1046 &child_with_item,
1047 Some(node_id), render_parent, state,
1050 patches,
1051 is_root && render_parent.is_none(),
1052 dependencies
1053 );
1054 let _ = child_id; }
1056 }
1057 }
1058
1059 node_id
1060}
1061
1062fn create_conditional_tree(
1064 tree: &mut InstanceTree,
1065 value: &Value,
1066 branches: &[ConditionalBranch],
1067 fallback: Option<&[IRNode]>,
1068 original_node: &IRNode,
1069 parent_id: Option<NodeId>,
1070 state: &serde_json::Value,
1071 patches: &mut Vec<Patch>,
1072 is_root: bool,
1073 dependencies: &mut DependencyGraph,
1074) -> NodeId {
1075 let evaluated_value = evaluate_value(value, state);
1077
1078 let mut raw_props = Props::new();
1080 raw_props.insert("__condition".to_string(), value.clone());
1081
1082 let node_id = tree.create_control_flow_node(
1083 "__Conditional",
1084 IndexMap::new(),
1085 raw_props,
1086 ControlFlowKind::Conditional,
1087 original_node.clone(),
1088 );
1089
1090 if let Value::Binding(binding) = value {
1092 dependencies.add_dependency(node_id, binding);
1093 } else if let Value::TemplateString { bindings, .. } = value {
1094 for binding in bindings {
1095 dependencies.add_dependency(node_id, binding);
1096 }
1097 }
1098
1099 if let Some(parent) = parent_id {
1102 tree.add_child(parent, node_id, None);
1103 }
1104
1105 let matched_children = find_matching_branch(&evaluated_value, branches, fallback);
1107
1108 let render_parent = parent_id; if let Some(children) = matched_children {
1112 for child in children {
1113 create_ir_node_tree_with_render_parent(
1115 tree,
1116 child,
1117 Some(node_id), render_parent, state,
1120 patches,
1121 is_root && render_parent.is_none(),
1122 dependencies
1123 );
1124 }
1125 }
1126
1127 node_id
1128}
1129
1130fn find_matching_branch<'a>(
1132 evaluated_value: &serde_json::Value,
1133 branches: &'a [ConditionalBranch],
1134 fallback: Option<&'a [IRNode]>,
1135) -> Option<&'a [IRNode]> {
1136 for branch in branches {
1137 if pattern_matches(evaluated_value, &branch.pattern) {
1138 return Some(&branch.children);
1139 }
1140 }
1141
1142 fallback
1143}
1144
1145fn pattern_matches(evaluated_value: &serde_json::Value, pattern: &Value) -> bool {
1151 match pattern {
1152 Value::Static(pattern_value) => {
1153 values_match(evaluated_value, pattern_value)
1154 }
1155 Value::Binding(_binding) => {
1156 false }
1161 Value::TemplateString { template, .. } => {
1162 evaluate_expression_pattern(template, evaluated_value)
1165 }
1166 Value::Action(_) => false, }
1168}
1169
1170fn evaluate_expression_pattern(template: &str, value: &serde_json::Value) -> bool {
1173 use crate::reactive::evaluate_expression;
1174 use std::collections::HashMap;
1175
1176 let mut context: HashMap<String, serde_json::Value> = HashMap::new();
1178 context.insert("value".to_string(), value.clone());
1179
1180 let expr = if template.starts_with("${") && template.ends_with("}") {
1182 &template[2..template.len()-1]
1183 } else if template.contains("${") {
1184 let json_context = serde_json::json!({ "value": value });
1187 match evaluate_template_string(template, &json_context, None) {
1188 Ok(result) => {
1189 return is_truthy_string(&result);
1191 }
1192 Err(_) => return false,
1193 }
1194 } else {
1195 template
1197 };
1198
1199 match evaluate_expression(expr, &context) {
1201 Ok(result) => is_truthy(&result),
1202 Err(_) => false,
1203 }
1204}
1205
1206fn is_truthy_string(s: &str) -> bool {
1208 !s.is_empty() && s != "false" && s != "0" && s != "null" && s != "undefined"
1209}
1210
1211fn is_truthy(value: &serde_json::Value) -> bool {
1213 match value {
1214 serde_json::Value::Null => false,
1215 serde_json::Value::Bool(b) => *b,
1216 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
1217 serde_json::Value::String(s) => is_truthy_string(s),
1218 serde_json::Value::Array(arr) => !arr.is_empty(),
1219 serde_json::Value::Object(obj) => !obj.is_empty(),
1220 }
1221}
1222
1223fn values_match(value: &serde_json::Value, pattern: &serde_json::Value) -> bool {
1230 match pattern {
1231 serde_json::Value::Array(patterns) => {
1233 patterns.iter().any(|p| values_match(value, p))
1234 }
1235
1236 serde_json::Value::String(pattern_str) => {
1238 if pattern_str == "_" || pattern_str == "*" {
1240 return true;
1241 }
1242
1243 if pattern_str.contains("${") {
1245 return evaluate_expression_pattern(pattern_str, value);
1246 }
1247
1248 matches!(value, serde_json::Value::String(s) if s == pattern_str)
1250 }
1251
1252 _ => {
1254 if value == pattern {
1255 return true;
1256 }
1257
1258 match (value, pattern) {
1260 (serde_json::Value::Bool(b), serde_json::Value::Bool(p)) => b == p,
1261 (serde_json::Value::Null, serde_json::Value::Null) => true,
1262 _ => false,
1263 }
1264 }
1265 }
1266}
1267
1268fn evaluate_value(value: &Value, state: &serde_json::Value) -> serde_json::Value {
1270 match value {
1271 Value::Static(v) => v.clone(),
1272 Value::Binding(binding) => {
1273 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
1274 }
1275 Value::TemplateString { template, .. } => {
1276 match evaluate_template_string(template, state, None) {
1277 Ok(result) => serde_json::Value::String(result),
1278 Err(_) => serde_json::Value::String(template.clone()),
1279 }
1280 }
1281 Value::Action(action) => serde_json::Value::String(format!("@{}", action)),
1282 }
1283}
1284
1285fn replace_ir_node_item_bindings(
1288 node: &IRNode,
1289 item: &serde_json::Value,
1290 index: usize,
1291 item_name: &str,
1292 item_key: &str,
1293) -> IRNode {
1294 match node {
1295 IRNode::Element(element) => {
1296 let mut new_element = replace_item_bindings_with_name(element, item, index, item_name);
1297 new_element.key = Some(item_key.to_string());
1299 IRNode::Element(new_element)
1300 }
1301 IRNode::ForEach { source, item_name: inner_item_name, key_path, template, props } => {
1302 let new_props = replace_props_item_bindings(props, item, item_name);
1305
1306 let new_template: Vec<IRNode> = template.iter()
1308 .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1309 .collect();
1310
1311 IRNode::ForEach {
1312 source: source.clone(),
1313 item_name: inner_item_name.clone(),
1314 key_path: key_path.clone(),
1315 template: new_template,
1316 props: new_props,
1317 }
1318 }
1319 IRNode::Conditional { value, branches, fallback } => {
1320 let new_value = replace_value_item_bindings(value, item, item_name);
1322
1323 let new_branches: Vec<ConditionalBranch> = branches.iter()
1325 .map(|branch| {
1326 let new_pattern = replace_value_item_bindings(&branch.pattern, item, item_name);
1327 let new_children: Vec<IRNode> = branch.children.iter()
1328 .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1329 .collect();
1330 ConditionalBranch::new(new_pattern, new_children)
1331 })
1332 .collect();
1333
1334 let new_fallback = fallback.as_ref().map(|f| {
1336 f.iter()
1337 .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1338 .collect()
1339 });
1340
1341 IRNode::Conditional {
1342 value: new_value,
1343 branches: new_branches,
1344 fallback: new_fallback,
1345 }
1346 }
1347 }
1348}
1349
1350fn replace_props_item_bindings(props: &Props, item: &serde_json::Value, item_name: &str) -> Props {
1352 let mut new_props = Props::new();
1353 for (key, value) in props {
1354 new_props.insert(key.clone(), replace_value_item_bindings(value, item, item_name));
1355 }
1356 new_props
1357}
1358
1359fn replace_value_item_bindings(value: &Value, item: &serde_json::Value, item_name: &str) -> Value {
1363 match value {
1364 Value::Binding(binding) => {
1365 if binding.is_item() {
1366 if binding.path.is_empty() {
1367 Value::Static(item.clone())
1368 } else {
1369 let path = binding.full_path();
1370 if let Some(val) = navigate_item_path(item, &path) {
1371 Value::Static(val.clone())
1372 } else {
1373 value.clone()
1374 }
1375 }
1376 } else {
1377 value.clone()
1378 }
1379 }
1380 Value::TemplateString { template, bindings } => {
1381 let has_item_bindings = bindings.iter().any(|b| b.is_item());
1382 if !has_item_bindings {
1383 return value.clone();
1384 }
1385
1386 let mut replacements = Vec::new();
1388 for binding in bindings {
1389 if binding.is_item() {
1390 let pattern = format!("${{{}}}", binding.full_path_with_source());
1391 if let Some(start) = template.find(&pattern) {
1392 let replacement = if binding.path.is_empty() {
1393 format_value_for_replacement(item, false)
1394 } else if let Some(val) = navigate_item_path(item, &binding.full_path()) {
1395 format_value_for_replacement(val, false)
1396 } else {
1397 continue;
1398 };
1399 replacements.push(Replacement {
1400 start,
1401 end: start + pattern.len(),
1402 text: replacement,
1403 });
1404 }
1405 }
1406 }
1407
1408 let mut result = apply_replacements(template, replacements);
1410
1411 let expr_replacements = collect_item_replacements_with_name(&result, item, true, item_name);
1414 result = apply_replacements(&result, expr_replacements);
1415
1416 let remaining_bindings: Vec<_> = bindings.iter()
1418 .filter(|b| b.is_state())
1419 .cloned()
1420 .collect();
1421
1422 if remaining_bindings.is_empty() {
1423 if result.contains("${") {
1424 match evaluate_template_string(&result, &serde_json::Value::Null, None) {
1425 Ok(evaluated) => Value::Static(serde_json::Value::String(evaluated)),
1426 Err(_) => Value::Static(serde_json::Value::String(result)),
1427 }
1428 } else {
1429 Value::Static(serde_json::Value::String(result))
1430 }
1431 } else {
1432 Value::TemplateString {
1433 template: result,
1434 bindings: remaining_bindings,
1435 }
1436 }
1437 }
1438 Value::Static(serde_json::Value::String(s)) if s.contains(&format!("${{{}.", item_name)) || s.contains(&format!("${{{}}}", item_name)) => {
1440 replace_static_item_bindings_with_name(s, item, item_name)
1441 }
1442 _ => value.clone(),
1443 }
1444}
1445
1446fn collect_item_replacements_with_name(s: &str, item: &serde_json::Value, quote_strings: bool, item_name: &str) -> Vec<Replacement> {
1448 let mut replacements = Vec::new();
1449 let mut pos = 0;
1450
1451 while pos < s.len() {
1452 if let Some(rel_start) = s[pos..].find(item_name) {
1453 let abs_start = pos + rel_start;
1454 let after_item = abs_start + item_name.len();
1455
1456 if after_item < s.len() && s.as_bytes()[after_item] == b'.' {
1457 let path_start = after_item + 1;
1458 let path_end = find_path_end(s, path_start);
1459
1460 if path_end > path_start {
1461 let path = &s[path_start..path_end];
1462 if let Some(val) = navigate_item_path(item, path) {
1463 let replacement = format_value_for_replacement(val, quote_strings);
1464 replacements.push(Replacement {
1465 start: abs_start,
1466 end: path_end,
1467 text: replacement,
1468 });
1469 }
1470 }
1471 pos = path_end.max(after_item + 1);
1472 } else {
1473 pos = after_item;
1474 }
1475 } else {
1476 break;
1477 }
1478 }
1479
1480 replacements
1481}
1482
1483fn replace_static_item_bindings_with_name(s: &str, item: &serde_json::Value, item_name: &str) -> Value {
1485 let mut replacements = Vec::new();
1486 let mut pos = 0;
1487
1488 while let Some(start) = s[pos..].find("${") {
1489 let abs_start = pos + start;
1490
1491 if let Some(end) = s[abs_start..].find('}') {
1492 let abs_end = abs_start + end;
1493 let content = &s[abs_start + 2..abs_end];
1494
1495 if content == item_name {
1496 replacements.push(Replacement {
1498 start: abs_start,
1499 end: abs_end + 1,
1500 text: format_value_for_replacement(item, false),
1501 });
1502 pos = abs_end + 1;
1503 } else if content.starts_with(&format!("{}.", item_name)) && is_simple_path_with_name(content, item_name) {
1504 let path = &content[item_name.len() + 1..];
1506 if let Some(val) = navigate_item_path(item, path) {
1507 replacements.push(Replacement {
1508 start: abs_start,
1509 end: abs_end + 1,
1510 text: format_value_for_replacement(val, false),
1511 });
1512 }
1513 pos = abs_end + 1;
1514 } else if content.contains(&format!("{}.", item_name)) || content.contains(&format!("{} ", item_name)) {
1515 let expr_replacements = collect_item_replacements_with_name(content, item, true, item_name);
1517 let substituted_content = apply_replacements(content, expr_replacements);
1518 let new_expr = format!("${{{}}}", substituted_content);
1519
1520 match evaluate_template_string(&new_expr, &serde_json::Value::Null, None) {
1521 Ok(evaluated) => {
1522 replacements.push(Replacement {
1523 start: abs_start,
1524 end: abs_end + 1,
1525 text: evaluated,
1526 });
1527 }
1528 Err(_) => {}
1529 }
1530 pos = abs_end + 1;
1531 } else {
1532 pos = abs_end + 1;
1533 }
1534 } else {
1535 break;
1536 }
1537 }
1538
1539 let result = apply_replacements(s, replacements);
1540 Value::Static(serde_json::Value::String(result))
1541}
1542
1543fn is_simple_path_with_name(s: &str, item_name: &str) -> bool {
1545 if !s.starts_with(item_name) {
1546 return false;
1547 }
1548
1549 let after_item = &s[item_name.len()..];
1550 if after_item.is_empty() {
1551 return true;
1552 }
1553
1554 if !after_item.starts_with('.') {
1555 return false;
1556 }
1557
1558 after_item[1..].chars().all(|c| c.is_alphanumeric() || c == '_' || c == '.')
1559}
1560
1561pub fn replace_item_bindings_with_name(
1564 element: &Element,
1565 item: &serde_json::Value,
1566 index: usize,
1567 item_name: &str,
1568) -> Element {
1569 let mut new_element = element.clone();
1570
1571 let mut new_props = Props::new();
1573 for (key, value) in &element.props {
1574 new_props.insert(key.clone(), replace_value_item_bindings(value, item, item_name));
1575 }
1576 new_element.props = new_props;
1577
1578 let key = item.get("id")
1580 .and_then(|v| v.as_str().map(|s| s.to_string()).or_else(|| v.as_i64().map(|n| n.to_string())))
1581 .or_else(|| item.get("key").and_then(|v| v.as_str().map(|s| s.to_string())))
1582 .map(|id| format!("{}-{}", item_name, id))
1583 .unwrap_or_else(|| format!("{}-{}", item_name, index));
1584 new_element.key = Some(key);
1585
1586 new_element.children = element.children.iter()
1588 .map(|child| std::sync::Arc::new(replace_item_bindings_with_name(child, item, index, item_name)))
1589 .collect();
1590
1591 new_element
1592}
1593
1594pub fn reconcile_ir_node(
1596 tree: &mut InstanceTree,
1597 node_id: NodeId,
1598 node: &IRNode,
1599 state: &serde_json::Value,
1600 patches: &mut Vec<Patch>,
1601 dependencies: &mut DependencyGraph,
1602) {
1603 let existing_node = tree.get(node_id).cloned();
1604 if existing_node.is_none() {
1605 return;
1606 }
1607 let existing = existing_node.unwrap();
1608
1609 match node {
1610 IRNode::Element(element) => {
1611 reconcile_node(tree, node_id, element, state, patches, dependencies);
1613 }
1614 IRNode::ForEach { source, item_name, key_path, template, props: _ } => {
1615 if !existing.is_foreach() {
1617 let parent_id = existing.parent;
1619 remove_subtree(tree, node_id, patches, dependencies);
1620 create_ir_node_tree(tree, node, parent_id, state, patches, parent_id.is_none(), dependencies);
1621 return;
1622 }
1623
1624 let array = evaluate_binding(source, state).unwrap_or(serde_json::Value::Array(vec![]));
1626
1627 if let serde_json::Value::Array(items) = &array {
1628 let old_children = existing.children.clone();
1629 let expected_children_count = items.len() * template.len();
1630
1631 if old_children.len() != expected_children_count {
1633 for &old_child_id in &old_children {
1635 patches.push(Patch::remove(old_child_id));
1636 }
1637
1638 if let Some(node) = tree.get_mut(node_id) {
1639 node.children.clear();
1640 }
1641
1642 for (index, item) in items.iter().enumerate() {
1644 let item_key = generate_item_key(item, key_path.as_deref(), item_name, index);
1645
1646 for child_template in template {
1647 let child_with_item = replace_ir_node_item_bindings(
1648 child_template,
1649 item,
1650 index,
1651 item_name,
1652 &item_key,
1653 );
1654 create_ir_node_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
1655 }
1656 }
1657 } else {
1658 let mut child_index = 0;
1660 for (item_index, item) in items.iter().enumerate() {
1661 let item_key = generate_item_key(item, key_path.as_deref(), item_name, item_index);
1662
1663 for child_template in template {
1664 if let Some(&old_child_id) = old_children.get(child_index) {
1665 let child_with_item = replace_ir_node_item_bindings(
1666 child_template,
1667 item,
1668 item_index,
1669 item_name,
1670 &item_key,
1671 );
1672 reconcile_ir_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
1673 }
1674 child_index += 1;
1675 }
1676 }
1677 }
1678 }
1679 }
1680 IRNode::Conditional { value, branches, fallback } => {
1681 if !existing.is_conditional() {
1683 let parent_id = existing.parent;
1685 remove_subtree(tree, node_id, patches, dependencies);
1686 create_ir_node_tree(tree, node, parent_id, state, patches, parent_id.is_none(), dependencies);
1687 return;
1688 }
1689
1690 let evaluated_value = evaluate_value(value, state);
1692 let matched_children = find_matching_branch(&evaluated_value, branches, fallback.as_deref());
1693
1694 let old_children = existing.children.clone();
1695
1696 if let Some(children) = matched_children {
1699 if old_children.len() != children.len() {
1700 for &old_child_id in &old_children {
1702 patches.push(Patch::remove(old_child_id));
1703 }
1704
1705 if let Some(node) = tree.get_mut(node_id) {
1706 node.children.clear();
1707 }
1708
1709 for child in children {
1711 create_ir_node_tree(tree, child, Some(node_id), state, patches, false, dependencies);
1712 }
1713 } else {
1714 for (i, child) in children.iter().enumerate() {
1716 if let Some(&old_child_id) = old_children.get(i) {
1717 reconcile_ir_node(tree, old_child_id, child, state, patches, dependencies);
1718 }
1719 }
1720 }
1721 } else {
1722 for &old_child_id in &old_children {
1724 patches.push(Patch::remove(old_child_id));
1725 }
1726
1727 if let Some(node) = tree.get_mut(node_id) {
1728 node.children.clear();
1729 }
1730 }
1731 }
1732 }
1733}
1734
1735fn remove_subtree(
1737 tree: &mut InstanceTree,
1738 node_id: NodeId,
1739 patches: &mut Vec<Patch>,
1740 dependencies: &mut DependencyGraph,
1741) {
1742 let ids = collect_subtree_ids(tree, node_id);
1743 for &id in &ids {
1744 patches.push(Patch::remove(id));
1745 dependencies.remove_node(id);
1746 }
1747 tree.remove(node_id);
1748}
1749
1750#[cfg(test)]
1751mod tests {
1752 use super::*;
1753 use crate::ir::Value;
1754 use serde_json::json;
1755
1756 #[test]
1757 fn test_create_simple_tree() {
1758 use crate::reactive::DependencyGraph;
1759
1760 let mut tree = InstanceTree::new();
1761 let mut patches = Vec::new();
1762 let mut dependencies = DependencyGraph::new();
1763
1764 let element = Element::new("Column")
1765 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
1766
1767 let state = json!({});
1768 create_tree(&mut tree, &element, None, &state, &mut patches, true, &mut dependencies);
1769
1770 assert_eq!(patches.len(), 4);
1773
1774 let root_insert = patches.iter().find(|p| {
1776 matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root")
1777 });
1778 assert!(root_insert.is_some(), "Root insert patch should exist");
1779 }
1780
1781 #[test]
1782 fn test_diff_props() {
1783 let node_id = NodeId::default();
1784 let old = indexmap::indexmap! {
1785 "color".to_string() => json!("red"),
1786 "size".to_string() => json!(16),
1787 };
1788 let new = indexmap::indexmap! {
1789 "color".to_string() => json!("blue"),
1790 "size".to_string() => json!(16),
1791 };
1792
1793 let patches = diff_props(node_id, &old, &new);
1794
1795 assert_eq!(patches.len(), 1);
1797 }
1798
1799 #[test]
1800 fn test_longest_increasing_subsequence_basic() {
1801 let positions = vec![Some(0), Some(1), Some(2), Some(3)];
1803 let lis = longest_increasing_subsequence(&positions);
1804 assert_eq!(lis, vec![0, 1, 2, 3]);
1805 }
1806
1807 #[test]
1808 fn test_longest_increasing_subsequence_reverse() {
1809 let positions = vec![Some(3), Some(2), Some(1), Some(0)];
1811 let lis = longest_increasing_subsequence(&positions);
1812 assert_eq!(lis.len(), 1);
1813 }
1814
1815 #[test]
1816 fn test_longest_increasing_subsequence_mixed() {
1817 let positions = vec![Some(0), Some(3), Some(1), Some(2)];
1819 let lis = longest_increasing_subsequence(&positions);
1820 assert_eq!(lis.len(), 3);
1821 assert!(lis.contains(&0));
1823 assert!(lis.contains(&2));
1824 assert!(lis.contains(&3));
1825 }
1826
1827 #[test]
1828 fn test_longest_increasing_subsequence_with_new_items() {
1829 let positions = vec![None, Some(0), None, Some(1)];
1831 let lis = longest_increasing_subsequence(&positions);
1832 assert_eq!(lis, vec![1, 3]); }
1834
1835 #[test]
1836 fn test_longest_increasing_subsequence_empty() {
1837 let positions: Vec<Option<usize>> = vec![];
1838 let lis = longest_increasing_subsequence(&positions);
1839 assert_eq!(lis, Vec::<usize>::new());
1840 }
1841
1842 #[test]
1843 fn test_longest_increasing_subsequence_all_new() {
1844 let positions = vec![None, None, None];
1846 let lis = longest_increasing_subsequence(&positions);
1847 assert_eq!(lis, Vec::<usize>::new());
1848 }
1849
1850 #[test]
1851 fn test_reconcile_keyed_children_basic() {
1852 let mut tree = InstanceTree::new();
1853 let mut dependencies = DependencyGraph::new();
1854 let state = json!({});
1855
1856 let parent = Element::new("Column");
1858 let parent_id = tree.create_node(&parent, &state);
1859
1860 let mut old_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1862 old_child_1.key = Some("item-1".to_string());
1863 let old_id_1 = tree.create_node(&old_child_1, &state);
1864
1865 let mut old_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1866 old_child_2.key = Some("item-2".to_string());
1867 let old_id_2 = tree.create_node(&old_child_2, &state);
1868
1869 tree.add_child(parent_id, old_id_1, None);
1870 tree.add_child(parent_id, old_id_2, None);
1871
1872 let mut new_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1874 new_child_2.key = Some("item-2".to_string());
1875
1876 let mut new_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1877 new_child_1.key = Some("item-1".to_string());
1878
1879 let new_children = vec![new_child_2, new_child_1];
1880 let old_children = vec![old_id_1, old_id_2];
1881
1882 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1883
1884 let move_count = patches.iter().filter(|p| matches!(p, Patch::Move { .. })).count();
1886 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1887 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1888
1889 assert_eq!(create_count, 0, "Should not create new children");
1890 assert_eq!(remove_count, 0, "Should not remove any children");
1891 assert!(move_count > 0, "Should have move patches for reordering");
1892 }
1893
1894 #[test]
1895 fn test_reconcile_keyed_children_add_remove() {
1896 let mut tree = InstanceTree::new();
1897 let mut dependencies = DependencyGraph::new();
1898 let state = json!({});
1899
1900 let parent = Element::new("Column");
1901 let parent_id = tree.create_node(&parent, &state);
1902
1903 let mut old_child_1 = Element::new("Text");
1905 old_child_1.key = Some("item-1".to_string());
1906 let old_id_1 = tree.create_node(&old_child_1, &state);
1907
1908 let mut old_child_2 = Element::new("Text");
1909 old_child_2.key = Some("item-2".to_string());
1910 let old_id_2 = tree.create_node(&old_child_2, &state);
1911
1912 tree.add_child(parent_id, old_id_1, None);
1913 tree.add_child(parent_id, old_id_2, None);
1914
1915 let mut new_child_2 = Element::new("Text");
1917 new_child_2.key = Some("item-2".to_string());
1918
1919 let mut new_child_3 = Element::new("Text");
1920 new_child_3.key = Some("item-3".to_string());
1921
1922 let new_children = vec![new_child_2, new_child_3];
1923 let old_children = vec![old_id_1, old_id_2];
1924
1925 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1926
1927 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1928 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1929
1930 assert_eq!(create_count, 1, "Should create item-3");
1931 assert_eq!(remove_count, 1, "Should remove item-1");
1932 }
1933}