reactive_state/
store.rs

1use crate::{
2    middleware::{Middleware, ReduceMiddlewareResult},
3    AsListener, Listener, Reducer,
4};
5use std::iter::FromIterator;
6use std::ops::Deref;
7use std::{
8    cell::{Cell, RefCell},
9    collections::{HashSet, VecDeque},
10    fmt::Debug,
11    hash::Hash,
12    marker::PhantomData,
13    rc::Rc,
14};
15
16/// A [Listener] associated with (listening to) a given set of
17/// `Events`s produced by a [Store::dispatch()].
18struct ListenerEventPair<State, Event> {
19    pub listener: Listener<State, Event>,
20    pub events: HashSet<Event>,
21}
22
23impl<State, Event> Debug for ListenerEventPair<State, Event> {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        write!(f, "ListenerEventPair")
26    }
27}
28
29/// An action to modify some aspect of the [Store], to be stored in a
30/// queue and executed at the start of a [Store::dispatch()] for a
31/// given `Action`.
32enum StoreModification<State, Action, Event, Effect> {
33    AddListener(ListenerEventPair<State, Event>),
34    AddMiddleware(Rc<dyn Middleware<State, Action, Event, Effect>>),
35}
36
37/// A wrapper for an [Rc] reference to a [Store].
38///
39/// This wrapper exists to provide a standard interface for re-useable
40/// middleware and other components which may require a long living
41/// reference to the store in order to dispatch actions or modify it
42/// in some manner that could not be handled by a simple `&Store`.
43pub struct StoreRef<State, Action, Event, Effect>(Rc<Store<State, Action, Event, Effect>>);
44
45impl<State, Action, Event, Effect> StoreRef<State, Action, Event, Effect>
46where
47    Event: Clone + Hash + Eq,
48{
49    pub fn new<R: Reducer<State, Action, Event, Effect> + 'static>(
50        reducer: R,
51        initial_state: State,
52    ) -> Self {
53        Self(Rc::new(Store::new(reducer, initial_state)))
54    }
55}
56
57impl<State, Action, Event, Effect> Clone for StoreRef<State, Action, Event, Effect> {
58    fn clone(&self) -> Self {
59        Self(Rc::clone(&self.0))
60    }
61}
62
63impl<State, Action, Event, Effect> Deref for StoreRef<State, Action, Event, Effect> {
64    type Target = Store<State, Action, Event, Effect>;
65
66    fn deref(&self) -> &Self::Target {
67        &*self.0
68    }
69}
70
71impl<State, Action, Event, Effect> PartialEq for StoreRef<State, Action, Event, Effect> {
72    fn eq(&self, other: &Self) -> bool {
73        Rc::ptr_eq(&self.0, &other.0)
74    }
75}
76
77/// This struct is designed to operate as a central source of truth
78/// and global "immutable" state within your application.
79///
80/// The current state of this store ([Store::state()]()) can only be
81/// modified by dispatching an `Action` via [Store::dispatch()] to the
82/// store. These actions are taken by a [Reducer] which you provided
83/// to the store (at construction) and a new current state is
84/// produced. The reducer also produces `Events` associated with the
85/// change. The previous state is never mutated, and remains as a
86/// reference for any element of your application which may rely upon
87/// it (ensure that it gets dropped when it is no longer required,
88/// lest it become a memory leak when large `State`s are involved).
89///
90/// Listeners can susbscribe to changes to the `State` in this store
91/// (and `Event`s produced) with [Store::subscribe()], or they can
92/// also subscribe to changes associated with specific `Event`s via
93/// [subscribe_event()](Store::subscribe_event())/[subscribe_events()](Store::subscribe_events()).
94pub struct Store<State, Action, Event, Effect> {
95    /// This lock is used to prevent dispatch recursion.
96    dispatch_lock: RefCell<()>,
97    /// Queue of actions to be dispatched by [Store::dispatch()].
98    dispatch_queue: RefCell<VecDeque<Action>>,
99    /// Queue of [StoreModification]s to be executed by
100    /// [Store::dispatch()] before the next `Action` is dispatched.
101    modification_queue: RefCell<VecDeque<StoreModification<State, Action, Event, Effect>>>,
102    /// The [Reducer] for this store, which takes `Actions`, modifies
103    /// the `State` stored in this store, and produces `Events` to be
104    /// sent to the store listeners.
105    reducer: Box<dyn Reducer<State, Action, Event, Effect>>,
106    /// The current state of this store.
107    state: RefCell<Rc<State>>,
108    /// The listeners which are notified of changes to the state of
109    /// this store, and events produced by this store during a
110    /// [Store::dispatch()].
111    listeners: RefCell<Vec<ListenerEventPair<State, Event>>>,
112    /// Middleware which modifies the functionality of this store.
113    #[allow(clippy::type_complexity)]
114    middleware: RefCell<Vec<Rc<dyn Middleware<State, Action, Event, Effect>>>>,
115    /// Used during recursive execution of [Middleware] to keep track
116    /// of the middleware currently executing. It is an index into
117    /// [Store::middleware].
118    prev_middleware: Cell<i32>,
119    phantom_action: PhantomData<Action>,
120    phantom_event: PhantomData<Event>,
121}
122
123impl<State, Action, Event, Effect> Store<State, Action, Event, Effect>
124where
125    Event: Clone + Hash + Eq,
126{
127    /// Create a new [Store], which uses the specified `reducer` to
128    /// handle `Action`s which mutate the state and produce `Event`s,
129    /// and with the `initial_state`.
130    pub fn new<R: Reducer<State, Action, Event, Effect> + 'static>(
131        reducer: R,
132        initial_state: State,
133    ) -> Self {
134        Self {
135            dispatch_lock: RefCell::new(()),
136            dispatch_queue: RefCell::new(VecDeque::new()),
137            modification_queue: RefCell::new(VecDeque::new()),
138            reducer: Box::new(reducer),
139            state: RefCell::new(Rc::new(initial_state)),
140            listeners: RefCell::new(Vec::new()),
141            middleware: RefCell::new(Vec::new()),
142            prev_middleware: Cell::new(-1),
143            phantom_action: PhantomData,
144            phantom_event: PhantomData,
145        }
146    }
147
148    /// Get the current `State` stored in this store.
149    ///
150    /// Modifications to this state need to be performed by
151    /// dispatching an `Action` to the store using
152    /// [dispatch()](Store::dispatch()).
153    pub fn state(&self) -> Rc<State> {
154        self.state.borrow().clone()
155    }
156
157    /// Dispatch an `Action` to the reducer on this `Store` without
158    /// invoking middleware.
159    fn dispatch_reducer(&self, action: &Action) -> ReduceMiddlewareResult<Event, Effect> {
160        let result = self.reducer.reduce(&self.state(), action);
161        *self.state.borrow_mut() = result.state;
162
163        ReduceMiddlewareResult {
164            events: result.events,
165            effects: result.effects,
166        }
167    }
168
169    /// Dispatch an `Action` to the reducer on this `Store`, invoking
170    /// all middleware's [reduce()][Middleware::reduce()] first.
171    fn middleware_reduce(&self, action: &Action) -> ReduceMiddlewareResult<Event, Effect> {
172        self.prev_middleware.set(-1);
173        self.middleware_reduce_next(Some(action))
174    }
175
176    /// A recursive function which executes each middleware for this
177    /// store, and invokes the next middleware, until all middleware
178    /// has been invoked, at which point the `Action` is sent to the
179    /// reducer.
180    fn middleware_reduce_next(
181        &self,
182        action: Option<&Action>,
183    ) -> ReduceMiddlewareResult<Event, Effect> {
184        let current_middleware = self.prev_middleware.get() + 1;
185        self.prev_middleware.set(current_middleware);
186
187        if current_middleware == self.middleware.borrow().len() as i32 {
188            return match action {
189                Some(action) => self.dispatch_reducer(action),
190                None => ReduceMiddlewareResult::default(),
191            };
192        }
193
194        self.middleware.borrow()[current_middleware as usize]
195            .clone()
196            .on_reduce(self, action, Self::middleware_reduce_next)
197    }
198
199    /// Process all the `Effect`s returned by the [Reducer::reduce()]
200    /// by invoking the middleware on this store to perform the
201    /// processing using [Middleware::process_effect()].q
202    fn middleware_process_effects(&self, effects: Vec<Effect>) {
203        for effect in effects {
204            self.middleware_process_effect(effect);
205        }
206    }
207
208    /// Process the specified `Effect`, invoking all middleware in this
209    /// store to perform the processing using
210    /// [Middleware::process_effect()].
211    fn middleware_process_effect(&self, effect: Effect) {
212        self.prev_middleware.set(-1);
213        self.middleware_process_effects_next(effect);
214    }
215
216    /// A recursive function which executes each middleware for this
217    /// store to process the specified `Effect` with
218    /// [Middleware::process_effect()], and invokes the next
219    /// middleware, until all middleware has been invoked.
220    fn middleware_process_effects_next(&self, effect: Effect) {
221        let current_middleware = self.prev_middleware.get() + 1;
222        self.prev_middleware.set(current_middleware);
223
224        if current_middleware == self.middleware.borrow().len() as i32 {
225            return;
226        }
227
228        if let Some(effect) = self.middleware.borrow()[current_middleware as usize]
229            .clone()
230            .process_effect(self, effect)
231        {
232            self.middleware_process_effects_next(effect);
233        }
234    }
235
236    /// Notify store listeners of events produced during a reduce as a
237    /// result of an `Action` being dispatched. Invokes all
238    /// middleware's [reduce()][Middleware::reduce()] first.
239    /// Notification occurs even if there are no events to report.
240    fn middleware_notify(&self, events: Vec<Event>) -> Vec<Event> {
241        self.prev_middleware.set(-1);
242        self.middleware_notify_next(events)
243    }
244
245    /// A recursive function which executes each middleware for this
246    /// store, and invokes the next middleware, until all middleware
247    /// has been invoked, at which point the listeners are notified of
248    /// the envents produced during a reduce as a result of an
249    /// `Action` being dispatched. Notification occurs even if there
250    /// are no events to report.
251    fn middleware_notify_next(&self, events: Vec<Event>) -> Vec<Event> {
252        let current_middleware = self.prev_middleware.get() + 1;
253        self.prev_middleware.set(current_middleware);
254
255        if current_middleware == self.middleware.borrow().len() as i32 {
256            return events;
257        }
258
259        self.middleware.borrow()[current_middleware as usize]
260            .clone()
261            .on_notify(self, events, Self::middleware_notify_next)
262    }
263
264    /// Notify store listeners of events produced during a result of
265    /// an `Action` being dispatched. Notification occurs even if
266    /// there are no events to report.
267    fn notify_listeners(&self, events: Vec<Event>) {
268        let mut listeners_to_remove: Vec<usize> = Vec::new();
269        for (i, pair) in self.listeners.borrow().iter().enumerate() {
270            let retain = match pair.listener.as_callback() {
271                Some(callback) => {
272                    if pair.events.is_empty() {
273                        callback.emit(self.state.borrow().clone(), None);
274                    } else {
275                        //  call the listener for every matching listener event
276                        for event in &events {
277                            if pair.events.contains(event) {
278                                callback.emit(self.state.borrow().clone(), Some(event.clone()));
279                            }
280                        }
281                    }
282
283                    true
284                }
285                None => false,
286            };
287
288            if !retain {
289                listeners_to_remove.insert(0, i);
290            }
291        }
292
293        for index in listeners_to_remove {
294            self.listeners.borrow_mut().swap_remove(index);
295        }
296    }
297
298    fn process_pending_modifications(&self) {
299        while let Some(modification) = self.modification_queue.borrow_mut().pop_front() {
300            match modification {
301                StoreModification::AddListener(listener_pair) => {
302                    self.listeners.borrow_mut().push(listener_pair);
303                }
304                StoreModification::AddMiddleware(middleware) => {
305                    self.middleware.borrow_mut().push(middleware);
306                }
307            }
308        }
309    }
310
311    /// Dispatch an `Action` to be passed to the [Reducer] in order to
312    /// modify the `State` in this store, and produce `Events` to be
313    /// sent to the store listeners.
314    pub fn dispatch<A: Into<Action>>(&self, action: A) {
315        self.dispatch_impl(action.into());
316    }
317
318    /// Concrete version of [Store::dispatch()], for code size
319    /// reduction purposes, to avoid generating multiple versions of
320    /// this complex function per action that implements
321    /// `Into<Action>`, it is expected that there will be many in a
322    /// typical application.
323    fn dispatch_impl(&self, action: Action) {
324        self.dispatch_queue.borrow_mut().push_back(action);
325
326        // If the lock fails to acquire, then the dispatch is already in progress.
327        // This prevents recursion, when a listener callback also triggers another
328        // dispatch.
329        if let Ok(_lock) = self.dispatch_lock.try_borrow_mut() {
330            // For some strange reason can't use a while let here because
331            // it requires Action to implement Copy, and also it was maintaining
332            // the dispatch_queue borrow during the loop (even though it wasn't needed).
333            loop {
334                let dispatch_action = self.dispatch_queue.borrow_mut().pop_front();
335
336                match dispatch_action {
337                    Some(action) => {
338                        self.process_pending_modifications();
339
340                        let reduce_middleware_result = if self.middleware.borrow().is_empty() {
341                            self.dispatch_reducer(&action)
342                        } else {
343                            self.middleware_reduce(&action)
344                        };
345
346                        #[allow(clippy::match_single_binding)] // destructuring the result
347                        match reduce_middleware_result {
348                            ReduceMiddlewareResult { events, effects } => {
349                                self.middleware_process_effects(effects);
350
351                                let middleware_events = self.middleware_notify(events);
352                                if !middleware_events.is_empty() {
353                                    self.notify_listeners(middleware_events);
354                                }
355                            }
356                        }
357                    }
358                    None => {
359                        break;
360                    }
361                }
362            }
363        }
364    }
365
366    /// Subscribe a [Listener] to changes in the store state and
367    /// events produced by the [Reducer] as a result of `Action`s
368    /// dispatched via [dispatch()](Store::dispatch()).
369    ///
370    /// The listener is a weak reference; when the strong reference
371    /// associated with it (usually [Callback](crate::Callback)) is
372    /// dropped, the listener will be removed from this store upon
373    /// [dispatch()](Store::dispatch()).
374    ///
375    /// If you want to subscribe to state changes associated with
376    /// specific `Event`s, see
377    /// [subscribe_event()](Store::subscribe_events()) or
378    /// [subscribe_event()](Store::subscribe_events())
379    pub fn subscribe<L: AsListener<State, Event>>(&self, listener: L) {
380        self.modification_queue
381            .borrow_mut()
382            .push_back(StoreModification::AddListener(ListenerEventPair {
383                listener: listener.as_listener(),
384                events: HashSet::new(),
385            }));
386    }
387
388    /// Subscribe a [Listener] to changes in the store state and
389    /// events produced by the [Reducer] as a result of `Action`s
390    /// being dispatched via [dispatch()](Store::dispatch()) and
391    /// reduced with the store's [Reducer]. This subscription is only
392    /// active changes which produce the specific matching `event`
393    /// from the [Reducer].
394    ///
395    /// The listener is a weak reference; when the strong reference
396    /// associated with it (usually [Callback](crate::Callback)) is
397    /// dropped, the listener will be removed from this store upon
398    /// [dispatch()](Store::dispatch()).
399    pub fn subscribe_event<L: AsListener<State, Event>>(&self, listener: L, event: Event) {
400        let mut events = HashSet::with_capacity(1);
401        events.insert(event);
402
403        self.modification_queue
404            .borrow_mut()
405            .push_back(StoreModification::AddListener(ListenerEventPair {
406                listener: listener.as_listener(),
407                events,
408            }));
409    }
410
411    /// Subscribe a [Listener] to changes in the store state and
412    /// events produced by the [Reducer] as a result of `Action`s
413    /// being dispatched via [dispatch()](Store::dispatch()) and
414    /// reduced with the store's [Reducer]. This subscription is only
415    /// active changes which produce any of the specific matching
416    /// `events` from the [Reducer].
417    ///
418    /// The listener is a weak reference; when the strong reference
419    /// associated with it (usually [Callback](crate::Callback)) is
420    /// dropped, the listener will be removed from this store upon
421    /// [dispatch()](Store::dispatch()).
422    pub fn subscribe_events<L: AsListener<State, Event>, E: IntoIterator<Item = Event>>(
423        &self,
424        listener: L,
425        events: E,
426    ) {
427        self.modification_queue
428            .borrow_mut()
429            .push_back(StoreModification::AddListener(ListenerEventPair {
430                listener: listener.as_listener(),
431                events: HashSet::from_iter(events.into_iter()),
432            }));
433    }
434
435    /// Add [Middleware] to modify the behaviour of this [Store]
436    /// during a [dispatch()](Store::dispatch()).
437    pub fn add_middleware<M: Middleware<State, Action, Event, Effect> + 'static>(
438        &self,
439        middleware: M,
440    ) {
441        self.modification_queue
442            .borrow_mut()
443            .push_back(StoreModification::AddMiddleware(Rc::new(middleware)));
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use crate::{
450        middleware::{Middleware, ReduceMiddlewareResult},
451        Callback, Reducer, ReducerResult, Store, StoreRef,
452    };
453    use std::{cell::RefCell, rc::Rc};
454
455    #[derive(Debug, PartialEq)]
456    struct TestState {
457        counter: i32,
458    }
459
460    #[derive(Copy, Clone)]
461    enum TestAction {
462        Increment,
463        Decrement,
464        Decrement2,
465        Decrent2Then1,
466        NoEvent,
467    }
468
469    enum TestEffect {
470        ChainAction(TestAction),
471    }
472
473    struct TestReducer;
474
475    impl Reducer<TestState, TestAction, TestEvent, TestEffect> for TestReducer {
476        fn reduce(
477            &self,
478            state: &Rc<TestState>,
479            action: &TestAction,
480        ) -> ReducerResult<TestState, TestEvent, TestEffect> {
481            let mut events = Vec::new();
482            let mut effects = Vec::new();
483
484            let new_state = match action {
485                TestAction::Increment => {
486                    events.push(TestEvent::CounterChanged);
487                    TestState {
488                        counter: state.counter + 1,
489                    }
490                }
491                TestAction::Decrement => {
492                    events.push(TestEvent::CounterChanged);
493                    TestState {
494                        counter: state.counter - 1,
495                    }
496                }
497                TestAction::Decrement2 => {
498                    events.push(TestEvent::CounterChanged);
499                    TestState {
500                        counter: state.counter - 2,
501                    }
502                }
503                TestAction::Decrent2Then1 => {
504                    effects.push(TestEffect::ChainAction(TestAction::Decrement));
505                    events.push(TestEvent::CounterChanged);
506
507                    TestState {
508                        counter: state.counter - 2,
509                    }
510                }
511                TestAction::NoEvent => TestState { counter: 42 },
512            };
513
514            if new_state.counter != state.counter && new_state.counter == 0 {
515                events.push(TestEvent::CounterIsZero);
516            }
517
518            ReducerResult {
519                state: Rc::new(new_state),
520                events,
521                effects,
522            }
523        }
524    }
525
526    struct TestReduceMiddleware {
527        new_action: TestAction,
528    }
529
530    impl Middleware<TestState, TestAction, TestEvent, TestEffect> for TestReduceMiddleware {
531        fn on_reduce(
532            &self,
533            store: &Store<TestState, TestAction, TestEvent, TestEffect>,
534            action: Option<&TestAction>,
535            reduce: crate::middleware::ReduceFn<TestState, TestAction, TestEvent, TestEffect>,
536        ) -> ReduceMiddlewareResult<TestEvent, TestEffect> {
537            reduce(store, action.map(|_| &self.new_action))
538        }
539    }
540
541    struct TestEffectMiddleware;
542
543    impl Middleware<TestState, TestAction, TestEvent, TestEffect> for TestEffectMiddleware {
544        fn process_effect(
545            &self,
546            store: &Store<TestState, TestAction, TestEvent, TestEffect>,
547            effect: TestEffect,
548        ) -> Option<TestEffect> {
549            match effect {
550                TestEffect::ChainAction(action) => {
551                    store.dispatch(action);
552                }
553            }
554
555            None
556        }
557    }
558
559    #[derive(Debug, PartialEq, Eq, Hash, Clone)]
560    enum TestEvent {
561        CounterIsZero,
562        CounterChanged,
563    }
564
565    #[test]
566    fn test_notify() {
567        let initial_state = TestState { counter: 0 };
568        let store: Rc<RefCell<Store<TestState, TestAction, TestEvent, TestEffect>>> =
569            Rc::new(RefCell::new(Store::new(TestReducer, initial_state)));
570
571        let callback_test = Rc::new(RefCell::new(0));
572        let callback_test_copy = callback_test.clone();
573        let callback: Callback<TestState, TestEvent> =
574            Callback::new(move |state: Rc<TestState>, _| {
575                *callback_test_copy.borrow_mut() = state.counter;
576            });
577
578        store.borrow_mut().subscribe(&callback);
579
580        assert_eq!(0, store.borrow().state().counter);
581
582        store.borrow_mut().dispatch(TestAction::Increment);
583        store.borrow_mut().dispatch(TestAction::Increment);
584        assert_eq!(2, *callback_test.borrow());
585        assert_eq!(2, store.borrow().state().counter);
586
587        store.borrow_mut().dispatch(TestAction::Decrement);
588        assert_eq!(1, store.borrow().state().counter);
589    }
590
591    #[test]
592    fn test_reduce_middleware() {
593        let initial_state = TestState { counter: 0 };
594        let store = StoreRef::new(TestReducer, initial_state);
595
596        let callback_test = Rc::new(RefCell::new(0));
597        let callback_test_copy = callback_test.clone();
598        let callback: Callback<TestState, TestEvent> =
599            Callback::new(move |state: Rc<TestState>, _| {
600                *callback_test_copy.borrow_mut() = state.counter;
601            });
602
603        store.subscribe(&callback);
604        store.add_middleware(TestReduceMiddleware {
605            new_action: TestAction::Decrement,
606        });
607        store.add_middleware(TestReduceMiddleware {
608            new_action: TestAction::Decrement2,
609        });
610
611        store.dispatch(TestAction::Increment);
612        assert_eq!(-2, *callback_test.borrow());
613    }
614
615    #[test]
616    fn test_reduce_middleware_reverse_order() {
617        let initial_state = TestState { counter: 0 };
618        let store = StoreRef::new(TestReducer, initial_state);
619
620        let callback_test = Rc::new(RefCell::new(0));
621        let callback_test_copy = callback_test.clone();
622        let callback: Callback<TestState, TestEvent> =
623            Callback::new(move |state: Rc<TestState>, _| {
624                *callback_test_copy.borrow_mut() = state.counter;
625            });
626
627        store.subscribe(&callback);
628        store.add_middleware(TestReduceMiddleware {
629            new_action: TestAction::Decrement2,
630        });
631        store.add_middleware(TestReduceMiddleware {
632            new_action: TestAction::Decrement,
633        });
634
635        store.dispatch(TestAction::Increment);
636        assert_eq!(-1, *callback_test.borrow());
637    }
638
639    #[test]
640    fn test_effect_middleware() {
641        let initial_state = TestState { counter: 0 };
642        let store = StoreRef::new(TestReducer, initial_state);
643        store.add_middleware(TestEffectMiddleware);
644
645        assert_eq!(store.state().counter, 0);
646        store.dispatch(TestAction::Decrent2Then1);
647        assert_eq!(store.state().counter, -3);
648    }
649
650    #[test]
651    fn test_subscribe_event() {
652        let initial_state = TestState { counter: -2 };
653        let store = StoreRef::new(TestReducer, initial_state);
654
655        let callback_test: Rc<RefCell<Option<TestEvent>>> = Rc::new(RefCell::new(None));
656        let callback_test_copy = callback_test.clone();
657
658        let callback_zero_subscription: Callback<TestState, TestEvent> =
659            Callback::new(move |_: Rc<TestState>, event| {
660                assert_eq!(Some(TestEvent::CounterIsZero), event);
661                *callback_test_copy.borrow_mut() = Some(TestEvent::CounterIsZero);
662            });
663
664        store.subscribe_event(&callback_zero_subscription, TestEvent::CounterIsZero);
665        store.dispatch(TestAction::Increment);
666        assert_eq!(None, *callback_test.borrow());
667        store.dispatch(TestAction::Increment);
668        assert_eq!(Some(TestEvent::CounterIsZero), *callback_test.borrow());
669    }
670
671    /// Subscribe to an action that produces no events.
672    #[test]
673    fn test_subscribe_no_event() {
674        let initial_state = TestState { counter: 0 };
675        let store = StoreRef::new(TestReducer, initial_state);
676
677        let callback_test: Rc<RefCell<i32>> = Rc::new(RefCell::new(0));
678        let callback_test_copy = callback_test.clone();
679
680        let callback: Callback<TestState, TestEvent> =
681            Callback::new(move |state: Rc<TestState>, _event| {
682                assert_eq!(42, state.counter);
683                *callback_test_copy.borrow_mut() = state.counter;
684            });
685
686        store.subscribe(&callback);
687
688        assert_eq!(0, store.state.borrow().counter);
689        assert_eq!(0, *callback_test.borrow());
690
691        store.dispatch(TestAction::NoEvent);
692
693        assert_eq!(42, store.state.borrow().counter);
694
695        // it is expected that the callback will not have been invoked,
696        // because the action produced no events.
697        assert_eq!(0, *callback_test.borrow());
698    }
699}