1use super::{InstanceTree, Patch};
2use crate::ir::{Element, NodeId, Value};
3use crate::reactive::DependencyGraph;
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
215pub fn replace_item_bindings(element: &Element, item: &serde_json::Value, index: usize) -> Element {
217 let mut new_element = element.clone();
218
219 let mut new_props = IndexMap::new();
221 for (key, value) in &element.props {
222 let new_value = match value {
223 Value::Binding(binding) => {
224 if binding.path.first().map(|s| s.as_str()) == Some("item") {
226 let prop_path = binding.path[1..].join(".");
228
229 if let Some(val) = navigate_item_path(item, &prop_path) {
231 Value::Static(val.clone())
232 } else {
233 value.clone()
234 }
235 } else {
236 value.clone()
237 }
238 }
239 Value::Static(serde_json::Value::String(s)) => {
240 if s.contains("${item.") {
242 let mut result = s.clone();
243
244 let mut pos = 0;
247 while let Some(start) = result[pos..].find("${item.") {
248 let abs_start = pos + start;
249 if let Some(end) = result[abs_start..].find('}') {
250 let abs_end = abs_start + end;
251 let path = &result[abs_start + 7..abs_end]; if let Some(val) = navigate_item_path(item, path) {
255 let pattern = format!("${{item.{}}}", path);
256 let replacement = match val {
257 serde_json::Value::String(s) => s.clone(),
258 serde_json::Value::Number(n) => n.to_string(),
259 serde_json::Value::Bool(b) => b.to_string(),
260 _ => serde_json::to_string(val).unwrap_or_default(),
261 };
262 result = result.replace(&pattern, &replacement);
263 pos = 0; } else {
265 pos = abs_end + 1; }
267 } else {
268 break; }
270 }
271
272 Value::Static(serde_json::Value::String(result))
273 } else {
274 value.clone()
275 }
276 }
277 _ => value.clone(),
278 };
279 new_props.insert(key.clone(), new_value);
280 }
281 new_element.props = new_props;
282
283 new_element.key = Some(format!("list-item-{}", index));
285
286 new_element.children = element.children.iter()
288 .map(|child| replace_item_bindings(child, item, index))
289 .collect();
290
291 new_element
292}
293
294pub fn reconcile_node(
296 tree: &mut InstanceTree,
297 node_id: NodeId,
298 element: &Element,
299 state: &serde_json::Value,
300 patches: &mut Vec<Patch>,
301 dependencies: &mut DependencyGraph,
302) {
303 let node = tree.get(node_id).cloned();
304 if node.is_none() {
305 return;
306 }
307 let node = node.unwrap();
308
309 let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
313
314 if is_iterable {
315 let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
317 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
318 } else {
319 serde_json::Value::Array(vec![])
320 };
321
322 if let serde_json::Value::Array(items) = &array {
324 let old_children = node.children.clone();
325
326 let expected_children_count = items.len() * element.children.len();
328
329 if old_children.len() != expected_children_count {
331 for &old_child_id in &old_children {
332 patches.push(Patch::remove(old_child_id));
333 }
334
335 if let Some(node) = tree.get_mut(node_id) {
337 node.children.clear();
338 }
339
340 for (index, item) in items.iter().enumerate() {
342 for child_template in &element.children {
343 let child_with_item = replace_item_bindings(child_template, item, index);
344 create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
345 }
346 }
347 } else {
348 let mut child_index = 0;
350 for (item_index, item) in items.iter().enumerate() {
351 for child_template in &element.children {
352 if let Some(&old_child_id) = old_children.get(child_index) {
353 let child_with_item = replace_item_bindings(child_template, item, item_index);
354 reconcile_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
355 }
356 child_index += 1;
357 }
358 }
359 }
360 }
361
362 return; }
364
365 if node.element_type != element.element_type {
367 replace_subtree(tree, node_id, &node, element, state, patches, dependencies);
368 return;
369 }
370
371 for value in element.props.values() {
373 if let Value::Binding(binding) = value {
374 dependencies.add_dependency(node_id, binding);
375 }
376 }
377
378 let new_props = resolve_props(&element.props, state);
380 let prop_patches = diff_props(node_id, &node.props, &new_props);
381 patches.extend(prop_patches);
382
383 if let Some(node) = tree.get_mut(node_id) {
385 node.props = new_props.clone();
386 node.raw_props = element.props.clone();
387 }
388
389 let is_lazy = element.props.get("__lazy")
391 .and_then(|v| {
392 if let Value::Static(val) = v {
393 val.as_bool()
394 } else {
395 None
396 }
397 })
398 .unwrap_or(false);
399
400 if !is_lazy {
401 let old_children = node.children.clone();
402 let new_children = &element.children;
403
404 for (i, new_child_element) in new_children.iter().enumerate() {
406 if let Some(&old_child_id) = old_children.get(i) {
407 reconcile_node(tree, old_child_id, new_child_element, state, patches, dependencies);
409 } else {
410 let new_child_id = create_tree(tree, new_child_element, Some(node_id), state, patches, false, dependencies);
412 if let Some(node) = tree.get_mut(node_id) {
413 node.children.push(new_child_id);
414 }
415 }
416 }
417
418 if old_children.len() > new_children.len() {
420 for &old_child_id in &old_children[new_children.len()..] {
421 patches.push(Patch::remove(old_child_id));
422 }
424 }
425 }
426}
427
428fn replace_subtree(
431 tree: &mut InstanceTree,
432 old_node_id: NodeId,
433 old_node: &super::InstanceNode,
434 new_element: &Element,
435 state: &serde_json::Value,
436 patches: &mut Vec<Patch>,
437 dependencies: &mut DependencyGraph,
438) {
439 let parent_id = old_node.parent;
440
441 let old_position = if let Some(pid) = parent_id {
443 tree.get(pid).and_then(|parent| {
444 parent.children.iter().position(|&id| id == old_node_id)
445 })
446 } else {
447 None
448 };
449
450 let ids_to_remove = collect_subtree_ids(tree, old_node_id);
452
453 for &id in &ids_to_remove {
455 patches.push(Patch::remove(id));
456 dependencies.remove_node(id);
457 }
458
459 if let Some(pid) = parent_id {
461 if let Some(parent) = tree.get_mut(pid) {
462 parent.children.retain(|&id| id != old_node_id);
463 }
464 }
465
466 tree.remove(old_node_id);
468
469 let is_root = parent_id.is_none();
471 let new_node_id = create_tree(tree, new_element, parent_id, state, patches, is_root, dependencies);
472
473 if is_root {
475 tree.set_root(new_node_id);
476 } else if let Some(pid) = parent_id {
477 if let Some(pos) = old_position {
478 if let Some(parent) = tree.get_mut(pid) {
479 let current_len = parent.children.len();
480 if pos < current_len - 1 {
483 let new_id = parent.children.pop().unwrap();
485 parent.children.insert(pos, new_id);
486
487 let next_sibling = parent.children.get(pos + 1).copied();
489 patches.push(Patch::move_node(pid, new_node_id, next_sibling));
490 }
491 }
493 }
494 }
495}
496
497fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
500 let mut result = Vec::new();
501 let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
502
503 while let Some((node_id, children_processed)) = stack.pop() {
504 if children_processed {
505 result.push(node_id);
507 } else {
508 stack.push((node_id, true));
510 if let Some(node) = tree.get(node_id) {
511 for &child_id in node.children.iter().rev() {
513 stack.push((child_id, false));
514 }
515 }
516 }
517 }
518
519 result
520}
521
522fn resolve_props(props: &IndexMap<String, crate::ir::Value>, state: &serde_json::Value) -> IndexMap<String, serde_json::Value> {
524 let mut resolved = IndexMap::new();
525
526 for (key, value) in props {
527 let resolved_value = match value {
528 crate::ir::Value::Static(v) => v.clone(),
529 crate::ir::Value::Binding(binding) => {
530 evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
532 }
533 crate::ir::Value::TemplateString { template, bindings } => {
534 let mut result = template.clone();
536 for binding in bindings {
537 let binding_placeholder = format!("${{state.{}}}", binding.full_path());
538 let value = evaluate_binding(binding, state)
539 .map(|v| match v {
540 serde_json::Value::String(s) => s,
541 other => other.to_string(),
542 })
543 .unwrap_or_default();
544 result = result.replace(&binding_placeholder, &value);
545 }
546 serde_json::Value::String(result)
547 }
548 crate::ir::Value::Action(action) => {
549 serde_json::Value::String(format!("@{}", action))
551 }
552 };
553 resolved.insert(key.clone(), resolved_value);
554 }
555
556 resolved
557}
558
559fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
561 let mut current = state;
562
563 for segment in &binding.path {
564 current = current.get(segment)?;
565 }
566
567 Some(current.clone())
568}
569
570fn reconcile_keyed_children(
572 tree: &mut InstanceTree,
573 parent_id: NodeId,
574 old_children: &[NodeId],
575 new_children: &[Element],
576 state: &serde_json::Value,
577 dependencies: &mut DependencyGraph,
578) -> Vec<Patch> {
579 let mut patches = Vec::new();
580
581 let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
583 let mut old_unkeyed: Vec<NodeId> = Vec::new();
584
585 for &child_id in old_children {
586 if let Some(node) = tree.get(child_id) {
587 if let Some(key) = &node.key {
588 old_keyed.insert(key.clone(), child_id);
589 } else {
590 old_unkeyed.push(child_id);
591 }
592 }
593 }
594
595 let mut new_child_ids: Vec<NodeId> = Vec::new();
596 let mut unkeyed_idx = 0;
597
598 for new_element in new_children {
600 let new_key = new_element.key.as_ref();
601
602 let child_id = if let Some(key) = new_key {
603 if let Some(&old_id) = old_keyed.get(key) {
605 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
607 old_keyed.shift_remove(key); old_id
609 } else {
610 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
612 }
613 } else {
614 if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
616 reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
617 unkeyed_idx += 1;
618 old_id
619 } else {
620 create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
622 }
623 };
624
625 new_child_ids.push(child_id);
626 }
627
628 let mut old_positions: Vec<Option<usize>> = Vec::new();
631 for &new_id in &new_child_ids {
632 let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
633 old_positions.push(old_pos);
634 }
635
636 let lis = longest_increasing_subsequence(&old_positions);
638
639 if let Some(parent_node) = tree.get_mut(parent_id) {
641 parent_node.children = new_child_ids.clone();
642 }
643
644 for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
646 if !lis.contains(&new_idx) {
648 let before_id = new_child_ids.get(new_idx + 1).copied();
650 patches.push(Patch::move_node(parent_id, child_id, before_id));
651 }
652 }
653
654 for old_id in old_keyed.values() {
656 patches.push(Patch::remove(*old_id));
657 }
658 for &old_id in &old_unkeyed[unkeyed_idx..] {
659 patches.push(Patch::remove(old_id));
660 }
661
662 patches
663}
664
665fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
669 let valid: Vec<(usize, usize)> = positions
671 .iter()
672 .enumerate()
673 .filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
674 .collect();
675
676 if valid.is_empty() {
677 return Vec::new();
678 }
679
680 let n = valid.len();
682 let mut dp: Vec<usize> = vec![1; n];
683 let mut prev: Vec<Option<usize>> = vec![None; n];
684
685 for i in 1..n {
686 for j in 0..i {
687 if valid[j].1 < valid[i].1 && dp[j] + 1 > dp[i] {
688 dp[i] = dp[j] + 1;
689 prev[i] = Some(j);
690 }
691 }
692 }
693
694 let mut max_idx = 0;
696 for i in 1..n {
697 if dp[i] > dp[max_idx] {
698 max_idx = i;
699 }
700 }
701
702 let mut result = Vec::new();
704 let mut idx = Some(max_idx);
705 while let Some(i) = idx {
706 result.push(valid[i].0);
707 idx = prev[i];
708 }
709 result.reverse();
710
711 result
712}
713
714pub fn diff_props(
716 node_id: NodeId,
717 old_props: &IndexMap<String, serde_json::Value>,
718 new_props: &IndexMap<String, serde_json::Value>,
719) -> Vec<Patch> {
720 let mut patches = Vec::new();
721
722 for (key, new_value) in new_props {
724 if old_props.get(key) != Some(new_value) {
725 patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
726 }
727 }
728
729 patches
733}
734
735#[cfg(test)]
736mod tests {
737 use super::*;
738 use crate::ir::Value;
739 use serde_json::json;
740
741 #[test]
742 fn test_create_simple_tree() {
743 use crate::reactive::DependencyGraph;
744
745 let mut tree = InstanceTree::new();
746 let mut patches = Vec::new();
747 let mut dependencies = DependencyGraph::new();
748
749 let element = Element::new("Column")
750 .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
751
752 let state = json!({});
753 create_tree(&mut tree, &element, None, &state, &mut patches, true, &mut dependencies);
754
755 assert_eq!(patches.len(), 4);
758
759 let root_insert = patches.iter().find(|p| {
761 matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root")
762 });
763 assert!(root_insert.is_some(), "Root insert patch should exist");
764 }
765
766 #[test]
767 fn test_diff_props() {
768 let node_id = NodeId::default();
769 let old = indexmap::indexmap! {
770 "color".to_string() => json!("red"),
771 "size".to_string() => json!(16),
772 };
773 let new = indexmap::indexmap! {
774 "color".to_string() => json!("blue"),
775 "size".to_string() => json!(16),
776 };
777
778 let patches = diff_props(node_id, &old, &new);
779
780 assert_eq!(patches.len(), 1);
782 }
783
784 #[test]
785 fn test_longest_increasing_subsequence_basic() {
786 let positions = vec![Some(0), Some(1), Some(2), Some(3)];
788 let lis = longest_increasing_subsequence(&positions);
789 assert_eq!(lis, vec![0, 1, 2, 3]);
790 }
791
792 #[test]
793 fn test_longest_increasing_subsequence_reverse() {
794 let positions = vec![Some(3), Some(2), Some(1), Some(0)];
796 let lis = longest_increasing_subsequence(&positions);
797 assert_eq!(lis.len(), 1);
798 }
799
800 #[test]
801 fn test_longest_increasing_subsequence_mixed() {
802 let positions = vec![Some(0), Some(3), Some(1), Some(2)];
804 let lis = longest_increasing_subsequence(&positions);
805 assert_eq!(lis.len(), 3);
806 assert!(lis.contains(&0));
808 assert!(lis.contains(&2));
809 assert!(lis.contains(&3));
810 }
811
812 #[test]
813 fn test_longest_increasing_subsequence_with_new_items() {
814 let positions = vec![None, Some(0), None, Some(1)];
816 let lis = longest_increasing_subsequence(&positions);
817 assert_eq!(lis, vec![1, 3]); }
819
820 #[test]
821 fn test_longest_increasing_subsequence_empty() {
822 let positions: Vec<Option<usize>> = vec![];
823 let lis = longest_increasing_subsequence(&positions);
824 assert_eq!(lis, Vec::<usize>::new());
825 }
826
827 #[test]
828 fn test_longest_increasing_subsequence_all_new() {
829 let positions = vec![None, None, None];
831 let lis = longest_increasing_subsequence(&positions);
832 assert_eq!(lis, Vec::<usize>::new());
833 }
834
835 #[test]
836 fn test_reconcile_keyed_children_basic() {
837 let mut tree = InstanceTree::new();
838 let mut dependencies = DependencyGraph::new();
839 let state = json!({});
840
841 let parent = Element::new("Column");
843 let parent_id = tree.create_node(&parent, &state);
844
845 let mut old_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
847 old_child_1.key = Some("item-1".to_string());
848 let old_id_1 = tree.create_node(&old_child_1, &state);
849
850 let mut old_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
851 old_child_2.key = Some("item-2".to_string());
852 let old_id_2 = tree.create_node(&old_child_2, &state);
853
854 tree.add_child(parent_id, old_id_1, None);
855 tree.add_child(parent_id, old_id_2, None);
856
857 let mut new_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
859 new_child_2.key = Some("item-2".to_string());
860
861 let mut new_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
862 new_child_1.key = Some("item-1".to_string());
863
864 let new_children = vec![new_child_2, new_child_1];
865 let old_children = vec![old_id_1, old_id_2];
866
867 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
868
869 let move_count = patches.iter().filter(|p| matches!(p, Patch::Move { .. })).count();
871 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
872 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
873
874 assert_eq!(create_count, 0, "Should not create new children");
875 assert_eq!(remove_count, 0, "Should not remove any children");
876 assert!(move_count > 0, "Should have move patches for reordering");
877 }
878
879 #[test]
880 fn test_reconcile_keyed_children_add_remove() {
881 let mut tree = InstanceTree::new();
882 let mut dependencies = DependencyGraph::new();
883 let state = json!({});
884
885 let parent = Element::new("Column");
886 let parent_id = tree.create_node(&parent, &state);
887
888 let mut old_child_1 = Element::new("Text");
890 old_child_1.key = Some("item-1".to_string());
891 let old_id_1 = tree.create_node(&old_child_1, &state);
892
893 let mut old_child_2 = Element::new("Text");
894 old_child_2.key = Some("item-2".to_string());
895 let old_id_2 = tree.create_node(&old_child_2, &state);
896
897 tree.add_child(parent_id, old_id_1, None);
898 tree.add_child(parent_id, old_id_2, None);
899
900 let mut new_child_2 = Element::new("Text");
902 new_child_2.key = Some("item-2".to_string());
903
904 let mut new_child_3 = Element::new("Text");
905 new_child_3.key = Some("item-3".to_string());
906
907 let new_children = vec![new_child_2, new_child_3];
908 let old_children = vec![old_id_1, old_id_2];
909
910 let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
911
912 let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
913 let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
914
915 assert_eq!(create_count, 1, "Should create item-3");
916 assert_eq!(remove_count, 1, "Should remove item-1");
917 }
918}