diff --git a/src/Redux.Tests/Reactive/StoreExtensionsTests.cs b/src/Redux.Tests/Reactive/StoreExtensionsTests.cs index 7d6443d..4bc6ffc 100644 --- a/src/Redux.Tests/Reactive/StoreExtensionsTests.cs +++ b/src/Redux.Tests/Reactive/StoreExtensionsTests.cs @@ -17,5 +17,19 @@ public void ObserveState_should_push_store_states() CollectionAssert.AreEqual(new[] { 1, 2 }, spyListener.Values); } + + [Test] + public void ObserveActions_should_push_actions() + { + var sut = new Store(Reducers.Replace, 0); + object receivedAction = null; + + sut.ObserveActions().Subscribe(action => receivedAction = action); + + var dispatchedAction = new object(); + sut.Dispatch(dispatchedAction); + + Assert.AreSame(receivedAction, dispatchedAction); + } } } diff --git a/src/Redux.Tests/StoreTests.cs b/src/Redux.Tests/StoreTests.cs index 3945172..8eb0513 100644 --- a/src/Redux.Tests/StoreTests.cs +++ b/src/Redux.Tests/StoreTests.cs @@ -119,5 +119,31 @@ await Task.WhenAll(Enumerable.Range(0, 1000) Assert.AreEqual(1000, sut.GetState()); } + + [Test] + public void Should_push_action_on_dispatch_to_reducer() + { + var sut = new Store((state, action) => state + 1, 0); + object pushedAction = null; + sut.ActionDispatched += action => pushedAction = action; + var dispatchedAction = new object(); + + sut.Dispatch(dispatchedAction); + + Assert.AreSame(dispatchedAction, pushedAction); + } + + [Test] + public void Should_not_push_action_when_middleware_stops() + { + Middleware stoppingMiddleware = store => next => action => null; + var sut = new Store((state, action) => state + 1, 0, stoppingMiddleware); + var actionWasPushed = false; + sut.ActionDispatched += action => actionWasPushed = true; + + sut.Dispatch(new object()); + + Assert.False(actionWasPushed); + } } } \ No newline at end of file diff --git a/src/Redux/IStore.cs b/src/Redux/IStore.cs index 0e6b5d3..d33e7b3 100644 --- a/src/Redux/IStore.cs +++ b/src/Redux/IStore.cs @@ -1,6 +1,6 @@ using System; -namespace Redux +namespace Redux { /// /// Represents a store that encapsulates a state tree and is used to dispatch actions to update the @@ -31,6 +31,18 @@ public interface IStore /// TState GetState(); + /// + /// Occurs after an action has been dispatched to the reducer. + /// event Action StateChanged; + + /// + /// Occurs after an action has been dispatched to the reducer. The action is passed to the event + /// handlers. + /// + /// + /// This event is primarily intended for extending the store. For subscribing to state changes, use . + /// + event Action ActionDispatched; } } \ No newline at end of file diff --git a/src/Redux/Reactive/StoreExtensions.cs b/src/Redux/Reactive/StoreExtensions.cs index 86dba8d..c60f409 100644 --- a/src/Redux/Reactive/StoreExtensions.cs +++ b/src/Redux/Reactive/StoreExtensions.cs @@ -13,5 +13,13 @@ public static IObservable ObserveState(this IStore store) h => store.StateChanged -= h) .Select(_ => store.GetState()); } + + public static IObservable ObserveActions(this IStore store) + { + return Observable + .FromEvent( + h => store.ActionDispatched += h, + h => store.ActionDispatched -= h); + } } } diff --git a/src/Redux/Store.cs b/src/Redux/Store.cs index ebc1a46..b018f20 100644 --- a/src/Redux/Store.cs +++ b/src/Redux/Store.cs @@ -32,6 +32,9 @@ public event Action StateChanged } } + public event Action ActionDispatched; + + public object Dispatch(object action) { return _dispatcher(action); @@ -60,6 +63,7 @@ private object InnerDispatch(object action) } _stateChanged?.Invoke(); + ActionDispatched?.Invoke(action); return action; }