presentar_core/
lifecycle.rs

1#![allow(clippy::unwrap_used, clippy::disallowed_methods)]
2//! Widget lifecycle hooks for mount, update, and unmount callbacks.
3//!
4//! This module provides a system for managing widget lifecycle events,
5//! similar to React's useEffect or Vue's lifecycle hooks.
6
7use crate::widget::WidgetId;
8use std::collections::HashMap;
9
10/// Lifecycle phase for widgets.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum LifecyclePhase {
13    /// Widget is being created/mounted.
14    Mount,
15    /// Widget is being updated (props/state changed).
16    Update,
17    /// Widget is being removed/unmounted.
18    Unmount,
19    /// Before the paint phase.
20    BeforePaint,
21    /// After the paint phase.
22    AfterPaint,
23    /// Widget gained focus.
24    Focus,
25    /// Widget lost focus.
26    Blur,
27    /// Widget became visible.
28    Visible,
29    /// Widget became hidden.
30    Hidden,
31}
32
33/// A lifecycle callback that can be registered.
34pub type LifecycleCallback = Box<dyn FnMut(LifecycleEvent) + Send>;
35
36/// Event passed to lifecycle callbacks.
37#[derive(Debug, Clone)]
38pub struct LifecycleEvent {
39    /// Widget ID.
40    pub widget_id: WidgetId,
41    /// Phase of the lifecycle.
42    pub phase: LifecyclePhase,
43    /// Timestamp (frame number or monotonic counter).
44    pub timestamp: u64,
45}
46
47impl LifecycleEvent {
48    /// Create a new lifecycle event.
49    pub fn new(widget_id: WidgetId, phase: LifecyclePhase, timestamp: u64) -> Self {
50        Self {
51            widget_id,
52            phase,
53            timestamp,
54        }
55    }
56}
57
58/// Unique ID for a lifecycle hook registration.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60pub struct HookId(pub u64);
61
62impl HookId {
63    /// Create a new hook ID.
64    pub const fn new(id: u64) -> Self {
65        Self(id)
66    }
67}
68
69/// Registration for a lifecycle hook.
70#[derive(Debug)]
71struct HookRegistration {
72    #[allow(dead_code)]
73    id: HookId,
74    widget_id: WidgetId,
75    phases: Vec<LifecyclePhase>,
76}
77
78/// Manager for widget lifecycle hooks.
79pub struct LifecycleManager {
80    /// Next hook ID.
81    next_id: u64,
82    /// Registered hooks (by hook ID).
83    hooks: HashMap<HookId, HookRegistration>,
84    /// Callbacks (by hook ID).
85    callbacks: HashMap<HookId, LifecycleCallback>,
86    /// Index of hooks by widget ID.
87    by_widget: HashMap<WidgetId, Vec<HookId>>,
88    /// Index of hooks by phase.
89    by_phase: HashMap<LifecyclePhase, Vec<HookId>>,
90    /// Current frame/timestamp.
91    timestamp: u64,
92    /// Pending events to dispatch.
93    pending_events: Vec<LifecycleEvent>,
94}
95
96impl LifecycleManager {
97    /// Create a new lifecycle manager.
98    pub fn new() -> Self {
99        Self {
100            next_id: 0,
101            hooks: HashMap::new(),
102            callbacks: HashMap::new(),
103            by_widget: HashMap::new(),
104            by_phase: HashMap::new(),
105            timestamp: 0,
106            pending_events: Vec::new(),
107        }
108    }
109
110    /// Register a lifecycle hook.
111    ///
112    /// Returns a hook ID that can be used to unregister the hook.
113    pub fn register(
114        &mut self,
115        widget_id: WidgetId,
116        phases: Vec<LifecyclePhase>,
117        callback: LifecycleCallback,
118    ) -> HookId {
119        let id = HookId::new(self.next_id);
120        self.next_id += 1;
121
122        let registration = HookRegistration {
123            id,
124            widget_id,
125            phases: phases.clone(),
126        };
127
128        self.hooks.insert(id, registration);
129        self.callbacks.insert(id, callback);
130
131        // Index by widget
132        self.by_widget.entry(widget_id).or_default().push(id);
133
134        // Index by phase
135        for phase in phases {
136            self.by_phase.entry(phase).or_default().push(id);
137        }
138
139        id
140    }
141
142    /// Register a mount hook.
143    pub fn on_mount(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
144        self.register(widget_id, vec![LifecyclePhase::Mount], callback)
145    }
146
147    /// Register an unmount hook.
148    pub fn on_unmount(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
149        self.register(widget_id, vec![LifecyclePhase::Unmount], callback)
150    }
151
152    /// Register an update hook.
153    pub fn on_update(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
154        self.register(widget_id, vec![LifecyclePhase::Update], callback)
155    }
156
157    /// Register a focus hook.
158    pub fn on_focus(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
159        self.register(widget_id, vec![LifecyclePhase::Focus], callback)
160    }
161
162    /// Register a blur hook.
163    pub fn on_blur(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
164        self.register(widget_id, vec![LifecyclePhase::Blur], callback)
165    }
166
167    /// Unregister a hook.
168    pub fn unregister(&mut self, hook_id: HookId) -> bool {
169        if let Some(registration) = self.hooks.remove(&hook_id) {
170            self.callbacks.remove(&hook_id);
171
172            // Remove from widget index
173            if let Some(hooks) = self.by_widget.get_mut(&registration.widget_id) {
174                hooks.retain(|&id| id != hook_id);
175            }
176
177            // Remove from phase index
178            for phase in &registration.phases {
179                if let Some(hooks) = self.by_phase.get_mut(phase) {
180                    hooks.retain(|&id| id != hook_id);
181                }
182            }
183
184            true
185        } else {
186            false
187        }
188    }
189
190    /// Unregister all hooks for a widget.
191    pub fn unregister_widget(&mut self, widget_id: WidgetId) {
192        if let Some(hook_ids) = self.by_widget.remove(&widget_id) {
193            for hook_id in hook_ids {
194                if let Some(registration) = self.hooks.remove(&hook_id) {
195                    self.callbacks.remove(&hook_id);
196
197                    for phase in &registration.phases {
198                        if let Some(hooks) = self.by_phase.get_mut(phase) {
199                            hooks.retain(|&id| id != hook_id);
200                        }
201                    }
202                }
203            }
204        }
205    }
206
207    /// Emit a lifecycle event immediately.
208    pub fn emit(&mut self, widget_id: WidgetId, phase: LifecyclePhase) {
209        let event = LifecycleEvent::new(widget_id, phase, self.timestamp);
210
211        // Get hooks for this widget and phase
212        let widget_hooks = self.by_widget.get(&widget_id).cloned().unwrap_or_default();
213        let phase_hooks = self.by_phase.get(&phase).cloned().unwrap_or_default();
214
215        // Find intersection (hooks registered for both this widget and phase)
216        for hook_id in widget_hooks {
217            if phase_hooks.contains(&hook_id) {
218                if let Some(callback) = self.callbacks.get_mut(&hook_id) {
219                    callback(event.clone());
220                }
221            }
222        }
223    }
224
225    /// Queue a lifecycle event for later dispatch.
226    pub fn queue(&mut self, widget_id: WidgetId, phase: LifecyclePhase) {
227        let event = LifecycleEvent::new(widget_id, phase, self.timestamp);
228        self.pending_events.push(event);
229    }
230
231    /// Dispatch all pending events.
232    pub fn flush(&mut self) {
233        let events: Vec<LifecycleEvent> = self.pending_events.drain(..).collect();
234
235        for event in events {
236            let widget_hooks = self
237                .by_widget
238                .get(&event.widget_id)
239                .cloned()
240                .unwrap_or_default();
241            let phase_hooks = self.by_phase.get(&event.phase).cloned().unwrap_or_default();
242
243            for hook_id in widget_hooks {
244                if phase_hooks.contains(&hook_id) {
245                    if let Some(callback) = self.callbacks.get_mut(&hook_id) {
246                        callback(event.clone());
247                    }
248                }
249            }
250        }
251    }
252
253    /// Get the number of pending events.
254    pub fn pending_count(&self) -> usize {
255        self.pending_events.len()
256    }
257
258    /// Advance the timestamp.
259    pub fn tick(&mut self) {
260        self.timestamp += 1;
261    }
262
263    /// Get the current timestamp.
264    pub fn timestamp(&self) -> u64 {
265        self.timestamp
266    }
267
268    /// Get the number of registered hooks.
269    pub fn hook_count(&self) -> usize {
270        self.hooks.len()
271    }
272
273    /// Check if a widget has any hooks.
274    pub fn has_hooks(&self, widget_id: WidgetId) -> bool {
275        self.by_widget
276            .get(&widget_id)
277            .is_some_and(|h| !h.is_empty())
278    }
279
280    /// Clear all hooks and events.
281    pub fn clear(&mut self) {
282        self.hooks.clear();
283        self.callbacks.clear();
284        self.by_widget.clear();
285        self.by_phase.clear();
286        self.pending_events.clear();
287    }
288}
289
290impl Default for LifecycleManager {
291    fn default() -> Self {
292        Self::new()
293    }
294}
295
296impl std::fmt::Debug for LifecycleManager {
297    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298        f.debug_struct("LifecycleManager")
299            .field("next_id", &self.next_id)
300            .field("hook_count", &self.hooks.len())
301            .field("timestamp", &self.timestamp)
302            .field("pending_count", &self.pending_events.len())
303            .finish()
304    }
305}
306
307/// Effect hook that runs a callback and optionally cleans up.
308pub struct Effect {
309    /// Effect function that returns an optional cleanup function.
310    effect: Option<Box<dyn FnOnce() -> Option<Box<dyn FnOnce() + Send>> + Send>>,
311    /// Cleanup function from the last run.
312    cleanup: Option<Box<dyn FnOnce() + Send>>,
313    /// Dependencies for determining when to re-run.
314    deps: Vec<u64>,
315}
316
317impl Effect {
318    /// Create a new effect.
319    pub fn new<F>(effect: F) -> Self
320    where
321        F: FnOnce() -> Option<Box<dyn FnOnce() + Send>> + Send + 'static,
322    {
323        Self {
324            effect: Some(Box::new(effect)),
325            cleanup: None,
326            deps: Vec::new(),
327        }
328    }
329
330    /// Create an effect with dependencies.
331    pub fn with_deps<F>(effect: F, deps: Vec<u64>) -> Self
332    where
333        F: FnOnce() -> Option<Box<dyn FnOnce() + Send>> + Send + 'static,
334    {
335        Self {
336            effect: Some(Box::new(effect)),
337            cleanup: None,
338            deps,
339        }
340    }
341
342    /// Check if dependencies changed.
343    pub fn deps_changed(&self, new_deps: &[u64]) -> bool {
344        if self.deps.len() != new_deps.len() {
345            return true;
346        }
347        self.deps.iter().zip(new_deps).any(|(a, b)| a != b)
348    }
349
350    /// Run the effect if dependencies changed.
351    pub fn run(&mut self, new_deps: Option<&[u64]>) -> bool {
352        // Check if we should run based on deps
353        let should_run = match new_deps {
354            Some(deps) if !self.deps_changed(deps) => false,
355            _ => true,
356        };
357
358        if !should_run {
359            return false;
360        }
361
362        // Run cleanup from previous effect
363        if let Some(cleanup) = self.cleanup.take() {
364            cleanup();
365        }
366
367        // Run the effect
368        if let Some(effect) = self.effect.take() {
369            self.cleanup = effect();
370        }
371
372        // Update deps
373        if let Some(deps) = new_deps {
374            self.deps = deps.to_vec();
375        }
376
377        true
378    }
379
380    /// Run cleanup without running the effect.
381    pub fn cleanup(&mut self) {
382        if let Some(cleanup) = self.cleanup.take() {
383            cleanup();
384        }
385    }
386
387    /// Check if the effect has a pending cleanup.
388    pub fn has_cleanup(&self) -> bool {
389        self.cleanup.is_some()
390    }
391}
392
393impl std::fmt::Debug for Effect {
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395        f.debug_struct("Effect")
396            .field("has_effect", &self.effect.is_some())
397            .field("has_cleanup", &self.cleanup.is_some())
398            .field("deps", &self.deps)
399            .finish()
400    }
401}
402
403/// Manager for effects with automatic cleanup.
404#[derive(Debug, Default)]
405pub struct EffectManager {
406    /// Effects by widget ID.
407    effects: HashMap<WidgetId, Vec<Effect>>,
408}
409
410impl EffectManager {
411    /// Create a new effect manager.
412    pub fn new() -> Self {
413        Self::default()
414    }
415
416    /// Add an effect for a widget.
417    pub fn add(&mut self, widget_id: WidgetId, effect: Effect) {
418        self.effects.entry(widget_id).or_default().push(effect);
419    }
420
421    /// Run all effects for a widget.
422    pub fn run_effects(&mut self, widget_id: WidgetId, deps: Option<&[u64]>) {
423        if let Some(effects) = self.effects.get_mut(&widget_id) {
424            for effect in effects {
425                effect.run(deps);
426            }
427        }
428    }
429
430    /// Clean up effects for a widget (e.g., on unmount).
431    pub fn cleanup_widget(&mut self, widget_id: WidgetId) {
432        if let Some(effects) = self.effects.get_mut(&widget_id) {
433            for effect in effects {
434                effect.cleanup();
435            }
436        }
437        self.effects.remove(&widget_id);
438    }
439
440    /// Get the number of widgets with effects.
441    pub fn widget_count(&self) -> usize {
442        self.effects.len()
443    }
444
445    /// Get the total number of effects.
446    pub fn effect_count(&self) -> usize {
447        self.effects.values().map(std::vec::Vec::len).sum()
448    }
449
450    /// Clear all effects.
451    pub fn clear(&mut self) {
452        for effects in self.effects.values_mut() {
453            for effect in effects {
454                effect.cleanup();
455            }
456        }
457        self.effects.clear();
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464    use std::sync::atomic::{AtomicUsize, Ordering};
465    use std::sync::Arc;
466
467    // LifecyclePhase tests
468    #[test]
469    fn test_lifecycle_phase_equality() {
470        assert_eq!(LifecyclePhase::Mount, LifecyclePhase::Mount);
471        assert_ne!(LifecyclePhase::Mount, LifecyclePhase::Unmount);
472    }
473
474    // LifecycleEvent tests
475    #[test]
476    fn test_lifecycle_event_new() {
477        let event = LifecycleEvent::new(WidgetId::new(1), LifecyclePhase::Mount, 42);
478        assert_eq!(event.widget_id, WidgetId::new(1));
479        assert_eq!(event.phase, LifecyclePhase::Mount);
480        assert_eq!(event.timestamp, 42);
481    }
482
483    // HookId tests
484    #[test]
485    fn test_hook_id() {
486        let id1 = HookId::new(1);
487        let id2 = HookId::new(1);
488        let id3 = HookId::new(2);
489
490        assert_eq!(id1, id2);
491        assert_ne!(id1, id3);
492    }
493
494    // LifecycleManager tests
495    #[test]
496    fn test_manager_new() {
497        let manager = LifecycleManager::new();
498        assert_eq!(manager.hook_count(), 0);
499        assert_eq!(manager.timestamp(), 0);
500    }
501
502    #[test]
503    fn test_manager_register() {
504        let mut manager = LifecycleManager::new();
505        let widget_id = WidgetId::new(1);
506
507        let hook_id = manager.register(widget_id, vec![LifecyclePhase::Mount], Box::new(|_| {}));
508
509        assert_eq!(manager.hook_count(), 1);
510        assert!(manager.has_hooks(widget_id));
511        assert!(!manager.has_hooks(WidgetId::new(999)));
512        assert_eq!(hook_id.0, 0);
513    }
514
515    #[test]
516    fn test_manager_on_mount() {
517        let mut manager = LifecycleManager::new();
518        let widget_id = WidgetId::new(1);
519
520        let _hook_id = manager.on_mount(widget_id, Box::new(|_| {}));
521        assert_eq!(manager.hook_count(), 1);
522    }
523
524    #[test]
525    fn test_manager_on_unmount() {
526        let mut manager = LifecycleManager::new();
527        let widget_id = WidgetId::new(1);
528
529        let _hook_id = manager.on_unmount(widget_id, Box::new(|_| {}));
530        assert_eq!(manager.hook_count(), 1);
531    }
532
533    #[test]
534    fn test_manager_emit() {
535        let counter = Arc::new(AtomicUsize::new(0));
536        let counter_clone = counter.clone();
537
538        let mut manager = LifecycleManager::new();
539        let widget_id = WidgetId::new(1);
540
541        manager.on_mount(
542            widget_id,
543            Box::new(move |_| {
544                counter_clone.fetch_add(1, Ordering::SeqCst);
545            }),
546        );
547
548        manager.emit(widget_id, LifecyclePhase::Mount);
549        assert_eq!(counter.load(Ordering::SeqCst), 1);
550
551        // Emit again
552        manager.emit(widget_id, LifecyclePhase::Mount);
553        assert_eq!(counter.load(Ordering::SeqCst), 2);
554    }
555
556    #[test]
557    fn test_manager_emit_wrong_phase() {
558        let counter = Arc::new(AtomicUsize::new(0));
559        let counter_clone = counter.clone();
560
561        let mut manager = LifecycleManager::new();
562        let widget_id = WidgetId::new(1);
563
564        manager.on_mount(
565            widget_id,
566            Box::new(move |_| {
567                counter_clone.fetch_add(1, Ordering::SeqCst);
568            }),
569        );
570
571        // Emit unmount instead of mount
572        manager.emit(widget_id, LifecyclePhase::Unmount);
573        assert_eq!(counter.load(Ordering::SeqCst), 0);
574    }
575
576    #[test]
577    fn test_manager_queue_and_flush() {
578        let counter = Arc::new(AtomicUsize::new(0));
579        let counter_clone = counter.clone();
580
581        let mut manager = LifecycleManager::new();
582        let widget_id = WidgetId::new(1);
583
584        manager.on_mount(
585            widget_id,
586            Box::new(move |_| {
587                counter_clone.fetch_add(1, Ordering::SeqCst);
588            }),
589        );
590
591        manager.queue(widget_id, LifecyclePhase::Mount);
592        manager.queue(widget_id, LifecyclePhase::Mount);
593        assert_eq!(manager.pending_count(), 2);
594        assert_eq!(counter.load(Ordering::SeqCst), 0);
595
596        manager.flush();
597        assert_eq!(manager.pending_count(), 0);
598        assert_eq!(counter.load(Ordering::SeqCst), 2);
599    }
600
601    #[test]
602    fn test_manager_unregister() {
603        let mut manager = LifecycleManager::new();
604        let widget_id = WidgetId::new(1);
605
606        let hook_id = manager.on_mount(widget_id, Box::new(|_| {}));
607        assert_eq!(manager.hook_count(), 1);
608
609        let removed = manager.unregister(hook_id);
610        assert!(removed);
611        assert_eq!(manager.hook_count(), 0);
612        assert!(!manager.has_hooks(widget_id));
613    }
614
615    #[test]
616    fn test_manager_unregister_widget() {
617        let mut manager = LifecycleManager::new();
618        let widget_id = WidgetId::new(1);
619
620        manager.on_mount(widget_id, Box::new(|_| {}));
621        manager.on_unmount(widget_id, Box::new(|_| {}));
622        manager.on_update(widget_id, Box::new(|_| {}));
623        assert_eq!(manager.hook_count(), 3);
624
625        manager.unregister_widget(widget_id);
626        assert_eq!(manager.hook_count(), 0);
627    }
628
629    #[test]
630    fn test_manager_tick() {
631        let mut manager = LifecycleManager::new();
632        assert_eq!(manager.timestamp(), 0);
633
634        manager.tick();
635        assert_eq!(manager.timestamp(), 1);
636
637        manager.tick();
638        manager.tick();
639        assert_eq!(manager.timestamp(), 3);
640    }
641
642    #[test]
643    fn test_manager_clear() {
644        let mut manager = LifecycleManager::new();
645        let widget_id = WidgetId::new(1);
646
647        manager.on_mount(widget_id, Box::new(|_| {}));
648        manager.queue(widget_id, LifecyclePhase::Mount);
649
650        manager.clear();
651        assert_eq!(manager.hook_count(), 0);
652        assert_eq!(manager.pending_count(), 0);
653    }
654
655    #[test]
656    fn test_manager_multiple_widgets() {
657        let counter1 = Arc::new(AtomicUsize::new(0));
658        let counter2 = Arc::new(AtomicUsize::new(0));
659        let c1 = counter1.clone();
660        let c2 = counter2.clone();
661
662        let mut manager = LifecycleManager::new();
663
664        manager.on_mount(
665            WidgetId::new(1),
666            Box::new(move |_| {
667                c1.fetch_add(1, Ordering::SeqCst);
668            }),
669        );
670        manager.on_mount(
671            WidgetId::new(2),
672            Box::new(move |_| {
673                c2.fetch_add(1, Ordering::SeqCst);
674            }),
675        );
676
677        manager.emit(WidgetId::new(1), LifecyclePhase::Mount);
678        assert_eq!(counter1.load(Ordering::SeqCst), 1);
679        assert_eq!(counter2.load(Ordering::SeqCst), 0);
680
681        manager.emit(WidgetId::new(2), LifecyclePhase::Mount);
682        assert_eq!(counter1.load(Ordering::SeqCst), 1);
683        assert_eq!(counter2.load(Ordering::SeqCst), 1);
684    }
685
686    // Effect tests
687    #[test]
688    fn test_effect_new() {
689        let effect = Effect::new(|| None);
690        assert!(!effect.has_cleanup());
691    }
692
693    #[test]
694    fn test_effect_with_deps() {
695        let effect = Effect::with_deps(|| None, vec![1, 2, 3]);
696        assert_eq!(effect.deps, vec![1, 2, 3]);
697    }
698
699    #[test]
700    fn test_effect_deps_changed() {
701        let effect = Effect::with_deps(|| None, vec![1, 2, 3]);
702
703        assert!(!effect.deps_changed(&[1, 2, 3]));
704        assert!(effect.deps_changed(&[1, 2, 4]));
705        assert!(effect.deps_changed(&[1, 2]));
706        assert!(effect.deps_changed(&[1, 2, 3, 4]));
707    }
708
709    #[test]
710    fn test_effect_run() {
711        let counter = Arc::new(AtomicUsize::new(0));
712        let c = counter.clone();
713
714        let mut effect = Effect::new(move || {
715            c.fetch_add(1, Ordering::SeqCst);
716            None
717        });
718
719        effect.run(None);
720        assert_eq!(counter.load(Ordering::SeqCst), 1);
721
722        // Effect can only run once (it's moved out)
723        effect.run(None);
724        assert_eq!(counter.load(Ordering::SeqCst), 1);
725    }
726
727    #[test]
728    fn test_effect_cleanup() {
729        let cleanup_counter = Arc::new(AtomicUsize::new(0));
730        let cc = cleanup_counter.clone();
731
732        let mut effect = Effect::new(move || {
733            let cc = cc.clone();
734            Some(Box::new(move || {
735                cc.fetch_add(1, Ordering::SeqCst);
736            }) as Box<dyn FnOnce() + Send>)
737        });
738
739        effect.run(None);
740        assert!(effect.has_cleanup());
741        assert_eq!(cleanup_counter.load(Ordering::SeqCst), 0);
742
743        effect.cleanup();
744        assert!(!effect.has_cleanup());
745        assert_eq!(cleanup_counter.load(Ordering::SeqCst), 1);
746    }
747
748    // EffectManager tests
749    #[test]
750    fn test_effect_manager_new() {
751        let manager = EffectManager::new();
752        assert_eq!(manager.widget_count(), 0);
753        assert_eq!(manager.effect_count(), 0);
754    }
755
756    #[test]
757    fn test_effect_manager_add() {
758        let mut manager = EffectManager::new();
759        let widget_id = WidgetId::new(1);
760
761        manager.add(widget_id, Effect::new(|| None));
762        manager.add(widget_id, Effect::new(|| None));
763
764        assert_eq!(manager.widget_count(), 1);
765        assert_eq!(manager.effect_count(), 2);
766    }
767
768    #[test]
769    fn test_effect_manager_run_effects() {
770        let counter = Arc::new(AtomicUsize::new(0));
771        let c = counter.clone();
772
773        let mut manager = EffectManager::new();
774        let widget_id = WidgetId::new(1);
775
776        manager.add(
777            widget_id,
778            Effect::new(move || {
779                c.fetch_add(1, Ordering::SeqCst);
780                None
781            }),
782        );
783
784        manager.run_effects(widget_id, None);
785        assert_eq!(counter.load(Ordering::SeqCst), 1);
786    }
787
788    #[test]
789    fn test_effect_manager_cleanup_widget() {
790        let cleanup_counter = Arc::new(AtomicUsize::new(0));
791        let cc = cleanup_counter.clone();
792
793        let mut manager = EffectManager::new();
794        let widget_id = WidgetId::new(1);
795
796        manager.add(
797            widget_id,
798            Effect::new(move || {
799                let cc = cc.clone();
800                Some(Box::new(move || {
801                    cc.fetch_add(1, Ordering::SeqCst);
802                }) as Box<dyn FnOnce() + Send>)
803            }),
804        );
805
806        manager.run_effects(widget_id, None);
807        assert_eq!(manager.effect_count(), 1);
808
809        manager.cleanup_widget(widget_id);
810        assert_eq!(manager.effect_count(), 0);
811        assert_eq!(cleanup_counter.load(Ordering::SeqCst), 1);
812    }
813
814    #[test]
815    fn test_effect_manager_clear() {
816        let mut manager = EffectManager::new();
817
818        manager.add(WidgetId::new(1), Effect::new(|| None));
819        manager.add(WidgetId::new(2), Effect::new(|| None));
820
821        manager.clear();
822        assert_eq!(manager.widget_count(), 0);
823        assert_eq!(manager.effect_count(), 0);
824    }
825}