orbit/component/
tree.rs

1//! Component tree management
2//!
3//! This module provides functionality to manage the tree of components, handling
4//! parent-child relationships, efficient updates, and lifecycle coordination.
5
6use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8
9#[cfg(test)]
10use crate::component::LifecyclePhase;
11use crate::component::{ComponentId, ComponentInstance, Context, LifecycleManager, Node};
12
13/// Result type for tree operations
14pub type TreeResult<T> = Result<T, TreeError>;
15
16/// Errors specific to tree operations
17#[derive(Debug, thiserror::Error)]
18pub enum TreeError {
19    /// Component not found in the tree
20    #[error("Component not found: {0}")]
21    ComponentNotFound(ComponentId),
22
23    /// Failed to add component (already exists)
24    #[error("Component already exists: {0}")]
25    ComponentAlreadyExists(ComponentId),
26
27    /// Failed to access component (lock error)
28    #[error("Failed to access component: {0}")]
29    LockError(String),
30
31    /// Component lifecycle error
32    #[error("Component lifecycle error: {0}")]
33    LifecycleError(#[from] crate::component::ComponentError),
34
35    /// Invalid parent-child relationship
36    #[error("Invalid parent-child relationship: {0}")]
37    InvalidRelationship(String),
38}
39
40/// Type alias for a thread-safe component instance
41pub type SharedComponentInstance = Arc<RwLock<ComponentInstance>>;
42
43/// Manager for the component tree
44///
45/// Handles parent-child relationships, lifecycle coordination, and efficient updates
46pub struct ComponentTree {
47    /// Map of component ID to component instance
48    components: RwLock<HashMap<ComponentId, SharedComponentInstance>>,
49
50    /// Map of component ID to lifecycle manager
51    lifecycle_managers: RwLock<HashMap<ComponentId, Arc<RwLock<LifecycleManager>>>>,
52
53    /// Map of component ID to its children's IDs
54    children: RwLock<HashMap<ComponentId, Vec<ComponentId>>>,
55
56    /// Map of component ID to its parent ID
57    parents: RwLock<HashMap<ComponentId, ComponentId>>,
58
59    /// Root component ID (if set)
60    root: RwLock<Option<ComponentId>>,
61
62    /// Application context
63    context: Context,
64}
65
66impl ComponentTree {
67    /// Create a new component tree
68    pub fn new(context: Context) -> Self {
69        Self {
70            components: RwLock::new(HashMap::new()),
71            lifecycle_managers: RwLock::new(HashMap::new()),
72            children: RwLock::new(HashMap::new()),
73            parents: RwLock::new(HashMap::new()),
74            root: RwLock::new(None),
75            context,
76        }
77    }
78
79    /// Set the root component
80    pub fn set_root(&self, component_id: ComponentId) -> TreeResult<()> {
81        // Check if component exists
82        if !self.has_component(component_id) {
83            return Err(TreeError::ComponentNotFound(component_id));
84        }
85
86        // Set as root
87        let mut root = self
88            .root
89            .write()
90            .map_err(|_| TreeError::LockError("Failed to lock root component".to_string()))?;
91
92        *root = Some(component_id);
93
94        Ok(())
95    }
96
97    /// Get the root component ID
98    pub fn root_id(&self) -> TreeResult<Option<ComponentId>> {
99        let root = self
100            .root
101            .read()
102            .map_err(|_| TreeError::LockError("Failed to read root component".to_string()))?;
103
104        Ok(*root)
105    }
106
107    /// Add a component to the tree
108    pub fn add_component(&self, component: ComponentInstance) -> TreeResult<ComponentId> {
109        let id = component.id();
110
111        // Check if component already exists
112        if self.has_component(id) {
113            return Err(TreeError::ComponentAlreadyExists(id));
114        }
115
116        // Create lifecycle manager
117        let lifecycle = LifecycleManager::new(component.clone(), self.context.clone());
118
119        // Add component to maps
120        {
121            let mut components = self
122                .components
123                .write()
124                .map_err(|_| TreeError::LockError("Failed to lock components map".to_string()))?;
125
126            components.insert(id, Arc::new(RwLock::new(component)));
127        }
128
129        {
130            let mut lifecycle_managers = self.lifecycle_managers.write().map_err(|_| {
131                TreeError::LockError("Failed to lock lifecycle managers map".to_string())
132            })?;
133
134            lifecycle_managers.insert(id, Arc::new(RwLock::new(lifecycle)));
135        }
136
137        {
138            let mut children = self
139                .children
140                .write()
141                .map_err(|_| TreeError::LockError("Failed to lock children map".to_string()))?;
142
143            children.insert(id, Vec::new());
144        }
145
146        Ok(id)
147    }
148
149    /// Remove a component from the tree
150    pub fn remove_component(&self, id: ComponentId) -> TreeResult<()> {
151        // Check if component exists
152        if !self.has_component(id) {
153            return Err(TreeError::ComponentNotFound(id));
154        }
155
156        // Get children to remove recursively
157        let children: Vec<ComponentId> = {
158            let children_map = self
159                .children
160                .read()
161                .map_err(|_| TreeError::LockError("Failed to read children map".to_string()))?;
162
163            children_map.get(&id).cloned().unwrap_or_default()
164        };
165
166        // Remove all children first (recursively)
167        for child_id in children {
168            self.remove_component(child_id)?;
169        }
170
171        // Remove from parent's children list
172        {
173            let parent_id_option = {
174                let parents = self
175                    .parents
176                    .read()
177                    .map_err(|_| TreeError::LockError("Failed to read parents map".to_string()))?;
178
179                parents.get(&id).cloned()
180            };
181
182            if let Some(parent_id) = parent_id_option {
183                let mut children_map = self.children.write().map_err(|_| {
184                    TreeError::LockError("Failed to write children map".to_string())
185                })?;
186
187                if let Some(siblings) = children_map.get_mut(&parent_id) {
188                    if let Some(index) = siblings.iter().position(|&c| c == id) {
189                        siblings.remove(index);
190                    }
191                }
192            }
193        }
194
195        // Remove from maps
196        {
197            let mut components = self
198                .components
199                .write()
200                .map_err(|_| TreeError::LockError("Failed to lock components map".to_string()))?;
201
202            components.remove(&id);
203        }
204
205        {
206            let mut lifecycle_managers = self.lifecycle_managers.write().map_err(|_| {
207                TreeError::LockError("Failed to lock lifecycle managers map".to_string())
208            })?;
209
210            lifecycle_managers.remove(&id);
211        }
212
213        {
214            let mut children = self
215                .children
216                .write()
217                .map_err(|_| TreeError::LockError("Failed to lock children map".to_string()))?;
218
219            children.remove(&id);
220        }
221
222        {
223            let mut parents = self
224                .parents
225                .write()
226                .map_err(|_| TreeError::LockError("Failed to lock parents map".to_string()))?;
227
228            parents.remove(&id);
229        }
230
231        // If this was the root, unset it
232        {
233            let mut root = self
234                .root
235                .write()
236                .map_err(|_| TreeError::LockError("Failed to lock root component".to_string()))?;
237
238            if let Some(root_id) = *root {
239                if root_id == id {
240                    *root = None;
241                }
242            }
243        }
244
245        Ok(())
246    }
247
248    /// Add a child component to a parent
249    pub fn add_child(&self, parent_id: ComponentId, child_id: ComponentId) -> TreeResult<()> {
250        // Verify both components exist
251        if !self.has_component(parent_id) {
252            return Err(TreeError::ComponentNotFound(parent_id));
253        }
254
255        if !self.has_component(child_id) {
256            return Err(TreeError::ComponentNotFound(child_id));
257        }
258
259        // Check if child already has a parent (can only have one)
260        {
261            let parents = self
262                .parents
263                .read()
264                .map_err(|_| TreeError::LockError("Failed to read parents map".to_string()))?;
265
266            if let Some(existing_parent) = parents.get(&child_id) {
267                if *existing_parent != parent_id {
268                    return Err(TreeError::InvalidRelationship(format!(
269                        "Component {} already has a parent {}",
270                        child_id.id(),
271                        existing_parent.id()
272                    )));
273                }
274
275                // Child is already added to this parent
276                return Ok(());
277            }
278        }
279
280        // Add child to parent's children list
281        {
282            let mut children_map = self
283                .children
284                .write()
285                .map_err(|_| TreeError::LockError("Failed to write children map".to_string()))?;
286
287            if let Some(children) = children_map.get_mut(&parent_id) {
288                if !children.contains(&child_id) {
289                    children.push(child_id);
290                }
291            }
292        }
293
294        // Add parent to child's parent reference
295        {
296            let mut parents = self
297                .parents
298                .write()
299                .map_err(|_| TreeError::LockError("Failed to write parents map".to_string()))?;
300
301            parents.insert(child_id, parent_id);
302        }
303
304        Ok(())
305    }
306
307    /// Remove a child from a parent
308    pub fn remove_child(&self, parent_id: ComponentId, child_id: ComponentId) -> TreeResult<()> {
309        // Verify both components exist
310        if !self.has_component(parent_id) {
311            return Err(TreeError::ComponentNotFound(parent_id));
312        }
313
314        if !self.has_component(child_id) {
315            return Err(TreeError::ComponentNotFound(child_id));
316        }
317
318        // Remove child from parent's children list
319        {
320            let mut children_map = self
321                .children
322                .write()
323                .map_err(|_| TreeError::LockError("Failed to write children map".to_string()))?;
324
325            if let Some(children) = children_map.get_mut(&parent_id) {
326                if let Some(index) = children.iter().position(|&c| c == child_id) {
327                    children.remove(index);
328                }
329            }
330        }
331
332        // Remove parent reference from child
333        {
334            let mut parents = self
335                .parents
336                .write()
337                .map_err(|_| TreeError::LockError("Failed to write parents map".to_string()))?;
338
339            parents.remove(&child_id);
340        }
341
342        Ok(())
343    }
344
345    /// Get a component instance by ID
346    pub fn get_component(&self, id: ComponentId) -> TreeResult<SharedComponentInstance> {
347        let components = self
348            .components
349            .read()
350            .map_err(|_| TreeError::LockError("Failed to read components map".to_string()))?;
351
352        if let Some(component) = components.get(&id) {
353            Ok(component.clone())
354        } else {
355            Err(TreeError::ComponentNotFound(id))
356        }
357    }
358
359    /// Get a lifecycle manager by component ID
360    pub fn get_lifecycle_manager(
361        &self,
362        id: ComponentId,
363    ) -> TreeResult<Arc<RwLock<LifecycleManager>>> {
364        let lifecycle_managers = self.lifecycle_managers.read().map_err(|_| {
365            TreeError::LockError("Failed to read lifecycle managers map".to_string())
366        })?;
367
368        if let Some(manager) = lifecycle_managers.get(&id) {
369            Ok(manager.clone())
370        } else {
371            Err(TreeError::ComponentNotFound(id))
372        }
373    }
374
375    /// Initialize a component
376    pub fn initialize_component(&self, id: ComponentId) -> TreeResult<()> {
377        let lifecycle_manager = self.get_lifecycle_manager(id)?;
378        let mut manager = lifecycle_manager
379            .write()
380            .map_err(|_| TreeError::LockError("Failed to lock lifecycle manager".to_string()))?;
381
382        manager.initialize().map_err(TreeError::LifecycleError)?;
383
384        Ok(())
385    }
386
387    /// Mount a component
388    pub fn mount_component(&self, id: ComponentId) -> TreeResult<()> {
389        let lifecycle_manager = self.get_lifecycle_manager(id)?;
390        let mut manager = lifecycle_manager
391            .write()
392            .map_err(|_| TreeError::LockError("Failed to lock lifecycle manager".to_string()))?;
393
394        manager.mount().map_err(TreeError::LifecycleError)?;
395
396        Ok(())
397    }
398
399    /// Unmount a component
400    pub fn unmount_component(&self, id: ComponentId) -> TreeResult<()> {
401        let lifecycle_manager = self.get_lifecycle_manager(id)?;
402        let mut manager = lifecycle_manager
403            .write()
404            .map_err(|_| TreeError::LockError("Failed to lock lifecycle manager".to_string()))?;
405
406        manager.unmount().map_err(TreeError::LifecycleError)?;
407
408        Ok(())
409    }
410
411    /// Render a component
412    pub fn render_component(&self, id: ComponentId) -> TreeResult<Vec<Node>> {
413        let lifecycle_manager = self.get_lifecycle_manager(id)?;
414        let manager = lifecycle_manager
415            .read()
416            .map_err(|_| TreeError::LockError("Failed to read lifecycle manager".to_string()))?;
417
418        manager.render().map_err(TreeError::LifecycleError)
419    }
420
421    /// Check if a component exists in the tree
422    pub fn has_component(&self, id: ComponentId) -> bool {
423        let components = match self.components.read() {
424            Ok(c) => c,
425            Err(_) => return false,
426        };
427
428        components.contains_key(&id)
429    }
430
431    /// Get the children of a component
432    pub fn get_children(&self, id: ComponentId) -> TreeResult<Vec<ComponentId>> {
433        let children_map = self
434            .children
435            .read()
436            .map_err(|_| TreeError::LockError("Failed to read children map".to_string()))?;
437
438        if let Some(children) = children_map.get(&id) {
439            Ok(children.clone())
440        } else {
441            // Return empty Vec if component doesn't exist or has no children
442            Ok(Vec::new())
443        }
444    }
445
446    /// Get the parent of a component
447    pub fn get_parent(&self, id: ComponentId) -> TreeResult<Option<ComponentId>> {
448        let parents = self
449            .parents
450            .read()
451            .map_err(|_| TreeError::LockError("Failed to read parents map".to_string()))?;
452
453        Ok(parents.get(&id).cloned())
454    }
455
456    /// Recursively mount a component and all its children
457    pub fn mount_component_tree(&self, id: ComponentId) -> TreeResult<()> {
458        // First mount this component
459        self.mount_component(id)?;
460
461        // Then mount all children recursively
462        let children = self.get_children(id)?;
463        for child_id in children {
464            self.mount_component_tree(child_id)?;
465        }
466
467        Ok(())
468    }
469
470    /// Recursively unmount a component and all its children
471    pub fn unmount_component_tree(&self, id: ComponentId) -> TreeResult<()> {
472        // First unmount all children recursively (bottom-up approach)
473        let children = self.get_children(id)?;
474        for child_id in children {
475            self.unmount_component_tree(child_id)?;
476        }
477
478        // Then unmount this component
479        self.unmount_component(id)?;
480
481        Ok(())
482    }
483
484    /// Update a component with new props
485    pub fn update_component<P: crate::component::Props + 'static>(
486        &self,
487        id: ComponentId,
488        props: P,
489    ) -> TreeResult<()> {
490        let lifecycle_manager = self.get_lifecycle_manager(id)?;
491        let mut manager = lifecycle_manager
492            .write()
493            .map_err(|_| TreeError::LockError("Failed to lock lifecycle manager".to_string()))?;
494
495        // Box the props for dynamic dispatch
496        let boxed_props = Box::new(props);
497
498        manager
499            .update(boxed_props)
500            .map_err(TreeError::LifecycleError)?;
501
502        Ok(())
503    }
504
505    /// Recursively update a component and its children if needed
506    pub fn update_component_tree<P: crate::component::Props + Clone + 'static>(
507        &self,
508        id: ComponentId,
509        props: P,
510    ) -> TreeResult<()> {
511        // Update this component
512        self.update_component(id, props.clone())?;
513
514        // Get children to potentially update
515        let _children = self.get_children(id)?;
516
517        // In a real implementation, we would check if child props need updating
518        // based on parent props and pass down appropriate derived props
519        // For now, we'll just note that we would do this
520
521        // Render the parent component
522        let _nodes = self.render_component(id)?;
523
524        // Process rendered nodes to extract child props and update children
525        // This is a simplified version - in a real implementation, this would be more complex
526        // and would match rendered nodes to child components
527
528        Ok(())
529    }
530
531    /// Get all components in the tree
532    pub fn get_all_components(&self) -> TreeResult<Vec<ComponentId>> {
533        let components = self
534            .components
535            .read()
536            .map_err(|_| TreeError::LockError("Failed to read components map".to_string()))?;
537
538        Ok(components.keys().cloned().collect())
539    }
540
541    /// Get all components that need updating
542    pub fn get_components_to_update(&self) -> TreeResult<Vec<ComponentId>> {
543        // In a real implementation, this would check component dirty flags
544        // or other state to determine which components need updating
545        // For now, just return an empty vector
546        Ok(Vec::new())
547    }
548
549    /// Perform state change detection for a component
550    pub fn detect_state_changes(&self, id: ComponentId) -> TreeResult<bool> {
551        let component = self.get_component(id)?;
552        let _component = component
553            .read()
554            .map_err(|_| TreeError::LockError("Failed to read component".to_string()))?;
555
556        // This would compare previous state with current state
557        // and return true if changes are detected
558        // For now, just return false indicating no changes
559        Ok(false)
560    }
561
562    /// Batch update multiple components
563    pub fn batch_update_components(&self, ids: &[ComponentId]) -> TreeResult<usize> {
564        for id in ids {
565            if !self.has_component(*id) {
566                return Err(TreeError::ComponentNotFound(*id));
567            }
568        }
569
570        // In a real implementation, this would optimize the update sequence
571        // based on component dependencies or tree structure
572        let mut updated_count = 0;
573
574        for &id in ids {
575            // Get the lifecycle manager for this component
576            let lifecycle_manager = self.get_lifecycle_manager(id)?;
577            let _manager = lifecycle_manager.read().map_err(|_| {
578                TreeError::LockError("Failed to lock lifecycle manager".to_string())
579            })?;
580
581            // For now, we'll just count this as an updated component
582            // In the future, we'll need to implement proper state change detection
583            updated_count += 1;
584        }
585
586        Ok(updated_count)
587    }
588}
589
590// Implement Debug manually to avoid requiring ComponentInstance and LifecycleManager to implement Debug
591impl std::fmt::Debug for ComponentTree {
592    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593        f.debug_struct("ComponentTree")
594            .field(
595                "component_count",
596                &self.components.try_read().map(|c| c.len()).unwrap_or(0),
597            )
598            .field("root", &self.root.try_read().ok())
599            .finish()
600    }
601}
602
603#[cfg(test)]
604mod tests {
605    use super::*;
606    use crate::component::{Component, ComponentError, Context, Node};
607
608    // Simple test component
609    struct TestComponent {
610        id: ComponentId,
611        #[allow(dead_code)]
612        context: Context,
613        name: String,
614    }
615
616    // Empty props for test component
617    #[derive(Clone)]
618    struct TestProps {
619        name: String,
620    }
621
622    // Implement Props trait for TestProps - this is automatically handled by the blanket implementation in mod.rs
623
624    impl Component for TestComponent {
625        type Props = TestProps;
626
627        fn component_id(&self) -> ComponentId {
628            self.id
629        }
630
631        fn create(props: Self::Props, context: Context) -> Self {
632            Self {
633                id: ComponentId::new(),
634                context,
635                name: props.name,
636            }
637        }
638
639        fn update(&mut self, props: Self::Props) -> Result<(), ComponentError> {
640            self.name = props.name;
641            Ok(())
642        }
643
644        fn render(&self) -> Result<Vec<Node>, ComponentError> {
645            Ok(vec![])
646        }
647
648        fn as_any(&self) -> &dyn std::any::Any {
649            self
650        }
651
652        fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
653            self
654        }
655    }
656
657    fn create_test_component(name: &str, context: Context) -> ComponentInstance {
658        let props = TestProps {
659            name: name.to_string(),
660        };
661
662        let component = TestComponent::create(props.clone(), context.clone());
663
664        ComponentInstance::new(component, props)
665    }
666
667    #[test]
668    fn test_component_tree_basic() {
669        // Create tree
670        let context = Context::new();
671        let tree = ComponentTree::new(context.clone());
672
673        // Add root component
674        let root_component = create_test_component("root", context.clone());
675        let root_id = tree.add_component(root_component).unwrap();
676
677        // Set as root
678        tree.set_root(root_id).unwrap();
679
680        // Verify root is set correctly
681        assert_eq!(tree.root_id().unwrap(), Some(root_id));
682
683        // Add child component
684        let child_component = create_test_component("child", context.clone());
685        let child_id = tree.add_component(child_component).unwrap();
686
687        // Add parent-child relationship
688        tree.add_child(root_id, child_id).unwrap();
689
690        // Verify relationships
691        let children = tree.get_children(root_id).unwrap();
692        assert_eq!(children.len(), 1);
693        assert_eq!(children[0], child_id);
694
695        let parent = tree.get_parent(child_id).unwrap();
696        assert_eq!(parent, Some(root_id));
697    }
698
699    #[test]
700    fn test_component_tree_lifecycle() {
701        // Create tree
702        let context = Context::new();
703        let tree = ComponentTree::new(context.clone());
704
705        // Add root component
706        let root_component = create_test_component("root", context.clone());
707        let root_id = tree.add_component(root_component).unwrap();
708        tree.set_root(root_id).unwrap();
709
710        // Add child components
711        let child1_component = create_test_component("child1", context.clone());
712        let child1_id = tree.add_component(child1_component).unwrap();
713
714        let child2_component = create_test_component("child2", context.clone());
715        let child2_id = tree.add_component(child2_component).unwrap();
716
717        // Set up parent-child relationships
718        tree.add_child(root_id, child1_id).unwrap();
719        tree.add_child(root_id, child2_id).unwrap();
720
721        // Initialize and mount recursively
722        tree.mount_component_tree(root_id).unwrap();
723
724        // Check if components are mounted
725        let root_lifecycle = tree.get_lifecycle_manager(root_id).unwrap();
726        let root_phase = {
727            let manager = root_lifecycle.read().unwrap();
728            manager.current_phase()
729        };
730        assert_eq!(root_phase, LifecyclePhase::Mounted);
731
732        let child1_lifecycle = tree.get_lifecycle_manager(child1_id).unwrap();
733        let child1_phase = {
734            let manager = child1_lifecycle.read().unwrap();
735            manager.current_phase()
736        };
737        assert_eq!(child1_phase, LifecyclePhase::Mounted);
738    }
739
740    #[test]
741    fn test_component_tree_removal() {
742        // Create tree
743        let context = Context::new();
744        let tree = ComponentTree::new(context.clone());
745
746        // Add root component
747        let root_component = create_test_component("root", context.clone());
748        let root_id = tree.add_component(root_component).unwrap();
749        tree.set_root(root_id).unwrap();
750
751        // Add child components
752        let child1_component = create_test_component("child1", context.clone());
753        let child1_id = tree.add_component(child1_component).unwrap();
754
755        let child2_component = create_test_component("child2", context.clone());
756        let child2_id = tree.add_component(child2_component).unwrap();
757
758        // Set up parent-child relationships
759        tree.add_child(root_id, child1_id).unwrap();
760        tree.add_child(root_id, child2_id).unwrap();
761
762        // Remove one child
763        tree.remove_child(root_id, child1_id).unwrap();
764
765        // Verify it's removed from parent's children
766        let children = tree.get_children(root_id).unwrap();
767        assert_eq!(children.len(), 1);
768        assert_eq!(children[0], child2_id);
769
770        // Verify parent is removed from child
771        let parent = tree.get_parent(child1_id).unwrap();
772        assert_eq!(parent, None);
773
774        // Component should still exist in the tree
775        assert!(tree.has_component(child1_id));
776
777        // Now fully remove the component
778        tree.remove_component(child1_id).unwrap();
779
780        // Verify it's gone
781        assert!(!tree.has_component(child1_id));
782
783        // Now remove the root (should remove child2 as well)
784        tree.remove_component(root_id).unwrap();
785
786        // Verify both are gone
787        assert!(!tree.has_component(root_id));
788        assert!(!tree.has_component(child2_id));
789
790        // Verify root is unset
791        assert_eq!(tree.root_id().unwrap(), None);
792    }
793
794    #[test]
795    fn test_dirty_component_updates() {
796        // Create tree
797        let context = Context::new();
798        let tree = ComponentTree::new(context.clone());
799
800        // Add components
801        let root_component = create_test_component("root", context.clone());
802        let root_id = tree.add_component(root_component).unwrap();
803        tree.set_root(root_id).unwrap();
804
805        let child1_component = create_test_component("child1", context.clone());
806        let child1_id = tree.add_component(child1_component).unwrap();
807
808        let child2_component = create_test_component("child2", context.clone());
809        let child2_id = tree.add_component(child2_component).unwrap();
810
811        // Set up relationships
812        tree.add_child(root_id, child1_id).unwrap();
813        tree.add_child(root_id, child2_id).unwrap();
814
815        // Initialize and mount
816        tree.mount_component_tree(root_id).unwrap();
817
818        // Mark some as dirty
819        // Create a collection of dirty components
820        let dirty_components = vec![root_id, child1_id];
821
822        // Update dirty components by batching
823        let updated = tree.batch_update_components(&dirty_components).unwrap();
824
825        // Should have updated 2 components
826        assert_eq!(updated, 2);
827    }
828}