1use super::{InstanceTree, Patch};
2use crate::ir::{Element, NodeId, Value};
3use crate::reactive::{DependencyGraph, evaluate_template_string};
4use indexmap::IndexMap;
5
6pub fn reconcile(
8 tree: &mut InstanceTree,
9 element: &Element,
10 parent_id: Option<NodeId>,
11 state: &serde_json::Value,
12 dependencies: &mut DependencyGraph,
13) -> Vec<Patch> {
14 let mut patches = Vec::new();
15
16 if tree.root().is_none() {
18 let node_id = create_tree(tree, element, parent_id, state, &mut patches, true, dependencies);
19 tree.set_root(node_id);
20 return patches;
21 }
22
23 if let Some(root_id) = tree.root() {
25 reconcile_node(tree, root_id, element, state, &mut patches, dependencies);
26 }
27
28 patches
29}
30
31pub fn create_tree(
33 tree: &mut InstanceTree,
34 element: &Element,
35 parent_id: Option<NodeId>,
36 state: &serde_json::Value,
37 patches: &mut Vec<Patch>,
38 is_root: bool,
39 dependencies: &mut DependencyGraph,
40) -> NodeId {
41 if let Some(Value::Binding(_)) = element.props.get("0") {
49 if !element.children.is_empty() {
50 return create_list_tree(tree, element, parent_id, state, patches, is_root, dependencies);
51 }
52 }
53
54 let node_id = tree.create_node(element, state);
56
57 for value in element.props.values() {
59 match value {
60 Value::Binding(binding) => {
61 dependencies.add_dependency(node_id, binding);
62 }
63 Value::TemplateString { bindings, .. } => {
64 for binding in bindings {
66 dependencies.add_dependency(node_id, binding);
67 }
68 }
69 _ => {}
70 }
71 }
72
73 let node = tree.get(node_id).unwrap();
75
76 let mut props = node.props.clone();
78 if let Some(Value::Static(val)) = element.props.get("__lazy") {
79 if val.as_bool().unwrap_or(false) && !element.children.is_empty() {
80 let child_component = &element.children[0].element_type;
82 props.insert("__lazy_child".to_string(), serde_json::json!(child_component));
83 }
84 }
85
86 patches.push(Patch::create(
87 node_id,
88 node.element_type.clone(),
89 props,
90 ));
91
92 if let Some(parent) = parent_id {
96 tree.add_child(parent, node_id, None);
97 patches.push(Patch::insert(parent, node_id, None));
98 } else if is_root {
99 patches.push(Patch::insert_root(node_id));
101 }
102
103 let is_lazy = element.props.get("__lazy")
105 .and_then(|v| {
106 if let Value::Static(val) = v {
107 val.as_bool()
108 } else {
109 None
110 }
111 })
112 .unwrap_or(false);
113
114 if !is_lazy {
115 for child_element in &element.children {
116 create_tree(tree, child_element, Some(node_id), state, patches, false, dependencies);
117 }
118 }
119
120 node_id
121}
122
123fn create_list_tree(
126 tree: &mut InstanceTree,
127 element: &Element,
128 parent_id: Option<NodeId>,
129 state: &serde_json::Value,
130 patches: &mut Vec<Patch>,
131 is_root: bool,
132 dependencies: &mut DependencyGraph,
133) -> NodeId {
134 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
136 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
137 } else {
138 serde_json::Value::Array(vec![])
139 };
140
141 let mut list_element = Element::new(&element.element_type);
144 for (key, value) in &element.props {
145 if key != "0" {
147 list_element.props.insert(key.clone(), value.clone());
148 }
149 }
150
151 let node_id = tree.create_node(&list_element, state);
152
153 if let Some(Value::Binding(binding)) = element.props.get("0") {
156 dependencies.add_dependency(node_id, binding);
157 }
158
159 if let Some(node) = tree.get_mut(node_id) {
162 node.raw_props = element.props.clone();
163 node.element_template = Some(element.clone());
164 }
165
166 let node = tree.get(node_id).unwrap();
168 patches.push(Patch::create(
169 node_id,
170 node.element_type.clone(),
171 node.props.clone(),
172 ));
173
174 if let Some(parent) = parent_id {
176 tree.add_child(parent, node_id, None);
177 patches.push(Patch::insert(parent, node_id, None));
178 } else if is_root {
179 patches.push(Patch::insert_root(node_id));
180 }
181
182 if let serde_json::Value::Array(items) = &array {
184 for (index, item) in items.iter().enumerate() {
186 for child_template in &element.children {
187 let child_with_item = replace_item_bindings(child_template, item, index);
189 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
190 }
191 }
192 }
193
194 node_id
195}
196
197fn navigate_item_path<'a>(item: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> {
200 let mut current = item;
201
202 for segment in path.split('.') {
203 if let Ok(index) = segment.parse::<usize>() {
205 current = current.get(index)?;
206 } else {
207 current = current.get(segment)?;
209 }
210 }
211
212 Some(current)
213}
214
215fn is_simple_path(s: &str) -> bool {
219 if !s.starts_with("item") {
221 return false;
222 }
223
224 let after_item = &s[4..]; if after_item.is_empty() {
226 return true; }
228
229 if !after_item.starts_with('.') {
231 return false;
232 }
233
234 after_item[1..].chars().all(|c| c.is_alphanumeric() || c == '_' || c == '.')
236}
237
238pub fn replace_item_bindings(element: &Element, item: &serde_json::Value, index: usize) -> Element {
240 let mut new_element = element.clone();
241
242 let mut new_props = IndexMap::new();
244 for (key, value) in &element.props {
245 let new_value = match value {
246 Value::Binding(binding) => {
247 if binding.is_item() {
249 if binding.path.is_empty() {
251 Value::Static(item.clone())
252 } else {
253 let prop_path = binding.full_path();
255 if let Some(val) = navigate_item_path(item, &prop_path) {
256 Value::Static(val.clone())
257 } else {
258 value.clone()
259 }
260 }
261 } else {
262 value.clone()
263 }
264 }
265 Value::TemplateString { template, bindings } => {
266 let has_item_bindings = bindings.iter().any(|b| b.is_item());
268
269 if has_item_bindings {
270 let mut result = template.clone();
271
272 for binding in bindings {
274 if binding.is_item() {
275 let pattern = format!("${{{}}}", binding.full_path_with_source());
276 let replacement = if binding.path.is_empty() {
277 match item {
279 serde_json::Value::String(s) => s.clone(),
280 serde_json::Value::Number(n) => n.to_string(),
281 serde_json::Value::Bool(b) => b.to_string(),
282 _ => serde_json::to_string(item).unwrap_or_default(),
283 }
284 } else if let Some(val) = navigate_item_path(item, &binding.full_path()) {
285 match val {
286 serde_json::Value::String(s) => s.clone(),
287 serde_json::Value::Number(n) => n.to_string(),
288 serde_json::Value::Bool(b) => b.to_string(),
289 _ => serde_json::to_string(val).unwrap_or_default(),
290 }
291 } else {
292 continue; };
294 result = result.replace(&pattern, &replacement);
295 }
296 }
297
298 let mut pos = 0;
302 while let Some(item_start) = result[pos..].find("item.") {
303 let item_abs_start = pos + item_start;
304 let path_start = item_abs_start + 5; let mut path_end = path_start;
308 let chars: Vec<char> = result[path_start..].chars().collect();
309
310 for (i, ch) in chars.iter().enumerate() {
311 if ch.is_alphanumeric() || *ch == '_' || *ch == '.' {
312 if *ch == '.' {
313 if i + 1 >= chars.len() || {
314 let next = chars[i + 1];
315 !next.is_alphanumeric() && next != '_'
316 } {
317 break;
318 }
319 }
320 path_end = path_start + i + 1;
321 } else {
322 break;
323 }
324 }
325
326 if path_end > path_start {
327 let path = &result[path_start..path_end];
328 if let Some(val) = navigate_item_path(item, path) {
329 let replacement = match val {
330 serde_json::Value::String(s) => format!("'{}'", s),
331 serde_json::Value::Number(n) => n.to_string(),
332 serde_json::Value::Bool(b) => b.to_string(),
333 serde_json::Value::Null => "null".to_string(),
334 _ => serde_json::to_string(val).unwrap_or_default(),
335 };
336 result = format!(
337 "{}{}{}",
338 &result[..item_abs_start],
339 replacement,
340 &result[path_end..]
341 );
342 pos = 0; continue;
344 }
345 }
346 pos = item_abs_start + 5;
347 }
348
349 let remaining_bindings: Vec<_> = bindings.iter()
351 .filter(|b| b.is_state())
352 .cloned()
353 .collect();
354
355 if remaining_bindings.is_empty() {
356 if result.contains("${") {
358 match evaluate_template_string(&result, &serde_json::Value::Null, None) {
360 Ok(evaluated) => Value::Static(serde_json::Value::String(evaluated)),
361 Err(_) => Value::Static(serde_json::Value::String(result)),
362 }
363 } else {
364 Value::Static(serde_json::Value::String(result))
365 }
366 } else {
367 Value::TemplateString {
368 template: result,
369 bindings: remaining_bindings,
370 }
371 }
372 } else {
373 value.clone()
374 }
375 }
376 Value::Static(serde_json::Value::String(s)) if s.contains("${item.") || s.contains("${item}") => {
380 let mut result = s.clone();
381
382 let mut pos = 0;
387 while let Some(start) = result[pos..].find("${") {
388 let abs_start = pos + start;
389
390 if let Some(end) = result[abs_start..].find('}') {
392 let abs_end = abs_start + end;
393 let content = &result[abs_start + 2..abs_end]; if content == "item" {
397 let replacement = match item {
399 serde_json::Value::String(s) => s.clone(),
400 serde_json::Value::Number(n) => n.to_string(),
401 serde_json::Value::Bool(b) => b.to_string(),
402 _ => serde_json::to_string(item).unwrap_or_default(),
403 };
404 result = format!("{}{}{}", &result[..abs_start], replacement, &result[abs_end + 1..]);
405 pos = 0;
406 continue;
407 } else if content.starts_with("item.") && is_simple_path(content) {
408 let path = &content[5..]; if let Some(val) = navigate_item_path(item, path) {
411 let replacement = match val {
412 serde_json::Value::String(s) => s.clone(),
413 serde_json::Value::Number(n) => n.to_string(),
414 serde_json::Value::Bool(b) => b.to_string(),
415 serde_json::Value::Null => "null".to_string(),
416 _ => serde_json::to_string(val).unwrap_or_default(),
417 };
418 result = format!("{}{}{}", &result[..abs_start], replacement, &result[abs_end + 1..]);
419 pos = 0;
420 continue;
421 }
422 } else if content.contains("item.") || content.contains("item ") {
423 let mut expr_content = content.to_string();
425
426 let mut expr_pos = 0;
428 while let Some(item_start) = expr_content[expr_pos..].find("item.") {
429 let item_abs_start = expr_pos + item_start;
430 let path_start = item_abs_start + 5; let mut path_end = path_start;
434 let chars: Vec<char> = expr_content[path_start..].chars().collect();
435
436 for (i, ch) in chars.iter().enumerate() {
437 if ch.is_alphanumeric() || *ch == '_' || *ch == '.' {
438 if *ch == '.' {
439 if i + 1 >= chars.len() || {
440 let next = chars[i + 1];
441 !next.is_alphanumeric() && next != '_'
442 } {
443 break;
444 }
445 }
446 path_end = path_start + i + 1;
447 } else {
448 break;
449 }
450 }
451
452 if path_end > path_start {
453 let path = &expr_content[path_start..path_end];
454 if let Some(val) = navigate_item_path(item, path) {
455 let replacement = match val {
456 serde_json::Value::String(s) => format!("'{}'", s),
457 serde_json::Value::Number(n) => n.to_string(),
458 serde_json::Value::Bool(b) => b.to_string(),
459 serde_json::Value::Null => "null".to_string(),
460 _ => serde_json::to_string(val).unwrap_or_default(),
461 };
462 expr_content = format!(
463 "{}{}{}",
464 &expr_content[..item_abs_start],
465 replacement,
466 &expr_content[path_end..]
467 );
468 expr_pos = 0;
469 continue;
470 }
471 }
472 expr_pos = item_abs_start + 5;
473 }
474
475 let new_expr = format!("${{{}}}", expr_content);
477 match evaluate_template_string(&new_expr, &serde_json::Value::Null, None) {
478 Ok(evaluated) => {
479 result = format!("{}{}{}", &result[..abs_start], evaluated, &result[abs_end + 1..]);
480 pos = 0;
481 continue;
482 }
483 Err(_) => {
484 pos = abs_end + 1;
486 continue;
487 }
488 }
489 }
490
491 pos = abs_end + 1;
493 } else {
494 break;
495 }
496 }
497
498 Value::Static(serde_json::Value::String(result))
499 }
500 _ => value.clone(),
501 };
502 new_props.insert(key.clone(), new_value);
503 }
504 new_element.props = new_props;
505
506 new_element.key = Some(format!("list-item-{}", index));
508
509 new_element.children = element.children.iter()
511 .map(|child| replace_item_bindings(child, item, index))
512 .collect();
513
514 new_element
515}
516
517pub fn reconcile_node(
519 tree: &mut InstanceTree,
520 node_id: NodeId,
521 element: &Element,
522 state: &serde_json::Value,
523 patches: &mut Vec<Patch>,
524 dependencies: &mut DependencyGraph,
525) {
526 let node = tree.get(node_id).cloned();
527 if node.is_none() {
528 return;
529 }
530 let node = node.unwrap();
531
532 let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
536
537 if is_iterable {
538 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
540 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
541 } else {
542 serde_json::Value::Array(vec![])
543 };
544
545 if let serde_json::Value::Array(items) = &array {
547 let old_children = node.children.clone();
548
549 let expected_children_count = items.len() * element.children.len();
551
552 if old_children.len() != expected_children_count {
554 for &old_child_id in &old_children {
555 patches.push(Patch::remove(old_child_id));
556 }
557
558 if let Some(node) = tree.get_mut(node_id) {
560 node.children.clear();
561 }
562
563 for (index, item) in items.iter().enumerate() {
565 for child_template in &element.children {
566 let child_with_item = replace_item_bindings(child_template, item, index);
567 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
568 }
569 }
570 } else {
571 let mut child_index = 0;
573 for (item_index, item) in items.iter().enumerate() {
574 for child_template in &element.children {
575 if let Some(&old_child_id) = old_children.get(child_index) {
576 let child_with_item = replace_item_bindings(child_template, item, item_index);
577 reconcile_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
578 }
579 child_index += 1;
580 }
581 }
582 }
583 }
584
585 return; }
587
588 if node.element_type != element.element_type {
590 replace_subtree(tree, node_id, &node, element, state, patches, dependencies);
591 return;
592 }
593
594 for value in element.props.values() {
596 if let Value::Binding(binding) = value {
597 dependencies.add_dependency(node_id, binding);
598 }
599 }
600
601 let new_props = resolve_props(&element.props, state);
603 let prop_patches = diff_props(node_id, &node.props, &new_props);
604 patches.extend(prop_patches);
605
606 if let Some(node) = tree.get_mut(node_id) {
608 node.props = new_props.clone();
609 node.raw_props = element.props.clone();
610 }
611
612 let is_lazy = element.props.get("__lazy")
614 .and_then(|v| {
615 if let Value::Static(val) = v {
616 val.as_bool()
617 } else {
618 None
619 }
620 })
621 .unwrap_or(false);
622
623 if !is_lazy {
624 let old_children = node.children.clone();
625 let new_children = &element.children;
626
627 for (i, new_child_element) in new_children.iter().enumerate() {
629 if let Some(&old_child_id) = old_children.get(i) {
630 reconcile_node(tree, old_child_id, new_child_element, state, patches, dependencies);
632 } else {
633 let new_child_id = create_tree(tree, new_child_element, Some(node_id), state, patches, false, dependencies);
635 if let Some(node) = tree.get_mut(node_id) {
636 node.children.push(new_child_id);
637 }
638 }
639 }
640
641 if old_children.len() > new_children.len() {
643 for &old_child_id in &old_children[new_children.len()..] {
644 patches.push(Patch::remove(old_child_id));
645 }
647 }
648 }
649}
650
651fn replace_subtree(
654 tree: &mut InstanceTree,
655 old_node_id: NodeId,
656 old_node: &super::InstanceNode,
657 new_element: &Element,
658 state: &serde_json::Value,
659 patches: &mut Vec<Patch>,
660 dependencies: &mut DependencyGraph,
661) {
662 let parent_id = old_node.parent;
663
664 let old_position = if let Some(pid) = parent_id {
666 tree.get(pid).and_then(|parent| {
667 parent.children.iter().position(|&id| id == old_node_id)
668 })
669 } else {
670 None
671 };
672
673 let ids_to_remove = collect_subtree_ids(tree, old_node_id);
675
676 for &id in &ids_to_remove {
678 patches.push(Patch::remove(id));
679 dependencies.remove_node(id);
680 }
681
682 if let Some(pid) = parent_id {
684 if let Some(parent) = tree.get_mut(pid) {
685 parent.children.retain(|&id| id != old_node_id);
686 }
687 }
688
689 tree.remove(old_node_id);
691
692 let is_root = parent_id.is_none();
694 let new_node_id = create_tree(tree, new_element, parent_id, state, patches, is_root, dependencies);
695
696 if is_root {
698 tree.set_root(new_node_id);
699 } else if let Some(pid) = parent_id {
700 if let Some(pos) = old_position {
701 if let Some(parent) = tree.get_mut(pid) {
702 let current_len = parent.children.len();
703 if pos < current_len - 1 {
706 let new_id = parent.children.pop().unwrap();
708 parent.children.insert(pos, new_id);
709
710 let next_sibling = parent.children.get(pos + 1).copied();
712 patches.push(Patch::move_node(pid, new_node_id, next_sibling));
713 }
714 }
716 }
717 }
718}
719
720fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
723 let mut result = Vec::new();
724 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
725
726 while let Some((node_id, children_processed)) = stack.pop() {
727 if children_processed {
728 result.push(node_id);
730 } else {
731 stack.push((node_id, true));
733 if let Some(node) = tree.get(node_id) {
734 for &child_id in node.children.iter().rev() {
736 stack.push((child_id, false));
737 }
738 }
739 }
740 }
741
742 result
743}
744
745fn resolve_props(props: &IndexMap<String, crate::ir::Value>, state: &serde_json::Value) -> IndexMap<String, serde_json::Value> {
747 resolve_props_with_item(props, state, None)
748}
749
750fn resolve_props_with_item(
752 props: &IndexMap<String, crate::ir::Value>,
753 state: &serde_json::Value,
754 item: Option<&serde_json::Value>,
755) -> IndexMap<String, serde_json::Value> {
756 let mut resolved = IndexMap::new();
757
758 for (key, value) in props {
759 let resolved_value = match value {
760 crate::ir::Value::Static(v) => v.clone(),
761 crate::ir::Value::Binding(binding) => {
762 if binding.is_item() {
763 if let Some(item_value) = item {
765 evaluate_item_binding(binding, item_value).unwrap_or(serde_json::Value::Null)
766 } else {
767 serde_json::Value::Null
768 }
769 } else {
770 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
772 }
773 }
774 crate::ir::Value::TemplateString { template, .. } => {
775 match crate::reactive::evaluate_template_string(template, state, item) {
778 Ok(result) => serde_json::Value::String(result),
779 Err(_) => serde_json::Value::String(template.clone()),
780 }
781 }
782 crate::ir::Value::Action(action) => {
783 serde_json::Value::String(format!("@{}", action))
785 }
786 };
787 resolved.insert(key.clone(), resolved_value);
788 }
789
790 resolved
791}
792
793fn evaluate_item_binding(binding: &crate::reactive::Binding, item: &serde_json::Value) -> Option<serde_json::Value> {
795 if binding.path.is_empty() {
796 return Some(item.clone());
797 }
798
799 let mut current = item;
800 for segment in &binding.path {
801 current = current.get(segment)?;
802 }
803 Some(current.clone())
804}
805
806fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
808 let mut current = state;
809
810 for segment in &binding.path {
811 current = current.get(segment)?;
812 }
813
814 Some(current.clone())
815}
816
817fn reconcile_keyed_children(
819 tree: &mut InstanceTree,
820 parent_id: NodeId,
821 old_children: &[NodeId],
822 new_children: &[Element],
823 state: &serde_json::Value,
824 dependencies: &mut DependencyGraph,
825) -> Vec<Patch> {
826 let mut patches = Vec::new();
827
828 let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
830 let mut old_unkeyed: Vec<NodeId> = Vec::new();
831
832 for &child_id in old_children {
833 if let Some(node) = tree.get(child_id) {
834 if let Some(key) = &node.key {
835 old_keyed.insert(key.clone(), child_id);
836 } else {
837 old_unkeyed.push(child_id);
838 }
839 }
840 }
841
842 let mut new_child_ids: Vec<NodeId> = Vec::new();
843 let mut unkeyed_idx = 0;
844
845 for new_element in new_children {
847 let new_key = new_element.key.as_ref();
848
849 let child_id = if let Some(key) = new_key {
850 if let Some(&old_id) = old_keyed.get(key) {
852 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
854 old_keyed.shift_remove(key); old_id
856 } else {
857 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
859 }
860 } else {
861 if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
863 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
864 unkeyed_idx += 1;
865 old_id
866 } else {
867 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
869 }
870 };
871
872 new_child_ids.push(child_id);
873 }
874
875 let mut old_positions: Vec<Option<usize>> = Vec::new();
878 for &new_id in &new_child_ids {
879 let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
880 old_positions.push(old_pos);
881 }
882
883 let lis = longest_increasing_subsequence(&old_positions);
885
886 if let Some(parent_node) = tree.get_mut(parent_id) {
888 parent_node.children = new_child_ids.clone();
889 }
890
891 for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
893 if !lis.contains(&new_idx) {
895 let before_id = new_child_ids.get(new_idx + 1).copied();
897 patches.push(Patch::move_node(parent_id, child_id, before_id));
898 }
899 }
900
901 for old_id in old_keyed.values() {
903 patches.push(Patch::remove(*old_id));
904 }
905 for &old_id in &old_unkeyed[unkeyed_idx..] {
906 patches.push(Patch::remove(old_id));
907 }
908
909 patches
910}
911
912fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
916 let valid: Vec<(usize, usize)> = positions
918 .iter()
919 .enumerate()
920 .filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
921 .collect();
922
923 if valid.is_empty() {
924 return Vec::new();
925 }
926
927 let n = valid.len();
929 let mut dp: Vec<usize> = vec![1; n];
930 let mut prev: Vec<Option<usize>> = vec![None; n];
931
932 for i in 1..n {
933 for j in 0..i {
934 if valid[j].1 < valid[i].1 && dp[j] + 1 > dp[i] {
935 dp[i] = dp[j] + 1;
936 prev[i] = Some(j);
937 }
938 }
939 }
940
941 let mut max_idx = 0;
943 for i in 1..n {
944 if dp[i] > dp[max_idx] {
945 max_idx = i;
946 }
947 }
948
949 let mut result = Vec::new();
951 let mut idx = Some(max_idx);
952 while let Some(i) = idx {
953 result.push(valid[i].0);
954 idx = prev[i];
955 }
956 result.reverse();
957
958 result
959}
960
961pub fn diff_props(
963 node_id: NodeId,
964 old_props: &IndexMap<String, serde_json::Value>,
965 new_props: &IndexMap<String, serde_json::Value>,
966) -> Vec<Patch> {
967 let mut patches = Vec::new();
968
969 for (key, new_value) in new_props {
971 if old_props.get(key) != Some(new_value) {
972 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
973 }
974 }
975
976 patches
980}
981
982#[cfg(test)]
983mod tests {
984 use super::*;
985 use crate::ir::Value;
986 use serde_json::json;
987
988 #[test]
989 fn test_create_simple_tree() {
990 use crate::reactive::DependencyGraph;
991
992 let mut tree = InstanceTree::new();
993 let mut patches = Vec::new();
994 let mut dependencies = DependencyGraph::new();
995
996 let element = Element::new("Column")
997 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
998
999 let state = json!({});
1000 create_tree(&mut tree, &element, None, &state, &mut patches, true, &mut dependencies);
1001
1002 assert_eq!(patches.len(), 4);
1005
1006 let root_insert = patches.iter().find(|p| {
1008 matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root")
1009 });
1010 assert!(root_insert.is_some(), "Root insert patch should exist");
1011 }
1012
1013 #[test]
1014 fn test_diff_props() {
1015 let node_id = NodeId::default();
1016 let old = indexmap::indexmap! {
1017 "color".to_string() => json!("red"),
1018 "size".to_string() => json!(16),
1019 };
1020 let new = indexmap::indexmap! {
1021 "color".to_string() => json!("blue"),
1022 "size".to_string() => json!(16),
1023 };
1024
1025 let patches = diff_props(node_id, &old, &new);
1026
1027 assert_eq!(patches.len(), 1);
1029 }
1030
1031 #[test]
1032 fn test_longest_increasing_subsequence_basic() {
1033 let positions = vec![Some(0), Some(1), Some(2), Some(3)];
1035 let lis = longest_increasing_subsequence(&positions);
1036 assert_eq!(lis, vec![0, 1, 2, 3]);
1037 }
1038
1039 #[test]
1040 fn test_longest_increasing_subsequence_reverse() {
1041 let positions = vec![Some(3), Some(2), Some(1), Some(0)];
1043 let lis = longest_increasing_subsequence(&positions);
1044 assert_eq!(lis.len(), 1);
1045 }
1046
1047 #[test]
1048 fn test_longest_increasing_subsequence_mixed() {
1049 let positions = vec![Some(0), Some(3), Some(1), Some(2)];
1051 let lis = longest_increasing_subsequence(&positions);
1052 assert_eq!(lis.len(), 3);
1053 assert!(lis.contains(&0));
1055 assert!(lis.contains(&2));
1056 assert!(lis.contains(&3));
1057 }
1058
1059 #[test]
1060 fn test_longest_increasing_subsequence_with_new_items() {
1061 let positions = vec![None, Some(0), None, Some(1)];
1063 let lis = longest_increasing_subsequence(&positions);
1064 assert_eq!(lis, vec![1, 3]); }
1066
1067 #[test]
1068 fn test_longest_increasing_subsequence_empty() {
1069 let positions: Vec<Option<usize>> = vec![];
1070 let lis = longest_increasing_subsequence(&positions);
1071 assert_eq!(lis, Vec::<usize>::new());
1072 }
1073
1074 #[test]
1075 fn test_longest_increasing_subsequence_all_new() {
1076 let positions = vec![None, None, None];
1078 let lis = longest_increasing_subsequence(&positions);
1079 assert_eq!(lis, Vec::<usize>::new());
1080 }
1081
1082 #[test]
1083 fn test_reconcile_keyed_children_basic() {
1084 let mut tree = InstanceTree::new();
1085 let mut dependencies = DependencyGraph::new();
1086 let state = json!({});
1087
1088 let parent = Element::new("Column");
1090 let parent_id = tree.create_node(&parent, &state);
1091
1092 let mut old_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1094 old_child_1.key = Some("item-1".to_string());
1095 let old_id_1 = tree.create_node(&old_child_1, &state);
1096
1097 let mut old_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1098 old_child_2.key = Some("item-2".to_string());
1099 let old_id_2 = tree.create_node(&old_child_2, &state);
1100
1101 tree.add_child(parent_id, old_id_1, None);
1102 tree.add_child(parent_id, old_id_2, None);
1103
1104 let mut new_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1106 new_child_2.key = Some("item-2".to_string());
1107
1108 let mut new_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1109 new_child_1.key = Some("item-1".to_string());
1110
1111 let new_children = vec![new_child_2, new_child_1];
1112 let old_children = vec![old_id_1, old_id_2];
1113
1114 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1115
1116 let move_count = patches.iter().filter(|p| matches!(p, Patch::Move { .. })).count();
1118 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1119 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1120
1121 assert_eq!(create_count, 0, "Should not create new children");
1122 assert_eq!(remove_count, 0, "Should not remove any children");
1123 assert!(move_count > 0, "Should have move patches for reordering");
1124 }
1125
1126 #[test]
1127 fn test_reconcile_keyed_children_add_remove() {
1128 let mut tree = InstanceTree::new();
1129 let mut dependencies = DependencyGraph::new();
1130 let state = json!({});
1131
1132 let parent = Element::new("Column");
1133 let parent_id = tree.create_node(&parent, &state);
1134
1135 let mut old_child_1 = Element::new("Text");
1137 old_child_1.key = Some("item-1".to_string());
1138 let old_id_1 = tree.create_node(&old_child_1, &state);
1139
1140 let mut old_child_2 = Element::new("Text");
1141 old_child_2.key = Some("item-2".to_string());
1142 let old_id_2 = tree.create_node(&old_child_2, &state);
1143
1144 tree.add_child(parent_id, old_id_1, None);
1145 tree.add_child(parent_id, old_id_2, None);
1146
1147 let mut new_child_2 = Element::new("Text");
1149 new_child_2.key = Some("item-2".to_string());
1150
1151 let mut new_child_3 = Element::new("Text");
1152 new_child_3.key = Some("item-3".to_string());
1153
1154 let new_children = vec![new_child_2, new_child_3];
1155 let old_children = vec![old_id_1, old_id_2];
1156
1157 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1158
1159 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1160 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1161
1162 assert_eq!(create_count, 1, "Should create item-3");
1163 assert_eq!(remove_count, 1, "Should remove item-1");
1164 }
1165}