From fb40c8bc480b74b8923e1736abd36b69705dea89 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 12:39:08 -0500 Subject: [PATCH 01/58] Rename Reduce to Update --- .../ReduxDevTools/ReduxDevToolsInterop.cs | 32 ++--- .../ReduxDevToolsStartupExtensions.cs | 8 +- .../AsyncLogic/AsyncError.cs | 6 +- .../AsyncLogic/AsyncOperation.cs | 6 +- .../AsyncLogic/StartupExtensions.cs | 6 +- .../ActionHandlers/ActionHandlersManager.cs | 6 +- src/StateR/DispatchContext.cs | 4 +- src/StateR/Dispatcher.cs | 2 +- src/StateR/IDispatchContext.cs | 2 +- src/StateR/IStatorBuilder.cs | 4 +- src/StateR/Internal/StatorBuilder.cs | 6 +- .../Internal/TypeScannerBuilderExtensions.cs | 14 +-- .../Reducers/Hooks/IAfterReducerHook.cs | 12 -- .../Reducers/Hooks/IBeforeReducerHook.cs | 12 -- .../Reducers/Hooks/IReducerHooksCollection.cs | 15 --- .../Reducers/Hooks/ReducerHooksCollection.cs | 38 ------ src/StateR/Reducers/IReducer.cs | 11 -- src/StateR/Reducers/ReducerHandler.cs | 36 ------ src/StateR/StatorStartupExtensions.cs | 42 +++---- src/StateR/Updaters/Hooks/IAfterUpdateHook.cs | 12 ++ .../Updaters/Hooks/IBeforeUpdateHook.cs | 12 ++ .../Updaters/Hooks/IUpdateHooksCollection.cs | 15 +++ .../Updaters/Hooks/UpdateHooksCollection.cs | 38 ++++++ src/StateR/Updaters/IUpdater.cs | 11 ++ src/StateR/Updaters/UpdaterHandler.cs | 36 ++++++ .../ActionHandlerManagerTest.cs | 12 +- test/StateR.Tests/DispatcherTest.cs | 18 ++- .../Hooks/ReducerHooksCollectionTest.cs | 70 ----------- .../Hooks/UpdateHooksCollectionTest.cs | 70 +++++++++++ .../Reducers/ReducerHandlerTest.cs | 109 ------------------ .../Reducers/UpdaterHandlerTest.cs | 109 ++++++++++++++++++ 31 files changed, 385 insertions(+), 389 deletions(-) delete mode 100644 src/StateR/Reducers/Hooks/IAfterReducerHook.cs delete mode 100644 src/StateR/Reducers/Hooks/IBeforeReducerHook.cs delete mode 100644 src/StateR/Reducers/Hooks/IReducerHooksCollection.cs delete mode 100644 src/StateR/Reducers/Hooks/ReducerHooksCollection.cs delete mode 100644 src/StateR/Reducers/IReducer.cs delete mode 100644 src/StateR/Reducers/ReducerHandler.cs create mode 100644 src/StateR/Updaters/Hooks/IAfterUpdateHook.cs create mode 100644 src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs create mode 100644 src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs create mode 100644 src/StateR/Updaters/Hooks/UpdateHooksCollection.cs create mode 100644 src/StateR/Updaters/IUpdater.cs create mode 100644 src/StateR/Updaters/UpdaterHandler.cs delete mode 100644 test/StateR.Tests/Reducers/Hooks/ReducerHooksCollectionTest.cs create mode 100644 test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs delete mode 100644 test/StateR.Tests/Reducers/ReducerHandlerTest.cs create mode 100644 test/StateR.Tests/Reducers/UpdaterHandlerTest.cs diff --git a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs index 45d81a7..358d246 100644 --- a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs +++ b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs @@ -7,11 +7,11 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; -using StateR.Reducers; +using StateR.Updater; using System.Threading; using System.Reflection; using System.Security.Cryptography.X509Certificates; -using StateR.Reducers.Hooks; +using StateR.Updater.Hooks; namespace StateR.Blazor.ReduxDevTools { @@ -53,7 +53,7 @@ IStore store } } - public class ReduxDevToolsInterop : IDisposable, IBeforeReducerHook, IAfterReducerHook + public class ReduxDevToolsInterop : IDisposable, IBeforeUpdateHook, IAfterUpdateHook { public bool DevToolsBrowserPluginDetected { get; private set; } private readonly IJSRuntime _jsRuntime; @@ -92,7 +92,7 @@ public async ValueTask InitializeAsync() } _history.Add(new TypeState(revertStateAction, executeAction) { - Status = TypeStateStatus.AfterReducer, + Status = TypeStateStatus.AfterUpdater, }); await _jsRuntime.InvokeAsync( "__StateRDevTools__.init", @@ -201,14 +201,14 @@ public TypeState(Action undoStateAction, Action redoStateAction) public void Undo() { - if (Status == TypeStateStatus.AfterReducer) + if (Status == TypeStateStatus.AfterUpdater) { _undoStateAction(); } } public void Redo() { - if (Status == TypeStateStatus.AfterReducer) + if (Status == TypeStateStatus.AfterUpdater) { _redoStateAction(); } @@ -218,43 +218,43 @@ public void Redo() private enum TypeStateStatus { Unknown, - BeforeReducer, - AfterReducer + BeforeUpdater, + AfterUpdater } private List _history { get; } = new(); - public Task BeforeReducerAsync(IDispatchContext context, IState state, IReducer reducer, CancellationToken cancellationToken) + public Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) where TAction : IAction where TState : StateBase { var action = context.Action; var current = state.Current; - var next = reducer.Reduce(action, current); + var next = updater.Update(action, current); _history.Add(new TypeState(undoStateAction, redoStateAction) { - ContextRef = reducer, - Status = TypeStateStatus.BeforeReducer, + ContextRef = updater, + Status = TypeStateStatus.BeforeUpdater, }); HistoryIndex = _history.Count - 1; return Task.CompletedTask; void undoStateAction() { - Console.WriteLine($"Undo {current.GetType().GetStatorName()} to {current} from action: {typeof(TAction).GetStatorName()} with reducer {reducer.GetType().GetStatorName()}"); + Console.WriteLine($"Undo {current.GetType().GetStatorName()} to {current} from action: {typeof(TAction).GetStatorName()} with updater {updater.GetType().GetStatorName()}"); state.Set(current); state.Notify(); } void redoStateAction() { - Console.WriteLine($"Redo {current.GetType().GetStatorName()} to {next} from action: {typeof(TAction).GetStatorName()} with reducer {reducer.GetType().GetStatorName()}"); + Console.WriteLine($"Redo {current.GetType().GetStatorName()} to {next} from action: {typeof(TAction).GetStatorName()} with updater {updater.GetType().GetStatorName()}"); state.Set(next); state.Notify(); } } - public async Task AfterReducerAsync(IDispatchContext context, IState state, IReducer reducer, CancellationToken cancellationToken) + public async Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) where TAction : IAction where TState : StateBase { @@ -266,7 +266,7 @@ await _jsRuntime.InvokeAsync( serializedActionInfo, states ); - _history.LastOrDefault(h => h.ContextRef == reducer).Status = TypeStateStatus.AfterReducer; + _history.LastOrDefault(h => h.ContextRef == updater).Status = TypeStateStatus.AfterUpdater; } } diff --git a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs index 3d86645..b8a2a5c 100644 --- a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs +++ b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; -using StateR.Reducers; -using StateR.Reducers.Hooks; +using StateR.Updater; +using StateR.Updater.Hooks; using System; using System.Collections.Generic; using System.Linq; @@ -27,8 +27,8 @@ public static IStatorBuilder AddReduxDevTools(this IStatorBuilder builder) }); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetService()); - builder.Services.AddSingleton(sp => sp.GetService()); + builder.Services.AddSingleton(sp => sp.GetService()); + builder.Services.AddSingleton(sp => sp.GetService()); return builder; } } diff --git a/src/StateR.Experiments/AsyncLogic/AsyncError.cs b/src/StateR.Experiments/AsyncLogic/AsyncError.cs index 13bae6e..0fbbe3f 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncError.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncError.cs @@ -1,4 +1,4 @@ -using StateR.Reducers; +using StateR.Updater; using System; namespace StateR.AsyncLogic @@ -24,9 +24,9 @@ public class InitialState : IInitialState public State Value => new(); } - public class Reducers : IReducer + public class Updaters : IUpdater { - public State Reduce(Occured action, State initialState) => initialState with { + public State Update(Occured action, State initialState) => initialState with { Action = action.Action, InitialState = action.InitialState, ActualState = action.ActualState, diff --git a/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs b/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs index 0323e07..fb486c8 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using StateR.AfterEffects; -using StateR.Reducers; +using StateR.Updater; using System; using System.Collections.Generic; using System.Linq; @@ -10,7 +10,7 @@ namespace StateR.AsyncLogic { - public abstract class AsyncOperation : IAfterEffects, IReducer, TState> + public abstract class AsyncOperation : IAfterEffects, IUpdater, TState> where TAction : IAction where TState : AsyncState where TSuccessAction : IAction @@ -22,7 +22,7 @@ public AsyncOperation(IStore store) protected IStore Store { get; } - public virtual TState Reduce(StatusUpdated action, TState state) + public virtual TState Update(StatusUpdated action, TState state) => state with { Status = action.status }; public async Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken) diff --git a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs index b8b9bc0..9177966 100644 --- a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs +++ b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using StateR.ActionHandlers; using StateR.AsyncLogic; -using StateR.Reducers; +using StateR.Updater; using System; using System.Collections.Generic; using System.Linq; @@ -18,8 +18,8 @@ public static IStatorBuilder AddAsyncOperations(this IStatorBuilder builder) builder.AddTypes(new[] { typeof(StatusUpdated<>) }); // Async Operation's Errors - builder.Services.TryAddSingleton, ReducerHandler>(); - builder.Services.TryAddSingleton, AsyncError.Reducers>(); + builder.Services.TryAddSingleton, UpdaterHandler>(); + builder.Services.TryAddSingleton, AsyncError.Updaters>(); builder.Services.TryAddSingleton, AsyncError.InitialState>(); builder.Services.TryAddSingleton, Internal.State>(); return builder; diff --git a/src/StateR/ActionHandlers/ActionHandlersManager.cs b/src/StateR/ActionHandlers/ActionHandlersManager.cs index 4ce04d1..93b4230 100644 --- a/src/StateR/ActionHandlers/ActionHandlersManager.cs +++ b/src/StateR/ActionHandlers/ActionHandlersManager.cs @@ -21,10 +21,10 @@ public ActionHandlersManager(IActionHandlerHooksCollection hooksCollection, ISer public async Task DispatchAsync(IDispatchContext dispatchContext, CancellationToken cancellationToken) where TAction : IAction { - var reducerHandlers = _serviceProvider.GetServices>().ToList(); - foreach (var handler in reducerHandlers) + var updaterHandlers = _serviceProvider.GetServices>().ToList(); + foreach (var handler in updaterHandlers) { - if (dispatchContext.StopReduce) + if (dispatchContext.StopUpdate) { break; } diff --git a/src/StateR/DispatchContext.cs b/src/StateR/DispatchContext.cs index eecf9b0..f55cfb9 100644 --- a/src/StateR/DispatchContext.cs +++ b/src/StateR/DispatchContext.cs @@ -13,7 +13,7 @@ public DispatchContext(TAction action, IDispatcher dispatcher) public IDispatcher Dispatcher { get; } public TAction Action { get; set; } - public bool StopReduce { get; set; } + public bool StopUpdate { get; set; } public bool StopInterception { get; set; } public bool StopAfterEffect { get; set; } @@ -21,7 +21,7 @@ public void DoNotContinue() { StopAfterEffect = true; StopInterception = true; - StopReduce = true; + StopUpdate = true; } } } diff --git a/src/StateR/Dispatcher.cs b/src/StateR/Dispatcher.cs index 160264f..96eb0d2 100644 --- a/src/StateR/Dispatcher.cs +++ b/src/StateR/Dispatcher.cs @@ -1,7 +1,7 @@ using StateR.ActionHandlers; using StateR.AfterEffects; using StateR.Interceptors; -using StateR.Reducers; +using StateR.Updater; using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/StateR/IDispatchContext.cs b/src/StateR/IDispatchContext.cs index d1a8f5d..0ae721a 100644 --- a/src/StateR/IDispatchContext.cs +++ b/src/StateR/IDispatchContext.cs @@ -5,7 +5,7 @@ public interface IDispatchContext { IDispatcher Dispatcher { get; } TAction Action { get; set; } - bool StopReduce { get; set; } + bool StopUpdate { get; set; } bool StopInterception { get; set; } bool StopAfterEffect { get; set; } diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index c62b1a8..eaa43bf 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -13,13 +13,13 @@ public interface IStatorBuilder //List Interceptors { get; } List ActionHandlers { get; } //List AfterEffects { get; } - List Reducers { get; } + List Updaters { get; } List All { get; } IStatorBuilder AddTypes(IEnumerable types); IStatorBuilder AddStates(IEnumerable states); IStatorBuilder AddActions(IEnumerable states); - IStatorBuilder AddReducers(IEnumerable states); + IStatorBuilder AddUpdaters(IEnumerable states); IStatorBuilder AddActionHandlers(IEnumerable types); } } \ No newline at end of file diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 0f5d0cc..c69e1f0 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -18,8 +18,8 @@ public IStatorBuilder AddStates(IEnumerable types) => AddDistinctTypes(States, types); public IStatorBuilder AddActions(IEnumerable types) => AddDistinctTypes(Actions, types); - public IStatorBuilder AddReducers(IEnumerable types) - => AddDistinctTypes(Reducers, types); + public IStatorBuilder AddUpdaters(IEnumerable types) + => AddDistinctTypes(Updaters, types); public IStatorBuilder AddActionHandlers(IEnumerable types) => AddDistinctTypes(ActionHandlers, types); @@ -29,7 +29,7 @@ public IStatorBuilder AddActionHandlers(IEnumerable types) public List Interceptors { get; } = new List(); public List ActionHandlers { get; } = new List(); public List AfterEffects { get; } = new List(); - public List Reducers { get; } = new List(); + public List Updaters { get; } = new List(); public List All { get; } = new List(); private IStatorBuilder AddDistinctTypes(List list, IEnumerable types) diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR/Internal/TypeScannerBuilderExtensions.cs index edbc65e..f7a0bfc 100644 --- a/src/StateR/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR/Internal/TypeScannerBuilderExtensions.cs @@ -1,5 +1,5 @@ using StateR.ActionHandlers; -using StateR.Reducers; +using StateR.Updater; using System; using System.Collections.Generic; using System.Linq; @@ -16,7 +16,7 @@ public static IStatorBuilder ScanTypes(this IStatorBuilder builder) return builder .FindStates() .FindActions() - .FindReducers() + .FindUpdaters() .FindActionHandlers() ; } @@ -39,15 +39,15 @@ public static IStatorBuilder FindActions(this IStatorBuilder builder) return builder.AddActions(actions); } - public static IStatorBuilder FindReducers(this IStatorBuilder builder) + public static IStatorBuilder FindUpdaters(this IStatorBuilder builder) { - var reducers = builder.All + var updaters = builder.All .Where(type => !type.IsAbstract && type .GetTypeInfo() .GetInterfaces() - .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReducer<,>)) + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IUpdater<,>)) ); - return builder.AddReducers(reducers); + return builder.AddUpdaters(updaters); } public static IStatorBuilder FindActionHandlers(this IStatorBuilder builder) { @@ -57,7 +57,7 @@ public static IStatorBuilder FindActionHandlers(this IStatorBuilder builder) .GetInterfaces() .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionHandler<>)) ); - return builder.AddReducers(handlers); + return builder.AddUpdaters(handlers); } //public IStatorBuilder FindInterceptors(this IStatorBuilder builder) diff --git a/src/StateR/Reducers/Hooks/IAfterReducerHook.cs b/src/StateR/Reducers/Hooks/IAfterReducerHook.cs deleted file mode 100644 index 05e158f..0000000 --- a/src/StateR/Reducers/Hooks/IAfterReducerHook.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Reducers.Hooks -{ - public interface IAfterReducerHook - { - Task AfterReducerAsync(IDispatchContext context, IState state, IReducer reducer, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - } -} diff --git a/src/StateR/Reducers/Hooks/IBeforeReducerHook.cs b/src/StateR/Reducers/Hooks/IBeforeReducerHook.cs deleted file mode 100644 index 10d15bd..0000000 --- a/src/StateR/Reducers/Hooks/IBeforeReducerHook.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Reducers.Hooks -{ - public interface IBeforeReducerHook - { - Task BeforeReducerAsync(IDispatchContext context, IState state, IReducer reducer, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - } -} diff --git a/src/StateR/Reducers/Hooks/IReducerHooksCollection.cs b/src/StateR/Reducers/Hooks/IReducerHooksCollection.cs deleted file mode 100644 index 159bc07..0000000 --- a/src/StateR/Reducers/Hooks/IReducerHooksCollection.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Reducers.Hooks -{ - public interface IReducerHooksCollection - { - Task BeforeReducerAsync(IDispatchContext context, IState state, IReducer reducer, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - Task AfterReducerAsync(IDispatchContext context, IState state, IReducer reducer, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - } -} diff --git a/src/StateR/Reducers/Hooks/ReducerHooksCollection.cs b/src/StateR/Reducers/Hooks/ReducerHooksCollection.cs deleted file mode 100644 index 8fe18f2..0000000 --- a/src/StateR/Reducers/Hooks/ReducerHooksCollection.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Reducers.Hooks -{ - public class ReducerHooksCollection : IReducerHooksCollection - { - private readonly IEnumerable _beforeReducerHooks; - private readonly IEnumerable _afterReducerHooks; - public ReducerHooksCollection(IEnumerable beforeReducerHooks, IEnumerable afterReducerHooks) - { - _beforeReducerHooks = beforeReducerHooks ?? throw new ArgumentNullException(nameof(beforeReducerHooks)); - _afterReducerHooks = afterReducerHooks ?? throw new ArgumentNullException(nameof(afterReducerHooks)); - } - - public async Task BeforeReducerAsync(IDispatchContext context, IState state, IReducer reducer, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase - { - foreach (var hook in _beforeReducerHooks) - { - await hook.BeforeReducerAsync(context, state, reducer, cancellationToken); - } - } - - public async Task AfterReducerAsync(IDispatchContext context, IState state, IReducer reducer, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase - { - foreach (var hook in _afterReducerHooks) - { - await hook.AfterReducerAsync(context, state, reducer, cancellationToken); - } - } - } -} diff --git a/src/StateR/Reducers/IReducer.cs b/src/StateR/Reducers/IReducer.cs deleted file mode 100644 index e18d8ff..0000000 --- a/src/StateR/Reducers/IReducer.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace StateR.Reducers -{ - public interface IReducer - where TAction : IAction - where TState : StateBase - { - TState Reduce(TAction action, TState state); - } -} \ No newline at end of file diff --git a/src/StateR/Reducers/ReducerHandler.cs b/src/StateR/Reducers/ReducerHandler.cs deleted file mode 100644 index 654b0b3..0000000 --- a/src/StateR/Reducers/ReducerHandler.cs +++ /dev/null @@ -1,36 +0,0 @@ -using StateR.ActionHandlers; -using StateR.Reducers.Hooks; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Reducers -{ - public class ReducerHandler : IActionHandler - where TState : StateBase - where TAction : IAction - { - private readonly IReducerHooksCollection _hooks; - private readonly IEnumerable> _reducers; - private readonly IState _state; - - public ReducerHandler(IState state, IEnumerable> reducers, IReducerHooksCollection hooks) - { - _state = state ?? throw new ArgumentNullException(nameof(state)); - _reducers = reducers ?? throw new ArgumentNullException(nameof(reducers)); - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - } - - public async Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken) - { - foreach (var reducer in _reducers) - { - await _hooks.BeforeReducerAsync(context, _state, reducer, cancellationToken); - _state.Set(reducer.Reduce(context.Action, _state.Current)); - await _hooks.AfterReducerAsync(context, _state, reducer, cancellationToken); - } - _state.Notify(); - } - } -} \ No newline at end of file diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 8ab0810..46922a1 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -7,8 +7,8 @@ using StateR.Interceptors; using StateR.Interceptors.Hooks; using StateR.Internal; -using StateR.Reducers; -using StateR.Reducers.Hooks; +using StateR.Updater; +using StateR.Updater.Hooks; using System; using System.Collections.Generic; using System.Linq; @@ -33,7 +33,7 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); return new StatorBuilder(services); } @@ -87,12 +87,12 @@ public static IServiceCollection Apply(this IStatorBuilder builder) .AsImplementedInterfaces() .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeReducerHook))) + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IBeforeUpdateHook))) .AsImplementedInterfaces() .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterReducerHook))) + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IAfterUpdateHook))) .AsImplementedInterfaces() .WithSingletonLifetime() @@ -118,29 +118,29 @@ public static IServiceCollection Apply(this IStatorBuilder builder) builder.Services.AddSingleton(stateServiceType, stateImplementationType); } - // Register Reducers and their respective IActionHandler - var iReducerType = typeof(IReducer<,>); - var reducerHandler = typeof(ReducerHandler<,>); + // Register Updaters and their respective IActionHandler + var iUpdaterType = typeof(IUpdater<,>); + var updaterHandler = typeof(UpdaterHandler<,>); var handlerType = typeof(IActionHandler<>); - foreach (var reducer in builder.Reducers) + foreach (var updater in builder.Updaters) { - Console.WriteLine($"reducer: {reducer.FullName}"); - var interfaces = reducer.GetInterfaces() - .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iReducerType); + Console.WriteLine($"updater: {updater.FullName}"); + var interfaces = updater.GetInterfaces() + .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); foreach (var @interface in interfaces) { - // Equivalent to: AddSingleton, ReducerHandler> + // Equivalent to: AddSingleton, UpdaterHandler> var actionType = @interface.GenericTypeArguments[0]; var stateType = @interface.GenericTypeArguments[1]; var iActionHandlerServiceType = handlerType.MakeGenericType(actionType); - var reducerHandlerImplementationType = reducerHandler.MakeGenericType(stateType, actionType); - builder.Services.AddSingleton(iActionHandlerServiceType, reducerHandlerImplementationType); + var updaterHandlerImplementationType = updaterHandler.MakeGenericType(stateType, actionType); + builder.Services.AddSingleton(iActionHandlerServiceType, updaterHandlerImplementationType); - // Equivalent to: AddSingleton, Reducer>(); - builder.Services.AddSingleton(@interface, reducer); + // Equivalent to: AddSingleton, Updater>(); + builder.Services.AddSingleton(@interface, updater); - Console.WriteLine($"- AddSingleton<{iActionHandlerServiceType.GetStatorName()}, {reducerHandlerImplementationType.GetStatorName()}>()"); - Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {reducer.GetStatorName()}>()"); + Console.WriteLine($"- AddSingleton<{iActionHandlerServiceType.GetStatorName()}, {updaterHandlerImplementationType.GetStatorName()}>()"); + Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); } } return builder.Services; diff --git a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs new file mode 100644 index 0000000..a6d0c40 --- /dev/null +++ b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace StateR.Updater.Hooks +{ + public interface IAfterUpdateHook + { + Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; + } +} diff --git a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs new file mode 100644 index 0000000..b9a0b6a --- /dev/null +++ b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace StateR.Updater.Hooks +{ + public interface IBeforeUpdateHook + { + Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; + } +} diff --git a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs new file mode 100644 index 0000000..08b43a0 --- /dev/null +++ b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs @@ -0,0 +1,15 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace StateR.Updater.Hooks +{ + public interface IUpdateHooksCollection + { + Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; + Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; + } +} diff --git a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs new file mode 100644 index 0000000..1512f0d --- /dev/null +++ b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace StateR.Updater.Hooks +{ + public class UpdateHooksCollection : IUpdateHooksCollection + { + private readonly IEnumerable _beforeUpdateHooks; + private readonly IEnumerable _afterUpdateHooks; + public UpdateHooksCollection(IEnumerable beforeUpdateHooks, IEnumerable afterUpdateHooks) + { + _beforeUpdateHooks = beforeUpdateHooks ?? throw new ArgumentNullException(nameof(beforeUpdateHooks)); + _afterUpdateHooks = afterUpdateHooks ?? throw new ArgumentNullException(nameof(afterUpdateHooks)); + } + + public async Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase + { + foreach (var hook in _beforeUpdateHooks) + { + await hook.BeforeUpdateAsync(context, state, updater, cancellationToken); + } + } + + public async Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase + { + foreach (var hook in _afterUpdateHooks) + { + await hook.AfterUpdateAsync(context, state, updater, cancellationToken); + } + } + } +} diff --git a/src/StateR/Updaters/IUpdater.cs b/src/StateR/Updaters/IUpdater.cs new file mode 100644 index 0000000..42cbe8e --- /dev/null +++ b/src/StateR/Updaters/IUpdater.cs @@ -0,0 +1,11 @@ +using System; + +namespace StateR.Updater +{ + public interface IUpdater + where TAction : IAction + where TState : StateBase + { + TState Update(TAction action, TState state); + } +} \ No newline at end of file diff --git a/src/StateR/Updaters/UpdaterHandler.cs b/src/StateR/Updaters/UpdaterHandler.cs new file mode 100644 index 0000000..8c9d478 --- /dev/null +++ b/src/StateR/Updaters/UpdaterHandler.cs @@ -0,0 +1,36 @@ +using StateR.ActionHandlers; +using StateR.Updater.Hooks; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace StateR.Updater +{ + public class UpdaterHandler : IActionHandler + where TState : StateBase + where TAction : IAction + { + private readonly IUpdateHooksCollection _hooks; + private readonly IEnumerable> _updaters; + private readonly IState _state; + + public UpdaterHandler(IState state, IEnumerable> updaters, IUpdateHooksCollection hooks) + { + _state = state ?? throw new ArgumentNullException(nameof(state)); + _updaters = updaters ?? throw new ArgumentNullException(nameof(updaters)); + _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); + } + + public async Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken) + { + foreach (var updater in _updaters) + { + await _hooks.BeforeUpdateAsync(context, _state, updater, cancellationToken); + _state.Set(updater.Update(context.Action, _state.Current)); + await _hooks.AfterUpdateAsync(context, _state, updater, cancellationToken); + } + _state.Notify(); + } + } +} \ No newline at end of file diff --git a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs index 0a3d1e1..5673f36 100644 --- a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs +++ b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs @@ -15,7 +15,7 @@ public class ActionHandlerManagerTest { private readonly Mock _hooksCollectionMock = new(); - protected ActionHandlersManager CreateReducersManager(Action configureServices) + protected ActionHandlersManager CreateUpdatersManager(Action configureServices) { var services = new ServiceCollection(); configureServices?.Invoke(services); @@ -34,7 +34,7 @@ public async Task Should_call_all_action_handlers() var handler1 = new Mock>(); var handler2 = new Mock>(); - var sut = CreateReducersManager(services => + var sut = CreateUpdatersManager(services => { services.AddSingleton(handler1.Object); services.AddSingleton(handler2.Object); @@ -49,7 +49,7 @@ public async Task Should_call_all_action_handlers() } [Fact] - public async Task Should_break_handlers_when_StopReduce_is_true() + public async Task Should_break_handlers_when_StopUpdate_is_true() { // Arrange var context = new DispatchContext(new TestAction(), new Mock().Object); @@ -57,9 +57,9 @@ public async Task Should_break_handlers_when_StopReduce_is_true() var afterEffect1 = new Mock>(); afterEffect1.Setup(x => x.HandleAsync(context, token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.StopReduce = true); + .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.StopUpdate = true); var afterEffect2 = new Mock>(); - var sut = CreateReducersManager(services => + var sut = CreateUpdatersManager(services => { services.AddSingleton(afterEffect1.Object); services.AddSingleton(afterEffect2.Object); @@ -94,7 +94,7 @@ public async Task Should_call_middleware_and_handlers_in_order() .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), token)) .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - var sut = CreateReducersManager(services => + var sut = CreateUpdatersManager(services => { services.AddSingleton(actionHandler1.Object); services.AddSingleton(actionHandler2.Object); diff --git a/test/StateR.Tests/DispatcherTest.cs b/test/StateR.Tests/DispatcherTest.cs index fab2049..75ecba1 100644 --- a/test/StateR.Tests/DispatcherTest.cs +++ b/test/StateR.Tests/DispatcherTest.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using System.Collections.Generic; using StateR.Interceptors; -using StateR.Reducers; +using StateR.Updater; using StateR.AfterEffects; using StateR.ActionHandlers; @@ -13,18 +13,14 @@ namespace StateR { public class DispatcherTest { - private readonly Mock _dispatchContextFactory; - private readonly Mock _interceptorsManager; - private readonly Mock _actionHandlersManager; - private readonly Mock _afterEffectsManager; + private readonly Mock _dispatchContextFactory = new(); + private readonly Mock _interceptorsManager = new(); + private readonly Mock _actionHandlersManager = new(); + private readonly Mock _afterEffectsManager = new(); private readonly Dispatcher sut; public DispatcherTest() { - _dispatchContextFactory = new(); - _interceptorsManager = new(); - _actionHandlersManager = new(); - _afterEffectsManager = new(); sut = new(_dispatchContextFactory.Object, _interceptorsManager.Object, _actionHandlersManager.Object, _afterEffectsManager.Object); } @@ -69,7 +65,7 @@ public async Task Should_call_managers_in_the_expected_order() .Callback(() => operationQueue.Enqueue("Interceptors")); _actionHandlersManager .Setup(x => x.DispatchAsync(It.IsAny>(), token)) - .Callback(() => operationQueue.Enqueue("Reducers")); + .Callback(() => operationQueue.Enqueue("Updaters")); _afterEffectsManager .Setup(x => x.DispatchAsync(It.IsAny>(), token)) .Callback(() => operationQueue.Enqueue("AfterEffects")); @@ -80,7 +76,7 @@ public async Task Should_call_managers_in_the_expected_order() // Assert Assert.Collection(operationQueue, operation => Assert.Equal("Interceptors", operation), - operation => Assert.Equal("Reducers", operation), + operation => Assert.Equal("Updaters", operation), operation => Assert.Equal("AfterEffects", operation) ); } diff --git a/test/StateR.Tests/Reducers/Hooks/ReducerHooksCollectionTest.cs b/test/StateR.Tests/Reducers/Hooks/ReducerHooksCollectionTest.cs deleted file mode 100644 index 76ed942..0000000 --- a/test/StateR.Tests/Reducers/Hooks/ReducerHooksCollectionTest.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Moq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace StateR.Reducers.Hooks -{ - public class ReducerHooksCollectionTest - { - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); - - private readonly Mock> _stateMock = new(); - private readonly Mock> _reducer = new(); - - private readonly IDispatchContext _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object); - private readonly CancellationToken _cancellationToken = CancellationToken.None; - - private readonly ReducerHooksCollection sut; - - public ReducerHooksCollectionTest() - { - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - - public class BeforeReducerAsync : ReducerHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken), Times.Never); - } - } - - public class AfterReducerAsync : ReducerHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterReducerAsync(_dispatchContext, _stateMock.Object, _reducer.Object, _cancellationToken), Times.Once); - } - } - - public record TestAction : IAction; - public record TestState : StateBase; - } -} diff --git a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs new file mode 100644 index 0000000..96d29a8 --- /dev/null +++ b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs @@ -0,0 +1,70 @@ +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StateR.Updater.Hooks +{ + public class UpdateHooksCollectionTest + { + private readonly Mock _before1Mock = new(); + private readonly Mock _before2Mock = new(); + private readonly Mock _after1Mock = new(); + private readonly Mock _after2Mock = new(); + + private readonly Mock> _stateMock = new(); + private readonly Mock> _updater = new(); + + private readonly IDispatchContext _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object); + private readonly CancellationToken _cancellationToken = CancellationToken.None; + + private readonly UpdateHooksCollection sut; + + public UpdateHooksCollectionTest() + { + sut = new UpdateHooksCollection( + new[] { _before1Mock.Object, _before2Mock.Object }, + new[] { _after1Mock.Object, _after2Mock.Object } + ); + } + + public class BeforeUpdateAsync : UpdateHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() + { + // Act + await sut.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); + + // Assert + _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); + _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); + _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); + _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); + } + } + + public class AfterUpdateAsync : UpdateHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() + { + // Act + await sut.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); + + // Assert + _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); + _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); + _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); + _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); + } + } + + public record TestAction : IAction; + public record TestState : StateBase; + } +} diff --git a/test/StateR.Tests/Reducers/ReducerHandlerTest.cs b/test/StateR.Tests/Reducers/ReducerHandlerTest.cs deleted file mode 100644 index 2dd0265..0000000 --- a/test/StateR.Tests/Reducers/ReducerHandlerTest.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Moq; -using StateR.Reducers.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace StateR.Reducers -{ - public class ReducerHandlerTest - { - private readonly TestState _state = new(); - private readonly Mock> _stateMock = new(); - private readonly List> _reducers = new(); - private readonly Mock _hooksMock = new(); - private readonly ReducerHandler sut; - private readonly Queue _operationQueue = new(); - private readonly TestAction _action = new(); - private readonly DispatchContext _context; - private readonly CancellationToken _token = CancellationToken.None; - - private readonly Mock> _reducer1Mock = new(); - private readonly Mock> _reducer2Mock = new(); - - public ReducerHandlerTest() - { - _context = new(_action, new Mock().Object); - - _stateMock.Setup(x => x.Current).Returns(_state); - _stateMock.Setup(x => x.Notify()) - .Callback(() => _operationQueue.Enqueue("state.Notify")); - - _reducer1Mock - .Setup(x => x.Reduce(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("reducer1.Reduce")); - _reducers.Add(_reducer1Mock.Object); - _reducer2Mock - .Setup(x => x.Reduce(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("reducer2.Reduce")); - _reducers.Add(_reducer2Mock.Object); - - sut = new ReducerHandler( - _stateMock.Object, - _reducers, - _hooksMock.Object - ); - } - - public class HandleAsync : ReducerHandlerTest - { - [Fact] - public async Task Should_call_reducers_then_notify() - { - // Act - await sut.HandleAsync(_context, _token); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("reducer1.Reduce", op), - op => Assert.Equal("reducer2.Reduce", op), - op => Assert.Equal("state.Notify", op) - ); - } - } - - [Fact] - public async Task Should_call_middleware_and_middlewares_methods_in_order() - { - // Arrange - _hooksMock - .Setup(x => x.BeforeReducerAsync(_context, _stateMock.Object, _reducer1Mock.Object, _token)) - .Callback(() => _operationQueue.Enqueue("BeforeReducerAsync:Reducer1")); - _hooksMock - .Setup(x => x.BeforeReducerAsync(_context, _stateMock.Object, _reducer2Mock.Object, _token)) - .Callback(() => _operationQueue.Enqueue("BeforeReducerAsync:Reducer2")); - _hooksMock - .Setup(x => x.AfterReducerAsync(_context, _stateMock.Object, _reducer1Mock.Object, _token)) - .Callback(() => _operationQueue.Enqueue("AfterReducerAsync:Reducer1")); - _hooksMock - .Setup(x => x.AfterReducerAsync(_context, _stateMock.Object, _reducer2Mock.Object, _token)) - .Callback(() => _operationQueue.Enqueue("AfterReducerAsync:Reducer2")); - - // Act - await sut.HandleAsync(_context, _token); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeReducerAsync:Reducer1", op), - op => Assert.Equal("reducer1.Reduce", op), - op => Assert.Equal("AfterReducerAsync:Reducer1", op), - - op => Assert.Equal("BeforeReducerAsync:Reducer2", op), - op => Assert.Equal("reducer2.Reduce", op), - op => Assert.Equal("AfterReducerAsync:Reducer2", op), - - op => Assert.Equal("state.Notify", op) - ); - } - - public record TestAction : IAction; - public record TestState : StateBase; - } -} diff --git a/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs new file mode 100644 index 0000000..9bb7477 --- /dev/null +++ b/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs @@ -0,0 +1,109 @@ +using Microsoft.Extensions.DependencyInjection; +using Moq; +using StateR.Updater.Hooks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StateR.Updater +{ + public class UpdaterHandlerTest + { + private readonly TestState _state = new(); + private readonly Mock> _stateMock = new(); + private readonly List> _updaters = new(); + private readonly Mock _hooksMock = new(); + private readonly UpdaterHandler sut; + private readonly Queue _operationQueue = new(); + private readonly TestAction _action = new(); + private readonly DispatchContext _context; + private readonly CancellationToken _token = CancellationToken.None; + + private readonly Mock> _updater1Mock = new(); + private readonly Mock> _updater2Mock = new(); + + public UpdaterHandlerTest() + { + _context = new(_action, new Mock().Object); + + _stateMock.Setup(x => x.Current).Returns(_state); + _stateMock.Setup(x => x.Notify()) + .Callback(() => _operationQueue.Enqueue("state.Notify")); + + _updater1Mock + .Setup(x => x.Update(_action, _state)) + .Returns(_state) + .Callback(() => _operationQueue.Enqueue("updater1.Update")); + _updaters.Add(_updater1Mock.Object); + _updater2Mock + .Setup(x => x.Update(_action, _state)) + .Returns(_state) + .Callback(() => _operationQueue.Enqueue("updater2.Update")); + _updaters.Add(_updater2Mock.Object); + + sut = new UpdaterHandler( + _stateMock.Object, + _updaters, + _hooksMock.Object + ); + } + + public class HandleAsync : UpdaterHandlerTest + { + [Fact] + public async Task Should_call_updaters_then_notify() + { + // Act + await sut.HandleAsync(_context, _token); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("updater2.Update", op), + op => Assert.Equal("state.Notify", op) + ); + } + } + + [Fact] + public async Task Should_call_middleware_and_middlewares_methods_in_order() + { + // Arrange + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _token)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _token)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _token)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _token)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); + + // Act + await sut.HandleAsync(_context, _token); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater1", op), + + op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), + op => Assert.Equal("updater2.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater2", op), + + op => Assert.Equal("state.Notify", op) + ); + } + + public record TestAction : IAction; + public record TestState : StateBase; + } +} From 8cad84e6fa340d06cf04d72200eb80553cacad8c Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 13:33:23 -0500 Subject: [PATCH 02/58] Rename StateR.Updater to StateR.Updaters --- .../ReduxDevTools/ReduxDevToolsInterop.cs | 4 ++-- .../ReduxDevTools/ReduxDevToolsStartupExtensions.cs | 4 ++-- src/StateR.Experiments/AsyncLogic/AsyncError.cs | 2 +- src/StateR.Experiments/AsyncLogic/AsyncOperation.cs | 2 +- src/StateR.Experiments/AsyncLogic/StartupExtensions.cs | 2 +- src/StateR/Dispatcher.cs | 2 +- src/StateR/Internal/TypeScannerBuilderExtensions.cs | 2 +- src/StateR/StatorStartupExtensions.cs | 4 ++-- src/StateR/Updaters/Hooks/IAfterUpdateHook.cs | 2 +- src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs | 2 +- src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs | 2 +- src/StateR/Updaters/Hooks/UpdateHooksCollection.cs | 2 +- src/StateR/Updaters/IUpdater.cs | 2 +- src/StateR/Updaters/UpdaterHandler.cs | 4 ++-- test/StateR.Tests/DispatcherTest.cs | 2 +- test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs | 2 +- test/StateR.Tests/Reducers/UpdaterHandlerTest.cs | 4 ++-- 17 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs index 358d246..063b8a0 100644 --- a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs +++ b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs @@ -7,11 +7,11 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; -using StateR.Updater; +using StateR.Updaters; using System.Threading; using System.Reflection; using System.Security.Cryptography.X509Certificates; -using StateR.Updater.Hooks; +using StateR.Updaters.Hooks; namespace StateR.Blazor.ReduxDevTools { diff --git a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs index b8a2a5c..6e0b786 100644 --- a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs +++ b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.JSInterop; -using StateR.Updater; -using StateR.Updater.Hooks; +using StateR.Updaters; +using StateR.Updaters.Hooks; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/StateR.Experiments/AsyncLogic/AsyncError.cs b/src/StateR.Experiments/AsyncLogic/AsyncError.cs index 0fbbe3f..366c3c7 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncError.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncError.cs @@ -1,4 +1,4 @@ -using StateR.Updater; +using StateR.Updaters; using System; namespace StateR.AsyncLogic diff --git a/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs b/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs index fb486c8..1b14a7c 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using StateR.AfterEffects; -using StateR.Updater; +using StateR.Updaters; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs index 9177966..49d1254 100644 --- a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs +++ b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using StateR.ActionHandlers; using StateR.AsyncLogic; -using StateR.Updater; +using StateR.Updaters; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/StateR/Dispatcher.cs b/src/StateR/Dispatcher.cs index 96eb0d2..28c4d16 100644 --- a/src/StateR/Dispatcher.cs +++ b/src/StateR/Dispatcher.cs @@ -1,7 +1,7 @@ using StateR.ActionHandlers; using StateR.AfterEffects; using StateR.Interceptors; -using StateR.Updater; +using StateR.Updaters; using System; using System.Threading; using System.Threading.Tasks; diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR/Internal/TypeScannerBuilderExtensions.cs index f7a0bfc..12aeb65 100644 --- a/src/StateR/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR/Internal/TypeScannerBuilderExtensions.cs @@ -1,5 +1,5 @@ using StateR.ActionHandlers; -using StateR.Updater; +using StateR.Updaters; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 46922a1..3e5ae61 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -7,8 +7,8 @@ using StateR.Interceptors; using StateR.Interceptors.Hooks; using StateR.Internal; -using StateR.Updater; -using StateR.Updater.Hooks; +using StateR.Updaters; +using StateR.Updaters.Hooks; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs index a6d0c40..4331f94 100644 --- a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs +++ b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Updater.Hooks +namespace StateR.Updaters.Hooks { public interface IAfterUpdateHook { diff --git a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs index b9a0b6a..87fc9d7 100644 --- a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs +++ b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Updater.Hooks +namespace StateR.Updaters.Hooks { public interface IBeforeUpdateHook { diff --git a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs index 08b43a0..0a235c1 100644 --- a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs +++ b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Updater.Hooks +namespace StateR.Updaters.Hooks { public interface IUpdateHooksCollection { diff --git a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs index 1512f0d..3c9c20d 100644 --- a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs +++ b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Updater.Hooks +namespace StateR.Updaters.Hooks { public class UpdateHooksCollection : IUpdateHooksCollection { diff --git a/src/StateR/Updaters/IUpdater.cs b/src/StateR/Updaters/IUpdater.cs index 42cbe8e..8906cda 100644 --- a/src/StateR/Updaters/IUpdater.cs +++ b/src/StateR/Updaters/IUpdater.cs @@ -1,6 +1,6 @@ using System; -namespace StateR.Updater +namespace StateR.Updaters { public interface IUpdater where TAction : IAction diff --git a/src/StateR/Updaters/UpdaterHandler.cs b/src/StateR/Updaters/UpdaterHandler.cs index 8c9d478..3875888 100644 --- a/src/StateR/Updaters/UpdaterHandler.cs +++ b/src/StateR/Updaters/UpdaterHandler.cs @@ -1,11 +1,11 @@ using StateR.ActionHandlers; -using StateR.Updater.Hooks; +using StateR.Updaters.Hooks; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace StateR.Updater +namespace StateR.Updaters { public class UpdaterHandler : IActionHandler where TState : StateBase diff --git a/test/StateR.Tests/DispatcherTest.cs b/test/StateR.Tests/DispatcherTest.cs index 75ecba1..b09b735 100644 --- a/test/StateR.Tests/DispatcherTest.cs +++ b/test/StateR.Tests/DispatcherTest.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using System.Collections.Generic; using StateR.Interceptors; -using StateR.Updater; +using StateR.Updaters; using StateR.AfterEffects; using StateR.ActionHandlers; diff --git a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs index 96d29a8..e4ac34a 100644 --- a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs +++ b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.Updater.Hooks +namespace StateR.Updaters.Hooks { public class UpdateHooksCollectionTest { diff --git a/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs index 9bb7477..e7c5748 100644 --- a/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs +++ b/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Moq; -using StateR.Updater.Hooks; +using StateR.Updaters.Hooks; using System; using System.Collections.Generic; using System.Linq; @@ -9,7 +9,7 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.Updater +namespace StateR.Updaters { public class UpdaterHandlerTest { From 56a080144d3cf59fae8ec851cae12e0375372aa4 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 15:18:16 -0500 Subject: [PATCH 03/58] Update CancellationToken --- .../AsyncLogic/StartupExtensions.cs | 2 +- .../ActionHandlers/ActionHandlersManager.cs | 14 +- .../AfterEffects/AfterEffectsManager.cs | 14 +- src/StateR/DispatchContext.cs | 21 +- src/StateR/DispatchContextFactory.cs | 8 +- src/StateR/Dispatcher.cs | 12 +- src/StateR/IDispatchContext.cs | 12 +- src/StateR/IDispatchContextFactory.cs | 6 +- src/StateR/IDispatchManager.cs | 2 +- .../Interceptors/InterceptorsManager.cs | 14 +- src/StateR/StatorStartupExtensions.cs | 2 +- ...aterHandler.cs => UpdaterActionHandler.cs} | 13 +- .../ActionHandlerManagerTest.cs | 42 ++-- .../Hooks/ActionHandlerHooksCollectionTest.cs | 4 +- .../AfterEffects/AfterEffectsManagerTest.cs | 41 ++-- .../Hooks/AfterEffectHooksCollectionTest.cs | 24 +-- test/StateR.Tests/DispatcherTest.cs | 25 ++- .../Hooks/InterceptorsHooksCollectionTest.cs | 24 +-- .../Interceptors/InterceptorsManagerTest.cs | 42 ++-- .../Hooks/UpdateHooksCollectionTest.cs | 4 +- .../Reducers/UpdaterActionHandlerTest.cs | 186 ++++++++++++++++++ .../Reducers/UpdaterHandlerTest.cs | 109 ---------- 22 files changed, 358 insertions(+), 263 deletions(-) rename src/StateR/Updaters/{UpdaterHandler.cs => UpdaterActionHandler.cs} (69%) create mode 100644 test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs delete mode 100644 test/StateR.Tests/Reducers/UpdaterHandlerTest.cs diff --git a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs index 49d1254..fb7c805 100644 --- a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs +++ b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs @@ -18,7 +18,7 @@ public static IStatorBuilder AddAsyncOperations(this IStatorBuilder builder) builder.AddTypes(new[] { typeof(StatusUpdated<>) }); // Async Operation's Errors - builder.Services.TryAddSingleton, UpdaterHandler>(); + builder.Services.TryAddSingleton, UpdaterActionHandler>(); builder.Services.TryAddSingleton, AsyncError.Updaters>(); builder.Services.TryAddSingleton, AsyncError.InitialState>(); builder.Services.TryAddSingleton, Internal.State>(); diff --git a/src/StateR/ActionHandlers/ActionHandlersManager.cs b/src/StateR/ActionHandlers/ActionHandlersManager.cs index 93b4230..1372cb9 100644 --- a/src/StateR/ActionHandlers/ActionHandlersManager.cs +++ b/src/StateR/ActionHandlers/ActionHandlersManager.cs @@ -19,18 +19,16 @@ public ActionHandlersManager(IActionHandlerHooksCollection hooksCollection, ISer _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } - public async Task DispatchAsync(IDispatchContext dispatchContext, CancellationToken cancellationToken) where TAction : IAction + public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction { var updaterHandlers = _serviceProvider.GetServices>().ToList(); foreach (var handler in updaterHandlers) { - if (dispatchContext.StopUpdate) - { - break; - } - await _hooksCollection.BeforeHandlerAsync(dispatchContext, handler, cancellationToken); - await handler.HandleAsync(dispatchContext, cancellationToken); - await _hooksCollection.AfterHandlerAsync(dispatchContext, handler, cancellationToken); + dispatchContext.CancellationToken.ThrowIfCancellationRequested(); + + await _hooksCollection.BeforeHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); + await handler.HandleAsync(dispatchContext, dispatchContext.CancellationToken); + await _hooksCollection.AfterHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); } } } diff --git a/src/StateR/AfterEffects/AfterEffectsManager.cs b/src/StateR/AfterEffects/AfterEffectsManager.cs index 5becd80..77bfc5b 100644 --- a/src/StateR/AfterEffects/AfterEffectsManager.cs +++ b/src/StateR/AfterEffects/AfterEffectsManager.cs @@ -19,18 +19,16 @@ public AfterEffectsManager(IAfterEffectHooksCollection hooks, IServiceProvider s _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } - public async Task DispatchAsync(IDispatchContext dispatchContext, CancellationToken cancellationToken) where TAction : IAction + public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction { var afterEffects = _serviceProvider.GetServices>().ToList(); foreach (var afterEffect in afterEffects) { - if (dispatchContext.StopAfterEffect) - { - break; - } - await _hooks.BeforeHandlerAsync(dispatchContext, afterEffect, cancellationToken); - await afterEffect.HandleAfterEffectAsync(dispatchContext, cancellationToken); - await _hooks.AfterHandlerAsync(dispatchContext, afterEffect, cancellationToken); + dispatchContext.CancellationToken.ThrowIfCancellationRequested(); + + await _hooks.BeforeHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); + await afterEffect.HandleAfterEffectAsync(dispatchContext, dispatchContext.CancellationToken); + await _hooks.AfterHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); } } } diff --git a/src/StateR/DispatchContext.cs b/src/StateR/DispatchContext.cs index f55cfb9..2b49c48 100644 --- a/src/StateR/DispatchContext.cs +++ b/src/StateR/DispatchContext.cs @@ -1,27 +1,24 @@ using System; +using System.Threading; + namespace StateR { public class DispatchContext : IDispatchContext where TAction : IAction { - public DispatchContext(TAction action, IDispatcher dispatcher) + private readonly CancellationTokenSource _cancellationTokenSource; + public DispatchContext(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) { Action = action; Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + _cancellationTokenSource = cancellationTokenSource ?? throw new ArgumentNullException(nameof(cancellationTokenSource)); } public IDispatcher Dispatcher { get; } - public TAction Action { get; set; } - - public bool StopUpdate { get; set; } - public bool StopInterception { get; set; } - public bool StopAfterEffect { get; set; } + public TAction Action { get; } + public CancellationToken CancellationToken => _cancellationTokenSource.Token; - public void DoNotContinue() - { - StopAfterEffect = true; - StopInterception = true; - StopUpdate = true; - } + public void Cancel() + => _cancellationTokenSource.Cancel(true); } } diff --git a/src/StateR/DispatchContextFactory.cs b/src/StateR/DispatchContextFactory.cs index d0c78d3..2716995 100644 --- a/src/StateR/DispatchContextFactory.cs +++ b/src/StateR/DispatchContextFactory.cs @@ -1,9 +1,11 @@ -namespace StateR +using System.Threading; + +namespace StateR { public class DispatchContextFactory : IDispatchContextFactory { - public IDispatchContext Create(TAction action, IDispatcher dispatcher) + public IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) where TAction : IAction - => new DispatchContext(action, dispatcher); + => new DispatchContext(action, dispatcher, cancellationTokenSource); } } diff --git a/src/StateR/Dispatcher.cs b/src/StateR/Dispatcher.cs index 28c4d16..e69fe3c 100644 --- a/src/StateR/Dispatcher.cs +++ b/src/StateR/Dispatcher.cs @@ -25,10 +25,14 @@ public Dispatcher(IDispatchContextFactory dispatchContextFactory, IInterceptorsM public async Task DispatchAsync(TAction action, CancellationToken cancellationToken) where TAction : IAction { - var dispatchContext = _dispatchContextFactory.Create(action, this); - await _interceptorsManager.DispatchAsync(dispatchContext, cancellationToken); - await _actionHandlersManager.DispatchAsync(dispatchContext, cancellationToken); - await _afterEffectsManager.DispatchAsync(dispatchContext, cancellationToken); + using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var dispatchContext = _dispatchContextFactory.Create(action, this, cancellationTokenSource); + // + // TODO: design how to handle OperationCanceledException + // + await _interceptorsManager.DispatchAsync(dispatchContext); + await _actionHandlersManager.DispatchAsync(dispatchContext); + await _afterEffectsManager.DispatchAsync(dispatchContext); } } } diff --git a/src/StateR/IDispatchContext.cs b/src/StateR/IDispatchContext.cs index 0ae721a..694e38d 100644 --- a/src/StateR/IDispatchContext.cs +++ b/src/StateR/IDispatchContext.cs @@ -1,14 +1,14 @@ -namespace StateR +using System.Threading; + +namespace StateR { public interface IDispatchContext where TAction : IAction { IDispatcher Dispatcher { get; } - TAction Action { get; set; } - bool StopUpdate { get; set; } - bool StopInterception { get; set; } - bool StopAfterEffect { get; set; } + TAction Action { get; } - void DoNotContinue(); + CancellationToken CancellationToken { get; } + void Cancel(); } } diff --git a/src/StateR/IDispatchContextFactory.cs b/src/StateR/IDispatchContextFactory.cs index 6f57b57..40648eb 100644 --- a/src/StateR/IDispatchContextFactory.cs +++ b/src/StateR/IDispatchContextFactory.cs @@ -1,7 +1,9 @@ -namespace StateR +using System.Threading; + +namespace StateR { public interface IDispatchContextFactory { - IDispatchContext Create(TAction action, IDispatcher dispatcher) where TAction : IAction; + IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) where TAction : IAction; } } diff --git a/src/StateR/IDispatchManager.cs b/src/StateR/IDispatchManager.cs index 9628500..dc07f9b 100644 --- a/src/StateR/IDispatchManager.cs +++ b/src/StateR/IDispatchManager.cs @@ -5,7 +5,7 @@ namespace StateR { public interface IDispatchManager { - Task DispatchAsync(IDispatchContext dispatchContext, CancellationToken cancellationToken) + Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction; } } diff --git a/src/StateR/Interceptors/InterceptorsManager.cs b/src/StateR/Interceptors/InterceptorsManager.cs index 9e59e1b..a974209 100644 --- a/src/StateR/Interceptors/InterceptorsManager.cs +++ b/src/StateR/Interceptors/InterceptorsManager.cs @@ -19,18 +19,16 @@ public InterceptorsManager(IInterceptorsHooksCollection hooks, IServiceProvider _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } - public async Task DispatchAsync(IDispatchContext dispatchContext, CancellationToken cancellationToken) where TAction : IAction + public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction { var interceptors = _serviceProvider.GetServices>().ToList(); foreach (var interceptor in interceptors) { - if (dispatchContext.StopInterception) - { - break; - } - await _hooks.BeforeHandlerAsync(dispatchContext, interceptor, cancellationToken); - await interceptor.InterceptAsync(dispatchContext, cancellationToken); - await _hooks.AfterHandlerAsync(dispatchContext, interceptor, cancellationToken); + dispatchContext.CancellationToken.ThrowIfCancellationRequested(); + + await _hooks.BeforeHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); + await interceptor.InterceptAsync(dispatchContext, dispatchContext.CancellationToken); + await _hooks.AfterHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); } } } diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 3e5ae61..390f5a2 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -120,7 +120,7 @@ public static IServiceCollection Apply(this IStatorBuilder builder) // Register Updaters and their respective IActionHandler var iUpdaterType = typeof(IUpdater<,>); - var updaterHandler = typeof(UpdaterHandler<,>); + var updaterHandler = typeof(UpdaterActionHandler<,>); var handlerType = typeof(IActionHandler<>); foreach (var updater in builder.Updaters) { diff --git a/src/StateR/Updaters/UpdaterHandler.cs b/src/StateR/Updaters/UpdaterActionHandler.cs similarity index 69% rename from src/StateR/Updaters/UpdaterHandler.cs rename to src/StateR/Updaters/UpdaterActionHandler.cs index 3875888..c3a77a4 100644 --- a/src/StateR/Updaters/UpdaterHandler.cs +++ b/src/StateR/Updaters/UpdaterActionHandler.cs @@ -7,7 +7,7 @@ namespace StateR.Updaters { - public class UpdaterHandler : IActionHandler + public class UpdaterActionHandler : IActionHandler where TState : StateBase where TAction : IAction { @@ -15,7 +15,7 @@ public class UpdaterHandler : IActionHandler private readonly IEnumerable> _updaters; private readonly IState _state; - public UpdaterHandler(IState state, IEnumerable> updaters, IUpdateHooksCollection hooks) + public UpdaterActionHandler(IState state, IEnumerable> updaters, IUpdateHooksCollection hooks) { _state = state ?? throw new ArgumentNullException(nameof(state)); _updaters = updaters ?? throw new ArgumentNullException(nameof(updaters)); @@ -26,11 +26,20 @@ public async Task HandleAsync(IDispatchContext context, CancellationTok { foreach (var updater in _updaters) { + if (cancellationToken.IsCancellationRequested) + { + break; + } await _hooks.BeforeUpdateAsync(context, _state, updater, cancellationToken); + if (cancellationToken.IsCancellationRequested) + { + break; + } _state.Set(updater.Update(context.Action, _state.Current)); await _hooks.AfterUpdateAsync(context, _state, updater, cancellationToken); } _state.Notify(); + cancellationToken.ThrowIfCancellationRequested(); } } } \ No newline at end of file diff --git a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs index 5673f36..db80de1 100644 --- a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs +++ b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs @@ -29,8 +29,8 @@ public class DispatchAsync : ActionHandlerManagerTest public async Task Should_call_all_action_handlers() { // Arrange - var context = new DispatchContext(new TestAction(), new Mock().Object); - var token = CancellationToken.None; + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); var handler1 = new Mock>(); var handler2 = new Mock>(); @@ -41,23 +41,24 @@ public async Task Should_call_all_action_handlers() }); // Act - await sut.DispatchAsync(context, token); + await sut.DispatchAsync(context); // Assert - handler1.Verify(x => x.HandleAsync(context, token), Times.Once); - handler2.Verify(x => x.HandleAsync(context, token), Times.Once); + handler1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); + handler2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); } [Fact] - public async Task Should_break_handlers_when_StopUpdate_is_true() + public async Task Should_break_handlers_when_Cancel() { // Arrange - var context = new DispatchContext(new TestAction(), new Mock().Object); - var token = CancellationToken.None; + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAsync(context, token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.StopUpdate = true); + afterEffect1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) + .Callback((IDispatchContext context, CancellationToken cancellationToken) + => context.Cancel()); var afterEffect2 = new Mock>(); var sut = CreateUpdatersManager(services => { @@ -66,32 +67,33 @@ public async Task Should_break_handlers_when_StopUpdate_is_true() }); // Act - await sut.DispatchAsync(context, token); + await Assert.ThrowsAsync(() + => sut.DispatchAsync(context)); // Assert - afterEffect1.Verify(x => x.HandleAsync(context, token), Times.Once); - afterEffect2.Verify(x => x.HandleAsync(context, token), Times.Never); + afterEffect1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); + afterEffect2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Never); } [Fact] public async Task Should_call_middleware_and_handlers_in_order() { // Arrange - var context = new DispatchContext(new TestAction(), new Mock().Object); - var token = CancellationToken.None; + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); var operationQueue = new Queue(); var actionHandler1 = new Mock>(); - actionHandler1.Setup(x => x.HandleAsync(context, token)) + actionHandler1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("actionHandler1.HandleAsync")); var actionHandler2 = new Mock>(); - actionHandler2.Setup(x => x.HandleAsync(context, token)) + actionHandler2.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("actionHandler2.HandleAsync")); _hooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), token)) + .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); _hooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), token)) + .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); var sut = CreateUpdatersManager(services => @@ -101,7 +103,7 @@ public async Task Should_call_middleware_and_handlers_in_order() }); // Act - await sut.DispatchAsync(context, token); + await sut.DispatchAsync(context); // Assert Assert.Collection(operationQueue, diff --git a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs b/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs index b263fc5..d71a65a 100644 --- a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs +++ b/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs @@ -17,13 +17,15 @@ public class ActionHandlerHooksCollectionTest private readonly Mock _after2Mock = new(); private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext dispatchContext = new DispatchContext(new TestAction(), new Mock().Object); + private readonly IDispatchContext dispatchContext; private readonly CancellationToken _cancellationToken = CancellationToken.None; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly ActionHandlerHooksCollection sut; public ActionHandlerHooksCollectionTest() { + dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); sut = new( new[] { _before1Mock.Object, _before2Mock.Object }, new[] { _after1Mock.Object, _after2Mock.Object } diff --git a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs b/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs index 6e35ae5..3618ffe 100644 --- a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs +++ b/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs @@ -31,8 +31,8 @@ public class DispatchAsync : AfterEffectsManagerTest public async Task Should_handle_all_after_effects() { // Arrange - var context = new DispatchContext(new TestAction(), new Mock().Object); - var token = CancellationToken.None; + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); var afterEffect1 = new Mock>(); var afterEffect2 = new Mock>(); @@ -43,23 +43,23 @@ public async Task Should_handle_all_after_effects() }); // Act - await sut.DispatchAsync(context, token); + await sut.DispatchAsync(context); // Assert - afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, token), Times.Once); - afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, token), Times.Once); + afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); + afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); } [Fact] - public async Task Should_break_after_effects_when_StopAfterEffect_is_true() + public async Task Should_break_after_effects_when_Cancel() { // Arrange - var context = new DispatchContext(new TestAction(), new Mock().Object); - var token = CancellationToken.None; + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.StopAfterEffect = true); + afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) + .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); var afterEffect2 = new Mock>(); var sut = CreateAfterEffectsManager(services => { @@ -68,32 +68,33 @@ public async Task Should_break_after_effects_when_StopAfterEffect_is_true() }); // Act - await sut.DispatchAsync(context, token); + await Assert.ThrowsAsync(() + => sut.DispatchAsync(context)); // Assert - afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, token), Times.Once); - afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, token), Times.Never); + afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); + afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Never); } [Fact] public async Task Should_call_hooks_and_after_effects_methods_in_order() { // Arrange - var context = new DispatchContext(new TestAction(), new Mock().Object); - var token = CancellationToken.None; + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); var operationQueue = new Queue(); var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, token)) + afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("afterEffect1.HandleAfterEffectAsync")); var afterEffect2 = new Mock>(); - afterEffect2.Setup(x => x.HandleAfterEffectAsync(context, token)) + afterEffect2.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("afterEffect2.HandleAfterEffectAsync")); _afterEffectHooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), token)) + .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); _afterEffectHooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), token)) + .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); var sut = CreateAfterEffectsManager(services => { @@ -102,7 +103,7 @@ public async Task Should_call_hooks_and_after_effects_methods_in_order() }); // Act - await sut.DispatchAsync(context, token); + await sut.DispatchAsync(context); // Assert Assert.Collection(operationQueue, diff --git a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs b/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs index 16e84c0..ac3782a 100644 --- a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs +++ b/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs @@ -17,13 +17,15 @@ public class AfterEffectHooksCollectionTest private readonly Mock _after2Mock = new(); private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext dispatchContext = new DispatchContext(new TestAction(), new Mock().Object); + private readonly IDispatchContext _dispatchContext; private readonly CancellationToken _cancellationToken = CancellationToken.None; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly AfterEffectHooksCollection sut; public AfterEffectHooksCollectionTest() { + _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); sut = new( new[] { _before1Mock.Object, _before2Mock.Object }, new[] { _after1Mock.Object, _after2Mock.Object } @@ -35,13 +37,13 @@ public class BeforeHandlerAsync : AfterEffectHooksCollectionTest public async Task Should_call_all_hooks() { // Act - await sut.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); + await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); } } @@ -52,13 +54,13 @@ public class AfterHandlerAsync : AfterEffectHooksCollectionTest public async Task Should_call_all_hooks() { // Act - await sut.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); + await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); } } public record TestAction : IAction; diff --git a/test/StateR.Tests/DispatcherTest.cs b/test/StateR.Tests/DispatcherTest.cs index b09b735..e1829d6 100644 --- a/test/StateR.Tests/DispatcherTest.cs +++ b/test/StateR.Tests/DispatcherTest.cs @@ -31,7 +31,8 @@ public async Task Should_create_DispatchContext_using_dispatchContextFactory() { var action = new TestAction(); await sut.DispatchAsync(action, CancellationToken.None); - _dispatchContextFactory.Verify(x => x.Create(action, sut), Times.Once); + _dispatchContextFactory + .Verify(x => x.Create(action, sut, It.IsAny()), Times.Once); } [Fact] @@ -39,39 +40,37 @@ public async Task Should_send_the_same_DispatchContext_to_all_managers() { // Arrange var action = new TestAction(); - var context = new DispatchContext(action, new Mock().Object); - var token = CancellationToken.None; + var context = new DispatchContext(action, new Mock().Object, new CancellationTokenSource()); _dispatchContextFactory - .Setup(x => x.Create(action, sut)) + .Setup(x => x.Create(action, sut, It.IsAny())) .Returns(context); // Act - await sut.DispatchAsync(action, token); + await sut.DispatchAsync(action, CancellationToken.None); // Assert - _interceptorsManager.Verify(x => x.DispatchAsync(context, token), Times.Once); - _actionHandlersManager.Verify(x => x.DispatchAsync(context, token), Times.Once); - _afterEffectsManager.Verify(x => x.DispatchAsync(context, token), Times.Once); + _interceptorsManager.Verify(x => x.DispatchAsync(context), Times.Once); + _actionHandlersManager.Verify(x => x.DispatchAsync(context), Times.Once); + _afterEffectsManager.Verify(x => x.DispatchAsync(context), Times.Once); } [Fact] public async Task Should_call_managers_in_the_expected_order() { // Arrange var action = new TestAction(); - var token = CancellationToken.None; var operationQueue = new Queue(); _interceptorsManager - .Setup(x => x.DispatchAsync(It.IsAny< IDispatchContext>(), token)) + .Setup(x => x.DispatchAsync(It.IsAny< IDispatchContext>())) .Callback(() => operationQueue.Enqueue("Interceptors")); _actionHandlersManager - .Setup(x => x.DispatchAsync(It.IsAny>(), token)) + .Setup(x => x.DispatchAsync(It.IsAny>())) .Callback(() => operationQueue.Enqueue("Updaters")); _afterEffectsManager - .Setup(x => x.DispatchAsync(It.IsAny>(), token)) + .Setup(x => x.DispatchAsync(It.IsAny>())) .Callback(() => operationQueue.Enqueue("AfterEffects")); // Act - await sut.DispatchAsync(action, token); + await sut.DispatchAsync(action, CancellationToken.None); // Assert Assert.Collection(operationQueue, diff --git a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs b/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs index b2251b5..3065d48 100644 --- a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs +++ b/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs @@ -18,13 +18,15 @@ public class InterceptorsHooksCollectionTest private readonly Mock _after2Mock = new(); private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext dispatchContext = new DispatchContext(new TestAction(), new Mock().Object); + private readonly IDispatchContext _dispatchContext; private readonly CancellationToken _cancellationToken = CancellationToken.None; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly InterceptorsHooksCollection sut; public InterceptorsHooksCollectionTest() { + _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); sut = new( new[] { _before1Mock.Object, _before2Mock.Object }, new[] { _after1Mock.Object, _after2Mock.Object } @@ -37,13 +39,13 @@ public class BeforeHandlerAsync : InterceptorsHooksCollectionTest public async Task Should_call_all_hooks() { // Act - await sut.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); + await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); } } public class AfterHandlerAsync : InterceptorsHooksCollectionTest @@ -52,13 +54,13 @@ public class AfterHandlerAsync : InterceptorsHooksCollectionTest public async Task Should_call_all_hooks() { // Act - await sut.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); + await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); } } public record TestAction : IAction; diff --git a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs b/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs index 052fc69..78a682b 100644 --- a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs +++ b/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs @@ -30,6 +30,7 @@ public class DispatchAsync : InterceptorsManagerTest public async Task Should_dispatch_to_all_interceptors() { // Arrange + var cancellationTokenSource = new CancellationTokenSource(); var interceptor1 = new Mock>(); var interceptor2 = new Mock>(); var sut = CreateInterceptorsManager(services => @@ -37,36 +38,35 @@ public async Task Should_dispatch_to_all_interceptors() services.AddSingleton(interceptor1.Object); services.AddSingleton(interceptor2.Object); }); - var context = new DispatchContext(new TestAction(), new Mock().Object); - var token = CancellationToken.None; + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); // Act - await sut.DispatchAsync(context, token); + await sut.DispatchAsync(context); // Assert - interceptor1.Verify(x => x.InterceptAsync(context, token), Times.Once); - interceptor2.Verify(x => x.InterceptAsync(context, token), Times.Once); + interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); + interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); } [Fact] public async Task Should_call_middleware_and_interceptors_methods_in_order() { // Arrange - var context = new DispatchContext(new TestAction(), new Mock().Object); - var token = CancellationToken.None; + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); var operationQueue = new Queue(); var interceptor1 = new Mock>(); - interceptor1.Setup(x => x.InterceptAsync(context, token)) + interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("interceptor1.InterceptAsync")); var interceptor2 = new Mock>(); - interceptor2.Setup(x => x.InterceptAsync(context, token)) + interceptor2.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("interceptor2.InterceptAsync")); _hooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), token)) + .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); _hooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), token)) + .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); var sut = CreateInterceptorsManager(services => { @@ -75,7 +75,7 @@ public async Task Should_call_middleware_and_interceptors_methods_in_order() }); // Act - await sut.DispatchAsync(context, token); + await sut.DispatchAsync(context); // Assert Assert.Collection(operationQueue, @@ -90,15 +90,15 @@ public async Task Should_call_middleware_and_interceptors_methods_in_order() } [Fact] - public async Task Should_break_interception_when_StopInterception_is_true() + public async Task Should_break_interception_when_Cancel() { // Arrange - var context = new DispatchContext(new TestAction(), new Mock().Object); - var token = CancellationToken.None; + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); var interceptor1 = new Mock>(); - interceptor1.Setup(x => x.InterceptAsync(context, token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.StopInterception = true); + interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) + .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); var interceptor2 = new Mock>(); var sut = CreateInterceptorsManager(services => { @@ -107,13 +107,13 @@ public async Task Should_break_interception_when_StopInterception_is_true() }); // Act - await sut.DispatchAsync(context, token); + await Assert.ThrowsAsync(() + => sut.DispatchAsync(context)); // Assert - interceptor1.Verify(x => x.InterceptAsync(context, token), Times.Once); - interceptor2.Verify(x => x.InterceptAsync(context, token), Times.Never); + interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); + interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Never); } - } public record TestAction : IAction; diff --git a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs index e4ac34a..3503a63 100644 --- a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs +++ b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs @@ -19,13 +19,15 @@ public class UpdateHooksCollectionTest private readonly Mock> _stateMock = new(); private readonly Mock> _updater = new(); - private readonly IDispatchContext _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object); + private readonly IDispatchContext _dispatchContext; private readonly CancellationToken _cancellationToken = CancellationToken.None; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly UpdateHooksCollection sut; public UpdateHooksCollectionTest() { + _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); sut = new UpdateHooksCollection( new[] { _before1Mock.Object, _before2Mock.Object }, new[] { _after1Mock.Object, _after2Mock.Object } diff --git a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs new file mode 100644 index 0000000..dd58379 --- /dev/null +++ b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs @@ -0,0 +1,186 @@ +using Microsoft.Extensions.DependencyInjection; +using Moq; +using StateR.Updaters.Hooks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace StateR.Updaters +{ + public class UpdaterActionHandlerTest + { + private readonly TestState _state = new(); + private readonly Mock> _stateMock = new(); + private readonly List> _updaters = new(); + private readonly Mock _hooksMock = new(); + private readonly UpdaterActionHandler sut; + private readonly Queue _operationQueue = new(); + private readonly TestAction _action = new(); + private readonly DispatchContext _context; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + private readonly Mock> _updater1Mock = new(); + private readonly Mock> _updater2Mock = new(); + + public UpdaterActionHandlerTest() + { + _context = new(_action, new Mock().Object, _cancellationTokenSource); + + _stateMock.Setup(x => x.Current).Returns(_state); + _stateMock.Setup(x => x.Notify()) + .Callback(() => _operationQueue.Enqueue("state.Notify")); + + _updater1Mock + .Setup(x => x.Update(_action, _state)) + .Returns(_state) + .Callback(() => _operationQueue.Enqueue("updater1.Update")); + _updaters.Add(_updater1Mock.Object); + _updater2Mock + .Setup(x => x.Update(_action, _state)) + .Returns(_state) + .Callback(() => _operationQueue.Enqueue("updater2.Update")); + _updaters.Add(_updater2Mock.Object); + + sut = new UpdaterActionHandler( + _stateMock.Object, + _updaters, + _hooksMock.Object + ); + } + + public class HandleAsync : UpdaterActionHandlerTest + { + [Fact] + public async Task Should_call_updaters_then_notify() + { + // Act + await sut.HandleAsync(_context, CancellationToken.None); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("updater2.Update", op), + op => Assert.Equal("state.Notify", op) + ); + } + + [Fact] + public async Task Should_break_updates_when_Cancel_is_called_in_a_BeforeUpdateAsync_hook() + { + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => + { + _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2"); + _cancellationTokenSource.Cancel(); + }); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); + + // Act + await Assert.ThrowsAsync(() + => sut.HandleAsync(_context, _cancellationTokenSource.Token)); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater1", op), + + op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), + + op => Assert.Equal("state.Notify", op) + ); + } + + [Fact] + public async Task Should_break_updates_when_Cancel_is_called_in_an_AfterUpdateAsync_hook() + { + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => + { + _operationQueue.Enqueue("AfterUpdaterAsync:Updater1"); + _cancellationTokenSource.Cancel(); + }); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); + + // Act + await Assert.ThrowsAsync(() + => sut.HandleAsync(_context, _cancellationTokenSource.Token)); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater1", op), + + op => Assert.Equal("state.Notify", op) + ); + } + + [Fact] + public async Task Should_call_hooks_methods_in_order() + { + // Arrange + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); + + // Act + await sut.HandleAsync(_context, CancellationToken.None); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater1", op), + + op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), + op => Assert.Equal("updater2.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater2", op), + + op => Assert.Equal("state.Notify", op) + ); + } + } + + + public record TestAction : IAction; + public record TestState : StateBase; + } +} diff --git a/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs deleted file mode 100644 index e7c5748..0000000 --- a/test/StateR.Tests/Reducers/UpdaterHandlerTest.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Moq; -using StateR.Updaters.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace StateR.Updaters -{ - public class UpdaterHandlerTest - { - private readonly TestState _state = new(); - private readonly Mock> _stateMock = new(); - private readonly List> _updaters = new(); - private readonly Mock _hooksMock = new(); - private readonly UpdaterHandler sut; - private readonly Queue _operationQueue = new(); - private readonly TestAction _action = new(); - private readonly DispatchContext _context; - private readonly CancellationToken _token = CancellationToken.None; - - private readonly Mock> _updater1Mock = new(); - private readonly Mock> _updater2Mock = new(); - - public UpdaterHandlerTest() - { - _context = new(_action, new Mock().Object); - - _stateMock.Setup(x => x.Current).Returns(_state); - _stateMock.Setup(x => x.Notify()) - .Callback(() => _operationQueue.Enqueue("state.Notify")); - - _updater1Mock - .Setup(x => x.Update(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("updater1.Update")); - _updaters.Add(_updater1Mock.Object); - _updater2Mock - .Setup(x => x.Update(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("updater2.Update")); - _updaters.Add(_updater2Mock.Object); - - sut = new UpdaterHandler( - _stateMock.Object, - _updaters, - _hooksMock.Object - ); - } - - public class HandleAsync : UpdaterHandlerTest - { - [Fact] - public async Task Should_call_updaters_then_notify() - { - // Act - await sut.HandleAsync(_context, _token); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("updater2.Update", op), - op => Assert.Equal("state.Notify", op) - ); - } - } - - [Fact] - public async Task Should_call_middleware_and_middlewares_methods_in_order() - { - // Arrange - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await sut.HandleAsync(_context, _token); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), - op => Assert.Equal("updater2.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater2", op), - - op => Assert.Equal("state.Notify", op) - ); - } - - public record TestAction : IAction; - public record TestState : StateBase; - } -} From 9fed1b9f14881185a2703cee75858967fac17a2c Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 15:30:40 -0500 Subject: [PATCH 04/58] Update to .NET 6 --- .github/workflows/master.yml | 17 +++++++++-------- src/StateR.Blazor/StateR.Blazor.csproj | 9 +++++++-- .../StateR.Experiments.csproj | 7 ++++++- src/StateR/StateR.csproj | 11 ++++++++--- test/StateR.Tests/StateR.Tests.csproj | 10 +++++----- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index d36c1b9..1d51aef 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -12,9 +12,6 @@ on: - master env: - DOTNET_2_VERSION: '2.1.x' - DOTNET_3_VERSION: '3.1.x' - DOTNET_5_VERSION: '5.0.x' BUILD_CONFIGURATION: Release ACTIONS_ALLOW_UNSECURE_COMMANDS: true @@ -24,10 +21,12 @@ jobs: strategy: fail-fast: false matrix: - dotnet: ['5.0.x'] + dotnet: ['6.0.x'] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Setup .NET Core uses: actions/setup-dotnet@v1 @@ -46,17 +45,19 @@ jobs: strategy: fail-fast: true matrix: - dotnet: ['5.0.x'] + dotnet: ['6.0.x'] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet }} - - uses: aarnott/nbgv@v0.3 + - uses: dotnet/nbgv@master with: setAllVars: true diff --git a/src/StateR.Blazor/StateR.Blazor.csproj b/src/StateR.Blazor/StateR.Blazor.csproj index 4c9228f..1880ef8 100644 --- a/src/StateR.Blazor/StateR.Blazor.csproj +++ b/src/StateR.Blazor/StateR.Blazor.csproj @@ -1,7 +1,7 @@  - net5.0 + net6.0 Blazor components and utilities for use with the StateR library. stator,stater,redux,blazor,state,dotnet,dotnetcore,net,netcore,aspnetcore,asp.net,core,aspnet,asp,forevolve @@ -17,6 +17,11 @@ + + + + + @@ -25,7 +30,7 @@ - + diff --git a/src/StateR.Experiments/StateR.Experiments.csproj b/src/StateR.Experiments/StateR.Experiments.csproj index e2e54d8..75cd702 100644 --- a/src/StateR.Experiments/StateR.Experiments.csproj +++ b/src/StateR.Experiments/StateR.Experiments.csproj @@ -1,7 +1,7 @@  - net5.0 + net6.0 StateR @@ -9,4 +9,9 @@ + + + + + diff --git a/src/StateR/StateR.csproj b/src/StateR/StateR.csproj index ea991b4..1de64b6 100644 --- a/src/StateR/StateR.csproj +++ b/src/StateR/StateR.csproj @@ -1,15 +1,20 @@  - net5.0 + net6.0 StateR, pronounced stator, is an experimental C# 9.0-based, Redux-inspired, state management library for .NET. stator,stater,redux,blazor,state,dotnet,dotnetcore,net,netcore,aspnetcore,asp.net,core,aspnet,asp,forevolve - + - + + + + + + diff --git a/test/StateR.Tests/StateR.Tests.csproj b/test/StateR.Tests/StateR.Tests.csproj index 24a35b9..00f2c64 100644 --- a/test/StateR.Tests/StateR.Tests.csproj +++ b/test/StateR.Tests/StateR.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false @@ -9,15 +9,15 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From b0a2df9eab8fcf0a2965be439814fb9c15b2d923 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 15:37:42 -0500 Subject: [PATCH 05/58] Add csharp_style_namespace_declarations --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index 9d3f0a1..f2eec4f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,6 +14,9 @@ indent_style = space #Formatting - indentation +# Namespace +csharp_style_namespace_declarations = file_scoped:warning + #size of soft tabs (spaces) indent_size = 4 #remove any whitespace characters preceding newline characters From 293b1e425cf5fb64b4b01ed1d68823d2ed5844fd Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 15:38:03 -0500 Subject: [PATCH 06/58] Enable Nullable and ImplicitUsings options --- src/StateR.Blazor/StateR.Blazor.csproj | 6 ++++-- src/StateR.Experiments/StateR.Experiments.csproj | 6 ++++-- src/StateR/StateR.csproj | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/StateR.Blazor/StateR.Blazor.csproj b/src/StateR.Blazor/StateR.Blazor.csproj index 1880ef8..041ef5b 100644 --- a/src/StateR.Blazor/StateR.Blazor.csproj +++ b/src/StateR.Blazor/StateR.Blazor.csproj @@ -1,8 +1,10 @@  - net6.0 - Blazor components and utilities for use with the StateR library. + net6.0 + enable + enable + Blazor components and utilities for use with the StateR library. stator,stater,redux,blazor,state,dotnet,dotnetcore,net,netcore,aspnetcore,asp.net,core,aspnet,asp,forevolve diff --git a/src/StateR.Experiments/StateR.Experiments.csproj b/src/StateR.Experiments/StateR.Experiments.csproj index 75cd702..1e9d59b 100644 --- a/src/StateR.Experiments/StateR.Experiments.csproj +++ b/src/StateR.Experiments/StateR.Experiments.csproj @@ -1,8 +1,10 @@  - net6.0 - StateR + net6.0 + enable + enable + StateR diff --git a/src/StateR/StateR.csproj b/src/StateR/StateR.csproj index 1de64b6..3bb2357 100644 --- a/src/StateR/StateR.csproj +++ b/src/StateR/StateR.csproj @@ -1,8 +1,10 @@  - net6.0 - StateR, pronounced stator, is an experimental C# 9.0-based, Redux-inspired, state management library for .NET. + net6.0 + enable + enable + StateR, pronounced stator, is an experimental C# 9.0-based, Redux-inspired, state management library for .NET. stator,stater,redux,blazor,state,dotnet,dotnetcore,net,netcore,aspnetcore,asp.net,core,aspnet,asp,forevolve From 1690b8c62dae8a8cf543ecb77d47b0000a4935af Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 15:38:49 -0500 Subject: [PATCH 07/58] Update to file-scoped namespace --- src/StateR.Blazor/StatorComponent.cs | 43 ++- src/StateR.Blazor/StatorComponentBase.cs | 49 ++- .../StoreBasedStatorComponent.cs | 47 ++- .../AsyncLogic/AsyncError.cs | 52 +-- .../AsyncLogic/AsyncOperation.cs | 79 +++-- .../AsyncLogic/AsyncOperationStatus.cs | 15 +- .../AsyncLogic/AsyncState.cs | 9 +- .../AsyncLogic/StartupExtensions.cs | 25 +- .../AsyncLogic/StatusUpdated.cs | 9 +- .../ActionHandlers/ActionHandlersManager.cs | 37 +- .../Hooks/ActionHandlerHooksCollection.cs | 37 +- .../Hooks/IActionHandlerHooksCollection.cs | 11 +- .../ActionHandlers/Hooks/IAfterActionHook.cs | 9 +- .../ActionHandlers/Hooks/IBeforeActionHook.cs | 9 +- src/StateR/ActionHandlers/IActionHandler.cs | 13 +- .../ActionHandlers/IActionHandlersManager.cs | 7 +- .../AfterEffects/AfterEffectsManager.cs | 37 +- .../Hooks/AfterEffectHooksCollection.cs | 37 +- .../Hooks/IAfterAfterEffectHook.cs | 9 +- .../Hooks/IAfterEffectHooksCollection.cs | 11 +- .../Hooks/IBeforeAfterEffectHook.cs | 9 +- src/StateR/AfterEffects/IAfterEffects.cs | 13 +- .../AfterEffects/IAfterEffectsManager.cs | 7 +- src/StateR/DispatchContext.cs | 31 +- src/StateR/DispatchContextFactory.cs | 13 +- src/StateR/Dispatcher.cs | 49 ++- src/StateR/IAction.cs | 7 +- src/StateR/IDispatchContext.cs | 17 +- src/StateR/IDispatchContextFactory.cs | 9 +- src/StateR/IDispatchManager.cs | 11 +- src/StateR/IDispatcher.cs | 9 +- src/StateR/IInitialState.cs | 11 +- src/StateR/IState.cs | 13 +- src/StateR/IStatorBuilder.cs | 35 +- src/StateR/IStore.cs | 15 +- src/StateR/ISubscribable.cs | 13 +- .../Hooks/IAfterInterceptorHook.cs | 9 +- .../Hooks/IBeforeInterceptorHook.cs | 9 +- .../Hooks/IInterceptorsHooksCollection.cs | 11 +- .../Hooks/InterceptorsHooksCollection.cs | 37 +- src/StateR/Interceptors/IInterceptor.cs | 11 +- .../Interceptors/IInterceptorsManager.cs | 7 +- .../Interceptors/InterceptorsManager.cs | 37 +- src/StateR/Internal/InitialState.cs | 19 +- src/StateR/Internal/State.cs | 63 ++-- src/StateR/Internal/StatorBuilder.cs | 61 ++-- src/StateR/Internal/TypeExtensions.cs | 57 ++- .../Internal/TypeScannerBuilderExtensions.cs | 131 ++++--- src/StateR/StateBase.cs | 7 +- src/StateR/StatorStartupExtensions.cs | 231 +++++++------ src/StateR/Store.cs | 79 +++-- src/StateR/Updaters/Hooks/IAfterUpdateHook.cs | 13 +- .../Updaters/Hooks/IBeforeUpdateHook.cs | 13 +- .../Updaters/Hooks/IUpdateHooksCollection.cs | 19 +- .../Updaters/Hooks/UpdateHooksCollection.cs | 45 ++- src/StateR/Updaters/IUpdater.cs | 15 +- src/StateR/Updaters/UpdaterActionHandler.cs | 59 ++-- .../ActionHandlerManagerTest.cs | 199 ++++++----- .../Hooks/ActionHandlerHooksCollectionTest.cs | 89 +++-- .../AfterEffects/AfterEffectsManagerTest.cs | 199 ++++++----- .../Hooks/AfterEffectHooksCollectionTest.cs | 91 +++-- test/StateR.Tests/DispatcherTest.cs | 121 ++++--- .../Hooks/InterceptorsHooksCollectionTest.cs | 89 +++-- .../Interceptors/InterceptorsManagerTest.cs | 177 +++++----- test/StateR.Tests/Internal/StateTest.cs | 109 +++--- .../Internal/StatorBuilderTest.cs | 45 ++- .../Hooks/UpdateHooksCollectionTest.cs | 95 +++-- .../Reducers/UpdaterActionHandlerTest.cs | 325 +++++++++--------- .../StatorStartupExtensionsTest.cs | 9 +- 69 files changed, 1610 insertions(+), 1678 deletions(-) diff --git a/src/StateR.Blazor/StatorComponent.cs b/src/StateR.Blazor/StatorComponent.cs index ba2c875..b81fddb 100644 --- a/src/StateR.Blazor/StatorComponent.cs +++ b/src/StateR.Blazor/StatorComponent.cs @@ -3,37 +3,36 @@ using System.Linq; using System.Reflection; -namespace StateR.Blazor +namespace StateR.Blazor; + +public abstract class StatorComponent : StatorComponentBase { - public abstract class StatorComponent : StatorComponentBase + private readonly List _unsubscribeDelegates = new(); + + protected override void OnInitialized() { - private readonly List _unsubscribeDelegates = new(); + base.OnInitialized(); + var subscribableStateType = typeof(ISubscribable); + var properties = GetType() + .GetTypeInfo() + .DeclaredProperties + .Concat(GetType().GetProperties()); - protected override void OnInitialized() + foreach (var propertyInfo in properties) { - base.OnInitialized(); - var subscribableStateType = typeof(ISubscribable); - var properties = GetType() - .GetTypeInfo() - .DeclaredProperties - .Concat(GetType().GetProperties()); - - foreach (var propertyInfo in properties) + if (propertyInfo.GetValue(this) is ISubscribable subscribableState) { - if (propertyInfo.GetValue(this) is ISubscribable subscribableState) - { - subscribableState.Subscribe(StateHasChanged); - _unsubscribeDelegates.Add(() => subscribableState.Unsubscribe(StateHasChanged)); - } + subscribableState.Subscribe(StateHasChanged); + _unsubscribeDelegates.Add(() => subscribableState.Unsubscribe(StateHasChanged)); } } + } - protected override void FreeManagedResources() + protected override void FreeManagedResources() + { + foreach (var unsubscribe in _unsubscribeDelegates) { - foreach (var unsubscribe in _unsubscribeDelegates) - { - unsubscribe(); - } + unsubscribe(); } } } diff --git a/src/StateR.Blazor/StatorComponentBase.cs b/src/StateR.Blazor/StatorComponentBase.cs index 368657a..3e13841 100644 --- a/src/StateR.Blazor/StatorComponentBase.cs +++ b/src/StateR.Blazor/StatorComponentBase.cs @@ -3,39 +3,38 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Blazor +namespace StateR.Blazor; + +public abstract class StatorComponentBase : ComponentBase, IDisposable { - public abstract class StatorComponentBase : ComponentBase, IDisposable - { - private bool disposedValue; + private bool disposedValue; - [Inject] - public IDispatcher Dispatcher { get; set; } + [Inject] + public IDispatcher Dispatcher { get; set; } - protected virtual async Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) - where TAction : IAction - => await Dispatcher.DispatchAsync(action, cancellationToken); + protected virtual async Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) + where TAction : IAction + => await Dispatcher.DispatchAsync(action, cancellationToken); - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) { - if (!disposedValue) + if (disposing) { - if (disposing) - { - FreeManagedResources(); - } - FreeUnmanagedResources(); - disposedValue = true; + FreeManagedResources(); } + FreeUnmanagedResources(); + disposedValue = true; } + } - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - protected virtual void FreeManagedResources() { } - protected virtual void FreeUnmanagedResources() { } + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); } + + protected virtual void FreeManagedResources() { } + protected virtual void FreeUnmanagedResources() { } } diff --git a/src/StateR.Blazor/StoreBasedStatorComponent.cs b/src/StateR.Blazor/StoreBasedStatorComponent.cs index 9342a1a..23f0dac 100644 --- a/src/StateR.Blazor/StoreBasedStatorComponent.cs +++ b/src/StateR.Blazor/StoreBasedStatorComponent.cs @@ -7,38 +7,37 @@ using System.Runtime.CompilerServices; using System.Security.Cryptography.X509Certificates; -namespace StateR.Blazor +namespace StateR.Blazor; + +public abstract class StoreBasedStatorComponent : StatorComponentBase { - public abstract class StoreBasedStatorComponent : StatorComponentBase - { - private bool _subscribed = false; - private readonly List _unsubscribeDelegates = new List(); + private bool _subscribed = false; + private readonly List _unsubscribeDelegates = new List(); - [Inject] - public IStore Store { get; set; } + [Inject] + public IStore Store { get; set; } - protected virtual TState GetState() where TState : StateBase - { - Subscribe(); - return Store.GetState(); - } + protected virtual TState GetState() where TState : StateBase + { + Subscribe(); + return Store.GetState(); + } - protected virtual void Subscribe() where TState : StateBase + protected virtual void Subscribe() where TState : StateBase + { + if (!_subscribed) { - if (!_subscribed) - { - _subscribed = true; - Store.Subscribe(StateHasChanged); - _unsubscribeDelegates.Add(() => Store.Unsubscribe(StateHasChanged)); - } + _subscribed = true; + Store.Subscribe(StateHasChanged); + _unsubscribeDelegates.Add(() => Store.Unsubscribe(StateHasChanged)); } + } - protected override void FreeManagedResources() + protected override void FreeManagedResources() + { + foreach (var unsubscribe in _unsubscribeDelegates) { - foreach (var unsubscribe in _unsubscribeDelegates) - { - unsubscribe(); - } + unsubscribe(); } } } diff --git a/src/StateR.Experiments/AsyncLogic/AsyncError.cs b/src/StateR.Experiments/AsyncLogic/AsyncError.cs index 366c3c7..f18036a 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncError.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncError.cs @@ -1,37 +1,37 @@ using StateR.Updaters; using System; -namespace StateR.AsyncLogic +namespace StateR.AsyncLogic; + +public class AsyncError { - public class AsyncError + public record Occured(IAction Action, AsyncState InitialState, AsyncState ActualState, Exception Exception) : IAction; + public record State : StateBase { - public record Occured(IAction Action, AsyncState InitialState, AsyncState ActualState, Exception Exception) : IAction; - public record State : StateBase - { - public IAction Action { get; init; } - public AsyncState InitialState { get; init; } - public AsyncState ActualState { get; init; } - public Exception Exception { get; init; } + public IAction Action { get; init; } + public AsyncState InitialState { get; init; } + public AsyncState ActualState { get; init; } + public Exception Exception { get; init; } - public bool HasException() => Exception != null; - public bool HasActualState() => ActualState != null; - public bool HasInitialState() => InitialState != null; - public bool HasAction() => Action != null; - } + public bool HasException() => Exception != null; + public bool HasActualState() => ActualState != null; + public bool HasInitialState() => InitialState != null; + public bool HasAction() => Action != null; + } - public class InitialState : IInitialState - { - public State Value => new(); - } + public class InitialState : IInitialState + { + public State Value => new(); + } - public class Updaters : IUpdater + public class Updaters : IUpdater + { + public State Update(Occured action, State initialState) => initialState with { - public State Update(Occured action, State initialState) => initialState with { - Action = action.Action, - InitialState = action.InitialState, - ActualState = action.ActualState, - Exception = action.Exception - }; - } + Action = action.Action, + InitialState = action.InitialState, + ActualState = action.ActualState, + Exception = action.Exception + }; } } diff --git a/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs b/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs index 1b14a7c..a3c0727 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs @@ -8,59 +8,58 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.AsyncLogic +namespace StateR.AsyncLogic; + +public abstract class AsyncOperation : IAfterEffects, IUpdater, TState> + where TAction : IAction + where TState : AsyncState + where TSuccessAction : IAction { - public abstract class AsyncOperation : IAfterEffects, IUpdater, TState> - where TAction : IAction - where TState : AsyncState - where TSuccessAction : IAction + public AsyncOperation(IStore store) { - public AsyncOperation(IStore store) - { - Store = store ?? throw new ArgumentNullException(nameof(store)); - } + Store = store ?? throw new ArgumentNullException(nameof(store)); + } - protected IStore Store { get; } + protected IStore Store { get; } - public virtual TState Update(StatusUpdated action, TState state) - => state with { Status = action.status }; + public virtual TState Update(StatusUpdated action, TState state) + => state with { Status = action.status }; - public async Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken) + public async Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken) + { + var state = Store.GetState(); + try { - var state = Store.GetState(); - try + if (ShouldLoadData(context, state)) { - if (ShouldLoadData(context, state)) - { - await DispatchStatusUpdateAsync(AsyncOperationStatus.Loading, cancellationToken); - var completedAction = await LoadAsync(context.Action, state); - await DispatchStatusUpdateAsync(AsyncOperationStatus.Succeeded, cancellationToken); - await Store.DispatchAsync(completedAction, cancellationToken); - } - } - catch (Exception ex) - { - await HandleExceptionAsync(context, state, ex, cancellationToken); + await DispatchStatusUpdateAsync(AsyncOperationStatus.Loading, cancellationToken); + var completedAction = await LoadAsync(context.Action, state); + await DispatchStatusUpdateAsync(AsyncOperationStatus.Succeeded, cancellationToken); + await Store.DispatchAsync(completedAction, cancellationToken); } } - - protected virtual bool ShouldLoadData(IDispatchContext context, TState state) + catch (Exception ex) { - return state.Status == AsyncOperationStatus.Idle; + await HandleExceptionAsync(context, state, ex, cancellationToken); } + } - protected virtual async Task HandleExceptionAsync(IDispatchContext context, TState state, Exception ex, CancellationToken cancellationToken) - { - await DispatchStatusUpdateAsync(AsyncOperationStatus.Failed, cancellationToken); - var errorAction = new AsyncError.Occured(context.Action, state, Store.GetState(), ex); - await Store.DispatchAsync(errorAction, cancellationToken); - } + protected virtual bool ShouldLoadData(IDispatchContext context, TState state) + { + return state.Status == AsyncOperationStatus.Idle; + } - protected virtual async Task DispatchStatusUpdateAsync(AsyncOperationStatus status, CancellationToken cancellationToken) - { - await Store.DispatchAsync(new StatusUpdated(status), cancellationToken); - } + protected virtual async Task HandleExceptionAsync(IDispatchContext context, TState state, Exception ex, CancellationToken cancellationToken) + { + await DispatchStatusUpdateAsync(AsyncOperationStatus.Failed, cancellationToken); + var errorAction = new AsyncError.Occured(context.Action, state, Store.GetState(), ex); + await Store.DispatchAsync(errorAction, cancellationToken); + } - protected abstract Task LoadAsync(TAction action, TState initalState, CancellationToken cancellationToken = default); + protected virtual async Task DispatchStatusUpdateAsync(AsyncOperationStatus status, CancellationToken cancellationToken) + { + await Store.DispatchAsync(new StatusUpdated(status), cancellationToken); } + + protected abstract Task LoadAsync(TAction action, TState initalState, CancellationToken cancellationToken = default); } diff --git a/src/StateR.Experiments/AsyncLogic/AsyncOperationStatus.cs b/src/StateR.Experiments/AsyncLogic/AsyncOperationStatus.cs index 901c6a3..c74de9d 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncOperationStatus.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncOperationStatus.cs @@ -1,10 +1,9 @@ -namespace StateR.AsyncLogic +namespace StateR.AsyncLogic; + +public enum AsyncOperationStatus { - public enum AsyncOperationStatus - { - Idle, - Loading, - Succeeded, - Failed, - } + Idle, + Loading, + Succeeded, + Failed, } diff --git a/src/StateR.Experiments/AsyncLogic/AsyncState.cs b/src/StateR.Experiments/AsyncLogic/AsyncState.cs index d224b57..da8933f 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncState.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncState.cs @@ -1,7 +1,6 @@ -namespace StateR.AsyncLogic +namespace StateR.AsyncLogic; + +public abstract record AsyncState : StateBase { - public abstract record AsyncState : StateBase - { - public AsyncOperationStatus Status { get; init; } - } + public AsyncOperationStatus Status { get; init; } } diff --git a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs index fb7c805..e3c9f88 100644 --- a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs +++ b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs @@ -8,21 +8,20 @@ using System.Text; using System.Threading.Tasks; -namespace StateR.Experiments.AsyncLogic +namespace StateR.Experiments.AsyncLogic; + +public static class StartupExtensions { - public static class StartupExtensions + public static IStatorBuilder AddAsyncOperations(this IStatorBuilder builder) { - public static IStatorBuilder AddAsyncOperations(this IStatorBuilder builder) - { - //Async Operations - builder.AddTypes(new[] { typeof(StatusUpdated<>) }); + //Async Operations + builder.AddTypes(new[] { typeof(StatusUpdated<>) }); - // Async Operation's Errors - builder.Services.TryAddSingleton, UpdaterActionHandler>(); - builder.Services.TryAddSingleton, AsyncError.Updaters>(); - builder.Services.TryAddSingleton, AsyncError.InitialState>(); - builder.Services.TryAddSingleton, Internal.State>(); - return builder; - } + // Async Operation's Errors + builder.Services.TryAddSingleton, UpdaterActionHandler>(); + builder.Services.TryAddSingleton, AsyncError.Updaters>(); + builder.Services.TryAddSingleton, AsyncError.InitialState>(); + builder.Services.TryAddSingleton, Internal.State>(); + return builder; } } diff --git a/src/StateR.Experiments/AsyncLogic/StatusUpdated.cs b/src/StateR.Experiments/AsyncLogic/StatusUpdated.cs index 2bb5a84..7b78d5d 100644 --- a/src/StateR.Experiments/AsyncLogic/StatusUpdated.cs +++ b/src/StateR.Experiments/AsyncLogic/StatusUpdated.cs @@ -1,5 +1,4 @@ -namespace StateR.AsyncLogic -{ - public record StatusUpdated(AsyncOperationStatus status) : IAction - where TState : AsyncState; -} +namespace StateR.AsyncLogic; + +public record StatusUpdated(AsyncOperationStatus status) : IAction + where TState : AsyncState; diff --git a/src/StateR/ActionHandlers/ActionHandlersManager.cs b/src/StateR/ActionHandlers/ActionHandlersManager.cs index 1372cb9..2c7f7bb 100644 --- a/src/StateR/ActionHandlers/ActionHandlersManager.cs +++ b/src/StateR/ActionHandlers/ActionHandlersManager.cs @@ -6,30 +6,29 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.ActionHandlers +namespace StateR.ActionHandlers; + +public class ActionHandlersManager : IActionHandlersManager { - public class ActionHandlersManager : IActionHandlersManager - { - private readonly IActionHandlerHooksCollection _hooksCollection; - private readonly IServiceProvider _serviceProvider; + private readonly IActionHandlerHooksCollection _hooksCollection; + private readonly IServiceProvider _serviceProvider; - public ActionHandlersManager(IActionHandlerHooksCollection hooksCollection, IServiceProvider serviceProvider) - { - _hooksCollection = hooksCollection ?? throw new ArgumentNullException(nameof(hooksCollection)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } + public ActionHandlersManager(IActionHandlerHooksCollection hooksCollection, IServiceProvider serviceProvider) + { + _hooksCollection = hooksCollection ?? throw new ArgumentNullException(nameof(hooksCollection)); + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } - public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction + public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction + { + var updaterHandlers = _serviceProvider.GetServices>().ToList(); + foreach (var handler in updaterHandlers) { - var updaterHandlers = _serviceProvider.GetServices>().ToList(); - foreach (var handler in updaterHandlers) - { - dispatchContext.CancellationToken.ThrowIfCancellationRequested(); + dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - await _hooksCollection.BeforeHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); - await handler.HandleAsync(dispatchContext, dispatchContext.CancellationToken); - await _hooksCollection.AfterHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); - } + await _hooksCollection.BeforeHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); + await handler.HandleAsync(dispatchContext, dispatchContext.CancellationToken); + await _hooksCollection.AfterHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); } } } diff --git a/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs b/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs index 877ea48..67d45d3 100644 --- a/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs +++ b/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs @@ -3,32 +3,31 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.ActionHandlers.Hooks +namespace StateR.ActionHandlers.Hooks; + +public class ActionHandlerHooksCollection : IActionHandlerHooksCollection { - public class ActionHandlerHooksCollection : IActionHandlerHooksCollection + private readonly IEnumerable _beforeActionHooks; + private readonly IEnumerable _afterActionHooks; + public ActionHandlerHooksCollection(IEnumerable beforeActionHooks, IEnumerable afterActionHooks) { - private readonly IEnumerable _beforeActionHooks; - private readonly IEnumerable _afterActionHooks; - public ActionHandlerHooksCollection(IEnumerable beforeActionHooks, IEnumerable afterActionHooks) - { - _beforeActionHooks = beforeActionHooks ?? throw new ArgumentNullException(nameof(beforeActionHooks)); - _afterActionHooks = afterActionHooks ?? throw new ArgumentNullException(nameof(afterActionHooks)); - } + _beforeActionHooks = beforeActionHooks ?? throw new ArgumentNullException(nameof(beforeActionHooks)); + _afterActionHooks = afterActionHooks ?? throw new ArgumentNullException(nameof(afterActionHooks)); + } - public async Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction + public async Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction + { + foreach (var hook in _beforeActionHooks) { - foreach (var hook in _beforeActionHooks) - { - await hook.BeforeHandlerAsync(context, actionHandler, cancellationToken); - } + await hook.BeforeHandlerAsync(context, actionHandler, cancellationToken); } + } - public async Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction + public async Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction + { + foreach (var hook in _afterActionHooks) { - foreach (var hook in _afterActionHooks) - { - await hook.AfterHandlerAsync(context, actionHandler, cancellationToken); - } + await hook.AfterHandlerAsync(context, actionHandler, cancellationToken); } } } diff --git a/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs b/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs index aa987b6..e94ce55 100644 --- a/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs +++ b/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs @@ -1,11 +1,10 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.ActionHandlers.Hooks +namespace StateR.ActionHandlers.Hooks; + +public interface IActionHandlerHooksCollection { - public interface IActionHandlerHooksCollection - { - Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; - Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; - } + Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; + Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs b/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs index 3bfcb67..4bf03b4 100644 --- a/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs +++ b/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs @@ -1,10 +1,9 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.ActionHandlers.Hooks +namespace StateR.ActionHandlers.Hooks; + +public interface IAfterActionHook { - public interface IAfterActionHook - { - Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; - } + Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs b/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs index 70fdbb2..91efef0 100644 --- a/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs +++ b/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs @@ -1,10 +1,9 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.ActionHandlers.Hooks +namespace StateR.ActionHandlers.Hooks; + +public interface IBeforeActionHook { - public interface IBeforeActionHook - { - Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; - } + Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/ActionHandlers/IActionHandler.cs b/src/StateR/ActionHandlers/IActionHandler.cs index e2d0e51..73dbb02 100644 --- a/src/StateR/ActionHandlers/IActionHandler.cs +++ b/src/StateR/ActionHandlers/IActionHandler.cs @@ -1,11 +1,10 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.ActionHandlers +namespace StateR.ActionHandlers; + +public interface IActionHandler + where TAction : IAction { - public interface IActionHandler - where TAction : IAction - { - Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken); - } -} \ No newline at end of file + Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken); +} diff --git a/src/StateR/ActionHandlers/IActionHandlersManager.cs b/src/StateR/ActionHandlers/IActionHandlersManager.cs index ade397f..aa0a671 100644 --- a/src/StateR/ActionHandlers/IActionHandlersManager.cs +++ b/src/StateR/ActionHandlers/IActionHandlersManager.cs @@ -1,4 +1,3 @@ -namespace StateR.ActionHandlers -{ - public interface IActionHandlersManager : IDispatchManager { } -} +namespace StateR.ActionHandlers; + +public interface IActionHandlersManager : IDispatchManager { } diff --git a/src/StateR/AfterEffects/AfterEffectsManager.cs b/src/StateR/AfterEffects/AfterEffectsManager.cs index 77bfc5b..abfa3e6 100644 --- a/src/StateR/AfterEffects/AfterEffectsManager.cs +++ b/src/StateR/AfterEffects/AfterEffectsManager.cs @@ -6,30 +6,29 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.AfterEffects +namespace StateR.AfterEffects; + +public class AfterEffectsManager : IAfterEffectsManager { - public class AfterEffectsManager : IAfterEffectsManager - { - private readonly IAfterEffectHooksCollection _hooks; - private readonly IServiceProvider _serviceProvider; + private readonly IAfterEffectHooksCollection _hooks; + private readonly IServiceProvider _serviceProvider; - public AfterEffectsManager(IAfterEffectHooksCollection hooks, IServiceProvider serviceProvider) - { - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } + public AfterEffectsManager(IAfterEffectHooksCollection hooks, IServiceProvider serviceProvider) + { + _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } - public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction + public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction + { + var afterEffects = _serviceProvider.GetServices>().ToList(); + foreach (var afterEffect in afterEffects) { - var afterEffects = _serviceProvider.GetServices>().ToList(); - foreach (var afterEffect in afterEffects) - { - dispatchContext.CancellationToken.ThrowIfCancellationRequested(); + dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - await _hooks.BeforeHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); - await afterEffect.HandleAfterEffectAsync(dispatchContext, dispatchContext.CancellationToken); - await _hooks.AfterHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); - } + await _hooks.BeforeHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); + await afterEffect.HandleAfterEffectAsync(dispatchContext, dispatchContext.CancellationToken); + await _hooks.AfterHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); } } } diff --git a/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs b/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs index 7f3625a..908dc9b 100644 --- a/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs +++ b/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs @@ -3,32 +3,31 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.AfterEffects.Hooks +namespace StateR.AfterEffects.Hooks; + +public class AfterEffectHooksCollection : IAfterEffectHooksCollection { - public class AfterEffectHooksCollection : IAfterEffectHooksCollection + private readonly IEnumerable _beforeAfterEffectHooks; + private readonly IEnumerable _afterAfterEffectHooks; + public AfterEffectHooksCollection(IEnumerable beforeAfterEffectHooks, IEnumerable afterAfterEffectHooks) { - private readonly IEnumerable _beforeAfterEffectHooks; - private readonly IEnumerable _afterAfterEffectHooks; - public AfterEffectHooksCollection(IEnumerable beforeAfterEffectHooks, IEnumerable afterAfterEffectHooks) - { - _beforeAfterEffectHooks = beforeAfterEffectHooks ?? throw new ArgumentNullException(nameof(beforeAfterEffectHooks)); - _afterAfterEffectHooks = afterAfterEffectHooks ?? throw new ArgumentNullException(nameof(afterAfterEffectHooks)); - } + _beforeAfterEffectHooks = beforeAfterEffectHooks ?? throw new ArgumentNullException(nameof(beforeAfterEffectHooks)); + _afterAfterEffectHooks = afterAfterEffectHooks ?? throw new ArgumentNullException(nameof(afterAfterEffectHooks)); + } - public async Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction + public async Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction + { + foreach (var hook in _beforeAfterEffectHooks) { - foreach (var hook in _beforeAfterEffectHooks) - { - await hook.BeforeHandlerAsync(context, afterEffect, cancellationToken); - } + await hook.BeforeHandlerAsync(context, afterEffect, cancellationToken); } + } - public async Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction + public async Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction + { + foreach (var hook in _afterAfterEffectHooks) { - foreach (var hook in _afterAfterEffectHooks) - { - await hook.AfterHandlerAsync(context, afterEffect, cancellationToken); - } + await hook.AfterHandlerAsync(context, afterEffect, cancellationToken); } } } diff --git a/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs b/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs index a78bf34..e4b1dd8 100644 --- a/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs +++ b/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs @@ -1,11 +1,10 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.AfterEffects.Hooks +namespace StateR.AfterEffects.Hooks; + +public interface IAfterAfterEffectHook { - public interface IAfterAfterEffectHook - { - Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; - } + Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs b/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs index c390d0b..a8947f8 100644 --- a/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs +++ b/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs @@ -1,12 +1,11 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.AfterEffects.Hooks +namespace StateR.AfterEffects.Hooks; + +public interface IAfterEffectHooksCollection { - public interface IAfterEffectHooksCollection - { - Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; - Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; - } + Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; + Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs b/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs index d0dc3f3..7c2fdce 100644 --- a/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs +++ b/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs @@ -1,11 +1,10 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.AfterEffects.Hooks +namespace StateR.AfterEffects.Hooks; + +public interface IBeforeAfterEffectHook { - public interface IBeforeAfterEffectHook - { - Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; - } + Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/AfterEffects/IAfterEffects.cs b/src/StateR/AfterEffects/IAfterEffects.cs index 7cfd681..2045900 100644 --- a/src/StateR/AfterEffects/IAfterEffects.cs +++ b/src/StateR/AfterEffects/IAfterEffects.cs @@ -1,11 +1,10 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.AfterEffects +namespace StateR.AfterEffects; + +public interface IAfterEffects + where TAction : IAction { - public interface IAfterEffects - where TAction : IAction - { - Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken); - } -} \ No newline at end of file + Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken); +} diff --git a/src/StateR/AfterEffects/IAfterEffectsManager.cs b/src/StateR/AfterEffects/IAfterEffectsManager.cs index 48adf69..20aa657 100644 --- a/src/StateR/AfterEffects/IAfterEffectsManager.cs +++ b/src/StateR/AfterEffects/IAfterEffectsManager.cs @@ -1,4 +1,3 @@ -namespace StateR.AfterEffects -{ - public interface IAfterEffectsManager : IDispatchManager { } -} +namespace StateR.AfterEffects; + +public interface IAfterEffectsManager : IDispatchManager { } diff --git a/src/StateR/DispatchContext.cs b/src/StateR/DispatchContext.cs index 2b49c48..9952c3a 100644 --- a/src/StateR/DispatchContext.cs +++ b/src/StateR/DispatchContext.cs @@ -1,24 +1,23 @@ using System; using System.Threading; -namespace StateR +namespace StateR; + +public class DispatchContext : IDispatchContext + where TAction : IAction { - public class DispatchContext : IDispatchContext - where TAction : IAction + private readonly CancellationTokenSource _cancellationTokenSource; + public DispatchContext(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) { - private readonly CancellationTokenSource _cancellationTokenSource; - public DispatchContext(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) - { - Action = action; - Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); - _cancellationTokenSource = cancellationTokenSource ?? throw new ArgumentNullException(nameof(cancellationTokenSource)); - } + Action = action; + Dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + _cancellationTokenSource = cancellationTokenSource ?? throw new ArgumentNullException(nameof(cancellationTokenSource)); + } - public IDispatcher Dispatcher { get; } - public TAction Action { get; } - public CancellationToken CancellationToken => _cancellationTokenSource.Token; + public IDispatcher Dispatcher { get; } + public TAction Action { get; } + public CancellationToken CancellationToken => _cancellationTokenSource.Token; - public void Cancel() - => _cancellationTokenSource.Cancel(true); - } + public void Cancel() + => _cancellationTokenSource.Cancel(true); } diff --git a/src/StateR/DispatchContextFactory.cs b/src/StateR/DispatchContextFactory.cs index 2716995..d283d63 100644 --- a/src/StateR/DispatchContextFactory.cs +++ b/src/StateR/DispatchContextFactory.cs @@ -1,11 +1,10 @@ using System.Threading; -namespace StateR +namespace StateR; + +public class DispatchContextFactory : IDispatchContextFactory { - public class DispatchContextFactory : IDispatchContextFactory - { - public IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) - where TAction : IAction - => new DispatchContext(action, dispatcher, cancellationTokenSource); - } + public IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) + where TAction : IAction + => new DispatchContext(action, dispatcher, cancellationTokenSource); } diff --git a/src/StateR/Dispatcher.cs b/src/StateR/Dispatcher.cs index e69fe3c..f645d84 100644 --- a/src/StateR/Dispatcher.cs +++ b/src/StateR/Dispatcher.cs @@ -6,33 +6,32 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR +namespace StateR; + +public class Dispatcher : IDispatcher { - public class Dispatcher : IDispatcher - { - private readonly IInterceptorsManager _interceptorsManager; - private readonly IActionHandlersManager _actionHandlersManager; - private readonly IAfterEffectsManager _afterEffectsManager; - private readonly IDispatchContextFactory _dispatchContextFactory; + private readonly IInterceptorsManager _interceptorsManager; + private readonly IActionHandlersManager _actionHandlersManager; + private readonly IAfterEffectsManager _afterEffectsManager; + private readonly IDispatchContextFactory _dispatchContextFactory; - public Dispatcher(IDispatchContextFactory dispatchContextFactory, IInterceptorsManager interceptorsManager, IActionHandlersManager actionHandlersManager, IAfterEffectsManager afterEffectsManager) - { - _dispatchContextFactory = dispatchContextFactory ?? throw new ArgumentNullException(nameof(dispatchContextFactory)); - _interceptorsManager = interceptorsManager ?? throw new ArgumentNullException(nameof(interceptorsManager)); - _actionHandlersManager = actionHandlersManager ?? throw new ArgumentNullException(nameof(actionHandlersManager)); - _afterEffectsManager = afterEffectsManager ?? throw new ArgumentNullException(nameof(afterEffectsManager)); - } + public Dispatcher(IDispatchContextFactory dispatchContextFactory, IInterceptorsManager interceptorsManager, IActionHandlersManager actionHandlersManager, IAfterEffectsManager afterEffectsManager) + { + _dispatchContextFactory = dispatchContextFactory ?? throw new ArgumentNullException(nameof(dispatchContextFactory)); + _interceptorsManager = interceptorsManager ?? throw new ArgumentNullException(nameof(interceptorsManager)); + _actionHandlersManager = actionHandlersManager ?? throw new ArgumentNullException(nameof(actionHandlersManager)); + _afterEffectsManager = afterEffectsManager ?? throw new ArgumentNullException(nameof(afterEffectsManager)); + } - public async Task DispatchAsync(TAction action, CancellationToken cancellationToken) where TAction : IAction - { - using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var dispatchContext = _dispatchContextFactory.Create(action, this, cancellationTokenSource); - // - // TODO: design how to handle OperationCanceledException - // - await _interceptorsManager.DispatchAsync(dispatchContext); - await _actionHandlersManager.DispatchAsync(dispatchContext); - await _afterEffectsManager.DispatchAsync(dispatchContext); - } + public async Task DispatchAsync(TAction action, CancellationToken cancellationToken) where TAction : IAction + { + using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var dispatchContext = _dispatchContextFactory.Create(action, this, cancellationTokenSource); + // + // TODO: design how to handle OperationCanceledException + // + await _interceptorsManager.DispatchAsync(dispatchContext); + await _actionHandlersManager.DispatchAsync(dispatchContext); + await _afterEffectsManager.DispatchAsync(dispatchContext); } } diff --git a/src/StateR/IAction.cs b/src/StateR/IAction.cs index 81873b1..648e6b5 100644 --- a/src/StateR/IAction.cs +++ b/src/StateR/IAction.cs @@ -1,4 +1,3 @@ -namespace StateR -{ - public interface IAction { } -} \ No newline at end of file +namespace StateR; + +public interface IAction { } diff --git a/src/StateR/IDispatchContext.cs b/src/StateR/IDispatchContext.cs index 694e38d..bdde9ac 100644 --- a/src/StateR/IDispatchContext.cs +++ b/src/StateR/IDispatchContext.cs @@ -1,14 +1,13 @@ using System.Threading; -namespace StateR +namespace StateR; + +public interface IDispatchContext + where TAction : IAction { - public interface IDispatchContext - where TAction : IAction - { - IDispatcher Dispatcher { get; } - TAction Action { get; } + IDispatcher Dispatcher { get; } + TAction Action { get; } - CancellationToken CancellationToken { get; } - void Cancel(); - } + CancellationToken CancellationToken { get; } + void Cancel(); } diff --git a/src/StateR/IDispatchContextFactory.cs b/src/StateR/IDispatchContextFactory.cs index 40648eb..047c772 100644 --- a/src/StateR/IDispatchContextFactory.cs +++ b/src/StateR/IDispatchContextFactory.cs @@ -1,9 +1,8 @@ using System.Threading; -namespace StateR +namespace StateR; + +public interface IDispatchContextFactory { - public interface IDispatchContextFactory - { - IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) where TAction : IAction; - } + IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) where TAction : IAction; } diff --git a/src/StateR/IDispatchManager.cs b/src/StateR/IDispatchManager.cs index dc07f9b..47d7f6e 100644 --- a/src/StateR/IDispatchManager.cs +++ b/src/StateR/IDispatchManager.cs @@ -1,11 +1,10 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR +namespace StateR; + +public interface IDispatchManager { - public interface IDispatchManager - { - Task DispatchAsync(IDispatchContext dispatchContext) - where TAction : IAction; - } + Task DispatchAsync(IDispatchContext dispatchContext) + where TAction : IAction; } diff --git a/src/StateR/IDispatcher.cs b/src/StateR/IDispatcher.cs index 999302a..8943fb5 100644 --- a/src/StateR/IDispatcher.cs +++ b/src/StateR/IDispatcher.cs @@ -3,10 +3,9 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR +namespace StateR; + +public interface IDispatcher { - public interface IDispatcher - { - Task DispatchAsync(TAction action, CancellationToken cancellationToken) where TAction : IAction; - } + Task DispatchAsync(TAction action, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/IInitialState.cs b/src/StateR/IInitialState.cs index 6dd487d..ae3cc54 100644 --- a/src/StateR/IInitialState.cs +++ b/src/StateR/IInitialState.cs @@ -1,8 +1,7 @@ -namespace StateR +namespace StateR; + +public interface IInitialState + where TState : StateBase { - public interface IInitialState - where TState : StateBase - { - TState Value { get; } - } + TState Value { get; } } diff --git a/src/StateR/IState.cs b/src/StateR/IState.cs index e7ea0cf..bac5b69 100644 --- a/src/StateR/IState.cs +++ b/src/StateR/IState.cs @@ -1,11 +1,10 @@ using System; -namespace StateR +namespace StateR; + +public interface IState : ISubscribable + where TState : StateBase { - public interface IState : ISubscribable - where TState : StateBase - { - TState Current { get; } - void Set(TState state); - } + TState Current { get; } + void Set(TState state); } diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index eaa43bf..36037b9 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -3,23 +3,22 @@ using System.Collections.Generic; using System.Collections.ObjectModel; -namespace StateR +namespace StateR; + +public interface IStatorBuilder { - public interface IStatorBuilder - { - IServiceCollection Services { get; } - List Actions { get; } - List States { get; } - //List Interceptors { get; } - List ActionHandlers { get; } - //List AfterEffects { get; } - List Updaters { get; } - List All { get; } + IServiceCollection Services { get; } + List Actions { get; } + List States { get; } + //List Interceptors { get; } + List ActionHandlers { get; } + //List AfterEffects { get; } + List Updaters { get; } + List All { get; } - IStatorBuilder AddTypes(IEnumerable types); - IStatorBuilder AddStates(IEnumerable states); - IStatorBuilder AddActions(IEnumerable states); - IStatorBuilder AddUpdaters(IEnumerable states); - IStatorBuilder AddActionHandlers(IEnumerable types); - } -} \ No newline at end of file + IStatorBuilder AddTypes(IEnumerable types); + IStatorBuilder AddStates(IEnumerable states); + IStatorBuilder AddActions(IEnumerable states); + IStatorBuilder AddUpdaters(IEnumerable states); + IStatorBuilder AddActionHandlers(IEnumerable types); +} diff --git a/src/StateR/IStore.cs b/src/StateR/IStore.cs index c1e5883..812262e 100644 --- a/src/StateR/IStore.cs +++ b/src/StateR/IStore.cs @@ -1,12 +1,11 @@ using System; -namespace StateR +namespace StateR; + +public interface IStore : IDispatcher { - public interface IStore : IDispatcher - { - TState GetState() where TState : StateBase; - void SetState(Func stateTransform) where TState : StateBase; - void Subscribe(Action stateHasChangedDelegate) where TState : StateBase; - void Unsubscribe(Action stateHasChangedDelegate) where TState : StateBase; - } + TState GetState() where TState : StateBase; + void SetState(Func stateTransform) where TState : StateBase; + void Subscribe(Action stateHasChangedDelegate) where TState : StateBase; + void Unsubscribe(Action stateHasChangedDelegate) where TState : StateBase; } diff --git a/src/StateR/ISubscribable.cs b/src/StateR/ISubscribable.cs index 439bd36..d43a147 100644 --- a/src/StateR/ISubscribable.cs +++ b/src/StateR/ISubscribable.cs @@ -1,11 +1,10 @@ using System; -namespace StateR +namespace StateR; + +public interface ISubscribable { - public interface ISubscribable - { - void Subscribe(Action stateHasChangedDelegate); - void Unsubscribe(Action stateHasChangedDelegate); - void Notify(); - } + void Subscribe(Action stateHasChangedDelegate); + void Unsubscribe(Action stateHasChangedDelegate); + void Notify(); } diff --git a/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs b/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs index 0a80d30..ca21ca8 100644 --- a/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs +++ b/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs @@ -1,10 +1,9 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Interceptors.Hooks +namespace StateR.Interceptors.Hooks; + +public interface IAfterInterceptorHook { - public interface IAfterInterceptorHook - { - Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; - } + Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs b/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs index ed7ee96..2e9f907 100644 --- a/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs +++ b/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs @@ -1,10 +1,9 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Interceptors.Hooks +namespace StateR.Interceptors.Hooks; + +public interface IBeforeInterceptorHook { - public interface IBeforeInterceptorHook - { - Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; - } + Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs b/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs index 62eb52a..d25b72d 100644 --- a/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs +++ b/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs @@ -1,11 +1,10 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Interceptors.Hooks +namespace StateR.Interceptors.Hooks; + +public interface IInterceptorsHooksCollection { - public interface IInterceptorsHooksCollection - { - Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; - Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; - } + Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; + Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; } diff --git a/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs b/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs index 760face..f2b205f 100644 --- a/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs +++ b/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs @@ -3,32 +3,31 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Interceptors.Hooks +namespace StateR.Interceptors.Hooks; + +public class InterceptorsHooksCollection : IInterceptorsHooksCollection { - public class InterceptorsHooksCollection : IInterceptorsHooksCollection + private readonly IEnumerable _beforeInterceptorHooks; + private readonly IEnumerable _afterInterceptorHooks; + public InterceptorsHooksCollection(IEnumerable beforeInterceptorHooks, IEnumerable afterInterceptorHooks) { - private readonly IEnumerable _beforeInterceptorHooks; - private readonly IEnumerable _afterInterceptorHooks; - public InterceptorsHooksCollection(IEnumerable beforeInterceptorHooks, IEnumerable afterInterceptorHooks) - { - _beforeInterceptorHooks = beforeInterceptorHooks ?? throw new ArgumentNullException(nameof(beforeInterceptorHooks)); - _afterInterceptorHooks = afterInterceptorHooks ?? throw new ArgumentNullException(nameof(afterInterceptorHooks)); - } + _beforeInterceptorHooks = beforeInterceptorHooks ?? throw new ArgumentNullException(nameof(beforeInterceptorHooks)); + _afterInterceptorHooks = afterInterceptorHooks ?? throw new ArgumentNullException(nameof(afterInterceptorHooks)); + } - public async Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction + public async Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction + { + foreach (var hook in _beforeInterceptorHooks) { - foreach (var hook in _beforeInterceptorHooks) - { - await hook.BeforeHandlerAsync(context, interceptor, cancellationToken); - } + await hook.BeforeHandlerAsync(context, interceptor, cancellationToken); } + } - public async Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction + public async Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction + { + foreach (var hook in _afterInterceptorHooks) { - foreach (var hook in _afterInterceptorHooks) - { - await hook.AfterHandlerAsync(context, interceptor, cancellationToken); - } + await hook.AfterHandlerAsync(context, interceptor, cancellationToken); } } } diff --git a/src/StateR/Interceptors/IInterceptor.cs b/src/StateR/Interceptors/IInterceptor.cs index 69a074f..68a0d50 100644 --- a/src/StateR/Interceptors/IInterceptor.cs +++ b/src/StateR/Interceptors/IInterceptor.cs @@ -1,11 +1,10 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Interceptors +namespace StateR.Interceptors; + +public interface IInterceptor + where TAction : IAction { - public interface IInterceptor - where TAction : IAction - { - Task InterceptAsync(IDispatchContext context, CancellationToken cancellationToken); - } + Task InterceptAsync(IDispatchContext context, CancellationToken cancellationToken); } diff --git a/src/StateR/Interceptors/IInterceptorsManager.cs b/src/StateR/Interceptors/IInterceptorsManager.cs index afe9534..23f6853 100644 --- a/src/StateR/Interceptors/IInterceptorsManager.cs +++ b/src/StateR/Interceptors/IInterceptorsManager.cs @@ -1,4 +1,3 @@ -namespace StateR.Interceptors -{ - public interface IInterceptorsManager : IDispatchManager { } -} +namespace StateR.Interceptors; + +public interface IInterceptorsManager : IDispatchManager { } diff --git a/src/StateR/Interceptors/InterceptorsManager.cs b/src/StateR/Interceptors/InterceptorsManager.cs index a974209..5810e70 100644 --- a/src/StateR/Interceptors/InterceptorsManager.cs +++ b/src/StateR/Interceptors/InterceptorsManager.cs @@ -6,30 +6,29 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Interceptors +namespace StateR.Interceptors; + +public class InterceptorsManager : IInterceptorsManager { - public class InterceptorsManager : IInterceptorsManager + private readonly IInterceptorsHooksCollection _hooks; + private readonly IServiceProvider _serviceProvider; + + public InterceptorsManager(IInterceptorsHooksCollection hooks, IServiceProvider serviceProvider) { - private readonly IInterceptorsHooksCollection _hooks; - private readonly IServiceProvider _serviceProvider; + _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } - public InterceptorsManager(IInterceptorsHooksCollection hooks, IServiceProvider serviceProvider) + public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction + { + var interceptors = _serviceProvider.GetServices>().ToList(); + foreach (var interceptor in interceptors) { - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } + dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction - { - var interceptors = _serviceProvider.GetServices>().ToList(); - foreach (var interceptor in interceptors) - { - dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - - await _hooks.BeforeHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); - await interceptor.InterceptAsync(dispatchContext, dispatchContext.CancellationToken); - await _hooks.AfterHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); - } + await _hooks.BeforeHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); + await interceptor.InterceptAsync(dispatchContext, dispatchContext.CancellationToken); + await _hooks.AfterHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); } } } diff --git a/src/StateR/Internal/InitialState.cs b/src/StateR/Internal/InitialState.cs index 7d270f6..4e80649 100644 --- a/src/StateR/Internal/InitialState.cs +++ b/src/StateR/Internal/InitialState.cs @@ -1,15 +1,14 @@ using System; -namespace StateR.Internal +namespace StateR.Internal; + +public class InitialState : IInitialState + where TState : StateBase { - public class InitialState : IInitialState - where TState : StateBase + public InitialState(TState value) { - public InitialState(TState value) - { - Value = value ?? throw new ArgumentNullException(nameof(value)); - } - - public TState Value { get; } + Value = value ?? throw new ArgumentNullException(nameof(value)); } -} \ No newline at end of file + + public TState Value { get; } +} diff --git a/src/StateR/Internal/State.cs b/src/StateR/Internal/State.cs index 030330b..1c5b9f7 100644 --- a/src/StateR/Internal/State.cs +++ b/src/StateR/Internal/State.cs @@ -2,53 +2,52 @@ using System.Collections.Generic; using System.Linq; -namespace StateR.Internal +namespace StateR.Internal; + +public class State : IState + where TState : StateBase { - public class State : IState - where TState : StateBase - { - private readonly List _subscribers = new(); - private readonly object _subscriberLock = new(); + private readonly List _subscribers = new(); + private readonly object _subscriberLock = new(); - public State(IInitialState initial) - => Set(initial.Value); + public State(IInitialState initial) + => Set(initial.Value); - public TState Current { get; private set; } + public TState Current { get; private set; } - public void Set(TState state) + public void Set(TState state) + { + if (Current == state) { - if (Current == state) - { - return; - } - Current = state; + return; } + Current = state; + } - public void Subscribe(Action stateHasChangedDelegate) + public void Subscribe(Action stateHasChangedDelegate) + { + lock (_subscriberLock) { - lock (_subscriberLock) - { - _subscribers.Add(stateHasChangedDelegate); - } + _subscribers.Add(stateHasChangedDelegate); } + } - public void Unsubscribe(Action stateHasChangedDelegate) + public void Unsubscribe(Action stateHasChangedDelegate) + { + lock (_subscriberLock) { - lock (_subscriberLock) - { - _subscribers.Remove(stateHasChangedDelegate); - } + _subscribers.Remove(stateHasChangedDelegate); } + } - public void Notify() + public void Notify() + { + lock (_subscriberLock) { - lock (_subscriberLock) + for (var i = 0; i < _subscribers.Count; i++) { - for (var i = 0; i < _subscribers.Count; i++) - { - _subscribers.ElementAt(i)(); - } + _subscribers.ElementAt(i)(); } } } -} \ No newline at end of file +} diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index c69e1f0..9075b10 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -3,40 +3,39 @@ using System.Collections.Generic; using System.Linq; -namespace StateR.Internal +namespace StateR.Internal; + +public class StatorBuilder : IStatorBuilder { - public class StatorBuilder : IStatorBuilder + public StatorBuilder(IServiceCollection services) { - public StatorBuilder(IServiceCollection services) - { - Services = services ?? throw new ArgumentNullException(nameof(services)); - } + Services = services ?? throw new ArgumentNullException(nameof(services)); + } - public IStatorBuilder AddTypes(IEnumerable types) - => AddDistinctTypes(All, types); - public IStatorBuilder AddStates(IEnumerable types) - => AddDistinctTypes(States, types); - public IStatorBuilder AddActions(IEnumerable types) - => AddDistinctTypes(Actions, types); - public IStatorBuilder AddUpdaters(IEnumerable types) - => AddDistinctTypes(Updaters, types); - public IStatorBuilder AddActionHandlers(IEnumerable types) - => AddDistinctTypes(ActionHandlers, types); + public IStatorBuilder AddTypes(IEnumerable types) + => AddDistinctTypes(All, types); + public IStatorBuilder AddStates(IEnumerable types) + => AddDistinctTypes(States, types); + public IStatorBuilder AddActions(IEnumerable types) + => AddDistinctTypes(Actions, types); + public IStatorBuilder AddUpdaters(IEnumerable types) + => AddDistinctTypes(Updaters, types); + public IStatorBuilder AddActionHandlers(IEnumerable types) + => AddDistinctTypes(ActionHandlers, types); - public IServiceCollection Services { get; } - public List Actions { get; } = new List(); - public List States { get; } = new List(); - public List Interceptors { get; } = new List(); - public List ActionHandlers { get; } = new List(); - public List AfterEffects { get; } = new List(); - public List Updaters { get; } = new List(); - public List All { get; } = new List(); + public IServiceCollection Services { get; } + public List Actions { get; } = new List(); + public List States { get; } = new List(); + public List Interceptors { get; } = new List(); + public List ActionHandlers { get; } = new List(); + public List AfterEffects { get; } = new List(); + public List Updaters { get; } = new List(); + public List All { get; } = new List(); - private IStatorBuilder AddDistinctTypes(List list, IEnumerable types) - { - var distinctTypes = types.Except(list).Distinct(); - list.AddRange(distinctTypes); - return this; - } + private IStatorBuilder AddDistinctTypes(List list, IEnumerable types) + { + var distinctTypes = types.Except(list).Distinct(); + list.AddRange(distinctTypes); + return this; } -} \ No newline at end of file +} diff --git a/src/StateR/Internal/TypeExtensions.cs b/src/StateR/Internal/TypeExtensions.cs index 0f0fdc5..72e8a0c 100644 --- a/src/StateR/Internal/TypeExtensions.cs +++ b/src/StateR/Internal/TypeExtensions.cs @@ -4,40 +4,39 @@ using System.Text; using System.Threading.Tasks; -namespace StateR.Internal +namespace StateR.Internal; + +public static class TypeExtensions { - public static class TypeExtensions + public static string GetStatorName(this Type type) { - public static string GetStatorName(this Type type) + //if (type == null) { throw new ArgumentNullException(nameof(type)); } + //Console.WriteLine($"[GetStatorName] {type.FullName}"); + if (type.IsGenericType) { - //if (type == null) { throw new ArgumentNullException(nameof(type)); } - //Console.WriteLine($"[GetStatorName] {type.FullName}"); - if (type.IsGenericType) - { - //Console.WriteLine($"[GetStatorName](type) {type}"); - //Console.WriteLine($"[GetStatorName](Name) {type.Name}"); - //Console.WriteLine($"[GetStatorName](type.FullName) {type.FullName}"); - var indexOfThing = type.FullName.IndexOf('`'); - var name = type.FullName.Substring(0, indexOfThing); - var indexOfDot = name.LastIndexOf('.'); - name = name.Substring(indexOfDot + 1); - name += "<"; - foreach (var gType in type.GetGenericArguments()) - { - name += gType.GetStatorName(); - name += ", "; - } - name = name.Trim(',', ' '); - name += ">"; - return name; - } - if (type.FullName?.IndexOf('+') > -1) + //Console.WriteLine($"[GetStatorName](type) {type}"); + //Console.WriteLine($"[GetStatorName](Name) {type.Name}"); + //Console.WriteLine($"[GetStatorName](type.FullName) {type.FullName}"); + var indexOfThing = type.FullName.IndexOf('`'); + var name = type.FullName.Substring(0, indexOfThing); + var indexOfDot = name.LastIndexOf('.'); + name = name.Substring(indexOfDot + 1); + name += "<"; + foreach (var gType in type.GetGenericArguments()) { - var fullName = type.FullName; - var lastDot = fullName.LastIndexOf('.'); - return fullName.Substring(lastDot + 1); + name += gType.GetStatorName(); + name += ", "; } - return type.Name; + name = name.Trim(',', ' '); + name += ">"; + return name; + } + if (type.FullName?.IndexOf('+') > -1) + { + var fullName = type.FullName; + var lastDot = fullName.LastIndexOf('.'); + return fullName.Substring(lastDot + 1); } + return type.Name; } } diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR/Internal/TypeScannerBuilderExtensions.cs index 12aeb65..6acea33 100644 --- a/src/StateR/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR/Internal/TypeScannerBuilderExtensions.cs @@ -7,78 +7,77 @@ using System.Text; using System.Threading.Tasks; -namespace StateR.Internal +namespace StateR.Internal; + +public static class TypeScannerBuilderExtensions { - public static class TypeScannerBuilderExtensions + public static IStatorBuilder ScanTypes(this IStatorBuilder builder) { - public static IStatorBuilder ScanTypes(this IStatorBuilder builder) - { - return builder - .FindStates() - .FindActions() - .FindUpdaters() - .FindActionHandlers() - ; - } + return builder + .FindStates() + .FindActions() + .FindUpdaters() + .FindActionHandlers() + ; + } - public static IStatorBuilder FindStates(this IStatorBuilder builder) - { - var states = builder.All - .Where(type => !type.IsAbstract && type.IsSubclassOf(typeof(StateBase))); - return builder.AddStates(states); - } + public static IStatorBuilder FindStates(this IStatorBuilder builder) + { + var states = builder.All + .Where(type => !type.IsAbstract && type.IsSubclassOf(typeof(StateBase))); + return builder.AddStates(states); + } - public static IStatorBuilder FindActions(this IStatorBuilder builder) - { - var actions = builder.All - .Where(type => !type.IsAbstract && type - .GetTypeInfo() - .GetInterfaces() - .Any(i => i == typeof(IAction)) - ); - return builder.AddActions(actions); - } + public static IStatorBuilder FindActions(this IStatorBuilder builder) + { + var actions = builder.All + .Where(type => !type.IsAbstract && type + .GetTypeInfo() + .GetInterfaces() + .Any(i => i == typeof(IAction)) + ); + return builder.AddActions(actions); + } - public static IStatorBuilder FindUpdaters(this IStatorBuilder builder) - { - var updaters = builder.All - .Where(type => !type.IsAbstract && type - .GetTypeInfo() - .GetInterfaces() - .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IUpdater<,>)) - ); - return builder.AddUpdaters(updaters); - } - public static IStatorBuilder FindActionHandlers(this IStatorBuilder builder) - { - var handlers= builder.All - .Where(type => !type.IsAbstract && type - .GetTypeInfo() - .GetInterfaces() - .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionHandler<>)) - ); - return builder.AddUpdaters(handlers); - } + public static IStatorBuilder FindUpdaters(this IStatorBuilder builder) + { + var updaters = builder.All + .Where(type => !type.IsAbstract && type + .GetTypeInfo() + .GetInterfaces() + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IUpdater<,>)) + ); + return builder.AddUpdaters(updaters); + } + public static IStatorBuilder FindActionHandlers(this IStatorBuilder builder) + { + var handlers = builder.All + .Where(type => !type.IsAbstract && type + .GetTypeInfo() + .GetInterfaces() + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionHandler<>)) + ); + return builder.AddUpdaters(handlers); + } - //public IStatorBuilder FindInterceptors(this IStatorBuilder builder) - //{ - // var iActionInterceptor = typeof(IInterceptor<>); - // return types.Where(type => !type.IsAbstract && type - // .GetTypeInfo() - // .GetInterfaces() - // .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == iActionInterceptor) - // ); - //} + //public IStatorBuilder FindInterceptors(this IStatorBuilder builder) + //{ + // var iActionInterceptor = typeof(IInterceptor<>); + // return types.Where(type => !type.IsAbstract && type + // .GetTypeInfo() + // .GetInterfaces() + // .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == iActionInterceptor) + // ); + //} - //public IStatorBuilder FindAfterEffects(this IStatorBuilder builder) - //{ - // var iAfterEffects = typeof(IAfterEffects<>); - // return types.Where(type => !type.IsAbstract && type - // .GetTypeInfo() - // .GetInterfaces() - // .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == iAfterEffects) - // ); - //} + //public IStatorBuilder FindAfterEffects(this IStatorBuilder builder) + //{ + // var iAfterEffects = typeof(IAfterEffects<>); + // return types.Where(type => !type.IsAbstract && type + // .GetTypeInfo() + // .GetInterfaces() + // .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == iAfterEffects) + // ); + //} - } } diff --git a/src/StateR/StateBase.cs b/src/StateR/StateBase.cs index 43a2870..b719991 100644 --- a/src/StateR/StateBase.cs +++ b/src/StateR/StateBase.cs @@ -1,4 +1,3 @@ -namespace StateR -{ - public abstract record StateBase { } -} +namespace StateR; + +public abstract record StateBase { } diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 390f5a2..d42324c 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -16,134 +16,133 @@ using System.Text; using System.Threading.Tasks; -namespace StateR +namespace StateR; + +public static class StatorStartupExtensions { - public static class StatorStartupExtensions + public static IStatorBuilder AddStateR(this IServiceCollection services) { - public static IStatorBuilder AddStateR(this IServiceCollection services) - { - services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); - return new StatorBuilder(services); - } + return new StatorBuilder(services); + } - public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScan) - { - var builder = services.AddStateR(); - var allTypes = assembliesToScan - .SelectMany(a => a.GetTypes()); - return builder.AddTypes(allTypes); - } + public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScan) + { + var builder = services.AddStateR(); + var allTypes = assembliesToScan + .SelectMany(a => a.GetTypes()); + return builder.AddTypes(allTypes); + } - public static IServiceCollection Apply(this IStatorBuilder builder) + public static IServiceCollection Apply(this IStatorBuilder builder) + { + // Extract types + builder.ScanTypes(); + + // Scan + builder.Services.Scan(s => s + .AddTypes(builder.All) + + // Equivalent to: AddSingleton, Implementation>(); + .AddClasses(classes => classes.AssignableTo(typeof(IInitialState<>))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IBeforeInterceptorHook))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IAfterInterceptorHook))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IBeforeAfterEffectHook))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IAfterAfterEffectHook))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IBeforeActionHook))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IAfterActionHook))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IBeforeUpdateHook))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + // Equivalent to: AddSingleton(); + .AddClasses(classes => classes.AssignableTo(typeof(IAfterUpdateHook))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + + // Equivalent to: AddSingleton, Implementation>(); + .AddClasses(classes => classes.AssignableTo(typeof(IInterceptor<>))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + + // Equivalent to: AddSingleton, Implementation>(); + .AddClasses(classes => classes.AssignableTo(typeof(IAfterEffects<>))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + ); + + // Register States + foreach (var state in builder.States) { - // Extract types - builder.ScanTypes(); - - // Scan - builder.Services.Scan(s => s - .AddTypes(builder.All) - - // Equivalent to: AddSingleton, Implementation>(); - .AddClasses(classes => classes.AssignableTo(typeof(IInitialState<>))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeInterceptorHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterInterceptorHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeAfterEffectHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterAfterEffectHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeActionHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterActionHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeUpdateHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterUpdateHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton, Implementation>(); - .AddClasses(classes => classes.AssignableTo(typeof(IInterceptor<>))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton, Implementation>(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterEffects<>))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - ); - - // Register States - foreach (var state in builder.States) - { - Console.WriteLine($"state: {state.FullName}"); + Console.WriteLine($"state: {state.FullName}"); - // Equivalent to: AddSingleton, State>(); - var stateServiceType = typeof(IState<>).MakeGenericType(state); - var stateImplementationType = typeof(State<>).MakeGenericType(state); - builder.Services.AddSingleton(stateServiceType, stateImplementationType); - } + // Equivalent to: AddSingleton, State>(); + var stateServiceType = typeof(IState<>).MakeGenericType(state); + var stateImplementationType = typeof(State<>).MakeGenericType(state); + builder.Services.AddSingleton(stateServiceType, stateImplementationType); + } - // Register Updaters and their respective IActionHandler - var iUpdaterType = typeof(IUpdater<,>); - var updaterHandler = typeof(UpdaterActionHandler<,>); - var handlerType = typeof(IActionHandler<>); - foreach (var updater in builder.Updaters) + // Register Updaters and their respective IActionHandler + var iUpdaterType = typeof(IUpdater<,>); + var updaterHandler = typeof(UpdaterActionHandler<,>); + var handlerType = typeof(IActionHandler<>); + foreach (var updater in builder.Updaters) + { + Console.WriteLine($"updater: {updater.FullName}"); + var interfaces = updater.GetInterfaces() + .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); + foreach (var @interface in interfaces) { - Console.WriteLine($"updater: {updater.FullName}"); - var interfaces = updater.GetInterfaces() - .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); - foreach (var @interface in interfaces) - { - // Equivalent to: AddSingleton, UpdaterHandler> - var actionType = @interface.GenericTypeArguments[0]; - var stateType = @interface.GenericTypeArguments[1]; - var iActionHandlerServiceType = handlerType.MakeGenericType(actionType); - var updaterHandlerImplementationType = updaterHandler.MakeGenericType(stateType, actionType); - builder.Services.AddSingleton(iActionHandlerServiceType, updaterHandlerImplementationType); - - // Equivalent to: AddSingleton, Updater>(); - builder.Services.AddSingleton(@interface, updater); - - Console.WriteLine($"- AddSingleton<{iActionHandlerServiceType.GetStatorName()}, {updaterHandlerImplementationType.GetStatorName()}>()"); - Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); - } + // Equivalent to: AddSingleton, UpdaterHandler> + var actionType = @interface.GenericTypeArguments[0]; + var stateType = @interface.GenericTypeArguments[1]; + var iActionHandlerServiceType = handlerType.MakeGenericType(actionType); + var updaterHandlerImplementationType = updaterHandler.MakeGenericType(stateType, actionType); + builder.Services.AddSingleton(iActionHandlerServiceType, updaterHandlerImplementationType); + + // Equivalent to: AddSingleton, Updater>(); + builder.Services.AddSingleton(@interface, updater); + + Console.WriteLine($"- AddSingleton<{iActionHandlerServiceType.GetStatorName()}, {updaterHandlerImplementationType.GetStatorName()}>()"); + Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); } - return builder.Services; } + return builder.Services; } } diff --git a/src/StateR/Store.cs b/src/StateR/Store.cs index 99a2d7b..7f236f4 100644 --- a/src/StateR/Store.cs +++ b/src/StateR/Store.cs @@ -6,47 +6,46 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -namespace StateR +namespace StateR; + +public class Store : IStore { - public class Store : IStore + private readonly IServiceProvider _serviceProvider; + private readonly IDispatcher _dispatcher; + + public Store(IServiceProvider serviceProvider, IDispatcher dispatcher) + { + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + } + + public Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) where TAction : IAction + { + return _dispatcher.DispatchAsync(action, cancellationToken); + } + + public TState GetState() where TState : StateBase + { + var state = _serviceProvider.GetRequiredService>(); + return state.Current; + } + + public void SetState(Func stateTransform) where TState : StateBase + { + var state = _serviceProvider.GetRequiredService>(); + state.Set(stateTransform(state.Current)); + state.Notify(); + } + + public void Subscribe(Action stateHasChangedDelegate) where TState : StateBase + { + var state = _serviceProvider.GetRequiredService>(); + state.Subscribe(stateHasChangedDelegate); + } + + public void Unsubscribe(Action stateHasChangedDelegate) where TState : StateBase { - private readonly IServiceProvider _serviceProvider; - private readonly IDispatcher _dispatcher; - - public Store(IServiceProvider serviceProvider, IDispatcher dispatcher) - { - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); - } - - public Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) where TAction : IAction - { - return _dispatcher.DispatchAsync(action, cancellationToken); - } - - public TState GetState() where TState : StateBase - { - var state = _serviceProvider.GetRequiredService>(); - return state.Current; - } - - public void SetState(Func stateTransform) where TState : StateBase - { - var state = _serviceProvider.GetRequiredService>(); - state.Set(stateTransform(state.Current)); - state.Notify(); - } - - public void Subscribe(Action stateHasChangedDelegate) where TState : StateBase - { - var state = _serviceProvider.GetRequiredService>(); - state.Subscribe(stateHasChangedDelegate); - } - - public void Unsubscribe(Action stateHasChangedDelegate) where TState : StateBase - { - var state = _serviceProvider.GetRequiredService>(); - state.Unsubscribe(stateHasChangedDelegate); - } + var state = _serviceProvider.GetRequiredService>(); + state.Unsubscribe(stateHasChangedDelegate); } } diff --git a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs index 4331f94..8ae0455 100644 --- a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs +++ b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs @@ -1,12 +1,11 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Updaters.Hooks +namespace StateR.Updaters.Hooks; + +public interface IAfterUpdateHook { - public interface IAfterUpdateHook - { - Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - } + Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; } diff --git a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs index 87fc9d7..9b7e3c2 100644 --- a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs +++ b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs @@ -1,12 +1,11 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Updaters.Hooks +namespace StateR.Updaters.Hooks; + +public interface IBeforeUpdateHook { - public interface IBeforeUpdateHook - { - Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - } + Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; } diff --git a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs index 0a235c1..9559feb 100644 --- a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs +++ b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs @@ -1,15 +1,14 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Updaters.Hooks +namespace StateR.Updaters.Hooks; + +public interface IUpdateHooksCollection { - public interface IUpdateHooksCollection - { - Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - } + Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; + Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; } diff --git a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs index 3c9c20d..5ae929c 100644 --- a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs +++ b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs @@ -3,36 +3,35 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Updaters.Hooks +namespace StateR.Updaters.Hooks; + +public class UpdateHooksCollection : IUpdateHooksCollection { - public class UpdateHooksCollection : IUpdateHooksCollection + private readonly IEnumerable _beforeUpdateHooks; + private readonly IEnumerable _afterUpdateHooks; + public UpdateHooksCollection(IEnumerable beforeUpdateHooks, IEnumerable afterUpdateHooks) { - private readonly IEnumerable _beforeUpdateHooks; - private readonly IEnumerable _afterUpdateHooks; - public UpdateHooksCollection(IEnumerable beforeUpdateHooks, IEnumerable afterUpdateHooks) - { - _beforeUpdateHooks = beforeUpdateHooks ?? throw new ArgumentNullException(nameof(beforeUpdateHooks)); - _afterUpdateHooks = afterUpdateHooks ?? throw new ArgumentNullException(nameof(afterUpdateHooks)); - } + _beforeUpdateHooks = beforeUpdateHooks ?? throw new ArgumentNullException(nameof(beforeUpdateHooks)); + _afterUpdateHooks = afterUpdateHooks ?? throw new ArgumentNullException(nameof(afterUpdateHooks)); + } - public async Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase + public async Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase + { + foreach (var hook in _beforeUpdateHooks) { - foreach (var hook in _beforeUpdateHooks) - { - await hook.BeforeUpdateAsync(context, state, updater, cancellationToken); - } + await hook.BeforeUpdateAsync(context, state, updater, cancellationToken); } + } - public async Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase + public async Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase + { + foreach (var hook in _afterUpdateHooks) { - foreach (var hook in _afterUpdateHooks) - { - await hook.AfterUpdateAsync(context, state, updater, cancellationToken); - } + await hook.AfterUpdateAsync(context, state, updater, cancellationToken); } } } diff --git a/src/StateR/Updaters/IUpdater.cs b/src/StateR/Updaters/IUpdater.cs index 8906cda..0f57079 100644 --- a/src/StateR/Updaters/IUpdater.cs +++ b/src/StateR/Updaters/IUpdater.cs @@ -1,11 +1,10 @@ using System; -namespace StateR.Updaters +namespace StateR.Updaters; + +public interface IUpdater + where TAction : IAction + where TState : StateBase { - public interface IUpdater - where TAction : IAction - where TState : StateBase - { - TState Update(TAction action, TState state); - } -} \ No newline at end of file + TState Update(TAction action, TState state); +} diff --git a/src/StateR/Updaters/UpdaterActionHandler.cs b/src/StateR/Updaters/UpdaterActionHandler.cs index c3a77a4..1d5400a 100644 --- a/src/StateR/Updaters/UpdaterActionHandler.cs +++ b/src/StateR/Updaters/UpdaterActionHandler.cs @@ -5,41 +5,40 @@ using System.Threading; using System.Threading.Tasks; -namespace StateR.Updaters +namespace StateR.Updaters; + +public class UpdaterActionHandler : IActionHandler + where TState : StateBase + where TAction : IAction { - public class UpdaterActionHandler : IActionHandler - where TState : StateBase - where TAction : IAction - { - private readonly IUpdateHooksCollection _hooks; - private readonly IEnumerable> _updaters; - private readonly IState _state; + private readonly IUpdateHooksCollection _hooks; + private readonly IEnumerable> _updaters; + private readonly IState _state; - public UpdaterActionHandler(IState state, IEnumerable> updaters, IUpdateHooksCollection hooks) - { - _state = state ?? throw new ArgumentNullException(nameof(state)); - _updaters = updaters ?? throw new ArgumentNullException(nameof(updaters)); - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - } + public UpdaterActionHandler(IState state, IEnumerable> updaters, IUpdateHooksCollection hooks) + { + _state = state ?? throw new ArgumentNullException(nameof(state)); + _updaters = updaters ?? throw new ArgumentNullException(nameof(updaters)); + _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); + } - public async Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken) + public async Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken) + { + foreach (var updater in _updaters) { - foreach (var updater in _updaters) + if (cancellationToken.IsCancellationRequested) + { + break; + } + await _hooks.BeforeUpdateAsync(context, _state, updater, cancellationToken); + if (cancellationToken.IsCancellationRequested) { - if (cancellationToken.IsCancellationRequested) - { - break; - } - await _hooks.BeforeUpdateAsync(context, _state, updater, cancellationToken); - if (cancellationToken.IsCancellationRequested) - { - break; - } - _state.Set(updater.Update(context.Action, _state.Current)); - await _hooks.AfterUpdateAsync(context, _state, updater, cancellationToken); + break; } - _state.Notify(); - cancellationToken.ThrowIfCancellationRequested(); + _state.Set(updater.Update(context.Action, _state.Current)); + await _hooks.AfterUpdateAsync(context, _state, updater, cancellationToken); } + _state.Notify(); + cancellationToken.ThrowIfCancellationRequested(); } -} \ No newline at end of file +} diff --git a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs index db80de1..8f13153 100644 --- a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs +++ b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs @@ -9,115 +9,114 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.ActionHandlers +namespace StateR.ActionHandlers; + +public class ActionHandlerManagerTest { - public class ActionHandlerManagerTest + private readonly Mock _hooksCollectionMock = new(); + + protected ActionHandlersManager CreateUpdatersManager(Action configureServices) { - private readonly Mock _hooksCollectionMock = new(); + var services = new ServiceCollection(); + configureServices?.Invoke(services); + var serviceProvider = services.BuildServiceProvider(); + return new ActionHandlersManager(_hooksCollectionMock.Object, serviceProvider); + } - protected ActionHandlersManager CreateUpdatersManager(Action configureServices) + public class DispatchAsync : ActionHandlerManagerTest + { + [Fact] + public async Task Should_call_all_action_handlers() { - var services = new ServiceCollection(); - configureServices?.Invoke(services); - var serviceProvider = services.BuildServiceProvider(); - return new ActionHandlersManager(_hooksCollectionMock.Object, serviceProvider); + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + + var handler1 = new Mock>(); + var handler2 = new Mock>(); + var sut = CreateUpdatersManager(services => + { + services.AddSingleton(handler1.Object); + services.AddSingleton(handler2.Object); + }); + + // Act + await sut.DispatchAsync(context); + + // Assert + handler1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); + handler2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); } - public class DispatchAsync : ActionHandlerManagerTest + [Fact] + public async Task Should_break_handlers_when_Cancel() { - [Fact] - public async Task Should_call_all_action_handlers() + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + + var afterEffect1 = new Mock>(); + afterEffect1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) + .Callback((IDispatchContext context, CancellationToken cancellationToken) + => context.Cancel()); + var afterEffect2 = new Mock>(); + var sut = CreateUpdatersManager(services => { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var handler1 = new Mock>(); - var handler2 = new Mock>(); - var sut = CreateUpdatersManager(services => - { - services.AddSingleton(handler1.Object); - services.AddSingleton(handler2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - handler1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); - handler2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); - } - - [Fact] - public async Task Should_break_handlers_when_Cancel() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) - => context.Cancel()); - var afterEffect2 = new Mock>(); - var sut = CreateUpdatersManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await Assert.ThrowsAsync(() - => sut.DispatchAsync(context)); - - // Assert - afterEffect1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); - afterEffect2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Never); - } - - [Fact] - public async Task Should_call_middleware_and_handlers_in_order() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var operationQueue = new Queue(); - var actionHandler1 = new Mock>(); - actionHandler1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("actionHandler1.HandleAsync")); - var actionHandler2 = new Mock>(); - actionHandler2.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("actionHandler2.HandleAsync")); - _hooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); - _hooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - - var sut = CreateUpdatersManager(services => - { - services.AddSingleton(actionHandler1.Object); - services.AddSingleton(actionHandler2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - Assert.Collection(operationQueue, - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("actionHandler1.HandleAsync", op), - op => Assert.Equal("AfterHandlerAsync", op), - - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("actionHandler2.HandleAsync", op), - op => Assert.Equal("AfterHandlerAsync", op) - ); - } + services.AddSingleton(afterEffect1.Object); + services.AddSingleton(afterEffect2.Object); + }); + + // Act + await Assert.ThrowsAsync(() + => sut.DispatchAsync(context)); + + // Assert + afterEffect1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); + afterEffect2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Never); } - public record TestAction : IAction; + [Fact] + public async Task Should_call_middleware_and_handlers_in_order() + { + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + + var operationQueue = new Queue(); + var actionHandler1 = new Mock>(); + actionHandler1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("actionHandler1.HandleAsync")); + var actionHandler2 = new Mock>(); + actionHandler2.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("actionHandler2.HandleAsync")); + _hooksCollectionMock + .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); + _hooksCollectionMock + .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); + + var sut = CreateUpdatersManager(services => + { + services.AddSingleton(actionHandler1.Object); + services.AddSingleton(actionHandler2.Object); + }); + + // Act + await sut.DispatchAsync(context); + + // Assert + Assert.Collection(operationQueue, + op => Assert.Equal("BeforeHandlerAsync", op), + op => Assert.Equal("actionHandler1.HandleAsync", op), + op => Assert.Equal("AfterHandlerAsync", op), + + op => Assert.Equal("BeforeHandlerAsync", op), + op => Assert.Equal("actionHandler2.HandleAsync", op), + op => Assert.Equal("AfterHandlerAsync", op) + ); + } } + + public record TestAction : IAction; } diff --git a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs b/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs index d71a65a..c80fa5d 100644 --- a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs +++ b/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs @@ -7,61 +7,60 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.ActionHandlers.Hooks +namespace StateR.ActionHandlers.Hooks; + +public class ActionHandlerHooksCollectionTest { - public class ActionHandlerHooksCollectionTest - { - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); + private readonly Mock _before1Mock = new(); + private readonly Mock _before2Mock = new(); + private readonly Mock _after1Mock = new(); + private readonly Mock _after2Mock = new(); - private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly Mock> _afterEffectMock = new(); + private readonly IDispatchContext dispatchContext; + private readonly CancellationToken _cancellationToken = CancellationToken.None; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private readonly ActionHandlerHooksCollection sut; + private readonly ActionHandlerHooksCollection sut; - public ActionHandlerHooksCollectionTest() - { - dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - public class BeforeHandlerAsync : ActionHandlerHooksCollectionTest + public ActionHandlerHooksCollectionTest() + { + dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); + sut = new( + new[] { _before1Mock.Object, _before2Mock.Object }, + new[] { _after1Mock.Object, _after2Mock.Object } + ); + } + public class BeforeHandlerAsync : ActionHandlerHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); + // Act + await sut.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - } + // Assert + _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); } + } - public class AfterHandlerAsync : ActionHandlerHooksCollectionTest + public class AfterHandlerAsync : ActionHandlerHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); + // Act + await sut.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - } + // Assert + _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); } - public record TestAction : IAction; } + public record TestAction : IAction; } diff --git a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs b/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs index 3618ffe..891e6a2 100644 --- a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs +++ b/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs @@ -10,114 +10,113 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.AfterEffects +namespace StateR.AfterEffects; + +public class AfterEffectsManagerTest { - public class AfterEffectsManagerTest + private readonly Mock _afterEffectHooksCollectionMock = new(); + protected AfterEffectsManager CreateAfterEffectsManager(Action configureServices) + { + var services = new ServiceCollection(); + configureServices?.Invoke(services); + services.TryAddSingleton(_afterEffectHooksCollectionMock.Object); + var serviceProvider = services.BuildServiceProvider(); + var afterEffectHooksCollection = serviceProvider.GetService(); + return new AfterEffectsManager(afterEffectHooksCollection, serviceProvider); + } + + public class DispatchAsync : AfterEffectsManagerTest { - private readonly Mock _afterEffectHooksCollectionMock = new(); - protected AfterEffectsManager CreateAfterEffectsManager(Action configureServices) + [Fact] + public async Task Should_handle_all_after_effects() { - var services = new ServiceCollection(); - configureServices?.Invoke(services); - services.TryAddSingleton(_afterEffectHooksCollectionMock.Object); - var serviceProvider = services.BuildServiceProvider(); - var afterEffectHooksCollection = serviceProvider.GetService(); - return new AfterEffectsManager(afterEffectHooksCollection, serviceProvider); + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + + var afterEffect1 = new Mock>(); + var afterEffect2 = new Mock>(); + var sut = CreateAfterEffectsManager(services => + { + services.AddSingleton(afterEffect1.Object); + services.AddSingleton(afterEffect2.Object); + }); + + // Act + await sut.DispatchAsync(context); + + // Assert + afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); + afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); } - public class DispatchAsync : AfterEffectsManagerTest + [Fact] + public async Task Should_break_after_effects_when_Cancel() { - [Fact] - public async Task Should_handle_all_after_effects() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var afterEffect1 = new Mock>(); - var afterEffect2 = new Mock>(); - var sut = CreateAfterEffectsManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); - afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); - } - - [Fact] - public async Task Should_break_after_effects_when_Cancel() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); - var afterEffect2 = new Mock>(); - var sut = CreateAfterEffectsManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await Assert.ThrowsAsync(() - => sut.DispatchAsync(context)); - - // Assert - afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); - afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Never); - } - - [Fact] - public async Task Should_call_hooks_and_after_effects_methods_in_order() + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + + var afterEffect1 = new Mock>(); + afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) + .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); + var afterEffect2 = new Mock>(); + var sut = CreateAfterEffectsManager(services => { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var operationQueue = new Queue(); - var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("afterEffect1.HandleAfterEffectAsync")); - var afterEffect2 = new Mock>(); - afterEffect2.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("afterEffect2.HandleAfterEffectAsync")); - _afterEffectHooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); - _afterEffectHooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - var sut = CreateAfterEffectsManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - Assert.Collection(operationQueue, - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("afterEffect1.HandleAfterEffectAsync", op), - op => Assert.Equal("AfterHandlerAsync", op), - - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("afterEffect2.HandleAfterEffectAsync", op), - op => Assert.Equal("AfterHandlerAsync", op) - ); - } + services.AddSingleton(afterEffect1.Object); + services.AddSingleton(afterEffect2.Object); + }); + + // Act + await Assert.ThrowsAsync(() + => sut.DispatchAsync(context)); + + // Assert + afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); + afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Never); } - public record TestAction : IAction; + [Fact] + public async Task Should_call_hooks_and_after_effects_methods_in_order() + { + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + + var operationQueue = new Queue(); + var afterEffect1 = new Mock>(); + afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("afterEffect1.HandleAfterEffectAsync")); + var afterEffect2 = new Mock>(); + afterEffect2.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("afterEffect2.HandleAfterEffectAsync")); + _afterEffectHooksCollectionMock + .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); + _afterEffectHooksCollectionMock + .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); + var sut = CreateAfterEffectsManager(services => + { + services.AddSingleton(afterEffect1.Object); + services.AddSingleton(afterEffect2.Object); + }); + + // Act + await sut.DispatchAsync(context); + + // Assert + Assert.Collection(operationQueue, + op => Assert.Equal("BeforeHandlerAsync", op), + op => Assert.Equal("afterEffect1.HandleAfterEffectAsync", op), + op => Assert.Equal("AfterHandlerAsync", op), + + op => Assert.Equal("BeforeHandlerAsync", op), + op => Assert.Equal("afterEffect2.HandleAfterEffectAsync", op), + op => Assert.Equal("AfterHandlerAsync", op) + ); + } } + + public record TestAction : IAction; } diff --git a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs b/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs index ac3782a..2388a0e 100644 --- a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs +++ b/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs @@ -7,62 +7,61 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.AfterEffects.Hooks +namespace StateR.AfterEffects.Hooks; + +public class AfterEffectHooksCollectionTest { - public class AfterEffectHooksCollectionTest - { - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); + private readonly Mock _before1Mock = new(); + private readonly Mock _before2Mock = new(); + private readonly Mock _after1Mock = new(); + private readonly Mock _after2Mock = new(); - private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext _dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly Mock> _afterEffectMock = new(); + private readonly IDispatchContext _dispatchContext; + private readonly CancellationToken _cancellationToken = CancellationToken.None; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private readonly AfterEffectHooksCollection sut; + private readonly AfterEffectHooksCollection sut; - public AfterEffectHooksCollectionTest() - { - _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - public class BeforeHandlerAsync : AfterEffectHooksCollectionTest + public AfterEffectHooksCollectionTest() + { + _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); + sut = new( + new[] { _before1Mock.Object, _before2Mock.Object }, + new[] { _after1Mock.Object, _after2Mock.Object } + ); + } + public class BeforeHandlerAsync : AfterEffectHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - } + // Act + await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); + // Assert + _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); } - public class AfterHandlerAsync : AfterEffectHooksCollectionTest + } + + public class AfterHandlerAsync : AfterEffectHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); + // Act + await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - } + // Assert + _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); } - public record TestAction : IAction; } + public record TestAction : IAction; } diff --git a/test/StateR.Tests/DispatcherTest.cs b/test/StateR.Tests/DispatcherTest.cs index e1829d6..d5d55cb 100644 --- a/test/StateR.Tests/DispatcherTest.cs +++ b/test/StateR.Tests/DispatcherTest.cs @@ -9,78 +9,77 @@ using StateR.AfterEffects; using StateR.ActionHandlers; -namespace StateR +namespace StateR; + +public class DispatcherTest { - public class DispatcherTest + private readonly Mock _dispatchContextFactory = new(); + private readonly Mock _interceptorsManager = new(); + private readonly Mock _actionHandlersManager = new(); + private readonly Mock _afterEffectsManager = new(); + private readonly Dispatcher sut; + + public DispatcherTest() { - private readonly Mock _dispatchContextFactory = new(); - private readonly Mock _interceptorsManager = new(); - private readonly Mock _actionHandlersManager = new(); - private readonly Mock _afterEffectsManager = new(); - private readonly Dispatcher sut; + sut = new(_dispatchContextFactory.Object, _interceptorsManager.Object, _actionHandlersManager.Object, _afterEffectsManager.Object); + } - public DispatcherTest() + public class DispatchAsync : DispatcherTest + { + [Fact] + public async Task Should_create_DispatchContext_using_dispatchContextFactory() { - sut = new(_dispatchContextFactory.Object, _interceptorsManager.Object, _actionHandlersManager.Object, _afterEffectsManager.Object); + var action = new TestAction(); + await sut.DispatchAsync(action, CancellationToken.None); + _dispatchContextFactory + .Verify(x => x.Create(action, sut, It.IsAny()), Times.Once); } - public class DispatchAsync : DispatcherTest + [Fact] + public async Task Should_send_the_same_DispatchContext_to_all_managers() { - [Fact] - public async Task Should_create_DispatchContext_using_dispatchContextFactory() - { - var action = new TestAction(); - await sut.DispatchAsync(action, CancellationToken.None); - _dispatchContextFactory - .Verify(x => x.Create(action, sut, It.IsAny()), Times.Once); - } - - [Fact] - public async Task Should_send_the_same_DispatchContext_to_all_managers() - { - // Arrange - var action = new TestAction(); - var context = new DispatchContext(action, new Mock().Object, new CancellationTokenSource()); - _dispatchContextFactory - .Setup(x => x.Create(action, sut, It.IsAny())) - .Returns(context); + // Arrange + var action = new TestAction(); + var context = new DispatchContext(action, new Mock().Object, new CancellationTokenSource()); + _dispatchContextFactory + .Setup(x => x.Create(action, sut, It.IsAny())) + .Returns(context); - // Act - await sut.DispatchAsync(action, CancellationToken.None); + // Act + await sut.DispatchAsync(action, CancellationToken.None); - // Assert - _interceptorsManager.Verify(x => x.DispatchAsync(context), Times.Once); - _actionHandlersManager.Verify(x => x.DispatchAsync(context), Times.Once); - _afterEffectsManager.Verify(x => x.DispatchAsync(context), Times.Once); - } - [Fact] - public async Task Should_call_managers_in_the_expected_order() - { - // Arrange - var action = new TestAction(); - var operationQueue = new Queue(); - _interceptorsManager - .Setup(x => x.DispatchAsync(It.IsAny< IDispatchContext>())) - .Callback(() => operationQueue.Enqueue("Interceptors")); - _actionHandlersManager - .Setup(x => x.DispatchAsync(It.IsAny>())) - .Callback(() => operationQueue.Enqueue("Updaters")); - _afterEffectsManager - .Setup(x => x.DispatchAsync(It.IsAny>())) - .Callback(() => operationQueue.Enqueue("AfterEffects")); + // Assert + _interceptorsManager.Verify(x => x.DispatchAsync(context), Times.Once); + _actionHandlersManager.Verify(x => x.DispatchAsync(context), Times.Once); + _afterEffectsManager.Verify(x => x.DispatchAsync(context), Times.Once); + } + [Fact] + public async Task Should_call_managers_in_the_expected_order() + { + // Arrange + var action = new TestAction(); + var operationQueue = new Queue(); + _interceptorsManager + .Setup(x => x.DispatchAsync(It.IsAny>())) + .Callback(() => operationQueue.Enqueue("Interceptors")); + _actionHandlersManager + .Setup(x => x.DispatchAsync(It.IsAny>())) + .Callback(() => operationQueue.Enqueue("Updaters")); + _afterEffectsManager + .Setup(x => x.DispatchAsync(It.IsAny>())) + .Callback(() => operationQueue.Enqueue("AfterEffects")); - // Act - await sut.DispatchAsync(action, CancellationToken.None); + // Act + await sut.DispatchAsync(action, CancellationToken.None); - // Assert - Assert.Collection(operationQueue, - operation => Assert.Equal("Interceptors", operation), - operation => Assert.Equal("Updaters", operation), - operation => Assert.Equal("AfterEffects", operation) - ); - } + // Assert + Assert.Collection(operationQueue, + operation => Assert.Equal("Interceptors", operation), + operation => Assert.Equal("Updaters", operation), + operation => Assert.Equal("AfterEffects", operation) + ); } - - private record TestAction : IAction; } + + private record TestAction : IAction; } diff --git a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs b/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs index 3065d48..9cbff76 100644 --- a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs +++ b/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs @@ -8,61 +8,60 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.Interceptors.Hooks +namespace StateR.Interceptors.Hooks; + +public class InterceptorsHooksCollectionTest { - public class InterceptorsHooksCollectionTest - { - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); + private readonly Mock _before1Mock = new(); + private readonly Mock _before2Mock = new(); + private readonly Mock _after1Mock = new(); + private readonly Mock _after2Mock = new(); - private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext _dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly Mock> _afterEffectMock = new(); + private readonly IDispatchContext _dispatchContext; + private readonly CancellationToken _cancellationToken = CancellationToken.None; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private readonly InterceptorsHooksCollection sut; + private readonly InterceptorsHooksCollection sut; - public InterceptorsHooksCollectionTest() - { - _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } + public InterceptorsHooksCollectionTest() + { + _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); + sut = new( + new[] { _before1Mock.Object, _before2Mock.Object }, + new[] { _after1Mock.Object, _after2Mock.Object } + ); + } - public class BeforeHandlerAsync : InterceptorsHooksCollectionTest + public class BeforeHandlerAsync : InterceptorsHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); + // Act + await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - } + // Assert + _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); } - public class AfterHandlerAsync : InterceptorsHooksCollectionTest + } + public class AfterHandlerAsync : InterceptorsHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); + // Act + await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - } + // Assert + _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); + _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); + _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); } - public record TestAction : IAction; } + public record TestAction : IAction; } diff --git a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs b/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs index 78a682b..35c8fa3 100644 --- a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs +++ b/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs @@ -10,112 +10,111 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.Interceptors +namespace StateR.Interceptors; + +public class InterceptorsManagerTest { - public class InterceptorsManagerTest + private readonly Mock _hooksCollectionMock = new(); + protected InterceptorsManager CreateInterceptorsManager(Action configureServices) { - private readonly Mock _hooksCollectionMock = new(); - protected InterceptorsManager CreateInterceptorsManager(Action configureServices) - { - var services = new ServiceCollection(); - configureServices?.Invoke(services); - services.TryAddSingleton(_hooksCollectionMock.Object); - var serviceProvider = services.BuildServiceProvider(); - return new InterceptorsManager(_hooksCollectionMock.Object, serviceProvider); - } + var services = new ServiceCollection(); + configureServices?.Invoke(services); + services.TryAddSingleton(_hooksCollectionMock.Object); + var serviceProvider = services.BuildServiceProvider(); + return new InterceptorsManager(_hooksCollectionMock.Object, serviceProvider); + } - public class DispatchAsync : InterceptorsManagerTest + public class DispatchAsync : InterceptorsManagerTest + { + [Fact] + public async Task Should_dispatch_to_all_interceptors() { - [Fact] - public async Task Should_dispatch_to_all_interceptors() + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var interceptor1 = new Mock>(); + var interceptor2 = new Mock>(); + var sut = CreateInterceptorsManager(services => { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var interceptor1 = new Mock>(); - var interceptor2 = new Mock>(); - var sut = CreateInterceptorsManager(services => - { - services.AddSingleton(interceptor1.Object); - services.AddSingleton(interceptor2.Object); - }); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + services.AddSingleton(interceptor1.Object); + services.AddSingleton(interceptor2.Object); + }); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - // Act - await sut.DispatchAsync(context); + // Act + await sut.DispatchAsync(context); - // Assert - interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); - interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); - } + // Assert + interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); + interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); + } - [Fact] - public async Task Should_call_middleware_and_interceptors_methods_in_order() + [Fact] + public async Task Should_call_middleware_and_interceptors_methods_in_order() + { + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + + var operationQueue = new Queue(); + var interceptor1 = new Mock>(); + interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("interceptor1.InterceptAsync")); + var interceptor2 = new Mock>(); + interceptor2.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("interceptor2.InterceptAsync")); + _hooksCollectionMock + .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); + _hooksCollectionMock + .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) + .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); + var sut = CreateInterceptorsManager(services => { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + services.AddSingleton(interceptor1.Object); + services.AddSingleton(interceptor2.Object); + }); - var operationQueue = new Queue(); - var interceptor1 = new Mock>(); - interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("interceptor1.InterceptAsync")); - var interceptor2 = new Mock>(); - interceptor2.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("interceptor2.InterceptAsync")); - _hooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); - _hooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - var sut = CreateInterceptorsManager(services => - { - services.AddSingleton(interceptor1.Object); - services.AddSingleton(interceptor2.Object); - }); + // Act + await sut.DispatchAsync(context); - // Act - await sut.DispatchAsync(context); + // Assert + Assert.Collection(operationQueue, + op => Assert.Equal("BeforeHandlerAsync", op), + op => Assert.Equal("interceptor1.InterceptAsync", op), + op => Assert.Equal("AfterHandlerAsync", op), - // Assert - Assert.Collection(operationQueue, - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("interceptor1.InterceptAsync", op), - op => Assert.Equal("AfterHandlerAsync", op), + op => Assert.Equal("BeforeHandlerAsync", op), + op => Assert.Equal("interceptor2.InterceptAsync", op), + op => Assert.Equal("AfterHandlerAsync", op) + ); + } - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("interceptor2.InterceptAsync", op), - op => Assert.Equal("AfterHandlerAsync", op) - ); - } + [Fact] + public async Task Should_break_interception_when_Cancel() + { + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - [Fact] - public async Task Should_break_interception_when_Cancel() + var interceptor1 = new Mock>(); + interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) + .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); + var interceptor2 = new Mock>(); + var sut = CreateInterceptorsManager(services => { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + services.AddSingleton(interceptor1.Object); + services.AddSingleton(interceptor2.Object); + }); - var interceptor1 = new Mock>(); - interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); - var interceptor2 = new Mock>(); - var sut = CreateInterceptorsManager(services => - { - services.AddSingleton(interceptor1.Object); - services.AddSingleton(interceptor2.Object); - }); + // Act + await Assert.ThrowsAsync(() + => sut.DispatchAsync(context)); - // Act - await Assert.ThrowsAsync(() - => sut.DispatchAsync(context)); - - // Assert - interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); - interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Never); - } + // Assert + interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); + interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Never); } - - public record TestAction : IAction; } + + public record TestAction : IAction; } diff --git a/test/StateR.Tests/Internal/StateTest.cs b/test/StateR.Tests/Internal/StateTest.cs index f5de1d1..4e68f2f 100644 --- a/test/StateR.Tests/Internal/StateTest.cs +++ b/test/StateR.Tests/Internal/StateTest.cs @@ -6,76 +6,75 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.Internal +namespace StateR.Internal; + +public class StateTest { - public class StateTest + private readonly TestState _initialState = new(0); + private readonly Mock> _initialMock; + private readonly State sut; + + public StateTest() { - private readonly TestState _initialState = new(0); - private readonly Mock> _initialMock; - private readonly State sut; + _initialMock = new(); + _initialMock.Setup(x => x.Value).Returns(_initialState); + sut = new(_initialMock.Object); + } - public StateTest() + public class Ctor : StateTest + { + [Fact] + public void Should_set_Current_with_initial_state() { - _initialMock = new(); - _initialMock.Setup(x => x.Value).Returns(_initialState); - sut = new(_initialMock.Object); + Assert.Same(_initialState, sut.Current); } + } - public class Ctor : StateTest + public class Set : StateTest + { + [Fact] + public void Should_set_Current() { - [Fact] - public void Should_set_Current_with_initial_state() - { - Assert.Same(_initialState, sut.Current); - } + var newState = new TestState(1); + sut.Set(newState); + Assert.Same(newState, sut.Current); } - public class Set : StateTest + [Fact] + public void Should_not_set_same_state() { - [Fact] - public void Should_set_Current() - { - var newState = new TestState(1); - sut.Set(newState); - Assert.Same(newState, sut.Current); - } - - [Fact] - public void Should_not_set_same_state() - { - var newState = new TestState(0); - sut.Set(newState); - Assert.NotSame(newState, sut.Current); - Assert.Same(_initialState, sut.Current); - } + var newState = new TestState(0); + sut.Set(newState); + Assert.NotSame(newState, sut.Current); + Assert.Same(_initialState, sut.Current); } + } - public class Notify : StateTest + public class Notify : StateTest + { + [Fact] + public void Should_notify_subscribers() { - [Fact] - public void Should_notify_subscribers() - { - // Arrange - var subscriberQueue = new Queue(); - Action sub1 = () => subscriberQueue.Enqueue("Sub1"); - Action sub2 = () => subscriberQueue.Enqueue("Sub2"); - Action sub3 = () => subscriberQueue.Enqueue("Sub3"); - sut.Subscribe(sub1); - sut.Subscribe(sub2); - sut.Subscribe(sub3); - sut.Unsubscribe(sub2); + // Arrange + var subscriberQueue = new Queue(); + Action sub1 = () => subscriberQueue.Enqueue("Sub1"); + Action sub2 = () => subscriberQueue.Enqueue("Sub2"); + Action sub3 = () => subscriberQueue.Enqueue("Sub3"); + sut.Subscribe(sub1); + sut.Subscribe(sub2); + sut.Subscribe(sub3); + sut.Unsubscribe(sub2); - // Act - sut.Notify(); + // Act + sut.Notify(); - // Assert - Assert.Collection(subscriberQueue, - op => Assert.Equal("Sub1", op), - op => Assert.Equal("Sub3", op) - ); - } + // Assert + Assert.Collection(subscriberQueue, + op => Assert.Equal("Sub1", op), + op => Assert.Equal("Sub3", op) + ); } - - public record TestState(int Value) : StateBase; } + + public record TestState(int Value) : StateBase; } diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index baaab18..1972136 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -6,39 +6,38 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.Internal +namespace StateR.Internal; + +public class StatorBuilderTest { - public class StatorBuilderTest + public class AddTypes : StatorBuilderTest { - public class AddTypes : StatorBuilderTest + [Fact] + public void Should_add_disctict_tally_types() { - [Fact] - public void Should_add_disctict_tally_types() + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var types = new Type[] { - // Arrange - var services = new ServiceCollection(); - var sut = new StatorBuilder(services); - var types = new Type[] - { typeof(StatorBuilderTest), typeof(StatorBuilderTest), - }; - var types2 = new Type[] - { + }; + var types2 = new Type[] + { typeof(AddTypes), typeof(StatorBuilderTest), - }; + }; - // Act - sut.AddTypes(types); - sut.AddTypes(types2); + // Act + sut.AddTypes(types); + sut.AddTypes(types2); - // Assert - Assert.Collection(sut.All, - type => Assert.Equal(typeof(StatorBuilderTest), type), - type => Assert.Equal(typeof(AddTypes), type) - ); - } + // Assert + Assert.Collection(sut.All, + type => Assert.Equal(typeof(StatorBuilderTest), type), + type => Assert.Equal(typeof(AddTypes), type) + ); } } } diff --git a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs index 3503a63..aa92a9f 100644 --- a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs +++ b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs @@ -7,66 +7,65 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.Updaters.Hooks +namespace StateR.Updaters.Hooks; + +public class UpdateHooksCollectionTest { - public class UpdateHooksCollectionTest - { - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); + private readonly Mock _before1Mock = new(); + private readonly Mock _before2Mock = new(); + private readonly Mock _after1Mock = new(); + private readonly Mock _after2Mock = new(); - private readonly Mock> _stateMock = new(); - private readonly Mock> _updater = new(); + private readonly Mock> _stateMock = new(); + private readonly Mock> _updater = new(); - private readonly IDispatchContext _dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly IDispatchContext _dispatchContext; + private readonly CancellationToken _cancellationToken = CancellationToken.None; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private readonly UpdateHooksCollection sut; + private readonly UpdateHooksCollection sut; - public UpdateHooksCollectionTest() - { - _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new UpdateHooksCollection( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } + public UpdateHooksCollectionTest() + { + _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); + sut = new UpdateHooksCollection( + new[] { _before1Mock.Object, _before2Mock.Object }, + new[] { _after1Mock.Object, _after2Mock.Object } + ); + } - public class BeforeUpdateAsync : UpdateHooksCollectionTest + public class BeforeUpdateAsync : UpdateHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); + // Act + await sut.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); - // Assert - _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - } + // Assert + _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); + _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); + _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); + _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); } + } - public class AfterUpdateAsync : UpdateHooksCollectionTest + public class AfterUpdateAsync : UpdateHooksCollectionTest + { + [Fact] + public async Task Should_call_all_hooks() { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); + // Act + await sut.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); - // Assert - _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - } + // Assert + _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); + _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); + _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); + _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); } - - public record TestAction : IAction; - public record TestState : StateBase; } + + public record TestAction : IAction; + public record TestState : StateBase; } diff --git a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs index dd58379..0da5228 100644 --- a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs +++ b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs @@ -9,178 +9,177 @@ using System.Threading.Tasks; using Xunit; -namespace StateR.Updaters +namespace StateR.Updaters; + +public class UpdaterActionHandlerTest { - public class UpdaterActionHandlerTest + private readonly TestState _state = new(); + private readonly Mock> _stateMock = new(); + private readonly List> _updaters = new(); + private readonly Mock _hooksMock = new(); + private readonly UpdaterActionHandler sut; + private readonly Queue _operationQueue = new(); + private readonly TestAction _action = new(); + private readonly DispatchContext _context; + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + private readonly Mock> _updater1Mock = new(); + private readonly Mock> _updater2Mock = new(); + + public UpdaterActionHandlerTest() + { + _context = new(_action, new Mock().Object, _cancellationTokenSource); + + _stateMock.Setup(x => x.Current).Returns(_state); + _stateMock.Setup(x => x.Notify()) + .Callback(() => _operationQueue.Enqueue("state.Notify")); + + _updater1Mock + .Setup(x => x.Update(_action, _state)) + .Returns(_state) + .Callback(() => _operationQueue.Enqueue("updater1.Update")); + _updaters.Add(_updater1Mock.Object); + _updater2Mock + .Setup(x => x.Update(_action, _state)) + .Returns(_state) + .Callback(() => _operationQueue.Enqueue("updater2.Update")); + _updaters.Add(_updater2Mock.Object); + + sut = new UpdaterActionHandler( + _stateMock.Object, + _updaters, + _hooksMock.Object + ); + } + + public class HandleAsync : UpdaterActionHandlerTest { - private readonly TestState _state = new(); - private readonly Mock> _stateMock = new(); - private readonly List> _updaters = new(); - private readonly Mock _hooksMock = new(); - private readonly UpdaterActionHandler sut; - private readonly Queue _operationQueue = new(); - private readonly TestAction _action = new(); - private readonly DispatchContext _context; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly Mock> _updater1Mock = new(); - private readonly Mock> _updater2Mock = new(); - - public UpdaterActionHandlerTest() + [Fact] + public async Task Should_call_updaters_then_notify() { - _context = new(_action, new Mock().Object, _cancellationTokenSource); - - _stateMock.Setup(x => x.Current).Returns(_state); - _stateMock.Setup(x => x.Notify()) - .Callback(() => _operationQueue.Enqueue("state.Notify")); - - _updater1Mock - .Setup(x => x.Update(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("updater1.Update")); - _updaters.Add(_updater1Mock.Object); - _updater2Mock - .Setup(x => x.Update(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("updater2.Update")); - _updaters.Add(_updater2Mock.Object); - - sut = new UpdaterActionHandler( - _stateMock.Object, - _updaters, - _hooksMock.Object + // Act + await sut.HandleAsync(_context, CancellationToken.None); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("updater2.Update", op), + op => Assert.Equal("state.Notify", op) ); } - public class HandleAsync : UpdaterActionHandlerTest + [Fact] + public async Task Should_break_updates_when_Cancel_is_called_in_a_BeforeUpdateAsync_hook() { - [Fact] - public async Task Should_call_updaters_then_notify() - { - // Act - await sut.HandleAsync(_context, CancellationToken.None); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("updater2.Update", op), - op => Assert.Equal("state.Notify", op) - ); - } - - [Fact] - public async Task Should_break_updates_when_Cancel_is_called_in_a_BeforeUpdateAsync_hook() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => - { - _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2"); - _cancellationTokenSource.Cancel(); - }); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await Assert.ThrowsAsync(() - => sut.HandleAsync(_context, _cancellationTokenSource.Token)); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), - - op => Assert.Equal("state.Notify", op) - ); - } - - [Fact] - public async Task Should_break_updates_when_Cancel_is_called_in_an_AfterUpdateAsync_hook() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => - { - _operationQueue.Enqueue("AfterUpdaterAsync:Updater1"); - _cancellationTokenSource.Cancel(); - }); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await Assert.ThrowsAsync(() - => sut.HandleAsync(_context, _cancellationTokenSource.Token)); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("state.Notify", op) - ); - } - - [Fact] - public async Task Should_call_hooks_methods_in_order() - { - // Arrange - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await sut.HandleAsync(_context, CancellationToken.None); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), - op => Assert.Equal("updater2.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater2", op), - - op => Assert.Equal("state.Notify", op) - ); - } + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => + { + _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2"); + _cancellationTokenSource.Cancel(); + }); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); + + // Act + await Assert.ThrowsAsync(() + => sut.HandleAsync(_context, _cancellationTokenSource.Token)); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater1", op), + + op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), + + op => Assert.Equal("state.Notify", op) + ); } + [Fact] + public async Task Should_break_updates_when_Cancel_is_called_in_an_AfterUpdateAsync_hook() + { + // Arrange + var cancellationTokenSource = new CancellationTokenSource(); + var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => + { + _operationQueue.Enqueue("AfterUpdaterAsync:Updater1"); + _cancellationTokenSource.Cancel(); + }); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); + + // Act + await Assert.ThrowsAsync(() + => sut.HandleAsync(_context, _cancellationTokenSource.Token)); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater1", op), + + op => Assert.Equal("state.Notify", op) + ); + } - public record TestAction : IAction; - public record TestState : StateBase; + [Fact] + public async Task Should_call_hooks_methods_in_order() + { + // Arrange + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) + .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); + _hooksMock + .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) + .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); + + // Act + await sut.HandleAsync(_context, CancellationToken.None); + + // Assert + Assert.Collection(_operationQueue, + op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), + op => Assert.Equal("updater1.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater1", op), + + op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), + op => Assert.Equal("updater2.Update", op), + op => Assert.Equal("AfterUpdaterAsync:Updater2", op), + + op => Assert.Equal("state.Notify", op) + ); + } } + + + public record TestAction : IAction; + public record TestState : StateBase; } diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 80c033e..34ec629 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -4,13 +4,12 @@ using System.Text; using System.Threading.Tasks; -namespace StateR +namespace StateR; + +public class StatorStartupExtensionsTest { - public class StatorStartupExtensionsTest + public class AddStateR : StatorStartupExtensionsTest { - public class AddStateR : StatorStartupExtensionsTest - { - } } } From 906b6fac372b2d4af83a918aad7bb330889ac41d Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 15:43:01 -0500 Subject: [PATCH 08/58] Enable ImplicitUsings --- test/StateR.Tests/StateR.Tests.csproj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/StateR.Tests/StateR.Tests.csproj b/test/StateR.Tests/StateR.Tests.csproj index 00f2c64..7709cc4 100644 --- a/test/StateR.Tests/StateR.Tests.csproj +++ b/test/StateR.Tests/StateR.Tests.csproj @@ -1,9 +1,11 @@ - + net6.0 + disable + enable - false + false StateR From a338ebb133e931581884eb65aa1df842f458235e Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 15:44:31 -0500 Subject: [PATCH 09/58] Cleanup usings --- src/StateR.Blazor/StatorComponent.cs | 5 +---- src/StateR.Blazor/StatorComponentBase.cs | 3 --- src/StateR.Blazor/StoreBasedStatorComponent.cs | 9 +-------- src/StateR.Experiments/AsyncLogic/AsyncError.cs | 1 - src/StateR.Experiments/AsyncLogic/AsyncOperation.cs | 9 +-------- .../AsyncLogic/StartupExtensions.cs | 5 ----- src/StateR/ActionHandlers/ActionHandlersManager.cs | 5 ----- .../Hooks/ActionHandlerHooksCollection.cs | 7 +------ .../Hooks/IActionHandlerHooksCollection.cs | 5 +---- src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs | 5 +---- src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs | 5 +---- src/StateR/ActionHandlers/IActionHandler.cs | 5 +---- src/StateR/AfterEffects/AfterEffectsManager.cs | 5 ----- .../AfterEffects/Hooks/AfterEffectHooksCollection.cs | 7 +------ .../AfterEffects/Hooks/IAfterAfterEffectHook.cs | 5 +---- .../AfterEffects/Hooks/IAfterEffectHooksCollection.cs | 5 +---- .../AfterEffects/Hooks/IBeforeAfterEffectHook.cs | 5 +---- src/StateR/AfterEffects/IAfterEffects.cs | 5 +---- src/StateR/DispatchContext.cs | 5 +---- src/StateR/DispatchContextFactory.cs | 4 +--- src/StateR/Dispatcher.cs | 4 ---- src/StateR/IDispatchContext.cs | 4 +--- src/StateR/IDispatchContextFactory.cs | 4 +--- src/StateR/IDispatchManager.cs | 5 +---- src/StateR/IDispatcher.cs | 7 +------ src/StateR/IState.cs | 4 +--- src/StateR/IStatorBuilder.cs | 3 --- src/StateR/IStore.cs | 4 +--- src/StateR/ISubscribable.cs | 4 +--- .../Interceptors/Hooks/IAfterInterceptorHook.cs | 5 +---- .../Interceptors/Hooks/IBeforeInterceptorHook.cs | 5 +---- .../Hooks/IInterceptorsHooksCollection.cs | 5 +---- .../Interceptors/Hooks/InterceptorsHooksCollection.cs | 7 +------ src/StateR/Interceptors/IInterceptor.cs | 5 +---- src/StateR/Interceptors/InterceptorsManager.cs | 5 ----- src/StateR/Internal/InitialState.cs | 4 +--- src/StateR/Internal/State.cs | 6 +----- src/StateR/Internal/StatorBuilder.cs | 3 --- src/StateR/Internal/TypeExtensions.cs | 8 +------- src/StateR/Internal/TypeScannerBuilderExtensions.cs | 5 ----- src/StateR/StatorStartupExtensions.cs | 5 ----- src/StateR/Store.cs | 8 +------- src/StateR/Updaters/Hooks/IAfterUpdateHook.cs | 5 +---- src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs | 5 +---- src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs | 5 +---- src/StateR/Updaters/Hooks/UpdateHooksCollection.cs | 7 +------ src/StateR/Updaters/IUpdater.cs | 4 +--- src/StateR/Updaters/UpdaterActionHandler.cs | 4 ---- .../ActionHandlers/ActionHandlerManagerTest.cs | 6 ------ .../Hooks/ActionHandlerHooksCollectionTest.cs | 6 ------ .../AfterEffects/AfterEffectsManagerTest.cs | 6 ------ .../Hooks/AfterEffectHooksCollectionTest.cs | 6 ------ test/StateR.Tests/DispatcherTest.cs | 11 +++-------- .../Hooks/InterceptorsHooksCollectionTest.cs | 9 +-------- .../Interceptors/InterceptorsManagerTest.cs | 6 ------ test/StateR.Tests/Internal/StateTest.cs | 5 ----- test/StateR.Tests/Internal/StatorBuilderTest.cs | 5 ----- .../Reducers/Hooks/UpdateHooksCollectionTest.cs | 6 ------ .../StateR.Tests/Reducers/UpdaterActionHandlerTest.cs | 9 +-------- test/StateR.Tests/StatorStartupExtensionsTest.cs | 8 +------- 60 files changed, 42 insertions(+), 286 deletions(-) diff --git a/src/StateR.Blazor/StatorComponent.cs b/src/StateR.Blazor/StatorComponent.cs index b81fddb..5084e11 100644 --- a/src/StateR.Blazor/StatorComponent.cs +++ b/src/StateR.Blazor/StatorComponent.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using System.Reflection; namespace StateR.Blazor; diff --git a/src/StateR.Blazor/StatorComponentBase.cs b/src/StateR.Blazor/StatorComponentBase.cs index 3e13841..e5315bc 100644 --- a/src/StateR.Blazor/StatorComponentBase.cs +++ b/src/StateR.Blazor/StatorComponentBase.cs @@ -1,7 +1,4 @@ using Microsoft.AspNetCore.Components; -using System; -using System.Threading; -using System.Threading.Tasks; namespace StateR.Blazor; diff --git a/src/StateR.Blazor/StoreBasedStatorComponent.cs b/src/StateR.Blazor/StoreBasedStatorComponent.cs index 23f0dac..9a6cefb 100644 --- a/src/StateR.Blazor/StoreBasedStatorComponent.cs +++ b/src/StateR.Blazor/StoreBasedStatorComponent.cs @@ -1,11 +1,4 @@ -using StateR; -using Microsoft.AspNetCore.Components; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Runtime.CompilerServices; -using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Components; namespace StateR.Blazor; diff --git a/src/StateR.Experiments/AsyncLogic/AsyncError.cs b/src/StateR.Experiments/AsyncLogic/AsyncError.cs index f18036a..7272abb 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncError.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncError.cs @@ -1,5 +1,4 @@ using StateR.Updaters; -using System; namespace StateR.AsyncLogic; diff --git a/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs b/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs index a3c0727..8b7ff2f 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncOperation.cs @@ -1,12 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; -using StateR.AfterEffects; +using StateR.AfterEffects; using StateR.Updaters; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace StateR.AsyncLogic; diff --git a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs index e3c9f88..e7fbd92 100644 --- a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs +++ b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs @@ -2,11 +2,6 @@ using StateR.ActionHandlers; using StateR.AsyncLogic; using StateR.Updaters; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace StateR.Experiments.AsyncLogic; diff --git a/src/StateR/ActionHandlers/ActionHandlersManager.cs b/src/StateR/ActionHandlers/ActionHandlersManager.cs index 2c7f7bb..df9c673 100644 --- a/src/StateR/ActionHandlers/ActionHandlersManager.cs +++ b/src/StateR/ActionHandlers/ActionHandlersManager.cs @@ -1,10 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using StateR.ActionHandlers.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace StateR.ActionHandlers; diff --git a/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs b/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs index 67d45d3..f2d8eab 100644 --- a/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs +++ b/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.ActionHandlers.Hooks; +namespace StateR.ActionHandlers.Hooks; public class ActionHandlerHooksCollection : IActionHandlerHooksCollection { diff --git a/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs b/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs index e94ce55..2964422 100644 --- a/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs +++ b/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.ActionHandlers.Hooks; +namespace StateR.ActionHandlers.Hooks; public interface IActionHandlerHooksCollection { diff --git a/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs b/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs index 4bf03b4..b1ecd82 100644 --- a/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs +++ b/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.ActionHandlers.Hooks; +namespace StateR.ActionHandlers.Hooks; public interface IAfterActionHook { diff --git a/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs b/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs index 91efef0..c69f7c8 100644 --- a/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs +++ b/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.ActionHandlers.Hooks; +namespace StateR.ActionHandlers.Hooks; public interface IBeforeActionHook { diff --git a/src/StateR/ActionHandlers/IActionHandler.cs b/src/StateR/ActionHandlers/IActionHandler.cs index 73dbb02..4f7747f 100644 --- a/src/StateR/ActionHandlers/IActionHandler.cs +++ b/src/StateR/ActionHandlers/IActionHandler.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.ActionHandlers; +namespace StateR.ActionHandlers; public interface IActionHandler where TAction : IAction diff --git a/src/StateR/AfterEffects/AfterEffectsManager.cs b/src/StateR/AfterEffects/AfterEffectsManager.cs index abfa3e6..72cdb58 100644 --- a/src/StateR/AfterEffects/AfterEffectsManager.cs +++ b/src/StateR/AfterEffects/AfterEffectsManager.cs @@ -1,10 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using StateR.AfterEffects.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace StateR.AfterEffects; diff --git a/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs b/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs index 908dc9b..1e6a603 100644 --- a/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs +++ b/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.AfterEffects.Hooks; +namespace StateR.AfterEffects.Hooks; public class AfterEffectHooksCollection : IAfterEffectHooksCollection { diff --git a/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs b/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs index e4b1dd8..8b8d888 100644 --- a/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs +++ b/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.AfterEffects.Hooks; +namespace StateR.AfterEffects.Hooks; public interface IAfterAfterEffectHook { diff --git a/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs b/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs index a8947f8..d0e4ce9 100644 --- a/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs +++ b/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.AfterEffects.Hooks; +namespace StateR.AfterEffects.Hooks; public interface IAfterEffectHooksCollection { diff --git a/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs b/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs index 7c2fdce..7a473b7 100644 --- a/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs +++ b/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.AfterEffects.Hooks; +namespace StateR.AfterEffects.Hooks; public interface IBeforeAfterEffectHook { diff --git a/src/StateR/AfterEffects/IAfterEffects.cs b/src/StateR/AfterEffects/IAfterEffects.cs index 2045900..b8761a2 100644 --- a/src/StateR/AfterEffects/IAfterEffects.cs +++ b/src/StateR/AfterEffects/IAfterEffects.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.AfterEffects; +namespace StateR.AfterEffects; public interface IAfterEffects where TAction : IAction diff --git a/src/StateR/DispatchContext.cs b/src/StateR/DispatchContext.cs index 9952c3a..cc7c097 100644 --- a/src/StateR/DispatchContext.cs +++ b/src/StateR/DispatchContext.cs @@ -1,7 +1,4 @@ -using System; -using System.Threading; - -namespace StateR; +namespace StateR; public class DispatchContext : IDispatchContext where TAction : IAction diff --git a/src/StateR/DispatchContextFactory.cs b/src/StateR/DispatchContextFactory.cs index d283d63..4238ffc 100644 --- a/src/StateR/DispatchContextFactory.cs +++ b/src/StateR/DispatchContextFactory.cs @@ -1,6 +1,4 @@ -using System.Threading; - -namespace StateR; +namespace StateR; public class DispatchContextFactory : IDispatchContextFactory { diff --git a/src/StateR/Dispatcher.cs b/src/StateR/Dispatcher.cs index f645d84..63d4483 100644 --- a/src/StateR/Dispatcher.cs +++ b/src/StateR/Dispatcher.cs @@ -1,10 +1,6 @@ using StateR.ActionHandlers; using StateR.AfterEffects; using StateR.Interceptors; -using StateR.Updaters; -using System; -using System.Threading; -using System.Threading.Tasks; namespace StateR; diff --git a/src/StateR/IDispatchContext.cs b/src/StateR/IDispatchContext.cs index bdde9ac..0f96e31 100644 --- a/src/StateR/IDispatchContext.cs +++ b/src/StateR/IDispatchContext.cs @@ -1,6 +1,4 @@ -using System.Threading; - -namespace StateR; +namespace StateR; public interface IDispatchContext where TAction : IAction diff --git a/src/StateR/IDispatchContextFactory.cs b/src/StateR/IDispatchContextFactory.cs index 047c772..6613960 100644 --- a/src/StateR/IDispatchContextFactory.cs +++ b/src/StateR/IDispatchContextFactory.cs @@ -1,6 +1,4 @@ -using System.Threading; - -namespace StateR; +namespace StateR; public interface IDispatchContextFactory { diff --git a/src/StateR/IDispatchManager.cs b/src/StateR/IDispatchManager.cs index 47d7f6e..3cdc889 100644 --- a/src/StateR/IDispatchManager.cs +++ b/src/StateR/IDispatchManager.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR; +namespace StateR; public interface IDispatchManager { diff --git a/src/StateR/IDispatcher.cs b/src/StateR/IDispatcher.cs index 8943fb5..6ef0bf0 100644 --- a/src/StateR/IDispatcher.cs +++ b/src/StateR/IDispatcher.cs @@ -1,9 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using System.Collections.Concurrent; -using System.Threading; -using System.Threading.Tasks; - -namespace StateR; +namespace StateR; public interface IDispatcher { diff --git a/src/StateR/IState.cs b/src/StateR/IState.cs index bac5b69..863d887 100644 --- a/src/StateR/IState.cs +++ b/src/StateR/IState.cs @@ -1,6 +1,4 @@ -using System; - -namespace StateR; +namespace StateR; public interface IState : ISubscribable where TState : StateBase diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 36037b9..2a2bd3d 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -1,7 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; namespace StateR; diff --git a/src/StateR/IStore.cs b/src/StateR/IStore.cs index 812262e..dc7f0a0 100644 --- a/src/StateR/IStore.cs +++ b/src/StateR/IStore.cs @@ -1,6 +1,4 @@ -using System; - -namespace StateR; +namespace StateR; public interface IStore : IDispatcher { diff --git a/src/StateR/ISubscribable.cs b/src/StateR/ISubscribable.cs index d43a147..594645b 100644 --- a/src/StateR/ISubscribable.cs +++ b/src/StateR/ISubscribable.cs @@ -1,6 +1,4 @@ -using System; - -namespace StateR; +namespace StateR; public interface ISubscribable { diff --git a/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs b/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs index ca21ca8..d57c3b8 100644 --- a/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs +++ b/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Interceptors.Hooks; +namespace StateR.Interceptors.Hooks; public interface IAfterInterceptorHook { diff --git a/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs b/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs index 2e9f907..218ab38 100644 --- a/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs +++ b/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Interceptors.Hooks; +namespace StateR.Interceptors.Hooks; public interface IBeforeInterceptorHook { diff --git a/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs b/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs index d25b72d..fa4b8f3 100644 --- a/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs +++ b/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Interceptors.Hooks; +namespace StateR.Interceptors.Hooks; public interface IInterceptorsHooksCollection { diff --git a/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs b/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs index f2b205f..ed254bc 100644 --- a/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs +++ b/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Interceptors.Hooks; +namespace StateR.Interceptors.Hooks; public class InterceptorsHooksCollection : IInterceptorsHooksCollection { diff --git a/src/StateR/Interceptors/IInterceptor.cs b/src/StateR/Interceptors/IInterceptor.cs index 68a0d50..0ca7b9d 100644 --- a/src/StateR/Interceptors/IInterceptor.cs +++ b/src/StateR/Interceptors/IInterceptor.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Interceptors; +namespace StateR.Interceptors; public interface IInterceptor where TAction : IAction diff --git a/src/StateR/Interceptors/InterceptorsManager.cs b/src/StateR/Interceptors/InterceptorsManager.cs index 5810e70..d23c6ea 100644 --- a/src/StateR/Interceptors/InterceptorsManager.cs +++ b/src/StateR/Interceptors/InterceptorsManager.cs @@ -1,10 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using StateR.Interceptors.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; namespace StateR.Interceptors; diff --git a/src/StateR/Internal/InitialState.cs b/src/StateR/Internal/InitialState.cs index 4e80649..5b36a56 100644 --- a/src/StateR/Internal/InitialState.cs +++ b/src/StateR/Internal/InitialState.cs @@ -1,6 +1,4 @@ -using System; - -namespace StateR.Internal; +namespace StateR.Internal; public class InitialState : IInitialState where TState : StateBase diff --git a/src/StateR/Internal/State.cs b/src/StateR/Internal/State.cs index 1c5b9f7..5fac678 100644 --- a/src/StateR/Internal/State.cs +++ b/src/StateR/Internal/State.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace StateR.Internal; +namespace StateR.Internal; public class State : IState where TState : StateBase diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 9075b10..d83df9e 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -1,7 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; namespace StateR.Internal; diff --git a/src/StateR/Internal/TypeExtensions.cs b/src/StateR/Internal/TypeExtensions.cs index 72e8a0c..c2bf96a 100644 --- a/src/StateR/Internal/TypeExtensions.cs +++ b/src/StateR/Internal/TypeExtensions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace StateR.Internal; +namespace StateR.Internal; public static class TypeExtensions { diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR/Internal/TypeScannerBuilderExtensions.cs index 6acea33..f96d985 100644 --- a/src/StateR/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR/Internal/TypeScannerBuilderExtensions.cs @@ -1,11 +1,6 @@ using StateR.ActionHandlers; using StateR.Updaters; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace StateR.Internal; diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index d42324c..728af72 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -9,12 +9,7 @@ using StateR.Internal; using StateR.Updaters; using StateR.Updaters.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace StateR; diff --git a/src/StateR/Store.cs b/src/StateR/Store.cs index 7f236f4..8d1e219 100644 --- a/src/StateR/Store.cs +++ b/src/StateR/Store.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; namespace StateR; diff --git a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs index 8ae0455..bcc066a 100644 --- a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs +++ b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Updaters.Hooks; +namespace StateR.Updaters.Hooks; public interface IAfterUpdateHook { diff --git a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs index 9b7e3c2..7bd32a7 100644 --- a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs +++ b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Updaters.Hooks; +namespace StateR.Updaters.Hooks; public interface IBeforeUpdateHook { diff --git a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs index 9559feb..1503e95 100644 --- a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs +++ b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs @@ -1,7 +1,4 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Updaters.Hooks; +namespace StateR.Updaters.Hooks; public interface IUpdateHooksCollection { diff --git a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs index 5ae929c..e8c56d8 100644 --- a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs +++ b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace StateR.Updaters.Hooks; +namespace StateR.Updaters.Hooks; public class UpdateHooksCollection : IUpdateHooksCollection { diff --git a/src/StateR/Updaters/IUpdater.cs b/src/StateR/Updaters/IUpdater.cs index 0f57079..ddeacdc 100644 --- a/src/StateR/Updaters/IUpdater.cs +++ b/src/StateR/Updaters/IUpdater.cs @@ -1,6 +1,4 @@ -using System; - -namespace StateR.Updaters; +namespace StateR.Updaters; public interface IUpdater where TAction : IAction diff --git a/src/StateR/Updaters/UpdaterActionHandler.cs b/src/StateR/Updaters/UpdaterActionHandler.cs index 1d5400a..33efa7b 100644 --- a/src/StateR/Updaters/UpdaterActionHandler.cs +++ b/src/StateR/Updaters/UpdaterActionHandler.cs @@ -1,9 +1,5 @@ using StateR.ActionHandlers; using StateR.Updaters.Hooks; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace StateR.Updaters; diff --git a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs index 8f13153..ff259c1 100644 --- a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs +++ b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs @@ -1,12 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using StateR.ActionHandlers.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Xunit; namespace StateR.ActionHandlers; diff --git a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs b/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs index c80fa5d..50a60fb 100644 --- a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs +++ b/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs @@ -1,10 +1,4 @@ using Moq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Xunit; namespace StateR.ActionHandlers.Hooks; diff --git a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs b/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs index 891e6a2..bd9a9b1 100644 --- a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs +++ b/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs @@ -2,12 +2,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; using StateR.AfterEffects.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Xunit; namespace StateR.AfterEffects; diff --git a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs b/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs index 2388a0e..baa3fea 100644 --- a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs +++ b/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs @@ -1,10 +1,4 @@ using Moq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Xunit; namespace StateR.AfterEffects.Hooks; diff --git a/test/StateR.Tests/DispatcherTest.cs b/test/StateR.Tests/DispatcherTest.cs index d5d55cb..dde69a4 100644 --- a/test/StateR.Tests/DispatcherTest.cs +++ b/test/StateR.Tests/DispatcherTest.cs @@ -1,13 +1,8 @@ -using System; -using Xunit; using Moq; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; -using StateR.Interceptors; -using StateR.Updaters; -using StateR.AfterEffects; using StateR.ActionHandlers; +using StateR.AfterEffects; +using StateR.Interceptors; +using Xunit; namespace StateR; diff --git a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs b/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs index 9cbff76..b9cb112 100644 --- a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs +++ b/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs @@ -1,11 +1,4 @@ -using Castle.DynamicProxy; -using Moq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; +using Moq; using Xunit; namespace StateR.Interceptors.Hooks; diff --git a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs b/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs index 35c8fa3..09e4521 100644 --- a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs +++ b/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs @@ -2,12 +2,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; using StateR.Interceptors.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Xunit; namespace StateR.Interceptors; diff --git a/test/StateR.Tests/Internal/StateTest.cs b/test/StateR.Tests/Internal/StateTest.cs index 4e68f2f..86fa8b6 100644 --- a/test/StateR.Tests/Internal/StateTest.cs +++ b/test/StateR.Tests/Internal/StateTest.cs @@ -1,9 +1,4 @@ using Moq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace StateR.Internal; diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 1972136..208f67b 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -1,9 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace StateR.Internal; diff --git a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs index aa92a9f..fd771ad 100644 --- a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs +++ b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs @@ -1,10 +1,4 @@ using Moq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Xunit; namespace StateR.Updaters.Hooks; diff --git a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs index 0da5228..cd65dba 100644 --- a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs +++ b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs @@ -1,12 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; -using Moq; +using Moq; using StateR.Updaters.Hooks; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Xunit; namespace StateR.Updaters; diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 34ec629..6e042f6 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace StateR; +namespace StateR; public class StatorStartupExtensionsTest { From 1744a5feab5c1b5ddf390d3e13d5bf257a3633d5 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 15:53:28 -0500 Subject: [PATCH 10/58] Update nullables --- src/StateR.Blazor/StatorComponentBase.cs | 15 +++++++++++++-- src/StateR.Blazor/StoreBasedStatorComponent.cs | 14 +++++++++++++- src/StateR.Experiments/AsyncLogic/AsyncError.cs | 8 ++++---- src/StateR/Internal/State.cs | 5 ++++- src/StateR/Internal/TypeExtensions.cs | 2 +- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/StateR.Blazor/StatorComponentBase.cs b/src/StateR.Blazor/StatorComponentBase.cs index e5315bc..8c2becc 100644 --- a/src/StateR.Blazor/StatorComponentBase.cs +++ b/src/StateR.Blazor/StatorComponentBase.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Components; +using System; +using System.Diagnostics.CodeAnalysis; namespace StateR.Blazor; @@ -7,11 +9,14 @@ public abstract class StatorComponentBase : ComponentBase, IDisposable private bool disposedValue; [Inject] - public IDispatcher Dispatcher { get; set; } + public IDispatcher? Dispatcher { get; set; } protected virtual async Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) where TAction : IAction - => await Dispatcher.DispatchAsync(action, cancellationToken); + { + GuardAgainstNullDispatcher(); + await Dispatcher.DispatchAsync(action, cancellationToken); + } protected virtual void Dispose(bool disposing) { @@ -34,4 +39,10 @@ public void Dispose() protected virtual void FreeManagedResources() { } protected virtual void FreeUnmanagedResources() { } + + [MemberNotNull(nameof(Dispatcher))] + protected void GuardAgainstNullDispatcher() + { + ArgumentNullException.ThrowIfNull(Dispatcher, nameof(Dispatcher)); + } } diff --git a/src/StateR.Blazor/StoreBasedStatorComponent.cs b/src/StateR.Blazor/StoreBasedStatorComponent.cs index 9a6cefb..f56544c 100644 --- a/src/StateR.Blazor/StoreBasedStatorComponent.cs +++ b/src/StateR.Blazor/StoreBasedStatorComponent.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Components; +using System.Diagnostics.CodeAnalysis; namespace StateR.Blazor; @@ -8,10 +9,11 @@ public abstract class StoreBasedStatorComponent : StatorComponentBase private readonly List _unsubscribeDelegates = new List(); [Inject] - public IStore Store { get; set; } + public IStore? Store { get; set; } protected virtual TState GetState() where TState : StateBase { + GuardAgainstNullStore(); Subscribe(); return Store.GetState(); } @@ -20,6 +22,7 @@ protected virtual void Subscribe() where TState : StateBase { if (!_subscribed) { + GuardAgainstNullStore(); _subscribed = true; Store.Subscribe(StateHasChanged); _unsubscribeDelegates.Add(() => Store.Unsubscribe(StateHasChanged)); @@ -33,4 +36,13 @@ protected override void FreeManagedResources() unsubscribe(); } } + + [MemberNotNull(nameof(Store))] + protected void GuardAgainstNullStore() + { + if (Store == null) + { + throw new ArgumentNullException(nameof(Store)); + } + } } diff --git a/src/StateR.Experiments/AsyncLogic/AsyncError.cs b/src/StateR.Experiments/AsyncLogic/AsyncError.cs index 7272abb..249dfb2 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncError.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncError.cs @@ -7,10 +7,10 @@ public class AsyncError public record Occured(IAction Action, AsyncState InitialState, AsyncState ActualState, Exception Exception) : IAction; public record State : StateBase { - public IAction Action { get; init; } - public AsyncState InitialState { get; init; } - public AsyncState ActualState { get; init; } - public Exception Exception { get; init; } + public IAction? Action { get; init; } + public AsyncState? InitialState { get; init; } + public AsyncState? ActualState { get; init; } + public Exception? Exception { get; init; } public bool HasException() => Exception != null; public bool HasActualState() => ActualState != null; diff --git a/src/StateR/Internal/State.cs b/src/StateR/Internal/State.cs index 5fac678..3ada24d 100644 --- a/src/StateR/Internal/State.cs +++ b/src/StateR/Internal/State.cs @@ -1,4 +1,6 @@ -namespace StateR.Internal; +using System.Diagnostics.CodeAnalysis; + +namespace StateR.Internal; public class State : IState where TState : StateBase @@ -11,6 +13,7 @@ public State(IInitialState initial) public TState Current { get; private set; } + [MemberNotNull(nameof(Current))] public void Set(TState state) { if (Current == state) diff --git a/src/StateR/Internal/TypeExtensions.cs b/src/StateR/Internal/TypeExtensions.cs index c2bf96a..f3ec689 100644 --- a/src/StateR/Internal/TypeExtensions.cs +++ b/src/StateR/Internal/TypeExtensions.cs @@ -4,10 +4,10 @@ public static class TypeExtensions { public static string GetStatorName(this Type type) { - //if (type == null) { throw new ArgumentNullException(nameof(type)); } //Console.WriteLine($"[GetStatorName] {type.FullName}"); if (type.IsGenericType) { + ArgumentNullException.ThrowIfNull(type.FullName, nameof(type.FullName)); //Console.WriteLine($"[GetStatorName](type) {type}"); //Console.WriteLine($"[GetStatorName](Name) {type.Name}"); //Console.WriteLine($"[GetStatorName](type.FullName) {type.FullName}"); From 38975a39999f205184eb3a778b69f8f0f4c6f290 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 16:16:22 -0500 Subject: [PATCH 11/58] Info fixes --- .editorconfig | 6 +++--- src/StateR.Blazor/StatorComponentBase.cs | 6 +++--- src/StateR.Blazor/StoreBasedStatorComponent.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index f2eec4f..240ca32 100644 --- a/.editorconfig +++ b/.editorconfig @@ -239,7 +239,7 @@ dotnet_diagnostic.IDE1006.severity = suggestion # private fields and Async methods naming are optional for tests. # this allows to declare `private sut` and name async test without the `Async` suffix. -[**/*.{Tests,IntegrationTests,FunctionalTests}/**.cs] -dotnet_naming_rule.private_fields_should_be_camel_case.severity = suggestion -dotnet_naming_rule.async_methods_should_end_in_async.severity = suggestion +[**/*.*Tests/**.cs] +dotnet_naming_rule.private_fields_should_be_camel_case.severity = none +dotnet_naming_rule.async_methods_should_end_in_async.severity = none diff --git a/src/StateR.Blazor/StatorComponentBase.cs b/src/StateR.Blazor/StatorComponentBase.cs index 8c2becc..d1f359a 100644 --- a/src/StateR.Blazor/StatorComponentBase.cs +++ b/src/StateR.Blazor/StatorComponentBase.cs @@ -6,7 +6,7 @@ namespace StateR.Blazor; public abstract class StatorComponentBase : ComponentBase, IDisposable { - private bool disposedValue; + private bool _disposedValue; [Inject] public IDispatcher? Dispatcher { get; set; } @@ -20,14 +20,14 @@ protected virtual async Task DispatchAsync(TAction action, Cancellation protected virtual void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { if (disposing) { FreeManagedResources(); } FreeUnmanagedResources(); - disposedValue = true; + _disposedValue = true; } } diff --git a/src/StateR.Blazor/StoreBasedStatorComponent.cs b/src/StateR.Blazor/StoreBasedStatorComponent.cs index f56544c..fd36ce1 100644 --- a/src/StateR.Blazor/StoreBasedStatorComponent.cs +++ b/src/StateR.Blazor/StoreBasedStatorComponent.cs @@ -6,7 +6,7 @@ namespace StateR.Blazor; public abstract class StoreBasedStatorComponent : StatorComponentBase { private bool _subscribed = false; - private readonly List _unsubscribeDelegates = new List(); + private readonly List _unsubscribeDelegates = new(); [Inject] public IStore? Store { get; set; } From 4d223cdf7f2b630caeab75ab2b235d4d679673f2 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 16:28:49 -0500 Subject: [PATCH 12/58] Update StateR.Blazor.Experiments to .NET 6 --- .../ReduxDevTools/ReduxDevToolsInterop.cs | 581 +++++++++--------- .../ReduxDevToolsStartupExtensions.cs | 37 +- .../StateR.Blazor.Experiments.csproj | 8 +- 3 files changed, 320 insertions(+), 306 deletions(-) diff --git a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs index 063b8a0..e7f1a8f 100644 --- a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs +++ b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs @@ -13,370 +13,383 @@ using System.Security.Cryptography.X509Certificates; using StateR.Updaters.Hooks; -namespace StateR.Blazor.ReduxDevTools +namespace StateR.Blazor.ReduxDevTools; + +public class DevToolsStateCollection : IEnumerable { - public class DevToolsStateCollection : IEnumerable + private readonly IEnumerable _states; + + public DevToolsStateCollection(IEnumerable states) { - private readonly IEnumerable _states; + _states = states ?? throw new ArgumentNullException(nameof(states)); + } - public DevToolsStateCollection(IEnumerable states) - { - _states = states ?? throw new ArgumentNullException(nameof(states)); - } + public IEnumerator GetEnumerator() + { + return _states.GetEnumerator(); + } - public IEnumerator GetEnumerator() - { - return _states.GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_states).GetEnumerator(); + } +} - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)_states).GetEnumerator(); - } +public class ReduxDevToolsInteropInitializer +{ + public IJSRuntime JSRuntime { get; } + public IStore Store { get; } + public DevToolsStateCollection States { get; } + + public ReduxDevToolsInteropInitializer( + IJSRuntime jsRuntime, + DevToolsStateCollection states, + IStore store + ) + { + JSRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); + States = states ?? throw new ArgumentNullException(nameof(states)); + Store = store ?? throw new ArgumentNullException(nameof(store)); } +} - public class ReduxDevToolsInteropInitializer +public class ReduxDevToolsInterop : IDisposable, IBeforeUpdateHook, IAfterUpdateHook +{ + public bool DevToolsBrowserPluginDetected { get; private set; } + private readonly IJSRuntime _jsRuntime; + private readonly DotNetObjectReference _dotNetRef; + private readonly IStore _store; + + private readonly DevToolsStateCollection _states; + + private int _historyIndex; + private int HistoryIndex { - public IJSRuntime JSRuntime { get; } - public IStore Store { get; } - public DevToolsStateCollection States { get; } - - public ReduxDevToolsInteropInitializer( - IJSRuntime jsRuntime, - DevToolsStateCollection states, - IStore store - ) + get => _historyIndex; + set { - JSRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); - States = states ?? throw new ArgumentNullException(nameof(states)); - Store = store ?? throw new ArgumentNullException(nameof(store)); + Console.WriteLine($"SET HistoryIndex = {value}"); + _historyIndex = value; } } - public class ReduxDevToolsInterop : IDisposable, IBeforeUpdateHook, IAfterUpdateHook + public ReduxDevToolsInterop(ReduxDevToolsInteropInitializer initializer) { - public bool DevToolsBrowserPluginDetected { get; private set; } - private readonly IJSRuntime _jsRuntime; - private readonly DotNetObjectReference _dotNetRef; - private readonly IStore _store; + if (initializer == null) { throw new ArgumentNullException(nameof(initializer)); } - private readonly DevToolsStateCollection _states; + _jsRuntime = initializer.JSRuntime; + _states = initializer.States; + _store = initializer.Store; + _dotNetRef = DotNetObjectReference.Create(this); + } - private int _historyIndex; - private int HistoryIndex + public async ValueTask InitializeAsync() + { + var states = GetAppState(); + foreach (var state in _states) { - get => _historyIndex; - set - { - Console.WriteLine($"SET HistoryIndex = {value}"); - _historyIndex = value; - } + _store.SubscribeToState(state, StateHasChanged); } - - public ReduxDevToolsInterop(ReduxDevToolsInteropInitializer initializer) + _history.Add(new TypeState(revertStateAction, executeAction) { - if (initializer == null) { throw new ArgumentNullException(nameof(initializer)); } - - _jsRuntime = initializer.JSRuntime; - _states = initializer.States; - _store = initializer.Store; - _dotNetRef = DotNetObjectReference.Create(this); - } + Status = TypeStateStatus.AfterUpdater, + }); + await _jsRuntime.InvokeAsync( + "__StateRDevTools__.init", + CancellationToken.None, + _dotNetRef, + states + ); + void revertStateAction() => Console.WriteLine("@@INIT (revertStateAction) | TODO: implement this"); + void executeAction() => Console.WriteLine("@@INIT (executeAction) | TODO: implement this"); + } - public async ValueTask InitializeAsync() + private void StateHasChanged() + { + if (!DevToolsBrowserPluginDetected) { - var states = GetAppState(); - foreach (var state in _states) - { - _store.SubscribeToState(state, StateHasChanged); - } - _history.Add(new TypeState(revertStateAction, executeAction) - { - Status = TypeStateStatus.AfterUpdater, - }); - await _jsRuntime.InvokeAsync( - "__StateRDevTools__.init", - CancellationToken.None, - _dotNetRef, - states - ); - void revertStateAction() => Console.WriteLine("@@INIT (revertStateAction) | TODO: implement this"); - void executeAction() => Console.WriteLine("@@INIT (executeAction) | TODO: implement this"); + return; } - private void StateHasChanged() - { - if (!DevToolsBrowserPluginDetected) - { - return; - } + } + [JSInvokable("DevToolsCallback")] + public Task DevToolsCallbackAsync(string messageAsJson) + { + if (string.IsNullOrWhiteSpace(messageAsJson)) + { + return Task.CompletedTask; } - [JSInvokable("DevToolsCallback")] - public Task DevToolsCallbackAsync(string messageAsJson) + var message = JsonSerializer.Deserialize(messageAsJson); + switch (message?.payload?.type) { - if (string.IsNullOrWhiteSpace(messageAsJson)) - { - return Task.CompletedTask; - } - - var message = JsonSerializer.Deserialize(messageAsJson); - switch (message?.payload?.type) - { - case "detected": - DevToolsBrowserPluginDetected = true; - break; - - case "COMMIT": - Console.WriteLine($"COMMIT | {messageAsJson}"); - break; - - case "JUMP_TO_STATE": - var jumpToState = JsonSerializer.Deserialize(messageAsJson); - if (HistoryIndex > jumpToState.payload.actionId) - { - HistoryIndex = jumpToState.payload.actionId; - _history[HistoryIndex].Undo(); - } - else - { - HistoryIndex = jumpToState.payload.actionId; - _history[HistoryIndex].Redo(); - } - break; - case "JUMP_TO_ACTION": - var jumpToAction = JsonSerializer.Deserialize(messageAsJson); - Console.WriteLine($"JUMP_TO_ACTION | _historyIndex: {HistoryIndex} | actionId: {jumpToAction.payload.actionId}"); - if (HistoryIndex > jumpToAction.payload.actionId) + case "detected": + DevToolsBrowserPluginDetected = true; + break; + + case "COMMIT": + Console.WriteLine($"COMMIT | {messageAsJson}"); + break; + + case "JUMP_TO_STATE": + var jumpToState = JsonSerializer.Deserialize(messageAsJson); + ArgumentNullException.ThrowIfNull(jumpToState, nameof(jumpToState)); + ArgumentNullException.ThrowIfNull(jumpToState.payload, nameof(jumpToState.payload)); + if (HistoryIndex > jumpToState.payload.actionId) + { + HistoryIndex = jumpToState.payload.actionId; + _history[HistoryIndex].Undo(); + } + else + { + HistoryIndex = jumpToState.payload.actionId; + _history[HistoryIndex].Redo(); + } + break; + case "JUMP_TO_ACTION": + var jumpToAction = JsonSerializer.Deserialize(messageAsJson); + ArgumentNullException.ThrowIfNull(jumpToAction, nameof(jumpToAction)); + ArgumentNullException.ThrowIfNull(jumpToAction.payload, nameof(jumpToAction.payload)); + Console.WriteLine($"JUMP_TO_ACTION | _historyIndex: {HistoryIndex} | actionId: {jumpToAction.payload.actionId}"); + if (HistoryIndex > jumpToAction.payload.actionId) + { + for (var i = HistoryIndex; i >= jumpToAction.payload.actionId; i--) { - for (var i = HistoryIndex; i >= jumpToAction.payload.actionId; i--) - { - Console.WriteLine($"JUMP_TO_ACTION | RevertState {i}"); - _history[i].Undo(); - } + Console.WriteLine($"JUMP_TO_ACTION | RevertState {i}"); + _history[i].Undo(); } - else + } + else + { + for (var i = HistoryIndex + 1; i <= jumpToAction.payload.actionId; i++) { - for (var i = HistoryIndex + 1; i <= jumpToAction.payload.actionId; i++) - { - Console.WriteLine($"JUMP_TO_ACTION | RevertState {i}"); - _history[i].Redo(); - } + Console.WriteLine($"JUMP_TO_ACTION | RevertState {i}"); + _history[i].Redo(); } - HistoryIndex = jumpToAction.payload.actionId; - break; - } - return Task.CompletedTask; + } + HistoryIndex = jumpToAction.payload.actionId; + break; } + return Task.CompletedTask; + } - void IDisposable.Dispose() + void IDisposable.Dispose() + { + _dotNetRef.Dispose(); + GC.SuppressFinalize(this); + } + + private Dictionary GetAppState() + { + var states = new Dictionary(); + foreach (var s in _states) { - _dotNetRef.Dispose(); + var value = _store.GetStateValue(s); + var name = s.GetStateName(); + states.Add(name, value); } - private Dictionary GetAppState() - { - var states = new Dictionary(); - foreach (var s in _states) - { - var value = _store.GetStateValue(s); - var name = s.GetStateName(); - states.Add(name, value); - } + return states; + } - return states; + private class TypeState + { + private readonly Action _undoStateAction; + private readonly Action _redoStateAction; + public TypeState(Action undoStateAction, Action redoStateAction) + { + _undoStateAction = undoStateAction ?? throw new ArgumentNullException(nameof(undoStateAction)); + _redoStateAction = redoStateAction ?? throw new ArgumentNullException(nameof(redoStateAction)); } + public object? ContextRef { get; init; } + public TypeStateStatus Status { get; set; } - private class TypeState { - private Action _undoStateAction; - private Action _redoStateAction; - public TypeState(Action undoStateAction, Action redoStateAction) + public void Undo() + { + if (Status == TypeStateStatus.AfterUpdater) { - _undoStateAction = undoStateAction ?? throw new ArgumentNullException(nameof(undoStateAction)); - _redoStateAction = redoStateAction ?? throw new ArgumentNullException(nameof(redoStateAction)); + _undoStateAction(); } - public object ContextRef { get; init; } - public TypeStateStatus Status { get; set; } - - public void Undo() - { - if (Status == TypeStateStatus.AfterUpdater) - { - _undoStateAction(); - } - } - public void Redo() + } + public void Redo() + { + if (Status == TypeStateStatus.AfterUpdater) { - if (Status == TypeStateStatus.AfterUpdater) - { - _redoStateAction(); - } + _redoStateAction(); } } + } - private enum TypeStateStatus - { - Unknown, - BeforeUpdater, - AfterUpdater - } + private enum TypeStateStatus + { + Unknown, + BeforeUpdater, + AfterUpdater + } - private List _history { get; } = new(); + private List _history { get; } = new(); - public Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase + public Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase + { + var action = context.Action; + var current = state.Current; + var next = updater.Update(action, current); + _history.Add(new TypeState(undoStateAction, redoStateAction) { - var action = context.Action; - var current = state.Current; - var next = updater.Update(action, current); - _history.Add(new TypeState(undoStateAction, redoStateAction) - { - ContextRef = updater, - Status = TypeStateStatus.BeforeUpdater, - }); - HistoryIndex = _history.Count - 1; - return Task.CompletedTask; + ContextRef = updater, + Status = TypeStateStatus.BeforeUpdater, + }); + HistoryIndex = _history.Count - 1; + return Task.CompletedTask; - void undoStateAction() - { - Console.WriteLine($"Undo {current.GetType().GetStatorName()} to {current} from action: {typeof(TAction).GetStatorName()} with updater {updater.GetType().GetStatorName()}"); - state.Set(current); - state.Notify(); - } - - void redoStateAction() - { - Console.WriteLine($"Redo {current.GetType().GetStatorName()} to {next} from action: {typeof(TAction).GetStatorName()} with updater {updater.GetType().GetStatorName()}"); - state.Set(next); - state.Notify(); - } + void undoStateAction() + { + Console.WriteLine($"Undo {current.GetType().GetStatorName()} to {current} from action: {typeof(TAction).GetStatorName()} with updater {updater.GetType().GetStatorName()}"); + state.Set(current); + state.Notify(); } - public async Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase + void redoStateAction() { - var serializedActionInfo = JsonSerializer.Serialize(new ActionInfo(context.Action)); - var states = GetAppState(); - await _jsRuntime.InvokeAsync( - "__StateRDevTools__.dispatch", - CancellationToken.None, - serializedActionInfo, - states - ); - _history.LastOrDefault(h => h.ContextRef == updater).Status = TypeStateStatus.AfterUpdater; + Console.WriteLine($"Redo {current.GetType().GetStatorName()} to {next} from action: {typeof(TAction).GetStatorName()} with updater {updater.GetType().GetStatorName()}"); + state.Set(next); + state.Notify(); } } - public class BaseCallbackObject - where TPayload : BasePayload + public async Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase { - public string type { get; set; } - public TPayload payload { get; set; } + var serializedActionInfo = JsonSerializer.Serialize(new ActionInfo(context.Action)); + var states = GetAppState(); + await _jsRuntime.InvokeAsync( + "__StateRDevTools__.dispatch", + CancellationToken.None, + serializedActionInfo, + states + ); + var typeState = _history.LastOrDefault(h => h.ContextRef == updater); + ArgumentNullException.ThrowIfNull(typeState, nameof(typeState)); + typeState.Status = TypeStateStatus.AfterUpdater; + } +} - public class BaseCallbackObject : BaseCallbackObject { } +public class BaseCallbackObject + where TPayload : BasePayload +{ + public string? type { get; set; } + public TPayload? payload { get; set; } +} + +public class BaseCallbackObject : BaseCallbackObject { } + +public class BasePayload +{ + public string? type { get; set; } +} + +public class JumpToStateCallback : BaseCallbackObject +{ + public string? state { get; set; } +} +public class JumpToStatePayload : BasePayload +{ + public int index { get; set; } + public int actionId { get; set; } +} +public class ActionInfo +{ + public string type { get; } + public object payload { get; } - public class BasePayload + public ActionInfo(object action) { - public string type { get; set; } + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + type = action.GetType().GetStatorName(); + payload = action; } +} - public class JumpToStateCallback : BaseCallbackObject +public static class StoreExtensions +{ + public static void SubscribeToState(this IStore store, Type state, Action stateHasChangedDelegate) { - public string state { get; set; } + var genericMethod = GetStoreMethod(store, state, nameof(IStore.Subscribe)); + genericMethod.Invoke(store, new[] { stateHasChangedDelegate }); } - public class JumpToStatePayload : BasePayload + + public static void UnsubscribeToState(this IStore store, Type state, Action stateHasChangedDelegate) { - public int index { get; set; } - public int actionId { get; set; } + var genericMethod = GetStoreMethod(store, state, nameof(IStore.Unsubscribe)); + genericMethod.Invoke(store, new[] { stateHasChangedDelegate }); } - public class ActionInfo - { - public string type { get; } - public object payload { get; } - - public ActionInfo(object action) - { - if (action == null) - throw new ArgumentNullException(nameof(action)); - type = action.GetType().GetStatorName(); - payload = action; - } + public static object GetStateValue(this IStore store, Type state) + { + var genericMethod = GetStoreMethod(store, state, nameof(IStore.GetState)); + var result = genericMethod.Invoke(store, null); + ArgumentNullException.ThrowIfNull(result, nameof(result)); + return result; } - public static class StoreExtensions + private static MethodInfo GetStoreMethod(IStore store, Type state, string methodName) { - public static void SubscribeToState(this IStore store, Type state, Action stateHasChangedDelegate) + var iStateType = typeof(IState<>); + if (!state.IsGenericType) { - var genericMethod = GetStoreMethod(store, state, nameof(IStore.Subscribe)); - genericMethod.Invoke(store, new[] { stateHasChangedDelegate }); + throw new InvalidStateTypeException(state); } - - public static void UnsubscribeToState(this IStore store, Type state, Action stateHasChangedDelegate) - { - var genericMethod = GetStoreMethod(store, state, nameof(IStore.Unsubscribe)); - genericMethod.Invoke(store, new[] { stateHasChangedDelegate }); - } - - public static object GetStateValue(this IStore store, Type state) + if (!state.GetGenericTypeDefinition().IsAssignableTo(iStateType)) { - var genericMethod = GetStoreMethod(store, state, nameof(IStore.GetState)); - var result = genericMethod.Invoke(store, null); - return result; + throw new InvalidStateTypeException(state); } + var getState = store.GetType().GetMethod(methodName); + ArgumentNullException.ThrowIfNull(getState, nameof(getState)); + var stateArgs = state.GetGenericArguments()[0]; + var genericMethod = getState.MakeGenericMethod(stateArgs); + return genericMethod; + } - private static MethodInfo GetStoreMethod(IStore store, Type state, string methodName) + public static string GetStateName(this Type state) + { + var iStateType = typeof(IState<>); + if (!state.IsGenericType) { - var iStateType = typeof(IState<>); - if (!state.IsGenericType) - { - throw new InvalidStateTypeException(state); - } - if (!state.GetGenericTypeDefinition().IsAssignableTo(iStateType)) - { - throw new InvalidStateTypeException(state); - } - var getState = store.GetType().GetMethod(methodName); - var stateArgs = state.GetGenericArguments()[0]; - var genericMethod = getState.MakeGenericMethod(stateArgs); - return genericMethod; + throw new InvalidStateTypeException(state); } - - public static string GetStateName(this Type state) + if (!state.GetGenericTypeDefinition().IsAssignableTo(iStateType)) { - var iStateType = typeof(IState<>); - if (!state.IsGenericType) - { - throw new InvalidStateTypeException(state); - } - if (!state.GetGenericTypeDefinition().IsAssignableTo(iStateType)) - { - throw new InvalidStateTypeException(state); - } - var stateArgs = state.GetGenericArguments()[0]; - //return stateArgs.GetStatorName(); - //var featuresIndex = stateArgs.FullName.IndexOf("Features."); - //if(featuresIndex > -1) - //{ - // var dotIndex = stateArgs.FullName.IndexOf('.', featuresIndex); - // return stateArgs.FullName.Substring(dotIndex + 1); - //} - return stateArgs.FullName; + throw new InvalidStateTypeException(state); } + var stateArgs = state.GetGenericArguments()[0]; + //return stateArgs.GetStatorName(); + //var featuresIndex = stateArgs.FullName.IndexOf("Features."); + //if(featuresIndex > -1) + //{ + // var dotIndex = stateArgs.FullName.IndexOf('.', featuresIndex); + // return stateArgs.FullName.Substring(dotIndex + 1); + //} + ArgumentNullException.ThrowIfNull(stateArgs.FullName, nameof(stateArgs.FullName)); + return stateArgs.FullName; } +} - public class InvalidStateTypeException : Exception +public class InvalidStateTypeException : Exception +{ + public InvalidStateTypeException(Type stateType) + : base($"The type {stateType} does not implement {typeof(IState<>)}") { - public InvalidStateTypeException(Type stateType) - : base($"The type {stateType} does not implement {typeof(IState<>)}") - { - StateType = stateType ?? throw new ArgumentNullException(nameof(stateType)); - } - - public Type StateType { get; } + StateType = stateType ?? throw new ArgumentNullException(nameof(stateType)); } + + public Type StateType { get; } } diff --git a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs index 6e0b786..5baae8d 100644 --- a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs +++ b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsStartupExtensions.cs @@ -8,28 +8,27 @@ using System.Text; using System.Threading.Tasks; -namespace StateR.Blazor.ReduxDevTools +namespace StateR.Blazor.ReduxDevTools; + +public static class ReduxDevToolsStartupExtensions { - public static class ReduxDevToolsStartupExtensions + public static IStatorBuilder AddReduxDevTools(this IStatorBuilder builder) { - public static IStatorBuilder AddReduxDevTools(this IStatorBuilder builder) + builder.Services.AddSingleton(sp => { - builder.Services.AddSingleton(sp => + var states = new List(); + var iStateType = typeof(IState<>); + foreach (var state in builder.States.OrderBy(x => x.Name)) { - var states = new List(); - var iStateType = typeof(IState<>); - foreach (var state in builder.States.OrderBy(x => x.Name)) - { - var x = iStateType.MakeGenericType(state); - states.Add(x); - } - return new DevToolsStateCollection(states); - }); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetService()); - builder.Services.AddSingleton(sp => sp.GetService()); - return builder; - } + var x = iStateType.MakeGenericType(state); + states.Add(x); + } + return new DevToolsStateCollection(states); + }); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(sp => sp.GetRequiredService()); + builder.Services.AddSingleton(sp => sp.GetRequiredService()); + return builder; } } diff --git a/src/StateR.Blazor.Experiments/StateR.Blazor.Experiments.csproj b/src/StateR.Blazor.Experiments/StateR.Blazor.Experiments.csproj index 9037beb..31ad073 100644 --- a/src/StateR.Blazor.Experiments/StateR.Blazor.Experiments.csproj +++ b/src/StateR.Blazor.Experiments/StateR.Blazor.Experiments.csproj @@ -1,12 +1,14 @@  - net5.0 - StateR.Blazor + net6.0 + enable + enable + StateR.Blazor - + From 4ab6cb271ed3d0c90d063f60d3a4f4451a4fbd51 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 24 Dec 2021 16:32:20 -0500 Subject: [PATCH 13/58] Remove `Use range operator` messages --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index 240ca32..cb956f2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -237,6 +237,9 @@ dotnet_naming_style.interfaces_style.required_prefix = I # This fix record class naming issues; to be targetted at records only when possible dotnet_diagnostic.IDE1006.severity = suggestion +# IDE0057: Use range operator +dotnet_diagnostic.IDE0057.severity = none + # private fields and Async methods naming are optional for tests. # this allows to declare `private sut` and name async test without the `Async` suffix. [**/*.*Tests/**.cs] From 2d8a8963814e190e6045ce70fa70527475f4e0b2 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Mon, 27 Dec 2021 19:54:36 -0500 Subject: [PATCH 14/58] Update GitVersioning and SourceLink --- src/Directory.Build.props | 7 ++----- src/StateR.Blazor/StateR.Blazor.csproj | 5 ----- src/StateR.Experiments/StateR.Experiments.csproj | 5 ----- src/StateR/StateR.csproj | 5 ----- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 493e011..7fe8f26 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -22,11 +22,8 @@ - - - 3.3.37 - all - + + \ No newline at end of file diff --git a/src/StateR.Blazor/StateR.Blazor.csproj b/src/StateR.Blazor/StateR.Blazor.csproj index 041ef5b..fe8c455 100644 --- a/src/StateR.Blazor/StateR.Blazor.csproj +++ b/src/StateR.Blazor/StateR.Blazor.csproj @@ -19,11 +19,6 @@ - - - - - diff --git a/src/StateR.Experiments/StateR.Experiments.csproj b/src/StateR.Experiments/StateR.Experiments.csproj index 1e9d59b..b91d2cd 100644 --- a/src/StateR.Experiments/StateR.Experiments.csproj +++ b/src/StateR.Experiments/StateR.Experiments.csproj @@ -11,9 +11,4 @@ - - - - - diff --git a/src/StateR/StateR.csproj b/src/StateR/StateR.csproj index 3bb2357..1e9d562 100644 --- a/src/StateR/StateR.csproj +++ b/src/StateR/StateR.csproj @@ -14,9 +14,4 @@ - - - - - From e77b1eecd96a986bfe01fd2853da7dc961d54845 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Mon, 27 Dec 2021 19:54:54 -0500 Subject: [PATCH 15/58] Add counter sample project --- StateR.sln | 41 +- .../CounterApp.Tests/CounterApp.Tests.csproj | 28 + .../CounterApp/CounterApp.Tests/UnitTest1.cs | 12 + samples/CounterApp/CounterApp/App.razor | 12 + .../CounterApp/CounterApp/CounterApp.csproj | 14 + .../CounterApp/CounterApp/Pages/Counter.razor | 18 + .../CounterApp/Pages/FetchData.razor | 57 ++ .../CounterApp/CounterApp/Pages/Index.razor | 9 + samples/CounterApp/CounterApp/Program.cs | 13 + .../CounterApp/Properties/launchSettings.json | 30 + .../CounterApp/Shared/MainLayout.razor | 17 + .../CounterApp/Shared/MainLayout.razor.css | 81 +++ .../CounterApp/Shared/NavMenu.razor | 39 ++ .../CounterApp/Shared/NavMenu.razor.css | 62 ++ .../CounterApp/Shared/SurveyPrompt.razor | 16 + samples/CounterApp/CounterApp/_Imports.razor | 10 + .../CounterApp/CounterApp/wwwroot/css/app.css | 64 +++ .../wwwroot/css/bootstrap/bootstrap.min.css | 7 + .../css/bootstrap/bootstrap.min.css.map | 1 + .../wwwroot/css/open-iconic/FONT-LICENSE | 86 +++ .../wwwroot/css/open-iconic/ICON-LICENSE | 21 + .../wwwroot/css/open-iconic/README.md | 114 ++++ .../font/css/open-iconic-bootstrap.min.css | 1 + .../open-iconic/font/fonts/open-iconic.eot | Bin 0 -> 28196 bytes .../open-iconic/font/fonts/open-iconic.otf | Bin 0 -> 20996 bytes .../open-iconic/font/fonts/open-iconic.svg | 543 ++++++++++++++++++ .../open-iconic/font/fonts/open-iconic.ttf | Bin 0 -> 28028 bytes .../open-iconic/font/fonts/open-iconic.woff | Bin 0 -> 14984 bytes .../CounterApp/CounterApp/wwwroot/favicon.ico | Bin 0 -> 5430 bytes .../CounterApp/wwwroot/icon-192.png | Bin 0 -> 2626 bytes .../CounterApp/CounterApp/wwwroot/index.html | 25 + .../wwwroot/sample-data/weather.json | 27 + 32 files changed, 1345 insertions(+), 3 deletions(-) create mode 100644 samples/CounterApp/CounterApp.Tests/CounterApp.Tests.csproj create mode 100644 samples/CounterApp/CounterApp.Tests/UnitTest1.cs create mode 100644 samples/CounterApp/CounterApp/App.razor create mode 100644 samples/CounterApp/CounterApp/CounterApp.csproj create mode 100644 samples/CounterApp/CounterApp/Pages/Counter.razor create mode 100644 samples/CounterApp/CounterApp/Pages/FetchData.razor create mode 100644 samples/CounterApp/CounterApp/Pages/Index.razor create mode 100644 samples/CounterApp/CounterApp/Program.cs create mode 100644 samples/CounterApp/CounterApp/Properties/launchSettings.json create mode 100644 samples/CounterApp/CounterApp/Shared/MainLayout.razor create mode 100644 samples/CounterApp/CounterApp/Shared/MainLayout.razor.css create mode 100644 samples/CounterApp/CounterApp/Shared/NavMenu.razor create mode 100644 samples/CounterApp/CounterApp/Shared/NavMenu.razor.css create mode 100644 samples/CounterApp/CounterApp/Shared/SurveyPrompt.razor create mode 100644 samples/CounterApp/CounterApp/_Imports.razor create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/app.css create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/bootstrap/bootstrap.min.css create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/bootstrap/bootstrap.min.css.map create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/open-iconic/FONT-LICENSE create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/open-iconic/ICON-LICENSE create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/open-iconic/README.md create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/open-iconic/font/fonts/open-iconic.eot create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/open-iconic/font/fonts/open-iconic.otf create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/open-iconic/font/fonts/open-iconic.svg create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf create mode 100644 samples/CounterApp/CounterApp/wwwroot/css/open-iconic/font/fonts/open-iconic.woff create mode 100644 samples/CounterApp/CounterApp/wwwroot/favicon.ico create mode 100644 samples/CounterApp/CounterApp/wwwroot/icon-192.png create mode 100644 samples/CounterApp/CounterApp/wwwroot/index.html create mode 100644 samples/CounterApp/CounterApp/wwwroot/sample-data/weather.json diff --git a/StateR.sln b/StateR.sln index ad3a263..1f67bf7 100644 --- a/StateR.sln +++ b/StateR.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30509.190 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.31911.260 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F0F6A2CA-0972-43BD-B777-B5656DFE20C3}" EndProject @@ -13,10 +13,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{372F8647-9 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateR.Tests", "test\StateR.Tests\StateR.Tests.csproj", "{3276327A-63D9-4BEA-BBB8-D3AFF841CC67}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateR.Experiments", "src\StateR.Experiments\StateR.Experiments.csproj", "{0EB20F7F-8AA1-48E2-9489-6975566469AF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateR.Experiments", "src\StateR.Experiments\StateR.Experiments.csproj", "{0EB20F7F-8AA1-48E2-9489-6975566469AF}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateR.Blazor.Experiments", "src\StateR.Blazor.Experiments\StateR.Blazor.Experiments.csproj", "{5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{22B777AC-AFAE-422A-B4CD-48C907251D9E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CounterApp", "CounterApp", "{E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CounterApp", "samples\CounterApp\CounterApp\CounterApp.csproj", "{99926EB0-84F9-4906-8F7E-4E1873A403EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CounterApp.Tests", "samples\CounterApp\CounterApp.Tests\CounterApp.Tests.csproj", "{FBDEBA94-7F63-4CB5-AC13-4D0874730316}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -87,6 +95,30 @@ Global {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Release|x64.Build.0 = Release|Any CPU {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Release|x86.ActiveCfg = Release|Any CPU {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Release|x86.Build.0 = Release|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Debug|x64.ActiveCfg = Debug|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Debug|x64.Build.0 = Debug|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Debug|x86.ActiveCfg = Debug|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Debug|x86.Build.0 = Debug|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Release|Any CPU.Build.0 = Release|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Release|x64.ActiveCfg = Release|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Release|x64.Build.0 = Release|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Release|x86.ActiveCfg = Release|Any CPU + {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Release|x86.Build.0 = Release|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Debug|x64.ActiveCfg = Debug|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Debug|x64.Build.0 = Debug|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Debug|x86.ActiveCfg = Debug|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Debug|x86.Build.0 = Debug|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|Any CPU.Build.0 = Release|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x64.ActiveCfg = Release|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x64.Build.0 = Release|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x86.ActiveCfg = Release|Any CPU + {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -97,6 +129,9 @@ Global {3276327A-63D9-4BEA-BBB8-D3AFF841CC67} = {372F8647-9F67-4176-B0C1-B0E5CAD87C9E} {0EB20F7F-8AA1-48E2-9489-6975566469AF} = {F0F6A2CA-0972-43BD-B777-B5656DFE20C3} {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D} = {F0F6A2CA-0972-43BD-B777-B5656DFE20C3} + {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} = {22B777AC-AFAE-422A-B4CD-48C907251D9E} + {99926EB0-84F9-4906-8F7E-4E1873A403EB} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} + {FBDEBA94-7F63-4CB5-AC13-4D0874730316} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6ADD1933-C449-475E-9409-AD333C1C48A0} diff --git a/samples/CounterApp/CounterApp.Tests/CounterApp.Tests.csproj b/samples/CounterApp/CounterApp.Tests/CounterApp.Tests.csproj new file mode 100644 index 0000000..2003055 --- /dev/null +++ b/samples/CounterApp/CounterApp.Tests/CounterApp.Tests.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + enable + CounterApp + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/samples/CounterApp/CounterApp.Tests/UnitTest1.cs b/samples/CounterApp/CounterApp.Tests/UnitTest1.cs new file mode 100644 index 0000000..11c6e34 --- /dev/null +++ b/samples/CounterApp/CounterApp.Tests/UnitTest1.cs @@ -0,0 +1,12 @@ +using Xunit; + +namespace test; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/App.razor b/samples/CounterApp/CounterApp/App.razor new file mode 100644 index 0000000..6fd3ed1 --- /dev/null +++ b/samples/CounterApp/CounterApp/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/samples/CounterApp/CounterApp/CounterApp.csproj b/samples/CounterApp/CounterApp/CounterApp.csproj new file mode 100644 index 0000000..1ee4a2f --- /dev/null +++ b/samples/CounterApp/CounterApp/CounterApp.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/samples/CounterApp/CounterApp/Pages/Counter.razor b/samples/CounterApp/CounterApp/Pages/Counter.razor new file mode 100644 index 0000000..ef23cb3 --- /dev/null +++ b/samples/CounterApp/CounterApp/Pages/Counter.razor @@ -0,0 +1,18 @@ +@page "/counter" + +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/samples/CounterApp/CounterApp/Pages/FetchData.razor b/samples/CounterApp/CounterApp/Pages/FetchData.razor new file mode 100644 index 0000000..7d004a5 --- /dev/null +++ b/samples/CounterApp/CounterApp/Pages/FetchData.razor @@ -0,0 +1,57 @@ +@page "/fetchdata" +@inject HttpClient Http + +Weather forecast + +

Weather forecast

+ +

This component demonstrates fetching data from the server.

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ + + + + + + + + + + + @foreach (var forecast in forecasts) + { + + + + + + + } + +
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
+} + +@code { + private WeatherForecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + forecasts = await Http.GetFromJsonAsync("sample-data/weather.json"); + } + + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public string? Summary { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/samples/CounterApp/CounterApp/Pages/Index.razor b/samples/CounterApp/CounterApp/Pages/Index.razor new file mode 100644 index 0000000..6085c4a --- /dev/null +++ b/samples/CounterApp/CounterApp/Pages/Index.razor @@ -0,0 +1,9 @@ +@page "/" + +Index + +

Hello, world!

+ +Welcome to your new app. + + diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs new file mode 100644 index 0000000..18de684 --- /dev/null +++ b/samples/CounterApp/CounterApp/Program.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using CounterApp; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +await builder.Build().RunAsync(); + +public partial class Program { } \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Properties/launchSettings.json b/samples/CounterApp/CounterApp/Properties/launchSettings.json new file mode 100644 index 0000000..099b875 --- /dev/null +++ b/samples/CounterApp/CounterApp/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:51685", + "sslPort": 44342 + } + }, + "profiles": { + "CounterApp": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7240;http://localhost:5041", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/CounterApp/CounterApp/Shared/MainLayout.razor b/samples/CounterApp/CounterApp/Shared/MainLayout.razor new file mode 100644 index 0000000..839b8fe --- /dev/null +++ b/samples/CounterApp/CounterApp/Shared/MainLayout.razor @@ -0,0 +1,17 @@ +@inherits LayoutComponentBase + +
+ + +
+
+ About +
+ +
+ @Body +
+
+
diff --git a/samples/CounterApp/CounterApp/Shared/MainLayout.razor.css b/samples/CounterApp/CounterApp/Shared/MainLayout.razor.css new file mode 100644 index 0000000..c865427 --- /dev/null +++ b/samples/CounterApp/CounterApp/Shared/MainLayout.razor.css @@ -0,0 +1,81 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row:not(.auth) { + display: none; + } + + .top-row.auth { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} diff --git a/samples/CounterApp/CounterApp/Shared/NavMenu.razor b/samples/CounterApp/CounterApp/Shared/NavMenu.razor new file mode 100644 index 0000000..2925aad --- /dev/null +++ b/samples/CounterApp/CounterApp/Shared/NavMenu.razor @@ -0,0 +1,39 @@ + + +
+ +
+ +@code { + private bool collapseNavMenu = true; + + private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } +} diff --git a/samples/CounterApp/CounterApp/Shared/NavMenu.razor.css b/samples/CounterApp/CounterApp/Shared/NavMenu.razor.css new file mode 100644 index 0000000..acc5f9f --- /dev/null +++ b/samples/CounterApp/CounterApp/Shared/NavMenu.razor.css @@ -0,0 +1,62 @@ +.navbar-toggler { + background-color: rgba(255, 255, 255, 0.1); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.oi { + width: 2rem; + font-size: 1.1rem; + vertical-align: text-top; + top: -2px; +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep a { + color: #d7d7d7; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.25); + color: white; +} + +.nav-item ::deep a:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .collapse { + /* Never collapse the sidebar for wide screens */ + display: block; + } +} diff --git a/samples/CounterApp/CounterApp/Shared/SurveyPrompt.razor b/samples/CounterApp/CounterApp/Shared/SurveyPrompt.razor new file mode 100644 index 0000000..962027f --- /dev/null +++ b/samples/CounterApp/CounterApp/Shared/SurveyPrompt.razor @@ -0,0 +1,16 @@ +
+ + @Title + + + Please take our + brief survey + + and tell us what you think. +
+ +@code { + // Demonstrates how a parent component can supply parameters + [Parameter] + public string? Title { get; set; } +} diff --git a/samples/CounterApp/CounterApp/_Imports.razor b/samples/CounterApp/CounterApp/_Imports.razor new file mode 100644 index 0000000..891c5d7 --- /dev/null +++ b/samples/CounterApp/CounterApp/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using CounterApp +@using CounterApp.Shared diff --git a/samples/CounterApp/CounterApp/wwwroot/css/app.css b/samples/CounterApp/CounterApp/wwwroot/css/app.css new file mode 100644 index 0000000..9cd148f --- /dev/null +++ b/samples/CounterApp/CounterApp/wwwroot/css/app.css @@ -0,0 +1,64 @@ +@import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); + +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +h1:focus { + outline: none; +} + +a, .btn-link { + color: #0071c1; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.content { + padding-top: 1.1rem; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid red; +} + +.validation-message { + color: red; +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } diff --git a/samples/CounterApp/CounterApp/wwwroot/css/bootstrap/bootstrap.min.css b/samples/CounterApp/CounterApp/wwwroot/css/bootstrap/bootstrap.min.css new file mode 100644 index 0000000..02ae65b --- /dev/null +++ b/samples/CounterApp/CounterApp/wwwroot/css/bootstrap/bootstrap.min.css @@ -0,0 +1,7 @@ +@charset "UTF-8";/*! + * Bootstrap v5.1.0 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/wwwroot/css/bootstrap/bootstrap.min.css.map b/samples/CounterApp/CounterApp/wwwroot/css/bootstrap/bootstrap.min.css.map new file mode 100644 index 0000000..afcd9e3 --- /dev/null +++ b/samples/CounterApp/CounterApp/wwwroot/css/bootstrap/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_border-radius.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/_containers.scss","../../scss/mixins/_container.scss","../../scss/mixins/_breakpoints.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/_tables.scss","../../scss/mixins/_table-variants.scss","../../scss/forms/_labels.scss","../../scss/forms/_form-text.scss","../../scss/forms/_form-control.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_gradients.scss","../../scss/forms/_form-select.scss","../../scss/forms/_form-check.scss","../../scss/forms/_form-range.scss","../../scss/forms/_floating-labels.scss","../../scss/forms/_input-group.scss","../../scss/mixins/_forms.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/_button-group.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_accordion.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/mixins/_backdrop.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/_offcanvas.scss","../../scss/_placeholders.scss","../../scss/helpers/_colored-links.scss","../../scss/helpers/_ratio.scss","../../scss/helpers/_position.scss","../../scss/helpers/_stacks.scss","../../scss/helpers/_visually-hidden.scss","../../scss/mixins/_visually-hidden.scss","../../scss/helpers/_stretched-link.scss","../../scss/helpers/_text-truncation.scss","../../scss/mixins/_text-truncate.scss","../../scss/helpers/_vr.scss","../../scss/mixins/_utilities.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap v5.1.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n// scss-docs-start import-stack\n// Configuration\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"utilities\";\n\n// Layout & components\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"containers\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"accordion\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"alert\";\n@import \"progress\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"offcanvas\";\n@import \"placeholders\";\n\n// Helpers\n@import \"helpers\";\n\n// Utilities\n@import \"utilities/api\";\n// scss-docs-end import-stack\n",":root {\n // Note: Custom variable values only support SassScript inside `#{}`.\n\n // Colors\n //\n // Generate palettes for full colors, grays, and theme colors.\n\n @each $color, $value in $colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $grays {\n --#{$variable-prefix}gray-#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors-rgb {\n --#{$variable-prefix}#{$color}-rgb: #{$value};\n }\n\n --#{$variable-prefix}white-rgb: #{to-rgb($white)};\n --#{$variable-prefix}black-rgb: #{to-rgb($black)};\n --#{$variable-prefix}body-rgb: #{to-rgb($body-color)};\n\n // Fonts\n\n // Note: Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --#{$variable-prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n --#{$variable-prefix}font-monospace: #{inspect($font-family-monospace)};\n --#{$variable-prefix}gradient: #{$gradient};\n\n // Root and body\n // stylelint-disable custom-property-empty-line-before\n // scss-docs-start root-body-variables\n @if $font-size-root != null {\n --#{$variable-prefix}root-font-size: #{$font-size-root};\n }\n --#{$variable-prefix}body-font-family: #{$font-family-base};\n --#{$variable-prefix}body-font-size: #{$font-size-base};\n --#{$variable-prefix}body-font-weight: #{$font-weight-base};\n --#{$variable-prefix}body-line-height: #{$line-height-base};\n --#{$variable-prefix}body-color: #{$body-color};\n @if $body-text-align != null {\n --#{$variable-prefix}body-text-align: #{$body-text-align};\n }\n --#{$variable-prefix}body-bg: #{$body-bg};\n // scss-docs-end root-body-variables\n // stylelint-enable custom-property-empty-line-before\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n @if $font-size-root != null {\n font-size: var(--#{$variable-prefix}-root-font-size);\n }\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\n// scss-docs-start reboot-body-rules\nbody {\n margin: 0; // 1\n font-family: var(--#{$variable-prefix}body-font-family);\n @include font-size(var(--#{$variable-prefix}body-font-size));\n font-weight: var(--#{$variable-prefix}body-font-weight);\n line-height: var(--#{$variable-prefix}body-line-height);\n color: var(--#{$variable-prefix}body-color);\n text-align: var(--#{$variable-prefix}body-text-align);\n background-color: var(--#{$variable-prefix}body-bg); // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n// scss-docs-end reboot-body-rules\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n background-color: currentColor;\n border: 0;\n opacity: $hr-opacity;\n}\n\nhr:not([size]) {\n height: $hr-height; // 2\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-bs-original-title] { // 1\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n text-decoration-skip-ink: none; // 4\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n\n &:hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n direction: ltr #{\"/* rtl:ignore */\"};\n unicode-bidi: bidi-override;\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n text-transform: none;\n}\n// Set the cursor for non-` + +

+
+ + The value should be greater than 0 or will fail validation. +
+
+
+ +
+
+ + The value should be lesser than 0 or will fail validation. +
+
+
+ @code{ - int count; + int countPositive = 0; + int countNegative = 0; protected override void OnInitialized() { - count = CounterState.Current.Count; base.OnInitialized(); } public async Task SetPositive() { - await DispatchAsync(new Features.Counter.SetPositive(count)); + await DispatchAsync(new Features.Counter.SetPositive(countPositive)); + countPositive = 0; + } + + public async Task SetNegative() + { + await DispatchAsync(new Features.Counter.SetNegative(countNegative)); + countNegative = 0; } } \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 0e25916..c16e4c5 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -1,16 +1,35 @@ +using Blazored.SessionStorage; using CounterApp; +using CounterApp.Features; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using StateR; +using StateR.AfterEffects; using StateR.Blazor.ReduxDevTools; using StateR.Experiments.AsyncLogic; +using StateR.Interceptors; using StateR.Validations.FluentValidation; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); + +////builder.Services.AddSingleton, PersistanceMiddleware>(); +//builder.Services.AddSingleton, PersistenceMiddleware>(); + +////builder.Services.AddSingleton, PersistanceMiddleware>(); +//builder.Services.AddSingleton, PersistenceMiddleware>(); + +////builder.Services.AddSingleton, PersistanceMiddleware>(); +//builder.Services.AddSingleton, PersistenceMiddleware>(); + builder.Services.RegisterServices(); +builder.Services.AddBlazoredSessionStorage(null, ServiceLifetime.Singleton); + +//builder.Services.Decorate, InitialSessionStateDecorator>(); +//IInitialState +//InitialSessionState : IInitialState await builder.Build().RunAsync(); @@ -27,6 +46,8 @@ public static void RegisterServices(this IServiceCollection services) .AddReduxDevTools() .AddFluentValidation(appAssembly) .Apply() + .AddPersistence(appAssembly) + .AddStateValidation(appAssembly) ; services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(sp.GetRequiredService().BaseAddress) }); } diff --git a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs new file mode 100644 index 0000000..9c89bf3 --- /dev/null +++ b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs @@ -0,0 +1,133 @@ +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using StateR.ActionHandlers; +using StateR.Internal; +using System; +using System.Reflection; + +namespace StateR.Validations.FluentValidation; + +public class StateValidationDecorator : IState + where TState : StateBase +{ + private readonly IState _next; + private readonly IEnumerable> _validators; + private readonly ILogger _logger; + + public StateValidationDecorator(IState next, IEnumerable> validators, ILogger> logger) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + _validators = validators ?? throw new ArgumentNullException(nameof(validators)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public void Set(TState state) + { + var result = _validators + .Select(validator => validator.Validate(state)); + if (result?.Any(validator => !validator.IsValid) ?? false) + { + var errors = result + .Where(validator => !validator.IsValid) + .SelectMany(validator => validator.Errors); + var exception = new ValidationException(errors); + _logger.LogError( + exception, + message: "A validation error occured on state {StateName}", + args: typeof(TState).GetStatorName()); + throw exception; + } + _next.Set(state); + } + + public TState Current => _next.Current; + public void Notify() => _next.Notify(); + public void Subscribe(Action stateHasChangedDelegate) + => _next.Subscribe(stateHasChangedDelegate); + public void Unsubscribe(Action stateHasChangedDelegate) + => _next.Unsubscribe(stateHasChangedDelegate); +} + +public static class StateValidatorStartupExtensions +{ + public static IServiceCollection AddStateValidation(this IServiceCollection services, params Assembly[] assembliesToScan) + { + ArgumentNullException.ThrowIfNull(assembliesToScan, nameof(assembliesToScan)); + if (assembliesToScan.Length == 0) { throw new ArgumentOutOfRangeException(nameof(assembliesToScan)); } + + var allTypes = assembliesToScan + .SelectMany(a => a.GetTypes()); + + RegisterStateDecorator(services, allTypes); + ActionHandlerDecorator(services); + return services; + } + private static void ActionHandlerDecorator(IServiceCollection services) + { + Console.WriteLine("- Decorate, ValidationExceptionActionHandlersManagerDecorator>()"); + services.Decorate(); + + } + private static void RegisterStateDecorator(IServiceCollection services, IEnumerable types) + { + var states = TypeScanner.FindStates(types); + Console.WriteLine("StateValidator:"); + foreach (var state in states) + { + Console.WriteLine($"- Decorate, StateValidationDecorator<{state.GetStatorName()}>>()"); + + // Equivalent to: Decorate, StateValidationDecorator>(); + var stateType = typeof(IState<>).MakeGenericType(state); + var stateSessionDecoratorType = typeof(StateValidationDecorator<>).MakeGenericType(state); + services.Decorate(stateType, stateSessionDecoratorType); + } + } +} + +//public class ValidationExceptionActionHandlerDecorator : IActionHandler +// where TAction : IAction +//{ +// private readonly IActionHandler _next; +// public ValidationExceptionActionHandlerDecorator(IActionHandler next) +// { +// _next = next ?? throw new ArgumentNullException(nameof(next)); +// } + +// public async Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken) +// { +// try +// { +// await _next.HandleAsync(context, cancellationToken); +// } +// catch (ValidationException ex) +// { +// await context.Dispatcher.DispatchAsync(new ReplaceValidationError(ex.Errors), cancellationToken); +// } +// } +//} + +public class ValidationExceptionActionHandlersManagerDecorator : IActionHandlersManager +{ + private readonly IActionHandlersManager _next; + public ValidationExceptionActionHandlersManagerDecorator(IActionHandlersManager next) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + } + + public async Task DispatchAsync(IDispatchContext dispatchContext) + where TAction : IAction + { + try + { + await _next.DispatchAsync(dispatchContext); + } + catch (ValidationException ex) + { + await dispatchContext.Dispatcher.DispatchAsync( + new ReplaceValidationError(ex.Errors), + dispatchContext.CancellationToken + ); + } + } +} diff --git a/src/StateR/Internal/InitialState.cs b/src/StateR/Internal/InitialState.cs deleted file mode 100644 index 5b36a56..0000000 --- a/src/StateR/Internal/InitialState.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace StateR.Internal; - -public class InitialState : IInitialState - where TState : StateBase -{ - public InitialState(TState value) - { - Value = value ?? throw new ArgumentNullException(nameof(value)); - } - - public TState Value { get; } -} diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR/Internal/TypeScannerBuilderExtensions.cs index f96d985..aef5c0c 100644 --- a/src/StateR/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR/Internal/TypeScannerBuilderExtensions.cs @@ -8,51 +8,60 @@ public static class TypeScannerBuilderExtensions { public static IStatorBuilder ScanTypes(this IStatorBuilder builder) { - return builder - .FindStates() - .FindActions() - .FindUpdaters() - .FindActionHandlers() - ; - } + var states = TypeScanner.FindStates(builder.All); + builder.AddStates(states); + + var actions = TypeScanner.FindActions(builder.All); + builder.AddActions(actions); + + var updaters = TypeScanner.FindUpdaters(builder.All); + builder.AddUpdaters(updaters); - public static IStatorBuilder FindStates(this IStatorBuilder builder) + var actionHandlers = TypeScanner.FindActionHandlers(builder.All); + builder.AddUpdaters(actionHandlers); + + return builder; + } +} +public static class TypeScanner +{ + public static IEnumerable FindStates(IEnumerable types) { - var states = builder.All + var states = types .Where(type => !type.IsAbstract && type.IsSubclassOf(typeof(StateBase))); - return builder.AddStates(states); + return states; } - public static IStatorBuilder FindActions(this IStatorBuilder builder) + public static IEnumerable FindActions(IEnumerable types) { - var actions = builder.All + var actions = types .Where(type => !type.IsAbstract && type .GetTypeInfo() .GetInterfaces() .Any(i => i == typeof(IAction)) ); - return builder.AddActions(actions); + return actions; } - public static IStatorBuilder FindUpdaters(this IStatorBuilder builder) + public static IEnumerable FindUpdaters(IEnumerable types) { - var updaters = builder.All + var updaters = types .Where(type => !type.IsAbstract && type .GetTypeInfo() .GetInterfaces() .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IUpdater<,>)) ); - return builder.AddUpdaters(updaters); + return updaters; } - public static IStatorBuilder FindActionHandlers(this IStatorBuilder builder) + public static IEnumerable FindActionHandlers(IEnumerable types) { - var handlers = builder.All + var handlers = types .Where(type => !type.IsAbstract && type .GetTypeInfo() .GetInterfaces() .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionHandler<>)) ); - return builder.AddUpdaters(handlers); + return handlers; } //public IStatorBuilder FindInterceptors(this IStatorBuilder builder) @@ -74,5 +83,4 @@ public static IStatorBuilder FindActionHandlers(this IStatorBuilder builder) // .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == iAfterEffects) // ); //} - -} +} \ No newline at end of file diff --git a/test/StateR.Tests/DispatcherTest.cs b/test/StateR.Tests/DispatcherTest.cs index dde69a4..42c81d3 100644 --- a/test/StateR.Tests/DispatcherTest.cs +++ b/test/StateR.Tests/DispatcherTest.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Logging; using Moq; using StateR.ActionHandlers; using StateR.AfterEffects; @@ -12,11 +13,12 @@ public class DispatcherTest private readonly Mock _interceptorsManager = new(); private readonly Mock _actionHandlersManager = new(); private readonly Mock _afterEffectsManager = new(); + private readonly Mock> _loggerMock = new(); private readonly Dispatcher sut; public DispatcherTest() { - sut = new(_dispatchContextFactory.Object, _interceptorsManager.Object, _actionHandlersManager.Object, _afterEffectsManager.Object); + sut = new(_dispatchContextFactory.Object, _interceptorsManager.Object, _actionHandlersManager.Object, _afterEffectsManager.Object, _loggerMock.Object); } public class DispatchAsync : DispatcherTest From 9be4b5bdd32c6380dc940a3d3038142453cb8707 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Sat, 8 Jan 2022 22:34:13 -0500 Subject: [PATCH 32/58] Update FluentValidation --- .../Shared/FluentValidationSummary.razor | 40 ++++++++++--------- .../FluentValidation/StartupExtensions.cs | 3 +- .../StateValidationDecorator.cs | 24 +---------- .../FluentValidation/ValidationInterceptor.cs | 20 +++++++--- 4 files changed, 39 insertions(+), 48 deletions(-) diff --git a/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor b/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor index 5b488fa..3c74c88 100644 --- a/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor +++ b/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor @@ -1,29 +1,31 @@ -@using StateR.Validations.FluentValidation; +@using FluentValidation.Results +@using StateR.Validations.FluentValidation; @inherits StatorComponent @inject IState ValidationState @if (ValidationState.Current.HasErrors()) { - + if (ValidationState.Current.Errors.Count > 1) + { +
+ +
+ } + foreach (var error in ValidationState.Current.Errors) + { + + } } @code{ - private async Task Dismiss() + private async Task Dismiss(ValidationFailure validationFailure) + { + await DispatchAsync(new RemoveValidationError(validationFailure)); + } + private async Task DismissAll() { await DispatchAsync(new CleanValidationError()); } -} \ No newline at end of file +} diff --git a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs index c9dd172..963b5e7 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs @@ -15,7 +15,8 @@ public static IStatorBuilder AddFluentValidation(this IStatorBuilder builder, pa // Validation action builder.AddTypes(new[] { - typeof(ReplaceValidationError), + typeof(AddValidationErrors), + typeof(ReplaceValidationErrors), typeof(ValidationUpdaters), typeof(ValidationInitialState), typeof(ValidationState), diff --git a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs index 9c89bf3..9df1859 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs @@ -85,28 +85,6 @@ private static void RegisterStateDecorator(IServiceCollection services, IEnumera } } -//public class ValidationExceptionActionHandlerDecorator : IActionHandler -// where TAction : IAction -//{ -// private readonly IActionHandler _next; -// public ValidationExceptionActionHandlerDecorator(IActionHandler next) -// { -// _next = next ?? throw new ArgumentNullException(nameof(next)); -// } - -// public async Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken) -// { -// try -// { -// await _next.HandleAsync(context, cancellationToken); -// } -// catch (ValidationException ex) -// { -// await context.Dispatcher.DispatchAsync(new ReplaceValidationError(ex.Errors), cancellationToken); -// } -// } -//} - public class ValidationExceptionActionHandlersManagerDecorator : IActionHandlersManager { private readonly IActionHandlersManager _next; @@ -125,7 +103,7 @@ public async Task DispatchAsync(IDispatchContext dispatchConte catch (ValidationException ex) { await dispatchContext.Dispatcher.DispatchAsync( - new ReplaceValidationError(ex.Errors), + new AddValidationErrors(ex.Errors), dispatchContext.CancellationToken ); } diff --git a/src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs b/src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs index 9363c26..9a140b9 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs @@ -24,7 +24,7 @@ public async Task InterceptAsync(IDispatchContext context, Cancellation var errors = result .Where(validator => !validator.IsValid) .SelectMany(validator => validator.Errors); - await context.Dispatcher.DispatchAsync(new ReplaceValidationError(errors), cancellationToken); + await context.Dispatcher.DispatchAsync(new AddValidationErrors(errors), cancellationToken); context.Cancel(); } } @@ -38,14 +38,24 @@ public class ValidationInitialState : IInitialState { public ValidationState Value => new(ImmutableList.Create()); } -public record class ReplaceValidationError(IEnumerable Errors) : IAction; + +public record class AddValidationErrors(IEnumerable Errors) : IAction; +public record class ReplaceValidationErrors(IEnumerable Errors) : IAction; public record class CleanValidationError() : IAction; +public record class RemoveValidationError(ValidationFailure Error) : IAction; -public class ValidationUpdaters : IUpdater, IUpdater +public class ValidationUpdaters : + IUpdater, + IUpdater, + IUpdater, + IUpdater { - public ValidationState Update(ReplaceValidationError action, ValidationState state) + public ValidationState Update(ReplaceValidationErrors action, ValidationState state) => state with { Errors = ImmutableList.Create(action.Errors.ToArray()) }; - public ValidationState Update(CleanValidationError action, ValidationState state) => state with { Errors = ImmutableList.Create() }; + public ValidationState Update(AddValidationErrors action, ValidationState state) + => state with { Errors = state.Errors.AddRange(action.Errors) }; + public ValidationState Update(RemoveValidationError action, ValidationState state) + => state with { Errors = state.Errors.Remove(action.Error) }; } \ No newline at end of file From ac9125b28ae30b6cbdf5c392d5a82bdbc73b9cee Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Sat, 8 Jan 2022 22:34:36 -0500 Subject: [PATCH 33/58] Simplify onclick --- samples/CounterApp/CounterApp/Pages/Counter.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/CounterApp/CounterApp/Pages/Counter.razor b/samples/CounterApp/CounterApp/Pages/Counter.razor index b868384..c9e5968 100644 --- a/samples/CounterApp/CounterApp/Pages/Counter.razor +++ b/samples/CounterApp/CounterApp/Pages/Counter.razor @@ -9,6 +9,6 @@

Current count: @CounterState.Current.Count

+ @onclick="() => DispatchAsync(new Features.Counter.Increment())">+ + @onclick="() => DispatchAsync(new Features.Counter.Decrement())">- From f0fdfa9654b458e44441e4c126bffbb940b2c58c Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Sat, 8 Jan 2022 22:34:51 -0500 Subject: [PATCH 34/58] Implement WebStorage wrapper --- StateR.sln | 17 +-- .../CounterApp/CounterApp/CounterApp.csproj | 1 - .../CounterApp/CounterApp/Features/Counter.cs | 22 ++-- samples/CounterApp/CounterApp/Program.cs | 4 +- .../WebStorage/IStorage.cs | 117 ++++++++++++++++++ .../WebStorage/IWebStorage.cs | 8 ++ .../WebStorage/LocalStorage.cs | 9 ++ .../WebStorage/SessionStorage.cs | 9 ++ .../WebStorage/Storage.cs | 61 +++++++++ .../WebStorage/StorageExtensions.cs | 47 +++++++ .../WebStorage/StorageType.cs | 7 ++ .../WebStorage/WebStorage.cs | 13 ++ .../WebStorage/WebStorageStartupExtensions.cs | 17 +++ 13 files changed, 303 insertions(+), 29 deletions(-) create mode 100644 src/StateR.Blazor.Experiments/WebStorage/IStorage.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/IWebStorage.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/LocalStorage.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/SessionStorage.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/Storage.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/StorageType.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/WebStorage.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs diff --git a/StateR.sln b/StateR.sln index 5a8b976..4dad392 100644 --- a/StateR.sln +++ b/StateR.sln @@ -15,7 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateR.Tests", "test\StateR EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateR.Experiments", "src\StateR.Experiments\StateR.Experiments.csproj", "{0EB20F7F-8AA1-48E2-9489-6975566469AF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateR.Blazor.Experiments", "src\StateR.Blazor.Experiments\StateR.Blazor.Experiments.csproj", "{5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateR.Blazor.Experiments", "src\StateR.Blazor.Experiments\StateR.Blazor.Experiments.csproj", "{5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{22B777AC-AFAE-422A-B4CD-48C907251D9E}" EndProject @@ -25,8 +25,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CounterApp", "samples\Count EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CounterApp.Tests", "samples\CounterApp\CounterApp.Tests\CounterApp.Tests.csproj", "{FBDEBA94-7F63-4CB5-AC13-4D0874730316}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazored.SessionStorage", "..\..\SessionStorage\src\Blazored.SessionStorage\Blazored.SessionStorage.csproj", "{29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -121,18 +119,6 @@ Global {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x64.Build.0 = Release|Any CPU {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x86.ActiveCfg = Release|Any CPU {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x86.Build.0 = Release|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Debug|Any CPU.Build.0 = Debug|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Debug|x64.ActiveCfg = Debug|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Debug|x64.Build.0 = Debug|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Debug|x86.ActiveCfg = Debug|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Debug|x86.Build.0 = Debug|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Release|Any CPU.ActiveCfg = Release|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Release|Any CPU.Build.0 = Release|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Release|x64.ActiveCfg = Release|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Release|x64.Build.0 = Release|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Release|x86.ActiveCfg = Release|Any CPU - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -146,7 +132,6 @@ Global {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} = {22B777AC-AFAE-422A-B4CD-48C907251D9E} {99926EB0-84F9-4906-8F7E-4E1873A403EB} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} {FBDEBA94-7F63-4CB5-AC13-4D0874730316} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} - {29D06BA0-0DCC-4EA6-AF8F-76D6AFE94928} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6ADD1933-C449-475E-9409-AD333C1C48A0} diff --git a/samples/CounterApp/CounterApp/CounterApp.csproj b/samples/CounterApp/CounterApp/CounterApp.csproj index 843edd7..54c4cc9 100644 --- a/samples/CounterApp/CounterApp/CounterApp.csproj +++ b/samples/CounterApp/CounterApp/CounterApp.csproj @@ -11,7 +11,6 @@ - diff --git a/samples/CounterApp/CounterApp/Features/Counter.cs b/samples/CounterApp/CounterApp/Features/Counter.cs index 21ce4fc..a4e2129 100644 --- a/samples/CounterApp/CounterApp/Features/Counter.cs +++ b/samples/CounterApp/CounterApp/Features/Counter.cs @@ -1,7 +1,7 @@ -using Blazored.SessionStorage; -using FluentValidation; +using FluentValidation; using StateR; using StateR.AfterEffects; +using StateR.Blazor.WebStorage; using StateR.Interceptors; using StateR.Internal; using StateR.Updaters; @@ -81,13 +81,14 @@ public enum PersistenceType public class SessionStateDecorator : IState where TState : StateBase { - private readonly ISyncSessionStorageService _sessionStorage; + private readonly IStorage _storage; private readonly IState _next; private readonly string _key; - public SessionStateDecorator(ISyncSessionStorageService sessionStorage, IState next) + public SessionStateDecorator(IWebStorage webStorage, IState next) { - _sessionStorage = sessionStorage ?? throw new ArgumentNullException(nameof(sessionStorage)); + ArgumentNullException.ThrowIfNull(webStorage, nameof(webStorage)); + _storage = webStorage.LocalStorage; _next = next ?? throw new ArgumentNullException(nameof(next)); _key = typeof(TState).GetStatorName(); } @@ -96,7 +97,7 @@ public void Set(TState state) { _next.Set(state); Console.WriteLine($"[SessionStateDecorator][{_key}] Set: {state}."); - _sessionStorage.SetItem(_key, state); + _storage.SetItem(_key, state); } public TState Current => _next.Current; @@ -110,13 +111,14 @@ public void Unsubscribe(Action stateHasChangedDelegate) public class InitialSessionStateDecorator : IInitialState where TState : StateBase { - private readonly ISyncSessionStorageService _sessionStorage; + private readonly IStorage _storage; private readonly IInitialState _next; private readonly string _key; - public InitialSessionStateDecorator(ISyncSessionStorageService sessionStorage, IInitialState next) + public InitialSessionStateDecorator(IWebStorage webStorage, IInitialState next) { - _sessionStorage = sessionStorage ?? throw new ArgumentNullException(nameof(sessionStorage)); + ArgumentNullException.ThrowIfNull(webStorage, nameof(webStorage)); + _storage = webStorage.LocalStorage; _next = next ?? throw new ArgumentNullException(nameof(next)); _key = typeof(TState).GetStatorName(); } @@ -125,7 +127,7 @@ public TState Value { get { - var item = _sessionStorage.GetItem(_key); + var item = _storage.GetItem(_key); if (item == null) { Console.WriteLine($"[InitialSessionStateDecorator][{_key}] Not item found in storage."); diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index c16e4c5..e644bb1 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -1,4 +1,3 @@ -using Blazored.SessionStorage; using CounterApp; using CounterApp.Features; using Microsoft.AspNetCore.Components.Web; @@ -6,6 +5,7 @@ using StateR; using StateR.AfterEffects; using StateR.Blazor.ReduxDevTools; +using StateR.Blazor.WebStorage; using StateR.Experiments.AsyncLogic; using StateR.Interceptors; using StateR.Validations.FluentValidation; @@ -25,7 +25,7 @@ //builder.Services.AddSingleton, PersistenceMiddleware>(); builder.Services.RegisterServices(); -builder.Services.AddBlazoredSessionStorage(null, ServiceLifetime.Singleton); +builder.Services.AddWebStorage(); //builder.Services.Decorate, InitialSessionStateDecorator>(); //IInitialState diff --git a/src/StateR.Blazor.Experiments/WebStorage/IStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/IStorage.cs new file mode 100644 index 0000000..4e5aed8 --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/IStorage.cs @@ -0,0 +1,117 @@ +namespace StateR.Blazor.WebStorage; + +public interface IStorage +{ + /// + /// Gets the number of data items stored in a given object. + /// + int Length { get; } + + /// + /// Returns the name of the nth key in a given object. + /// The order of keys is user-agent defined, so you should not rely on it. + /// + /// + /// The number of the key you want to get the name of. + /// This is a zero-based index. + /// + /// + /// The name of the key. If the index does not exist, null is returned. + /// + string? Key(int index); + + /// + /// Returns the specified key's value, or null if the key does not exist, in the + /// given object. + /// + /// The name of the key you want to retrieve the value of. + /// The value of the key. If the key does not exist, null is returned. + string? GetItem(string keyName); + + /// + /// Adds the specified key to the given object, or + /// updates that key's value if it already exists. + /// + /// The name of the key you want to create/update. + /// The value you want to give the key you are creating/updating. + /// + /// setItem() may throw an exception if the storage is full. Particularly, in + /// Mobile Safari (since iOS 5) it always throws when the user enters private + /// mode. (Safari sets the quota to 0 bytes in private mode, unlike other browsers, + /// which allow storage in private mode using separate data containers.) Hence + /// developers should make sure to always catch possible exceptions from setItem(). + /// + void SetItem(string keyName, string keyValue); + + /// + /// Removes the specified key from the given object if it exists. + /// + /// The name of the key you want to remove. + void RemoveItem(string keyName); + + /// + /// Clears all keys stored in a given object. + /// + void Clear(); + + /// + /// Gets the number of data items stored in a given object. + /// + /// + /// + /// The number of data items stored in the object. + ValueTask GetLengthAsync(CancellationToken? cancellationToken = default); + + /// + /// Returns the name of the nth key in a given object. + /// The order of keys is user-agent defined, so you should not rely on it. + /// + /// + /// The number of the key you want to get the name of. + /// This is a zero-based index. + /// + /// + /// + /// The name of the key. If the index does not exist, null is returned. + /// + ValueTask KeyAsync(int index, CancellationToken? cancellationToken = default); + + /// + /// Returns the specified key's value, or null if the key does not exist, in the + /// given object. + /// + /// The name of the key you want to retrieve the value of. + /// + /// The value of the key. If the key does not exist, null is returned. + ValueTask GetItemAsync(string keyName, CancellationToken? cancellationToken = default); + + /// + /// Adds the specified key to the given object, or + /// updates that key's value if it already exists. + /// + /// The name of the key you want to create/update. + /// The value you want to give the key you are creating/updating. + /// + /// + /// setItem() may throw an exception if the storage is full. Particularly, in + /// Mobile Safari (since iOS 5) it always throws when the user enters private + /// mode. (Safari sets the quota to 0 bytes in private mode, unlike other browsers, + /// which allow storage in private mode using separate data containers.) Hence + /// developers should make sure to always catch possible exceptions from setItem(). + /// + ValueTask SetItemAsync(string keyName, string keyValue, CancellationToken? cancellationToken = default); + + /// + /// Removes the specified key from the given object if it exists. + /// + /// The name of the key you want to remove. + /// + ValueTask RemoveItemAsync(string keyName, CancellationToken? cancellationToken = default); + + /// + /// Clears all keys stored in a given object. + /// + /// + /// + ValueTask ClearAsync(CancellationToken? cancellationToken = default); +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/IWebStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/IWebStorage.cs new file mode 100644 index 0000000..41cafd8 --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/IWebStorage.cs @@ -0,0 +1,8 @@ +namespace StateR.Blazor.WebStorage; + +public interface IWebStorage +{ + IStorage LocalStorage { get; } + IStorage SessionStorage { get; } + //onstorage event +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/LocalStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/LocalStorage.cs new file mode 100644 index 0000000..a01dd44 --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/LocalStorage.cs @@ -0,0 +1,9 @@ +using Microsoft.JSInterop; + +namespace StateR.Blazor.WebStorage; + +public sealed class LocalStorage : Storage +{ + public LocalStorage(IJSInProcessRuntime jsInProcessRuntime, IJSRuntime jsRuntime) + : base(StorageType.Local, jsInProcessRuntime, jsRuntime) { } +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/SessionStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/SessionStorage.cs new file mode 100644 index 0000000..8ec6437 --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/SessionStorage.cs @@ -0,0 +1,9 @@ +using Microsoft.JSInterop; + +namespace StateR.Blazor.WebStorage; + +public sealed class SessionStorage : Storage +{ + public SessionStorage(IJSInProcessRuntime jsInProcessRuntime, IJSRuntime jsRuntime) + : base(StorageType.Session, jsInProcessRuntime, jsRuntime) { } +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/Storage.cs b/src/StateR.Blazor.Experiments/WebStorage/Storage.cs new file mode 100644 index 0000000..c967a70 --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/Storage.cs @@ -0,0 +1,61 @@ +using Microsoft.JSInterop; + +namespace StateR.Blazor.WebStorage; + +public abstract class Storage : IStorage +{ + private readonly string _clearIdentifier; + private readonly string _lengthIdentifier; + private readonly string _getItemIdentifier; + private readonly string _keyIdentifier; + private readonly string _removeItemIdentifier; + private readonly string _setItemIdentifier; + + private readonly IJSInProcessRuntime _jsInProcessRuntime; + private readonly IJSRuntime _jsRuntime; + + public Storage(StorageType storageType, IJSInProcessRuntime jsInProcessRuntime, IJSRuntime jsRuntime) + { + _jsInProcessRuntime = jsInProcessRuntime ?? throw new ArgumentNullException(nameof(jsInProcessRuntime)); + _jsRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); + + var windowPropertyName = storageType == StorageType.Session + ? "sessionStorage" + : "localStorage"; + _lengthIdentifier = $"{windowPropertyName}.length"; + _clearIdentifier = $"{windowPropertyName}.clear"; + _getItemIdentifier = $"{windowPropertyName}.getItem"; + _keyIdentifier = $"{windowPropertyName}.key"; + _removeItemIdentifier = $"{windowPropertyName}.removeItem"; + _setItemIdentifier = $"{windowPropertyName}.setItem"; + } + + public int Length => _jsInProcessRuntime.Invoke("eval", _lengthIdentifier); + public ValueTask GetLengthAsync(CancellationToken? cancellationToken = null) + => _jsRuntime.InvokeAsync("eval", cancellationToken ?? CancellationToken.None, _lengthIdentifier); + + public void Clear() + => _jsInProcessRuntime.InvokeVoid(_clearIdentifier); + public ValueTask ClearAsync(CancellationToken? cancellationToken = null) + => _jsRuntime.InvokeVoidAsync(_clearIdentifier, cancellationToken); + + public string? GetItem(string keyName) + => _jsInProcessRuntime.Invoke(_getItemIdentifier, keyName); + public ValueTask GetItemAsync(string keyName, CancellationToken? cancellationToken = null) + => _jsRuntime.InvokeAsync(_getItemIdentifier, cancellationToken ?? CancellationToken.None, keyName); + + public string? Key(int index) + => _jsInProcessRuntime.Invoke(_keyIdentifier, index); + public ValueTask KeyAsync(int index, CancellationToken? cancellationToken = null) + => _jsRuntime.InvokeAsync(_keyIdentifier, cancellationToken ?? CancellationToken.None, index); + + public void RemoveItem(string keyName) + => _jsInProcessRuntime.Invoke(_removeItemIdentifier, keyName); + public ValueTask RemoveItemAsync(string keyName, CancellationToken? cancellationToken = null) + => _jsRuntime.InvokeVoidAsync(_removeItemIdentifier, cancellationToken ?? CancellationToken.None, keyName); + + public void SetItem(string keyName, string keyValue) + => _jsInProcessRuntime.Invoke(_setItemIdentifier, keyName, keyValue); + public ValueTask SetItemAsync(string keyName, string keyValue, CancellationToken? cancellationToken = null) + => _jsRuntime.InvokeVoidAsync(_setItemIdentifier, cancellationToken ?? CancellationToken.None, keyName, keyValue); +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs b/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs new file mode 100644 index 0000000..2753c6e --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs @@ -0,0 +1,47 @@ +using System.Text.Json; + +namespace StateR.Blazor.WebStorage; + +public static class StorageExtensions +{ + private static readonly JsonSerializerOptions _options = new(JsonSerializerDefaults.Web); + + public static void SetItem(this IStorage storage, string keyName, T keyValue) + where T : notnull + { + ArgumentNullException.ThrowIfNull(storage, nameof(storage)); + var value = JsonSerializer.Serialize(keyValue, _options); + storage.SetItem(keyName, value); + } + + public static T? GetItem(this IStorage storage, string keyName) + { + ArgumentNullException.ThrowIfNull(storage, nameof(storage)); + var rawValue = storage.GetItem(keyName); + if( rawValue == null) + { + return default; + } + var value = JsonSerializer.Deserialize(rawValue, _options); + return value; + } + + public static async ValueTask SetItemAsync(this IStorage storage, string keyName, T keyValue, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(storage, nameof(storage)); + var value = JsonSerializer.Serialize(keyValue, _options); + await storage.SetItemAsync(keyName, value, cancellationToken); + } + + public static async ValueTask GetItemAsync(this IStorage storage, string keyName, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(storage, nameof(storage)); + var rawValue = await storage.GetItemAsync(keyName, cancellationToken); + if (rawValue == null) + { + return default; + } + var value = JsonSerializer.Deserialize(rawValue, _options); + return value; + } +} \ No newline at end of file diff --git a/src/StateR.Blazor.Experiments/WebStorage/StorageType.cs b/src/StateR.Blazor.Experiments/WebStorage/StorageType.cs new file mode 100644 index 0000000..9d495db --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/StorageType.cs @@ -0,0 +1,7 @@ +namespace StateR.Blazor.WebStorage; + +public enum StorageType +{ + Session, + Local +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/WebStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/WebStorage.cs new file mode 100644 index 0000000..8feea40 --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/WebStorage.cs @@ -0,0 +1,13 @@ +namespace StateR.Blazor.WebStorage; + +public sealed class WebStorage : IWebStorage +{ + public WebStorage(LocalStorage localStorage, SessionStorage sessionStorage) + { + LocalStorage = localStorage ?? throw new ArgumentNullException(nameof(localStorage)); + SessionStorage = sessionStorage ?? throw new ArgumentNullException(nameof(sessionStorage)); + } + + public IStorage LocalStorage { get; } + public IStorage SessionStorage { get; } +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs b/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs new file mode 100644 index 0000000..4e06145 --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.JSInterop; + +namespace StateR.Blazor.WebStorage; + +public static class WebStorageStartupExtensions +{ + public static IServiceCollection AddWebStorage(this IServiceCollection services) + { + services.TryAddSingleton(sp => (sp.GetRequiredService() as IJSInProcessRuntime)!); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + return services; + } +} From ee11cd0a80865c8582356bf242a9b6768818dfa7 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Sat, 8 Jan 2022 23:44:27 -0500 Subject: [PATCH 35/58] Add extensibility --- .../WebStorage/IWebStorageSerializer.cs | 9 +++++ .../WebStorage/JsonWebStorageSerializer.cs | 36 +++++++++++++++++ .../JsonWebStorageSerializerOptions.cs | 20 ++++++++++ .../WebStorage/StorageExtensions.cs | 13 +++--- .../WebStorage/WebStorageOptions.cs | 27 +++++++++++++ .../WebStorage/WebStorageStartupExtensions.cs | 40 +++++++++++++++++++ 6 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 src/StateR.Blazor.Experiments/WebStorage/IWebStorageSerializer.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializer.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializerOptions.cs create mode 100644 src/StateR.Blazor.Experiments/WebStorage/WebStorageOptions.cs diff --git a/src/StateR.Blazor.Experiments/WebStorage/IWebStorageSerializer.cs b/src/StateR.Blazor.Experiments/WebStorage/IWebStorageSerializer.cs new file mode 100644 index 0000000..df13aab --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/IWebStorageSerializer.cs @@ -0,0 +1,9 @@ +namespace StateR.Blazor.WebStorage; + +public interface IWebStorageSerializer +{ + string Serialize(TValue keyValue); + TValue? Deserialize(string keyValue); + ValueTask SerializeAsync(TValue keyValue, CancellationToken cancellationToken); + ValueTask DeserializeAsync(string keyValue, CancellationToken cancellationToken); +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializer.cs b/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializer.cs new file mode 100644 index 0000000..64b3883 --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializer.cs @@ -0,0 +1,36 @@ +using System.Text.Json; + +namespace StateR.Blazor.WebStorage; + +public sealed class JsonWebStorageSerializer : IWebStorageSerializer +{ + private readonly JsonWebStorageSerializerOptions _options; + public JsonWebStorageSerializer(JsonWebStorageSerializerOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + + public TValue? Deserialize(string keyValue) + { + var value = JsonSerializer.Deserialize(keyValue, _options.JsonSerializerOptions); + return value; + } + + public ValueTask DeserializeAsync(string keyValue, CancellationToken cancellationToken) + { + var value = Deserialize(keyValue); + return new(value); + } + + public string Serialize(TValue keyValue) + { + var value = JsonSerializer.Serialize(keyValue, _options.JsonSerializerOptions); + return value; + } + + public ValueTask SerializeAsync(TValue keyValue, CancellationToken cancellationToken) + { + var value = Serialize(keyValue); + return new(value); + } +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializerOptions.cs b/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializerOptions.cs new file mode 100644 index 0000000..67384c2 --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializerOptions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Options; +using System.Text.Json; + +namespace StateR.Blazor.WebStorage; + +public class JsonWebStorageSerializerOptions +{ + public JsonSerializerOptions? JsonSerializerOptions { get; set; } +} + +public class ConfigureJsonWebStorageSerializerOptions : IConfigureOptions +{ + public void Configure(JsonWebStorageSerializerOptions options) + { + if (options.JsonSerializerOptions == null) + { + options.JsonSerializerOptions = new(JsonSerializerDefaults.Web); + } + } +} diff --git a/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs b/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs index 2753c6e..6d451f2 100644 --- a/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs +++ b/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs @@ -1,16 +1,17 @@ -using System.Text.Json; +using System.Diagnostics.CodeAnalysis; namespace StateR.Blazor.WebStorage; public static class StorageExtensions { - private static readonly JsonSerializerOptions _options = new(JsonSerializerDefaults.Web); + [NotNull] + internal static WebStorageOptions? WebStorageOptions { get; set; } public static void SetItem(this IStorage storage, string keyName, T keyValue) where T : notnull { ArgumentNullException.ThrowIfNull(storage, nameof(storage)); - var value = JsonSerializer.Serialize(keyValue, _options); + var value = WebStorageOptions.Serializer.Serialize(keyValue); storage.SetItem(keyName, value); } @@ -22,14 +23,14 @@ public static void SetItem(this IStorage storage, string keyName, T keyValue) { return default; } - var value = JsonSerializer.Deserialize(rawValue, _options); + var value = WebStorageOptions.Serializer.Deserialize(rawValue); return value; } public static async ValueTask SetItemAsync(this IStorage storage, string keyName, T keyValue, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(storage, nameof(storage)); - var value = JsonSerializer.Serialize(keyValue, _options); + var value = await WebStorageOptions.Serializer.SerializeAsync(keyValue, cancellationToken); await storage.SetItemAsync(keyName, value, cancellationToken); } @@ -41,7 +42,7 @@ public static async ValueTask SetItemAsync(this IStorage storage, string keyN { return default; } - var value = JsonSerializer.Deserialize(rawValue, _options); + var value = await WebStorageOptions.Serializer.DeserializeAsync(rawValue, cancellationToken); return value; } } \ No newline at end of file diff --git a/src/StateR.Blazor.Experiments/WebStorage/WebStorageOptions.cs b/src/StateR.Blazor.Experiments/WebStorage/WebStorageOptions.cs new file mode 100644 index 0000000..55a587f --- /dev/null +++ b/src/StateR.Blazor.Experiments/WebStorage/WebStorageOptions.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Options; +using System.Diagnostics.CodeAnalysis; + +namespace StateR.Blazor.WebStorage; + +public class WebStorageOptions +{ + public StorageType DefaultStorageType { get; set; } = StorageType.Local; + + [NotNull] + public IWebStorageSerializer? Serializer { get; internal set; } +} + +public class PostConfigureWebStorageOptions : IPostConfigureOptions +{ + private readonly IWebStorageSerializer _webStorageSerializer; + public PostConfigureWebStorageOptions(IWebStorageSerializer webStorageSerializer) + { + _webStorageSerializer = webStorageSerializer ?? throw new ArgumentNullException(nameof(webStorageSerializer)); + } + + public void PostConfigure(string name, WebStorageOptions options) + { + options.Serializer = _webStorageSerializer; + StorageExtensions.WebStorageOptions = options; + } +} \ No newline at end of file diff --git a/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs b/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs index 4e06145..35bf432 100644 --- a/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs +++ b/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; using Microsoft.JSInterop; namespace StateR.Blazor.WebStorage; @@ -7,11 +8,50 @@ namespace StateR.Blazor.WebStorage; public static class WebStorageStartupExtensions { public static IServiceCollection AddWebStorage(this IServiceCollection services) + => services.AddWebStorage(default); + + public static IServiceCollection AddWebStorage(this IServiceCollection services, Action? configure) { services.TryAddSingleton(sp => (sp.GetRequiredService() as IJSInProcessRuntime)!); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(sp => + { + var options = sp.GetRequiredService(); + var webStorage = sp.GetRequiredService(); + return options.DefaultStorageType == StorageType.Local + ? webStorage.LocalStorage + : webStorage.SessionStorage; + }); + services + .AddWebStorageOptions(configure) + .AddDefaultSerializer() + ; + return services; + } + + private static IServiceCollection AddDefaultSerializer(this IServiceCollection services) + { + services.AddOptions(); + services.TryAddSingleton< + IConfigureOptions, + ConfigureJsonWebStorageSerializerOptions + >(); + services.TryAddSingleton(sp => sp.GetRequiredService>().Value); + services.TryAddSingleton(); + return services; + } + + private static IServiceCollection AddWebStorageOptions(this IServiceCollection services, Action? configure) + { + services.Configure(configureOptions + => configure?.Invoke(configureOptions)); + services.TryAddSingleton< + IPostConfigureOptions, + PostConfigureWebStorageOptions + >(); + services.TryAddSingleton(sp => sp.GetRequiredService>().Value); return services; } } From 604c8622336d646ed8b2a4205078f268ad205262 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Sat, 8 Jan 2022 23:44:49 -0500 Subject: [PATCH 36/58] Inject the default IStorage instead of IWebStorage --- samples/CounterApp/CounterApp/Features/Counter.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/samples/CounterApp/CounterApp/Features/Counter.cs b/samples/CounterApp/CounterApp/Features/Counter.cs index a4e2129..1ea528b 100644 --- a/samples/CounterApp/CounterApp/Features/Counter.cs +++ b/samples/CounterApp/CounterApp/Features/Counter.cs @@ -85,10 +85,9 @@ public class SessionStateDecorator : IState private readonly IState _next; private readonly string _key; - public SessionStateDecorator(IWebStorage webStorage, IState next) + public SessionStateDecorator(IStorage storage, IState next) { - ArgumentNullException.ThrowIfNull(webStorage, nameof(webStorage)); - _storage = webStorage.LocalStorage; + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _next = next ?? throw new ArgumentNullException(nameof(next)); _key = typeof(TState).GetStatorName(); } @@ -115,10 +114,9 @@ public class InitialSessionStateDecorator : IInitialState private readonly IInitialState _next; private readonly string _key; - public InitialSessionStateDecorator(IWebStorage webStorage, IInitialState next) + public InitialSessionStateDecorator(IStorage storage, IInitialState next) { - ArgumentNullException.ThrowIfNull(webStorage, nameof(webStorage)); - _storage = webStorage.LocalStorage; + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); _next = next ?? throw new ArgumentNullException(nameof(next)); _key = typeof(TState).GetStatorName(); } From cc7319318a335edd07a525cb97f0c804358092ac Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Sat, 8 Jan 2022 23:56:30 -0500 Subject: [PATCH 37/58] Move Persistance away from app --- .../CounterApp/CounterApp/Features/Counter.cs | 119 +----------------- samples/CounterApp/CounterApp/Program.cs | 8 +- .../Persistance/PersistAttribute.cs | 6 + .../PersistenceStartupExtensions.cs | 44 +++++++ .../WebStorageInitialStateDecorator.cs | 34 +++++ .../Persistance/WebStorageStateDecorator.cs | 33 +++++ .../StateValidationDecorator.cs | 8 +- src/StateR/StatorStartupExtensions.cs | 6 +- 8 files changed, 132 insertions(+), 126 deletions(-) create mode 100644 src/StateR.Blazor.Experiments/Persistance/PersistAttribute.cs create mode 100644 src/StateR.Blazor.Experiments/Persistance/PersistenceStartupExtensions.cs create mode 100644 src/StateR.Blazor.Experiments/Persistance/WebStorageInitialStateDecorator.cs create mode 100644 src/StateR.Blazor.Experiments/Persistance/WebStorageStateDecorator.cs diff --git a/samples/CounterApp/CounterApp/Features/Counter.cs b/samples/CounterApp/CounterApp/Features/Counter.cs index 1ea528b..00d73a2 100644 --- a/samples/CounterApp/CounterApp/Features/Counter.cs +++ b/samples/CounterApp/CounterApp/Features/Counter.cs @@ -1,6 +1,7 @@ using FluentValidation; using StateR; using StateR.AfterEffects; +using StateR.Blazor.Persistance; using StateR.Blazor.WebStorage; using StateR.Interceptors; using StateR.Internal; @@ -61,122 +62,4 @@ public StateValidator() RuleFor(x => x.Count).LessThan(100); } } -} - -// -// Persistence experimentations -// -[AttributeUsage(AttributeTargets.Class)] -public class PersistAttribute : Attribute -{ - public PersistenceType Type { get; set; } = PersistenceType.SessionStorage; -} - -public enum PersistenceType -{ - SessionStorage, - LocalStorage -} - -public class SessionStateDecorator : IState - where TState : StateBase -{ - private readonly IStorage _storage; - private readonly IState _next; - private readonly string _key; - - public SessionStateDecorator(IStorage storage, IState next) - { - _storage = storage ?? throw new ArgumentNullException(nameof(storage)); - _next = next ?? throw new ArgumentNullException(nameof(next)); - _key = typeof(TState).GetStatorName(); - } - - public void Set(TState state) - { - _next.Set(state); - Console.WriteLine($"[SessionStateDecorator][{_key}] Set: {state}."); - _storage.SetItem(_key, state); - } - - public TState Current => _next.Current; - public void Notify() => _next.Notify(); - public void Subscribe(Action stateHasChangedDelegate) - => _next.Subscribe(stateHasChangedDelegate); - public void Unsubscribe(Action stateHasChangedDelegate) - => _next.Unsubscribe(stateHasChangedDelegate); -} - -public class InitialSessionStateDecorator : IInitialState - where TState : StateBase -{ - private readonly IStorage _storage; - private readonly IInitialState _next; - private readonly string _key; - - public InitialSessionStateDecorator(IStorage storage, IInitialState next) - { - _storage = storage ?? throw new ArgumentNullException(nameof(storage)); - _next = next ?? throw new ArgumentNullException(nameof(next)); - _key = typeof(TState).GetStatorName(); - } - - public TState Value - { - get - { - var item = _storage.GetItem(_key); - if (item == null) - { - Console.WriteLine($"[InitialSessionStateDecorator][{_key}] Not item found in storage."); - return _next.Value; - } - Console.WriteLine($"[InitialSessionStateDecorator][{_key}] Item found: {item}"); - return item; - } - } -} - -public static class PersistenceStartupExtensions -{ - public static IServiceCollection AddPersistence(this IServiceCollection services, params Assembly[] assembliesToScan) - { - ArgumentNullException.ThrowIfNull(assembliesToScan, nameof(assembliesToScan)); - if (assembliesToScan.Length == 0) { throw new ArgumentOutOfRangeException(nameof(assembliesToScan)); } - - var states = assembliesToScan - .SelectMany(a => a.GetTypes()) - .Where(type => !type.IsAbstract && type.IsSubclassOf(typeof(StateBase))); - ; - foreach (var state in states) - { - var persistAttribute = state.GetCustomAttribute(); - if (persistAttribute == null) - { - continue; - } - // TODO: do something with Type - //switch (persistAttribute.Type) - //{ - // case PersistanceType.SessionStorage: - // break; - //} - - Console.WriteLine($"Persistence ({persistAttribute.Type}): {state.GetStatorName()}"); - Console.WriteLine($"- Decorate, InitialSessionStateDecorator<{state.GetStatorName()}>>()"); - - // Equivalent to: Decorate, InitialSessionStateDecorator>(); - var initialStateType = typeof(IInitialState<>).MakeGenericType(state); - var decoratedInitialStateType = typeof(InitialSessionStateDecorator<>).MakeGenericType(state); - services.Decorate(initialStateType, decoratedInitialStateType); - - Console.WriteLine($"- Decorate, SessionStateDecorator<{state.GetStatorName()}>>()"); - - // Equivalent to: Decorate, SessionStateDecorator>(); - var stateType = typeof(IState<>).MakeGenericType(state); - var stateSessionDecoratorType = typeof(SessionStateDecorator<>).MakeGenericType(state); - services.Decorate(stateType, stateSessionDecoratorType); - } - return services; - } } \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index e644bb1..08e1874 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using StateR; using StateR.AfterEffects; +using StateR.Blazor.Persistance; using StateR.Blazor.ReduxDevTools; using StateR.Blazor.WebStorage; using StateR.Experiments.AsyncLogic; @@ -45,9 +46,10 @@ public static void RegisterServices(this IServiceCollection services) .AddAsyncOperations() .AddReduxDevTools() .AddFluentValidation(appAssembly) - .Apply() - .AddPersistence(appAssembly) - .AddStateValidation(appAssembly) + .Apply(buidler => buidler + .AddPersistence(appAssembly) + .AddStateValidation(appAssembly) + ) ; services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(sp.GetRequiredService().BaseAddress) }); } diff --git a/src/StateR.Blazor.Experiments/Persistance/PersistAttribute.cs b/src/StateR.Blazor.Experiments/Persistance/PersistAttribute.cs new file mode 100644 index 0000000..3f5f57b --- /dev/null +++ b/src/StateR.Blazor.Experiments/Persistance/PersistAttribute.cs @@ -0,0 +1,6 @@ +namespace StateR.Blazor.Persistance; + +[AttributeUsage(AttributeTargets.Class)] +public class PersistAttribute : Attribute +{ +} diff --git a/src/StateR.Blazor.Experiments/Persistance/PersistenceStartupExtensions.cs b/src/StateR.Blazor.Experiments/Persistance/PersistenceStartupExtensions.cs new file mode 100644 index 0000000..1542c60 --- /dev/null +++ b/src/StateR.Blazor.Experiments/Persistance/PersistenceStartupExtensions.cs @@ -0,0 +1,44 @@ + +using Microsoft.Extensions.DependencyInjection; +using StateR.Internal; +using System.Reflection; + +namespace StateR.Blazor.Persistance; + +public static class PersistenceStartupExtensions +{ + public static IStatorBuilder AddPersistence(this IStatorBuilder builder, params Assembly[] assembliesToScan) + { + ArgumentNullException.ThrowIfNull(assembliesToScan, nameof(assembliesToScan)); + if (assembliesToScan.Length == 0) { throw new ArgumentOutOfRangeException(nameof(assembliesToScan)); } + + var states = assembliesToScan + .SelectMany(a => a.GetTypes()) + .Where(type => !type.IsAbstract && type.IsSubclassOf(typeof(StateBase))); + ; + foreach (var state in states) + { + var persistAttribute = state.GetCustomAttribute(); + if (persistAttribute == null) + { + continue; + } + + Console.WriteLine($"Persistence: {state.GetStatorName()}"); + Console.WriteLine($"- Decorate, WebStorageInitialStateDecorator<{state.GetStatorName()}>>()"); + + // Equivalent to: Decorate, WebStorageInitialStateDecorator>(); + var initialStateType = typeof(IInitialState<>).MakeGenericType(state); + var decoratedInitialStateType = typeof(WebStorageInitialStateDecorator<>).MakeGenericType(state); + builder.Services.Decorate(initialStateType, decoratedInitialStateType); + + Console.WriteLine($"- Decorate, WebStorageStateDecorator<{state.GetStatorName()}>>()"); + + // Equivalent to: Decorate, WebStorageStateDecorator>(); + var stateType = typeof(IState<>).MakeGenericType(state); + var stateSessionDecoratorType = typeof(WebStorageStateDecorator<>).MakeGenericType(state); + builder.Services.Decorate(stateType, stateSessionDecoratorType); + } + return builder; + } +} \ No newline at end of file diff --git a/src/StateR.Blazor.Experiments/Persistance/WebStorageInitialStateDecorator.cs b/src/StateR.Blazor.Experiments/Persistance/WebStorageInitialStateDecorator.cs new file mode 100644 index 0000000..072f93c --- /dev/null +++ b/src/StateR.Blazor.Experiments/Persistance/WebStorageInitialStateDecorator.cs @@ -0,0 +1,34 @@ +using StateR.Blazor.WebStorage; +using StateR.Internal; + +namespace StateR.Blazor.Persistance; + +public class WebStorageInitialStateDecorator : IInitialState + where TState : StateBase +{ + private readonly IStorage _storage; + private readonly IInitialState _next; + private readonly string _key; + + public WebStorageInitialStateDecorator(IStorage storage, IInitialState next) + { + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + _next = next ?? throw new ArgumentNullException(nameof(next)); + _key = typeof(TState).GetStatorName(); + } + + public TState Value + { + get + { + var item = _storage.GetItem(_key); + if (item == null) + { + Console.WriteLine($"[InitialSessionStateDecorator][{_key}] Not item found in storage."); + return _next.Value; + } + Console.WriteLine($"[InitialSessionStateDecorator][{_key}] Item found: {item}"); + return item; + } + } +} diff --git a/src/StateR.Blazor.Experiments/Persistance/WebStorageStateDecorator.cs b/src/StateR.Blazor.Experiments/Persistance/WebStorageStateDecorator.cs new file mode 100644 index 0000000..a508471 --- /dev/null +++ b/src/StateR.Blazor.Experiments/Persistance/WebStorageStateDecorator.cs @@ -0,0 +1,33 @@ +using StateR.Blazor.WebStorage; +using StateR.Internal; + +namespace StateR.Blazor.Persistance; + +public class WebStorageStateDecorator : IState + where TState : StateBase +{ + private readonly IStorage _storage; + private readonly IState _next; + private readonly string _key; + + public WebStorageStateDecorator(IStorage storage, IState next) + { + _storage = storage ?? throw new ArgumentNullException(nameof(storage)); + _next = next ?? throw new ArgumentNullException(nameof(next)); + _key = typeof(TState).GetStatorName(); + } + + public void Set(TState state) + { + _next.Set(state); + Console.WriteLine($"[SessionStateDecorator][{_key}] Set: {state}."); + _storage.SetItem(_key, state); + } + + public TState Current => _next.Current; + public void Notify() => _next.Notify(); + public void Subscribe(Action stateHasChangedDelegate) + => _next.Subscribe(stateHasChangedDelegate); + public void Unsubscribe(Action stateHasChangedDelegate) + => _next.Unsubscribe(stateHasChangedDelegate); +} diff --git a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs index 9df1859..56f2682 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs @@ -51,7 +51,7 @@ public void Unsubscribe(Action stateHasChangedDelegate) public static class StateValidatorStartupExtensions { - public static IServiceCollection AddStateValidation(this IServiceCollection services, params Assembly[] assembliesToScan) + public static IStatorBuilder AddStateValidation(this IStatorBuilder builder, params Assembly[] assembliesToScan) { ArgumentNullException.ThrowIfNull(assembliesToScan, nameof(assembliesToScan)); if (assembliesToScan.Length == 0) { throw new ArgumentOutOfRangeException(nameof(assembliesToScan)); } @@ -59,9 +59,9 @@ public static IServiceCollection AddStateValidation(this IServiceCollection serv var allTypes = assembliesToScan .SelectMany(a => a.GetTypes()); - RegisterStateDecorator(services, allTypes); - ActionHandlerDecorator(services); - return services; + RegisterStateDecorator(builder.Services, allTypes); + ActionHandlerDecorator(builder.Services); + return builder; } private static void ActionHandlerDecorator(IServiceCollection services) { diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 728af72..093f7bd 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -41,7 +41,7 @@ public static IStatorBuilder AddStateR(this IServiceCollection services, params return builder.AddTypes(allTypes); } - public static IServiceCollection Apply(this IStatorBuilder builder) + public static IServiceCollection Apply(this IStatorBuilder builder, Action? postConfiguration = null) { // Extract types builder.ScanTypes(); @@ -138,6 +138,10 @@ public static IServiceCollection Apply(this IStatorBuilder builder) Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); } } + + // Run post-configuration + postConfiguration?.Invoke(builder); + return builder.Services; } } From 4c1f673220d8ade25e213a6286d733e6c482c3c8 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Sat, 8 Jan 2022 23:59:50 -0500 Subject: [PATCH 38/58] Remove assembly re-scanning --- samples/CounterApp/CounterApp/Program.cs | 4 ++-- .../Persistance/PersistenceStartupExtensions.cs | 11 ++--------- .../FluentValidation/StateValidationDecorator.cs | 10 ++-------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 08e1874..3ccc1ed 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -47,8 +47,8 @@ public static void RegisterServices(this IServiceCollection services) .AddReduxDevTools() .AddFluentValidation(appAssembly) .Apply(buidler => buidler - .AddPersistence(appAssembly) - .AddStateValidation(appAssembly) + .AddPersistence() + .AddStateValidation() ) ; services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(sp.GetRequiredService().BaseAddress) }); diff --git a/src/StateR.Blazor.Experiments/Persistance/PersistenceStartupExtensions.cs b/src/StateR.Blazor.Experiments/Persistance/PersistenceStartupExtensions.cs index 1542c60..5696f09 100644 --- a/src/StateR.Blazor.Experiments/Persistance/PersistenceStartupExtensions.cs +++ b/src/StateR.Blazor.Experiments/Persistance/PersistenceStartupExtensions.cs @@ -7,16 +7,9 @@ namespace StateR.Blazor.Persistance; public static class PersistenceStartupExtensions { - public static IStatorBuilder AddPersistence(this IStatorBuilder builder, params Assembly[] assembliesToScan) + public static IStatorBuilder AddPersistence(this IStatorBuilder builder) { - ArgumentNullException.ThrowIfNull(assembliesToScan, nameof(assembliesToScan)); - if (assembliesToScan.Length == 0) { throw new ArgumentOutOfRangeException(nameof(assembliesToScan)); } - - var states = assembliesToScan - .SelectMany(a => a.GetTypes()) - .Where(type => !type.IsAbstract && type.IsSubclassOf(typeof(StateBase))); - ; - foreach (var state in states) + foreach (var state in builder.States) { var persistAttribute = state.GetCustomAttribute(); if (persistAttribute == null) diff --git a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs index 56f2682..91a8a2b 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs @@ -51,15 +51,9 @@ public void Unsubscribe(Action stateHasChangedDelegate) public static class StateValidatorStartupExtensions { - public static IStatorBuilder AddStateValidation(this IStatorBuilder builder, params Assembly[] assembliesToScan) + public static IStatorBuilder AddStateValidation(this IStatorBuilder builder) { - ArgumentNullException.ThrowIfNull(assembliesToScan, nameof(assembliesToScan)); - if (assembliesToScan.Length == 0) { throw new ArgumentOutOfRangeException(nameof(assembliesToScan)); } - - var allTypes = assembliesToScan - .SelectMany(a => a.GetTypes()); - - RegisterStateDecorator(builder.Services, allTypes); + RegisterStateDecorator(builder.Services, builder.All); ActionHandlerDecorator(builder.Services); return builder; } From 3d7e19d87c6773573921fb6563915801d669bab3 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 12 Jan 2022 19:52:10 -0500 Subject: [PATCH 39/58] Extracted StateR.Blazor.WebStorage to ForEvolve.Blazor.WebStorage --- .../CounterApp/CounterApp/CounterApp.csproj | 1 - .../CounterApp/CounterApp/Features/Counter.cs | 2 +- samples/CounterApp/CounterApp/Program.cs | 2 +- .../WebStorageInitialStateDecorator.cs | 2 +- .../Persistance/WebStorageStateDecorator.cs | 2 +- .../StateR.Blazor.Experiments.csproj | 3 +- .../WebStorage/IStorage.cs | 117 ------------------ .../WebStorage/IWebStorage.cs | 8 -- .../WebStorage/IWebStorageSerializer.cs | 9 -- .../WebStorage/JsonWebStorageSerializer.cs | 36 ------ .../JsonWebStorageSerializerOptions.cs | 20 --- .../WebStorage/LocalStorage.cs | 9 -- .../WebStorage/SessionStorage.cs | 9 -- .../WebStorage/Storage.cs | 61 --------- .../WebStorage/StorageExtensions.cs | 48 ------- .../WebStorage/StorageType.cs | 7 -- .../WebStorage/WebStorage.cs | 13 -- .../WebStorage/WebStorageOptions.cs | 27 ---- .../WebStorage/WebStorageStartupExtensions.cs | 57 --------- src/StateR.Blazor/StateR.Blazor.csproj | 17 +-- 20 files changed, 10 insertions(+), 440 deletions(-) delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/IStorage.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/IWebStorage.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/IWebStorageSerializer.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializer.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializerOptions.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/LocalStorage.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/SessionStorage.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/Storage.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/StorageType.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/WebStorage.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/WebStorageOptions.cs delete mode 100644 src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs diff --git a/samples/CounterApp/CounterApp/CounterApp.csproj b/samples/CounterApp/CounterApp/CounterApp.csproj index 54c4cc9..bb327bf 100644 --- a/samples/CounterApp/CounterApp/CounterApp.csproj +++ b/samples/CounterApp/CounterApp/CounterApp.csproj @@ -5,7 +5,6 @@ - diff --git a/samples/CounterApp/CounterApp/Features/Counter.cs b/samples/CounterApp/CounterApp/Features/Counter.cs index 00d73a2..34f30ca 100644 --- a/samples/CounterApp/CounterApp/Features/Counter.cs +++ b/samples/CounterApp/CounterApp/Features/Counter.cs @@ -2,7 +2,7 @@ using StateR; using StateR.AfterEffects; using StateR.Blazor.Persistance; -using StateR.Blazor.WebStorage; +using ForEvolve.Blazor.WebStorage; using StateR.Interceptors; using StateR.Internal; using StateR.Updaters; diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 3ccc1ed..3065212 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -6,7 +6,7 @@ using StateR.AfterEffects; using StateR.Blazor.Persistance; using StateR.Blazor.ReduxDevTools; -using StateR.Blazor.WebStorage; +using ForEvolve.Blazor.WebStorage; using StateR.Experiments.AsyncLogic; using StateR.Interceptors; using StateR.Validations.FluentValidation; diff --git a/src/StateR.Blazor.Experiments/Persistance/WebStorageInitialStateDecorator.cs b/src/StateR.Blazor.Experiments/Persistance/WebStorageInitialStateDecorator.cs index 072f93c..7310f91 100644 --- a/src/StateR.Blazor.Experiments/Persistance/WebStorageInitialStateDecorator.cs +++ b/src/StateR.Blazor.Experiments/Persistance/WebStorageInitialStateDecorator.cs @@ -1,4 +1,4 @@ -using StateR.Blazor.WebStorage; +using ForEvolve.Blazor.WebStorage; using StateR.Internal; namespace StateR.Blazor.Persistance; diff --git a/src/StateR.Blazor.Experiments/Persistance/WebStorageStateDecorator.cs b/src/StateR.Blazor.Experiments/Persistance/WebStorageStateDecorator.cs index a508471..dc90030 100644 --- a/src/StateR.Blazor.Experiments/Persistance/WebStorageStateDecorator.cs +++ b/src/StateR.Blazor.Experiments/Persistance/WebStorageStateDecorator.cs @@ -1,4 +1,4 @@ -using StateR.Blazor.WebStorage; +using ForEvolve.Blazor.WebStorage; using StateR.Internal; namespace StateR.Blazor.Persistance; diff --git a/src/StateR.Blazor.Experiments/StateR.Blazor.Experiments.csproj b/src/StateR.Blazor.Experiments/StateR.Blazor.Experiments.csproj index 9df7184..835274f 100644 --- a/src/StateR.Blazor.Experiments/StateR.Blazor.Experiments.csproj +++ b/src/StateR.Blazor.Experiments/StateR.Blazor.Experiments.csproj @@ -6,7 +6,8 @@ - + + diff --git a/src/StateR.Blazor.Experiments/WebStorage/IStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/IStorage.cs deleted file mode 100644 index 4e5aed8..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/IStorage.cs +++ /dev/null @@ -1,117 +0,0 @@ -namespace StateR.Blazor.WebStorage; - -public interface IStorage -{ - /// - /// Gets the number of data items stored in a given object. - /// - int Length { get; } - - /// - /// Returns the name of the nth key in a given object. - /// The order of keys is user-agent defined, so you should not rely on it. - /// - /// - /// The number of the key you want to get the name of. - /// This is a zero-based index. - /// - /// - /// The name of the key. If the index does not exist, null is returned. - /// - string? Key(int index); - - /// - /// Returns the specified key's value, or null if the key does not exist, in the - /// given object. - /// - /// The name of the key you want to retrieve the value of. - /// The value of the key. If the key does not exist, null is returned. - string? GetItem(string keyName); - - /// - /// Adds the specified key to the given object, or - /// updates that key's value if it already exists. - /// - /// The name of the key you want to create/update. - /// The value you want to give the key you are creating/updating. - /// - /// setItem() may throw an exception if the storage is full. Particularly, in - /// Mobile Safari (since iOS 5) it always throws when the user enters private - /// mode. (Safari sets the quota to 0 bytes in private mode, unlike other browsers, - /// which allow storage in private mode using separate data containers.) Hence - /// developers should make sure to always catch possible exceptions from setItem(). - /// - void SetItem(string keyName, string keyValue); - - /// - /// Removes the specified key from the given object if it exists. - /// - /// The name of the key you want to remove. - void RemoveItem(string keyName); - - /// - /// Clears all keys stored in a given object. - /// - void Clear(); - - /// - /// Gets the number of data items stored in a given object. - /// - /// - /// - /// The number of data items stored in the object. - ValueTask GetLengthAsync(CancellationToken? cancellationToken = default); - - /// - /// Returns the name of the nth key in a given object. - /// The order of keys is user-agent defined, so you should not rely on it. - /// - /// - /// The number of the key you want to get the name of. - /// This is a zero-based index. - /// - /// - /// - /// The name of the key. If the index does not exist, null is returned. - /// - ValueTask KeyAsync(int index, CancellationToken? cancellationToken = default); - - /// - /// Returns the specified key's value, or null if the key does not exist, in the - /// given object. - /// - /// The name of the key you want to retrieve the value of. - /// - /// The value of the key. If the key does not exist, null is returned. - ValueTask GetItemAsync(string keyName, CancellationToken? cancellationToken = default); - - /// - /// Adds the specified key to the given object, or - /// updates that key's value if it already exists. - /// - /// The name of the key you want to create/update. - /// The value you want to give the key you are creating/updating. - /// - /// - /// setItem() may throw an exception if the storage is full. Particularly, in - /// Mobile Safari (since iOS 5) it always throws when the user enters private - /// mode. (Safari sets the quota to 0 bytes in private mode, unlike other browsers, - /// which allow storage in private mode using separate data containers.) Hence - /// developers should make sure to always catch possible exceptions from setItem(). - /// - ValueTask SetItemAsync(string keyName, string keyValue, CancellationToken? cancellationToken = default); - - /// - /// Removes the specified key from the given object if it exists. - /// - /// The name of the key you want to remove. - /// - ValueTask RemoveItemAsync(string keyName, CancellationToken? cancellationToken = default); - - /// - /// Clears all keys stored in a given object. - /// - /// - /// - ValueTask ClearAsync(CancellationToken? cancellationToken = default); -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/IWebStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/IWebStorage.cs deleted file mode 100644 index 41cafd8..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/IWebStorage.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StateR.Blazor.WebStorage; - -public interface IWebStorage -{ - IStorage LocalStorage { get; } - IStorage SessionStorage { get; } - //onstorage event -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/IWebStorageSerializer.cs b/src/StateR.Blazor.Experiments/WebStorage/IWebStorageSerializer.cs deleted file mode 100644 index df13aab..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/IWebStorageSerializer.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace StateR.Blazor.WebStorage; - -public interface IWebStorageSerializer -{ - string Serialize(TValue keyValue); - TValue? Deserialize(string keyValue); - ValueTask SerializeAsync(TValue keyValue, CancellationToken cancellationToken); - ValueTask DeserializeAsync(string keyValue, CancellationToken cancellationToken); -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializer.cs b/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializer.cs deleted file mode 100644 index 64b3883..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Text.Json; - -namespace StateR.Blazor.WebStorage; - -public sealed class JsonWebStorageSerializer : IWebStorageSerializer -{ - private readonly JsonWebStorageSerializerOptions _options; - public JsonWebStorageSerializer(JsonWebStorageSerializerOptions options) - { - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - public TValue? Deserialize(string keyValue) - { - var value = JsonSerializer.Deserialize(keyValue, _options.JsonSerializerOptions); - return value; - } - - public ValueTask DeserializeAsync(string keyValue, CancellationToken cancellationToken) - { - var value = Deserialize(keyValue); - return new(value); - } - - public string Serialize(TValue keyValue) - { - var value = JsonSerializer.Serialize(keyValue, _options.JsonSerializerOptions); - return value; - } - - public ValueTask SerializeAsync(TValue keyValue, CancellationToken cancellationToken) - { - var value = Serialize(keyValue); - return new(value); - } -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializerOptions.cs b/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializerOptions.cs deleted file mode 100644 index 67384c2..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/JsonWebStorageSerializerOptions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.Extensions.Options; -using System.Text.Json; - -namespace StateR.Blazor.WebStorage; - -public class JsonWebStorageSerializerOptions -{ - public JsonSerializerOptions? JsonSerializerOptions { get; set; } -} - -public class ConfigureJsonWebStorageSerializerOptions : IConfigureOptions -{ - public void Configure(JsonWebStorageSerializerOptions options) - { - if (options.JsonSerializerOptions == null) - { - options.JsonSerializerOptions = new(JsonSerializerDefaults.Web); - } - } -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/LocalStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/LocalStorage.cs deleted file mode 100644 index a01dd44..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/LocalStorage.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.JSInterop; - -namespace StateR.Blazor.WebStorage; - -public sealed class LocalStorage : Storage -{ - public LocalStorage(IJSInProcessRuntime jsInProcessRuntime, IJSRuntime jsRuntime) - : base(StorageType.Local, jsInProcessRuntime, jsRuntime) { } -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/SessionStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/SessionStorage.cs deleted file mode 100644 index 8ec6437..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/SessionStorage.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.JSInterop; - -namespace StateR.Blazor.WebStorage; - -public sealed class SessionStorage : Storage -{ - public SessionStorage(IJSInProcessRuntime jsInProcessRuntime, IJSRuntime jsRuntime) - : base(StorageType.Session, jsInProcessRuntime, jsRuntime) { } -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/Storage.cs b/src/StateR.Blazor.Experiments/WebStorage/Storage.cs deleted file mode 100644 index c967a70..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/Storage.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.JSInterop; - -namespace StateR.Blazor.WebStorage; - -public abstract class Storage : IStorage -{ - private readonly string _clearIdentifier; - private readonly string _lengthIdentifier; - private readonly string _getItemIdentifier; - private readonly string _keyIdentifier; - private readonly string _removeItemIdentifier; - private readonly string _setItemIdentifier; - - private readonly IJSInProcessRuntime _jsInProcessRuntime; - private readonly IJSRuntime _jsRuntime; - - public Storage(StorageType storageType, IJSInProcessRuntime jsInProcessRuntime, IJSRuntime jsRuntime) - { - _jsInProcessRuntime = jsInProcessRuntime ?? throw new ArgumentNullException(nameof(jsInProcessRuntime)); - _jsRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); - - var windowPropertyName = storageType == StorageType.Session - ? "sessionStorage" - : "localStorage"; - _lengthIdentifier = $"{windowPropertyName}.length"; - _clearIdentifier = $"{windowPropertyName}.clear"; - _getItemIdentifier = $"{windowPropertyName}.getItem"; - _keyIdentifier = $"{windowPropertyName}.key"; - _removeItemIdentifier = $"{windowPropertyName}.removeItem"; - _setItemIdentifier = $"{windowPropertyName}.setItem"; - } - - public int Length => _jsInProcessRuntime.Invoke("eval", _lengthIdentifier); - public ValueTask GetLengthAsync(CancellationToken? cancellationToken = null) - => _jsRuntime.InvokeAsync("eval", cancellationToken ?? CancellationToken.None, _lengthIdentifier); - - public void Clear() - => _jsInProcessRuntime.InvokeVoid(_clearIdentifier); - public ValueTask ClearAsync(CancellationToken? cancellationToken = null) - => _jsRuntime.InvokeVoidAsync(_clearIdentifier, cancellationToken); - - public string? GetItem(string keyName) - => _jsInProcessRuntime.Invoke(_getItemIdentifier, keyName); - public ValueTask GetItemAsync(string keyName, CancellationToken? cancellationToken = null) - => _jsRuntime.InvokeAsync(_getItemIdentifier, cancellationToken ?? CancellationToken.None, keyName); - - public string? Key(int index) - => _jsInProcessRuntime.Invoke(_keyIdentifier, index); - public ValueTask KeyAsync(int index, CancellationToken? cancellationToken = null) - => _jsRuntime.InvokeAsync(_keyIdentifier, cancellationToken ?? CancellationToken.None, index); - - public void RemoveItem(string keyName) - => _jsInProcessRuntime.Invoke(_removeItemIdentifier, keyName); - public ValueTask RemoveItemAsync(string keyName, CancellationToken? cancellationToken = null) - => _jsRuntime.InvokeVoidAsync(_removeItemIdentifier, cancellationToken ?? CancellationToken.None, keyName); - - public void SetItem(string keyName, string keyValue) - => _jsInProcessRuntime.Invoke(_setItemIdentifier, keyName, keyValue); - public ValueTask SetItemAsync(string keyName, string keyValue, CancellationToken? cancellationToken = null) - => _jsRuntime.InvokeVoidAsync(_setItemIdentifier, cancellationToken ?? CancellationToken.None, keyName, keyValue); -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs b/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs deleted file mode 100644 index 6d451f2..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/StorageExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace StateR.Blazor.WebStorage; - -public static class StorageExtensions -{ - [NotNull] - internal static WebStorageOptions? WebStorageOptions { get; set; } - - public static void SetItem(this IStorage storage, string keyName, T keyValue) - where T : notnull - { - ArgumentNullException.ThrowIfNull(storage, nameof(storage)); - var value = WebStorageOptions.Serializer.Serialize(keyValue); - storage.SetItem(keyName, value); - } - - public static T? GetItem(this IStorage storage, string keyName) - { - ArgumentNullException.ThrowIfNull(storage, nameof(storage)); - var rawValue = storage.GetItem(keyName); - if( rawValue == null) - { - return default; - } - var value = WebStorageOptions.Serializer.Deserialize(rawValue); - return value; - } - - public static async ValueTask SetItemAsync(this IStorage storage, string keyName, T keyValue, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(storage, nameof(storage)); - var value = await WebStorageOptions.Serializer.SerializeAsync(keyValue, cancellationToken); - await storage.SetItemAsync(keyName, value, cancellationToken); - } - - public static async ValueTask GetItemAsync(this IStorage storage, string keyName, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(storage, nameof(storage)); - var rawValue = await storage.GetItemAsync(keyName, cancellationToken); - if (rawValue == null) - { - return default; - } - var value = await WebStorageOptions.Serializer.DeserializeAsync(rawValue, cancellationToken); - return value; - } -} \ No newline at end of file diff --git a/src/StateR.Blazor.Experiments/WebStorage/StorageType.cs b/src/StateR.Blazor.Experiments/WebStorage/StorageType.cs deleted file mode 100644 index 9d495db..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/StorageType.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.Blazor.WebStorage; - -public enum StorageType -{ - Session, - Local -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/WebStorage.cs b/src/StateR.Blazor.Experiments/WebStorage/WebStorage.cs deleted file mode 100644 index 8feea40..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/WebStorage.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace StateR.Blazor.WebStorage; - -public sealed class WebStorage : IWebStorage -{ - public WebStorage(LocalStorage localStorage, SessionStorage sessionStorage) - { - LocalStorage = localStorage ?? throw new ArgumentNullException(nameof(localStorage)); - SessionStorage = sessionStorage ?? throw new ArgumentNullException(nameof(sessionStorage)); - } - - public IStorage LocalStorage { get; } - public IStorage SessionStorage { get; } -} diff --git a/src/StateR.Blazor.Experiments/WebStorage/WebStorageOptions.cs b/src/StateR.Blazor.Experiments/WebStorage/WebStorageOptions.cs deleted file mode 100644 index 55a587f..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/WebStorageOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.Extensions.Options; -using System.Diagnostics.CodeAnalysis; - -namespace StateR.Blazor.WebStorage; - -public class WebStorageOptions -{ - public StorageType DefaultStorageType { get; set; } = StorageType.Local; - - [NotNull] - public IWebStorageSerializer? Serializer { get; internal set; } -} - -public class PostConfigureWebStorageOptions : IPostConfigureOptions -{ - private readonly IWebStorageSerializer _webStorageSerializer; - public PostConfigureWebStorageOptions(IWebStorageSerializer webStorageSerializer) - { - _webStorageSerializer = webStorageSerializer ?? throw new ArgumentNullException(nameof(webStorageSerializer)); - } - - public void PostConfigure(string name, WebStorageOptions options) - { - options.Serializer = _webStorageSerializer; - StorageExtensions.WebStorageOptions = options; - } -} \ No newline at end of file diff --git a/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs b/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs deleted file mode 100644 index 35bf432..0000000 --- a/src/StateR.Blazor.Experiments/WebStorage/WebStorageStartupExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; -using Microsoft.JSInterop; - -namespace StateR.Blazor.WebStorage; - -public static class WebStorageStartupExtensions -{ - public static IServiceCollection AddWebStorage(this IServiceCollection services) - => services.AddWebStorage(default); - - public static IServiceCollection AddWebStorage(this IServiceCollection services, Action? configure) - { - services.TryAddSingleton(sp => (sp.GetRequiredService() as IJSInProcessRuntime)!); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(sp => - { - var options = sp.GetRequiredService(); - var webStorage = sp.GetRequiredService(); - return options.DefaultStorageType == StorageType.Local - ? webStorage.LocalStorage - : webStorage.SessionStorage; - }); - services - .AddWebStorageOptions(configure) - .AddDefaultSerializer() - ; - return services; - } - - private static IServiceCollection AddDefaultSerializer(this IServiceCollection services) - { - services.AddOptions(); - services.TryAddSingleton< - IConfigureOptions, - ConfigureJsonWebStorageSerializerOptions - >(); - services.TryAddSingleton(sp => sp.GetRequiredService>().Value); - services.TryAddSingleton(); - return services; - } - - private static IServiceCollection AddWebStorageOptions(this IServiceCollection services, Action? configure) - { - services.Configure(configureOptions - => configure?.Invoke(configureOptions)); - services.TryAddSingleton< - IPostConfigureOptions, - PostConfigureWebStorageOptions - >(); - services.TryAddSingleton(sp => sp.GetRequiredService>().Value); - return services; - } -} diff --git a/src/StateR.Blazor/StateR.Blazor.csproj b/src/StateR.Blazor/StateR.Blazor.csproj index 25406cf..7873b2e 100644 --- a/src/StateR.Blazor/StateR.Blazor.csproj +++ b/src/StateR.Blazor/StateR.Blazor.csproj @@ -5,6 +5,10 @@ Blazor components and utilities for use with the StateR library. stator,stater,redux,blazor,state,dotnet,dotnetcore,net,netcore,aspnetcore,asp.net,core,aspnet,asp,forevolve + + + + @@ -16,17 +20,4 @@ - - - - - - - - - - - - - From 91408414d060a0711bb74a80293cb181f367ff56 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Sat, 15 Jan 2022 01:11:40 -0500 Subject: [PATCH 40/58] Core updates --- .../CounterApp/CounterApp/CounterApp.csproj | 11 +- .../CounterApp/CounterApp/Features/Counter.cs | 64 ++++---- .../CounterApp/Features/WeatherForecast.cs | 32 ++-- .../CounterApp/Pages/FetchData.razor | 7 +- samples/CounterApp/CounterApp/Program.cs | 21 ++- .../Shared/FluentValidationSummary.razor | 3 +- .../CounterApp/Shared/MainLayout.razor | 4 +- .../ReduxDevTools/ReduxDevToolsInterop.cs | 1 - src/StateR.Blazor/StatorComponentBase.cs | 12 +- .../AsyncLogic/StartupExtensions.cs | 2 +- .../FluentValidation/StartupExtensions.cs | 4 +- .../ActionHandlers/ActionHandlersManager.cs | 29 ---- .../Hooks/ActionHandlerHooksCollection.cs | 28 ---- .../Hooks/IActionHandlerHooksCollection.cs | 7 - .../ActionHandlers/Hooks/IAfterActionHook.cs | 6 - .../ActionHandlers/Hooks/IBeforeActionHook.cs | 6 - src/StateR/ActionHandlers/IActionHandler.cs | 7 - .../ActionHandlers/IActionHandlersManager.cs | 3 - .../AfterEffects/AfterEffectsManager.cs | 29 ---- .../Hooks/AfterEffectHooksCollection.cs | 29 ---- .../Hooks/IAfterAfterEffectHook.cs | 7 - .../Hooks/IAfterEffectHooksCollection.cs | 8 - .../Hooks/IBeforeAfterEffectHook.cs | 7 - src/StateR/AfterEffects/IAfterEffects.cs | 7 - .../AfterEffects/IAfterEffectsManager.cs | 3 - src/StateR/DispatchContext.cs | 12 +- src/StateR/DispatchContextFactory.cs | 7 +- src/StateR/Dispatcher.cs | 50 +++--- src/StateR/IAction.cs | 2 +- src/StateR/IDispatchContext.cs | 5 +- src/StateR/IDispatchContextFactory.cs | 4 +- src/StateR/IDispatchManager.cs | 5 +- src/StateR/IDispatcher.cs | 5 +- src/StateR/IStatorBuilder.cs | 4 + .../Hooks/IAfterInterceptorHook.cs | 6 - .../Hooks/IBeforeInterceptorHook.cs | 6 - .../Hooks/IInterceptorsHooksCollection.cs | 7 - .../Hooks/InterceptorsHooksCollection.cs | 28 ---- src/StateR/Interceptors/IInterceptor.cs | 7 - .../Interceptors/IInterceptorsManager.cs | 3 - .../Interceptors/InterceptorsManager.cs | 29 ---- src/StateR/Internal/StatorBuilder.cs | 4 + .../Internal/TypeScannerBuilderExtensions.cs | 30 +--- src/StateR/Pipeline/IActionFilter.cs | 13 ++ src/StateR/Pipeline/IActionFilterFactory.cs | 26 ++++ src/StateR/StatorStartupExtensions.cs | 142 +++++++++--------- src/StateR/Store.cs | 9 +- src/StateR/Updaters/Hooks/IAfterUpdateHook.cs | 8 - .../Updaters/Hooks/IBeforeUpdateHook.cs | 8 - .../Updaters/Hooks/IUpdateHooksCollection.cs | 11 -- .../Updaters/Hooks/UpdateHooksCollection.cs | 32 ---- src/StateR/Updaters/IUpdater.cs | 2 +- src/StateR/Updaters/UpdaterActionHandler.cs | 40 ----- src/StateR/Updaters/UpdaterMiddleware.cs | 34 +++++ .../Reducers/UpdaterActionHandlerTest.cs | 2 +- 55 files changed, 309 insertions(+), 569 deletions(-) delete mode 100644 src/StateR/ActionHandlers/ActionHandlersManager.cs delete mode 100644 src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs delete mode 100644 src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs delete mode 100644 src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs delete mode 100644 src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs delete mode 100644 src/StateR/ActionHandlers/IActionHandler.cs delete mode 100644 src/StateR/ActionHandlers/IActionHandlersManager.cs delete mode 100644 src/StateR/AfterEffects/AfterEffectsManager.cs delete mode 100644 src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs delete mode 100644 src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs delete mode 100644 src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs delete mode 100644 src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs delete mode 100644 src/StateR/AfterEffects/IAfterEffects.cs delete mode 100644 src/StateR/AfterEffects/IAfterEffectsManager.cs delete mode 100644 src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs delete mode 100644 src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs delete mode 100644 src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs delete mode 100644 src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs delete mode 100644 src/StateR/Interceptors/IInterceptor.cs delete mode 100644 src/StateR/Interceptors/IInterceptorsManager.cs delete mode 100644 src/StateR/Interceptors/InterceptorsManager.cs create mode 100644 src/StateR/Pipeline/IActionFilter.cs create mode 100644 src/StateR/Pipeline/IActionFilterFactory.cs delete mode 100644 src/StateR/Updaters/Hooks/IAfterUpdateHook.cs delete mode 100644 src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs delete mode 100644 src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs delete mode 100644 src/StateR/Updaters/Hooks/UpdateHooksCollection.cs delete mode 100644 src/StateR/Updaters/UpdaterActionHandler.cs create mode 100644 src/StateR/Updaters/UpdaterMiddleware.cs diff --git a/samples/CounterApp/CounterApp/CounterApp.csproj b/samples/CounterApp/CounterApp/CounterApp.csproj index bb327bf..98b8158 100644 --- a/samples/CounterApp/CounterApp/CounterApp.csproj +++ b/samples/CounterApp/CounterApp/CounterApp.csproj @@ -5,14 +5,21 @@ + + + + + + + + + - - diff --git a/samples/CounterApp/CounterApp/Features/Counter.cs b/samples/CounterApp/CounterApp/Features/Counter.cs index 34f30ca..e9ce5cc 100644 --- a/samples/CounterApp/CounterApp/Features/Counter.cs +++ b/samples/CounterApp/CounterApp/Features/Counter.cs @@ -1,19 +1,13 @@ -using FluentValidation; +//using FluentValidation; using StateR; -using StateR.AfterEffects; -using StateR.Blazor.Persistance; -using ForEvolve.Blazor.WebStorage; -using StateR.Interceptors; -using StateR.Internal; +//using StateR.Blazor.Persistance; using StateR.Updaters; -using System; -using System.Reflection; namespace CounterApp.Features; public class Counter { - [Persist] + //[Persist] public record class State(int Count) : StateBase; public class InitialState : IInitialState @@ -21,10 +15,10 @@ public class InitialState : IInitialState public State Value => new(0); } - public record class Increment : IAction; - public record class Decrement : IAction; - public record class SetPositive(int Count) : IAction; - public record class SetNegative(int Count) : IAction; + public record class Increment : IAction; + public record class Decrement : IAction; + public record class SetPositive(int Count) : IAction; + public record class SetNegative(int Count) : IAction; public class Updaters : IUpdater, IUpdater, IUpdater, IUpdater { @@ -38,28 +32,28 @@ public State Update(SetNegative action, State state) => state with { Count = action.Count }; } - public class SetPositiveValidator : AbstractValidator - { - public SetPositiveValidator() - { - RuleFor(x => x.Count).GreaterThan(0); - } - } + //public class SetPositiveValidator : AbstractValidator + //{ + // public SetPositiveValidator() + // { + // RuleFor(x => x.Count).GreaterThan(0); + // } + //} - public class SetNegativeValidator : AbstractValidator - { - public SetNegativeValidator() - { - RuleFor(x => x.Count).LessThan(0); - } - } + //public class SetNegativeValidator : AbstractValidator + //{ + // public SetNegativeValidator() + // { + // RuleFor(x => x.Count).LessThan(0); + // } + //} - public class StateValidator : AbstractValidator - { - public StateValidator() - { - RuleFor(x => x.Count).GreaterThan(-100); - RuleFor(x => x.Count).LessThan(100); - } - } + //public class StateValidator : AbstractValidator + //{ + // public StateValidator() + // { + // RuleFor(x => x.Count).GreaterThan(-100); + // RuleFor(x => x.Count).LessThan(100); + // } + //} } \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Features/WeatherForecast.cs b/samples/CounterApp/CounterApp/Features/WeatherForecast.cs index c1c294d..e0f1818 100644 --- a/samples/CounterApp/CounterApp/Features/WeatherForecast.cs +++ b/samples/CounterApp/CounterApp/Features/WeatherForecast.cs @@ -1,7 +1,5 @@ using StateR; -using StateR.AfterEffects; using StateR.AsyncLogic; -using StateR.Interceptors; using StateR.Internal; using StateR.Updaters; using System.Collections.Immutable; @@ -37,13 +35,13 @@ public State Update(Reload action, State state) => state with { Status = AsyncOperationStatus.Idle, Forecasts = ImmutableList.Create() }; } - public class ReloadEffect : IAfterEffects - { - public async Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken) - { - await context.Dispatcher.DispatchAsync(new Fetch(), cancellationToken); - } - } + //public class ReloadEffect : IAfterEffects + //{ + // public async Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken) + // { + // await context.Dispatcher.DispatchAsync(new Fetch(), cancellationToken); + // } + //} public class FetchOperation : AsyncOperation { @@ -61,12 +59,12 @@ protected override async Task LoadAsync(Fetch action, State initalState } } - public class Delays : IInterceptor> - { - public async Task InterceptAsync(IDispatchContext> context, CancellationToken cancellationToken) - { - Console.WriteLine($"{context.Action.GetType().GetStatorName()}: {context.Action.status}"); - await Task.Delay(2000, cancellationToken); - } - } + //public class Delays : IInterceptor> + //{ + // public async Task InterceptAsync(IDispatchContext> context, CancellationToken cancellationToken) + // { + // Console.WriteLine($"{context.Action.GetType().GetStatorName()}: {context.Action.status}"); + // await Task.Delay(2000, cancellationToken); + // } + //} } diff --git a/samples/CounterApp/CounterApp/Pages/FetchData.razor b/samples/CounterApp/CounterApp/Pages/FetchData.razor index bfb84d9..f66ae6b 100644 --- a/samples/CounterApp/CounterApp/Pages/FetchData.razor +++ b/samples/CounterApp/CounterApp/Pages/FetchData.razor @@ -1,4 +1,4 @@ -@using StateR.AsyncLogic +@*@using StateR.AsyncLogic @using StateR.Blazor.Components @page "/fetchdata" @inherits StatorComponent @@ -10,14 +10,14 @@

This component demonstrates fetching data from the server.

Async Status: @WeatherState.Current.Status

- +*@ @**@ @**@ - +@*
Loading... @@ -60,3 +60,4 @@
+*@ \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 3065212..36530f0 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -1,15 +1,12 @@ using CounterApp; -using CounterApp.Features; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using StateR; -using StateR.AfterEffects; -using StateR.Blazor.Persistance; -using StateR.Blazor.ReduxDevTools; +//using StateR.Blazor.Persistance; +//using StateR.Blazor.ReduxDevTools; using ForEvolve.Blazor.WebStorage; -using StateR.Experiments.AsyncLogic; -using StateR.Interceptors; -using StateR.Validations.FluentValidation; +//using StateR.Experiments.AsyncLogic; +//using StateR.Validations.FluentValidation; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -43,13 +40,13 @@ public static void RegisterServices(this IServiceCollection services) var appAssembly = typeof(App).Assembly; services .AddStateR(appAssembly) - .AddAsyncOperations() - .AddReduxDevTools() - .AddFluentValidation(appAssembly) - .Apply(buidler => buidler + //.AddAsyncOperations() + //.AddReduxDevTools() + //.AddFluentValidation(appAssembly) + .Apply(/*buidler => buidler .AddPersistence() .AddStateValidation() - ) + */) ; services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(sp.GetRequiredService().BaseAddress) }); } diff --git a/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor b/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor index 3c74c88..3bd1c75 100644 --- a/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor +++ b/samples/CounterApp/CounterApp/Shared/FluentValidationSummary.razor @@ -1,4 +1,4 @@ -@using FluentValidation.Results +@*@using FluentValidation.Results @using StateR.Validations.FluentValidation; @inherits StatorComponent @inject IState ValidationState @@ -29,3 +29,4 @@ await DispatchAsync(new CleanValidationError()); } } +*@ \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Shared/MainLayout.razor b/samples/CounterApp/CounterApp/Shared/MainLayout.razor index 8751a37..73eba5d 100644 --- a/samples/CounterApp/CounterApp/Shared/MainLayout.razor +++ b/samples/CounterApp/CounterApp/Shared/MainLayout.razor @@ -1,4 +1,4 @@ -@using StateR.Blazor.Components; +@*@using StateR.Blazor.Components;*@ @inherits LayoutComponentBase
@@ -15,5 +15,5 @@ @Body - + @**@
diff --git a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs index cf9133d..3e98ce1 100644 --- a/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs +++ b/src/StateR.Blazor.Experiments/ReduxDevTools/ReduxDevToolsInterop.cs @@ -1,7 +1,6 @@ using Microsoft.JSInterop; using StateR.Internal; using StateR.Updaters; -using StateR.Updaters.Hooks; using System.Collections; using System.Reflection; using System.Text.Json; diff --git a/src/StateR.Blazor/StatorComponentBase.cs b/src/StateR.Blazor/StatorComponentBase.cs index b0bcaa4..f90d387 100644 --- a/src/StateR.Blazor/StatorComponentBase.cs +++ b/src/StateR.Blazor/StatorComponentBase.cs @@ -10,13 +10,21 @@ public abstract class StatorComponentBase : ComponentBase, IDisposable [Inject] public IDispatcher? Dispatcher { get; set; } - protected virtual async Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) - where TAction : IAction + protected virtual async Task DispatchAsync(object action, CancellationToken cancellationToken = default) { GuardAgainstNullDispatcher(); await Dispatcher.DispatchAsync(action, cancellationToken); } + + protected virtual async Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) + where TAction : IAction + where TState : StateBase + { + GuardAgainstNullDispatcher(); + await Dispatcher.DispatchAsync(action, cancellationToken); + } + private void Dispose(bool disposing) { if (!_disposedValue) diff --git a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs index e7fbd92..9496dda 100644 --- a/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs +++ b/src/StateR.Experiments/AsyncLogic/StartupExtensions.cs @@ -13,7 +13,7 @@ public static IStatorBuilder AddAsyncOperations(this IStatorBuilder builder) builder.AddTypes(new[] { typeof(StatusUpdated<>) }); // Async Operation's Errors - builder.Services.TryAddSingleton, UpdaterActionHandler>(); + builder.Services.TryAddSingleton, UpdaterMiddleware>(); builder.Services.TryAddSingleton, AsyncError.Updaters>(); builder.Services.TryAddSingleton, AsyncError.InitialState>(); builder.Services.TryAddSingleton, Internal.State>(); diff --git a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs index 963b5e7..d01fa92 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs @@ -1,7 +1,7 @@ using FluentValidation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using StateR.Interceptors; +using StateR.Pipeline; using System.Reflection; namespace StateR.Validations.FluentValidation; @@ -23,7 +23,7 @@ public static IStatorBuilder AddFluentValidation(this IStatorBuilder builder, pa }); // Validation interceptor and state - builder.Services.TryAddSingleton(typeof(IInterceptor<>), typeof(ValidationInterceptor<>)); + builder.Services.TryAddSingleton(typeof(IActionFilter<,>), typeof(ValidationInterceptor<>)); // Scan for validators builder.Services.AddValidatorsFromAssemblies(assembliesToScan, ServiceLifetime.Singleton); diff --git a/src/StateR/ActionHandlers/ActionHandlersManager.cs b/src/StateR/ActionHandlers/ActionHandlersManager.cs deleted file mode 100644 index df9c673..0000000 --- a/src/StateR/ActionHandlers/ActionHandlersManager.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using StateR.ActionHandlers.Hooks; - -namespace StateR.ActionHandlers; - -public class ActionHandlersManager : IActionHandlersManager -{ - private readonly IActionHandlerHooksCollection _hooksCollection; - private readonly IServiceProvider _serviceProvider; - - public ActionHandlersManager(IActionHandlerHooksCollection hooksCollection, IServiceProvider serviceProvider) - { - _hooksCollection = hooksCollection ?? throw new ArgumentNullException(nameof(hooksCollection)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction - { - var updaterHandlers = _serviceProvider.GetServices>().ToList(); - foreach (var handler in updaterHandlers) - { - dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - - await _hooksCollection.BeforeHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); - await handler.HandleAsync(dispatchContext, dispatchContext.CancellationToken); - await _hooksCollection.AfterHandlerAsync(dispatchContext, handler, dispatchContext.CancellationToken); - } - } -} diff --git a/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs b/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs deleted file mode 100644 index f2d8eab..0000000 --- a/src/StateR/ActionHandlers/Hooks/ActionHandlerHooksCollection.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace StateR.ActionHandlers.Hooks; - -public class ActionHandlerHooksCollection : IActionHandlerHooksCollection -{ - private readonly IEnumerable _beforeActionHooks; - private readonly IEnumerable _afterActionHooks; - public ActionHandlerHooksCollection(IEnumerable beforeActionHooks, IEnumerable afterActionHooks) - { - _beforeActionHooks = beforeActionHooks ?? throw new ArgumentNullException(nameof(beforeActionHooks)); - _afterActionHooks = afterActionHooks ?? throw new ArgumentNullException(nameof(afterActionHooks)); - } - - public async Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _beforeActionHooks) - { - await hook.BeforeHandlerAsync(context, actionHandler, cancellationToken); - } - } - - public async Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _afterActionHooks) - { - await hook.AfterHandlerAsync(context, actionHandler, cancellationToken); - } - } -} diff --git a/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs b/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs deleted file mode 100644 index 2964422..0000000 --- a/src/StateR/ActionHandlers/Hooks/IActionHandlerHooksCollection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.ActionHandlers.Hooks; - -public interface IActionHandlerHooksCollection -{ - Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; - Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs b/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs deleted file mode 100644 index b1ecd82..0000000 --- a/src/StateR/ActionHandlers/Hooks/IAfterActionHook.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StateR.ActionHandlers.Hooks; - -public interface IAfterActionHook -{ - Task AfterHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs b/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs deleted file mode 100644 index c69f7c8..0000000 --- a/src/StateR/ActionHandlers/Hooks/IBeforeActionHook.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StateR.ActionHandlers.Hooks; - -public interface IBeforeActionHook -{ - Task BeforeHandlerAsync(IDispatchContext context, IActionHandler actionHandler, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/ActionHandlers/IActionHandler.cs b/src/StateR/ActionHandlers/IActionHandler.cs deleted file mode 100644 index 4f7747f..0000000 --- a/src/StateR/ActionHandlers/IActionHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.ActionHandlers; - -public interface IActionHandler - where TAction : IAction -{ - Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken); -} diff --git a/src/StateR/ActionHandlers/IActionHandlersManager.cs b/src/StateR/ActionHandlers/IActionHandlersManager.cs deleted file mode 100644 index aa0a671..0000000 --- a/src/StateR/ActionHandlers/IActionHandlersManager.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace StateR.ActionHandlers; - -public interface IActionHandlersManager : IDispatchManager { } diff --git a/src/StateR/AfterEffects/AfterEffectsManager.cs b/src/StateR/AfterEffects/AfterEffectsManager.cs deleted file mode 100644 index 72cdb58..0000000 --- a/src/StateR/AfterEffects/AfterEffectsManager.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using StateR.AfterEffects.Hooks; - -namespace StateR.AfterEffects; - -public class AfterEffectsManager : IAfterEffectsManager -{ - private readonly IAfterEffectHooksCollection _hooks; - private readonly IServiceProvider _serviceProvider; - - public AfterEffectsManager(IAfterEffectHooksCollection hooks, IServiceProvider serviceProvider) - { - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction - { - var afterEffects = _serviceProvider.GetServices>().ToList(); - foreach (var afterEffect in afterEffects) - { - dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - - await _hooks.BeforeHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); - await afterEffect.HandleAfterEffectAsync(dispatchContext, dispatchContext.CancellationToken); - await _hooks.AfterHandlerAsync(dispatchContext, afterEffect, dispatchContext.CancellationToken); - } - } -} diff --git a/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs b/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs deleted file mode 100644 index 1e6a603..0000000 --- a/src/StateR/AfterEffects/Hooks/AfterEffectHooksCollection.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace StateR.AfterEffects.Hooks; - -public class AfterEffectHooksCollection : IAfterEffectHooksCollection -{ - private readonly IEnumerable _beforeAfterEffectHooks; - private readonly IEnumerable _afterAfterEffectHooks; - public AfterEffectHooksCollection(IEnumerable beforeAfterEffectHooks, IEnumerable afterAfterEffectHooks) - { - _beforeAfterEffectHooks = beforeAfterEffectHooks ?? throw new ArgumentNullException(nameof(beforeAfterEffectHooks)); - _afterAfterEffectHooks = afterAfterEffectHooks ?? throw new ArgumentNullException(nameof(afterAfterEffectHooks)); - } - - public async Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _beforeAfterEffectHooks) - { - await hook.BeforeHandlerAsync(context, afterEffect, cancellationToken); - } - } - - public async Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _afterAfterEffectHooks) - { - await hook.AfterHandlerAsync(context, afterEffect, cancellationToken); - } - } -} - diff --git a/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs b/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs deleted file mode 100644 index 8b8d888..0000000 --- a/src/StateR/AfterEffects/Hooks/IAfterAfterEffectHook.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.AfterEffects.Hooks; - -public interface IAfterAfterEffectHook -{ - Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; -} - diff --git a/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs b/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs deleted file mode 100644 index d0e4ce9..0000000 --- a/src/StateR/AfterEffects/Hooks/IAfterEffectHooksCollection.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StateR.AfterEffects.Hooks; - -public interface IAfterEffectHooksCollection -{ - Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; - Task AfterHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; -} - diff --git a/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs b/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs deleted file mode 100644 index 7a473b7..0000000 --- a/src/StateR/AfterEffects/Hooks/IBeforeAfterEffectHook.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.AfterEffects.Hooks; - -public interface IBeforeAfterEffectHook -{ - Task BeforeHandlerAsync(IDispatchContext context, IAfterEffects afterEffect, CancellationToken cancellationToken) where TAction : IAction; -} - diff --git a/src/StateR/AfterEffects/IAfterEffects.cs b/src/StateR/AfterEffects/IAfterEffects.cs deleted file mode 100644 index b8761a2..0000000 --- a/src/StateR/AfterEffects/IAfterEffects.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.AfterEffects; - -public interface IAfterEffects - where TAction : IAction -{ - Task HandleAfterEffectAsync(IDispatchContext context, CancellationToken cancellationToken); -} diff --git a/src/StateR/AfterEffects/IAfterEffectsManager.cs b/src/StateR/AfterEffects/IAfterEffectsManager.cs deleted file mode 100644 index 20aa657..0000000 --- a/src/StateR/AfterEffects/IAfterEffectsManager.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace StateR.AfterEffects; - -public interface IAfterEffectsManager : IDispatchManager { } diff --git a/src/StateR/DispatchContext.cs b/src/StateR/DispatchContext.cs index 8f7ea55..085ca0b 100644 --- a/src/StateR/DispatchContext.cs +++ b/src/StateR/DispatchContext.cs @@ -1,7 +1,8 @@ namespace StateR; -public class DispatchContext : IDispatchContext - where TAction : IAction +public class DispatchContext : IDispatchContext + where TAction : IAction + where TState : StateBase { private readonly CancellationTokenSource _cancellationTokenSource; public DispatchContext(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) @@ -16,12 +17,11 @@ public DispatchContext(TAction action, IDispatcher dispatcher, CancellationToken public CancellationToken CancellationToken => _cancellationTokenSource.Token; public void Cancel() - => throw new DispatchCancelledException(Action); - //=> _cancellationTokenSource.Cancel(true); + => throw new DispatchCancelledException(Action.GetType()); } public class DispatchCancelledException : Exception { - public DispatchCancelledException(IAction action) - : base($"The dispatch operation '{action.GetType().FullName}' has been cancelled.") { } + public DispatchCancelledException(Type actionType) + : base($"The dispatch operation '{actionType.FullName}' has been cancelled.") { } } \ No newline at end of file diff --git a/src/StateR/DispatchContextFactory.cs b/src/StateR/DispatchContextFactory.cs index 4238ffc..9ba0810 100644 --- a/src/StateR/DispatchContextFactory.cs +++ b/src/StateR/DispatchContextFactory.cs @@ -2,7 +2,8 @@ public class DispatchContextFactory : IDispatchContextFactory { - public IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) - where TAction : IAction - => new DispatchContext(action, dispatcher, cancellationTokenSource); + public IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) + where TAction : IAction + where TState : StateBase + => new DispatchContext(action, dispatcher, cancellationTokenSource); } diff --git a/src/StateR/Dispatcher.cs b/src/StateR/Dispatcher.cs index 607c2fb..63eff9d 100644 --- a/src/StateR/Dispatcher.cs +++ b/src/StateR/Dispatcher.cs @@ -1,44 +1,58 @@ using Microsoft.Extensions.Logging; -using StateR.ActionHandlers; -using StateR.AfterEffects; -using StateR.Interceptors; +using StateR.Pipeline; using System; namespace StateR; public class Dispatcher : IDispatcher { - private readonly IInterceptorsManager _interceptorsManager; - private readonly IActionHandlersManager _actionHandlersManager; - private readonly IAfterEffectsManager _afterEffectsManager; private readonly IDispatchContextFactory _dispatchContextFactory; + private readonly IActionFilterFactory _actionFilterFactory; private readonly ILogger _logger; - public Dispatcher(IDispatchContextFactory dispatchContextFactory, IInterceptorsManager interceptorsManager, IActionHandlersManager actionHandlersManager, IAfterEffectsManager afterEffectsManager, ILogger logger) + public Dispatcher(IDispatchContextFactory dispatchContextFactory, IActionFilterFactory actionFilterFactory, ILogger logger) { _dispatchContextFactory = dispatchContextFactory ?? throw new ArgumentNullException(nameof(dispatchContextFactory)); - _interceptorsManager = interceptorsManager ?? throw new ArgumentNullException(nameof(interceptorsManager)); - _actionHandlersManager = actionHandlersManager ?? throw new ArgumentNullException(nameof(actionHandlersManager)); - _afterEffectsManager = afterEffectsManager ?? throw new ArgumentNullException(nameof(afterEffectsManager)); + _actionFilterFactory = actionFilterFactory ?? throw new ArgumentNullException(nameof(actionFilterFactory)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public async Task DispatchAsync(TAction action, CancellationToken cancellationToken) where TAction : IAction + public async Task DispatchAsync(TAction action, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase { using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var dispatchContext = _dispatchContextFactory.Create(action, this, cancellationTokenSource); - // - // TODO: design how to handle OperationCanceledException - // + var dispatchContext = _dispatchContextFactory.Create(action, this, cancellationTokenSource); + var actionFilter = _actionFilterFactory.Create(dispatchContext); try { - await _interceptorsManager.DispatchAsync(dispatchContext); - await _actionHandlersManager.DispatchAsync(dispatchContext); - await _afterEffectsManager.DispatchAsync(dispatchContext); + await actionFilter.InvokeAsync(dispatchContext, null, cancellationToken); } catch (DispatchCancelledException ex) { _logger.LogWarning(ex, ex.Message); } } + + public Task DispatchAsync(object action, CancellationToken cancellationToken) + { + var actionType = action + .GetType(); + var actionInterface = actionType.GetInterfaces() + .FirstOrDefault(@interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(IAction<>)); + if (actionInterface == null) + { + // TODO: Find a better exception + throw new InvalidOperationException($"The action must implement the {typeof(IAction<>).Name} interface."); + } + var stateType = actionInterface.GetGenericArguments()[0]; + var method = GetType().GetMethods().FirstOrDefault(m => m.IsGenericMethod && m.Name == nameof(DispatchAsync)); + if(method == null) + { + throw new MissingMethodException(nameof(Dispatcher), nameof(DispatchAsync)); + } + var genericMethod = method.MakeGenericMethod(actionType, stateType); + var task = genericMethod.Invoke(this, new[] { action, cancellationToken }); + return (Task)task!; + } } diff --git a/src/StateR/IAction.cs b/src/StateR/IAction.cs index 648e6b5..c4138a9 100644 --- a/src/StateR/IAction.cs +++ b/src/StateR/IAction.cs @@ -1,3 +1,3 @@ namespace StateR; -public interface IAction { } +public interface IAction where TState : StateBase { } diff --git a/src/StateR/IDispatchContext.cs b/src/StateR/IDispatchContext.cs index 0f96e31..bfe5295 100644 --- a/src/StateR/IDispatchContext.cs +++ b/src/StateR/IDispatchContext.cs @@ -1,7 +1,8 @@ namespace StateR; -public interface IDispatchContext - where TAction : IAction +public interface IDispatchContext + where TAction : IAction + where TState : StateBase { IDispatcher Dispatcher { get; } TAction Action { get; } diff --git a/src/StateR/IDispatchContextFactory.cs b/src/StateR/IDispatchContextFactory.cs index 6613960..1abb378 100644 --- a/src/StateR/IDispatchContextFactory.cs +++ b/src/StateR/IDispatchContextFactory.cs @@ -2,5 +2,7 @@ public interface IDispatchContextFactory { - IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) where TAction : IAction; + IDispatchContext Create(TAction action, IDispatcher dispatcher, CancellationTokenSource cancellationTokenSource) + where TAction : IAction + where TState : StateBase; } diff --git a/src/StateR/IDispatchManager.cs b/src/StateR/IDispatchManager.cs index 3cdc889..70b1c50 100644 --- a/src/StateR/IDispatchManager.cs +++ b/src/StateR/IDispatchManager.cs @@ -2,6 +2,7 @@ public interface IDispatchManager { - Task DispatchAsync(IDispatchContext dispatchContext) - where TAction : IAction; + Task DispatchAsync(IDispatchContext dispatchContext) + where TAction : IAction + where TState : StateBase; } diff --git a/src/StateR/IDispatcher.cs b/src/StateR/IDispatcher.cs index 6ef0bf0..510ccf1 100644 --- a/src/StateR/IDispatcher.cs +++ b/src/StateR/IDispatcher.cs @@ -2,5 +2,8 @@ public interface IDispatcher { - Task DispatchAsync(TAction action, CancellationToken cancellationToken) where TAction : IAction; + Task DispatchAsync(object action, CancellationToken cancellationToken); + Task DispatchAsync(TAction action, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; } diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 2a2bd3d..f7605af 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -18,4 +18,8 @@ public interface IStatorBuilder IStatorBuilder AddActions(IEnumerable states); IStatorBuilder AddUpdaters(IEnumerable states); IStatorBuilder AddActionHandlers(IEnumerable types); + + IStatorBuilder AddMiddlewares(IEnumerable types); + List Middlewares { get; } + } diff --git a/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs b/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs deleted file mode 100644 index d57c3b8..0000000 --- a/src/StateR/Interceptors/Hooks/IAfterInterceptorHook.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StateR.Interceptors.Hooks; - -public interface IAfterInterceptorHook -{ - Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs b/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs deleted file mode 100644 index 218ab38..0000000 --- a/src/StateR/Interceptors/Hooks/IBeforeInterceptorHook.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace StateR.Interceptors.Hooks; - -public interface IBeforeInterceptorHook -{ - Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs b/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs deleted file mode 100644 index fa4b8f3..0000000 --- a/src/StateR/Interceptors/Hooks/IInterceptorsHooksCollection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.Interceptors.Hooks; - -public interface IInterceptorsHooksCollection -{ - Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; - Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction; -} diff --git a/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs b/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs deleted file mode 100644 index ed254bc..0000000 --- a/src/StateR/Interceptors/Hooks/InterceptorsHooksCollection.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace StateR.Interceptors.Hooks; - -public class InterceptorsHooksCollection : IInterceptorsHooksCollection -{ - private readonly IEnumerable _beforeInterceptorHooks; - private readonly IEnumerable _afterInterceptorHooks; - public InterceptorsHooksCollection(IEnumerable beforeInterceptorHooks, IEnumerable afterInterceptorHooks) - { - _beforeInterceptorHooks = beforeInterceptorHooks ?? throw new ArgumentNullException(nameof(beforeInterceptorHooks)); - _afterInterceptorHooks = afterInterceptorHooks ?? throw new ArgumentNullException(nameof(afterInterceptorHooks)); - } - - public async Task BeforeHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _beforeInterceptorHooks) - { - await hook.BeforeHandlerAsync(context, interceptor, cancellationToken); - } - } - - public async Task AfterHandlerAsync(IDispatchContext context, IInterceptor interceptor, CancellationToken cancellationToken) where TAction : IAction - { - foreach (var hook in _afterInterceptorHooks) - { - await hook.AfterHandlerAsync(context, interceptor, cancellationToken); - } - } -} diff --git a/src/StateR/Interceptors/IInterceptor.cs b/src/StateR/Interceptors/IInterceptor.cs deleted file mode 100644 index 0ca7b9d..0000000 --- a/src/StateR/Interceptors/IInterceptor.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace StateR.Interceptors; - -public interface IInterceptor - where TAction : IAction -{ - Task InterceptAsync(IDispatchContext context, CancellationToken cancellationToken); -} diff --git a/src/StateR/Interceptors/IInterceptorsManager.cs b/src/StateR/Interceptors/IInterceptorsManager.cs deleted file mode 100644 index 23f6853..0000000 --- a/src/StateR/Interceptors/IInterceptorsManager.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace StateR.Interceptors; - -public interface IInterceptorsManager : IDispatchManager { } diff --git a/src/StateR/Interceptors/InterceptorsManager.cs b/src/StateR/Interceptors/InterceptorsManager.cs deleted file mode 100644 index d23c6ea..0000000 --- a/src/StateR/Interceptors/InterceptorsManager.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using StateR.Interceptors.Hooks; - -namespace StateR.Interceptors; - -public class InterceptorsManager : IInterceptorsManager -{ - private readonly IInterceptorsHooksCollection _hooks; - private readonly IServiceProvider _serviceProvider; - - public InterceptorsManager(IInterceptorsHooksCollection hooks, IServiceProvider serviceProvider) - { - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public async Task DispatchAsync(IDispatchContext dispatchContext) where TAction : IAction - { - var interceptors = _serviceProvider.GetServices>().ToList(); - foreach (var interceptor in interceptors) - { - dispatchContext.CancellationToken.ThrowIfCancellationRequested(); - - await _hooks.BeforeHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); - await interceptor.InterceptAsync(dispatchContext, dispatchContext.CancellationToken); - await _hooks.AfterHandlerAsync(dispatchContext, interceptor, dispatchContext.CancellationToken); - } - } -} diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index d83df9e..9148111 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -29,6 +29,10 @@ public IStatorBuilder AddActionHandlers(IEnumerable types) public List Updaters { get; } = new List(); public List All { get; } = new List(); + public IStatorBuilder AddMiddlewares(IEnumerable types) + => AddDistinctTypes(Middlewares, types); + public List Middlewares { get; } = new List(); + private IStatorBuilder AddDistinctTypes(List list, IEnumerable types) { var distinctTypes = types.Except(list).Distinct(); diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR/Internal/TypeScannerBuilderExtensions.cs index aef5c0c..628dbd0 100644 --- a/src/StateR/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR/Internal/TypeScannerBuilderExtensions.cs @@ -1,4 +1,4 @@ -using StateR.ActionHandlers; +using StateR.Pipeline; using StateR.Updaters; using System.Reflection; @@ -17,7 +17,7 @@ public static IStatorBuilder ScanTypes(this IStatorBuilder builder) var updaters = TypeScanner.FindUpdaters(builder.All); builder.AddUpdaters(updaters); - var actionHandlers = TypeScanner.FindActionHandlers(builder.All); + var actionHandlers = TypeScanner.FindMiddlewares(builder.All); builder.AddUpdaters(actionHandlers); return builder; @@ -38,7 +38,7 @@ public static IEnumerable FindActions(IEnumerable types) .Where(type => !type.IsAbstract && type .GetTypeInfo() .GetInterfaces() - .Any(i => i == typeof(IAction)) + .Any(i => i == typeof(IAction<>)) ); return actions; } @@ -53,34 +53,14 @@ public static IEnumerable FindUpdaters(IEnumerable types) ); return updaters; } - public static IEnumerable FindActionHandlers(IEnumerable types) + public static IEnumerable FindMiddlewares(IEnumerable types) { var handlers = types .Where(type => !type.IsAbstract && type .GetTypeInfo() .GetInterfaces() - .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionHandler<>)) + .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IActionFilter<,>)) ); return handlers; } - - //public IStatorBuilder FindInterceptors(this IStatorBuilder builder) - //{ - // var iActionInterceptor = typeof(IInterceptor<>); - // return types.Where(type => !type.IsAbstract && type - // .GetTypeInfo() - // .GetInterfaces() - // .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == iActionInterceptor) - // ); - //} - - //public IStatorBuilder FindAfterEffects(this IStatorBuilder builder) - //{ - // var iAfterEffects = typeof(IAfterEffects<>); - // return types.Where(type => !type.IsAbstract && type - // .GetTypeInfo() - // .GetInterfaces() - // .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == iAfterEffects) - // ); - //} } \ No newline at end of file diff --git a/src/StateR/Pipeline/IActionFilter.cs b/src/StateR/Pipeline/IActionFilter.cs new file mode 100644 index 0000000..2b97cc9 --- /dev/null +++ b/src/StateR/Pipeline/IActionFilter.cs @@ -0,0 +1,13 @@ + +namespace StateR.Pipeline; + +public interface IActionFilter + where TAction : IAction + where TState : StateBase +{ + Task InvokeAsync(IDispatchContext context, ActionDelegate? next, CancellationToken cancellationToken); +} + +public delegate Task ActionDelegate(IDispatchContext context, CancellationToken cancellationToken) + where TAction : IAction + where TState : StateBase; diff --git a/src/StateR/Pipeline/IActionFilterFactory.cs b/src/StateR/Pipeline/IActionFilterFactory.cs new file mode 100644 index 0000000..10cb79a --- /dev/null +++ b/src/StateR/Pipeline/IActionFilterFactory.cs @@ -0,0 +1,26 @@ + +using Microsoft.Extensions.DependencyInjection; +using System; +namespace StateR.Pipeline; + +public interface IActionFilterFactory +{ + IActionFilter Create(IDispatchContext context) + where TAction : IAction + where TState : StateBase; +} + +public class ActionFilterFactory : IActionFilterFactory +{ + private readonly IServiceProvider _serviceProvider; + public ActionFilterFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + public IActionFilter Create(IDispatchContext context) + where TAction : IAction + where TState : StateBase + => _serviceProvider.GetRequiredService>(); + +} \ No newline at end of file diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 093f7bd..5c64773 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -1,14 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using StateR.ActionHandlers; -using StateR.ActionHandlers.Hooks; -using StateR.AfterEffects; -using StateR.AfterEffects.Hooks; -using StateR.Interceptors; -using StateR.Interceptors.Hooks; using StateR.Internal; +using StateR.Pipeline; using StateR.Updaters; -using StateR.Updaters.Hooks; using System.Reflection; namespace StateR; @@ -19,16 +13,17 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) { services.TryAddSingleton(); services.TryAddSingleton(); - - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); + + //services.TryAddSingleton(); + //services.TryAddSingleton(); + //services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); + //services.TryAddSingleton(); + //services.TryAddSingleton(); + //services.TryAddSingleton(); + //services.TryAddSingleton(); return new StatorBuilder(services); } @@ -41,6 +36,11 @@ public static IStatorBuilder AddStateR(this IServiceCollection services, params return builder.AddTypes(allTypes); } + //public static IStatorBuilder AddMiddleware(this IStatorBuilder builder) + //{ + + //} + public static IServiceCollection Apply(this IStatorBuilder builder, Action? postConfiguration = null) { // Extract types @@ -55,51 +55,51 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeInterceptorHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterInterceptorHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeAfterEffectHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterAfterEffectHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeActionHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterActionHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IBeforeUpdateHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - // Equivalent to: AddSingleton(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterUpdateHook))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton, Implementation>(); - .AddClasses(classes => classes.AssignableTo(typeof(IInterceptor<>))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - // Equivalent to: AddSingleton, Implementation>(); - .AddClasses(classes => classes.AssignableTo(typeof(IAfterEffects<>))) - .AsImplementedInterfaces() - .WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeInterceptorHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterInterceptorHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeAfterEffectHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterAfterEffectHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeActionHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterActionHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeUpdateHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + //// Equivalent to: AddSingleton(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterUpdateHook))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton, Implementation>(); + //.AddClasses(classes => classes.AssignableTo(typeof(IInterceptor<>))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() + + //// Equivalent to: AddSingleton, Implementation>(); + //.AddClasses(classes => classes.AssignableTo(typeof(IAfterEffects<>))) + //.AsImplementedInterfaces() + //.WithSingletonLifetime() ); // Register States @@ -113,10 +113,10 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action); - var updaterHandler = typeof(UpdaterActionHandler<,>); - var handlerType = typeof(IActionHandler<>); + var updaterHandler = typeof(UpdaterMiddleware<,>); + var handlerType = typeof(IActionFilter<,>); foreach (var updater in builder.Updaters) { Console.WriteLine($"updater: {updater.FullName}"); @@ -124,21 +124,27 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); foreach (var @interface in interfaces) { - // Equivalent to: AddSingleton, UpdaterHandler> + // Equivalent to: AddSingleton, UpdaterMiddleware> var actionType = @interface.GenericTypeArguments[0]; var stateType = @interface.GenericTypeArguments[1]; - var iActionHandlerServiceType = handlerType.MakeGenericType(actionType); - var updaterHandlerImplementationType = updaterHandler.MakeGenericType(stateType, actionType); - builder.Services.AddSingleton(iActionHandlerServiceType, updaterHandlerImplementationType); + var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); + var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); + builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); // Equivalent to: AddSingleton, Updater>(); builder.Services.AddSingleton(@interface, updater); - Console.WriteLine($"- AddSingleton<{iActionHandlerServiceType.GetStatorName()}, {updaterHandlerImplementationType.GetStatorName()}>()"); + Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); } } + // Register Middleware + foreach (var middleware in builder.Middlewares.AsEnumerable().Reverse()) + { + builder.Services.Decorate(typeof(IActionFilter<,>), middleware); + } + // Run post-configuration postConfiguration?.Invoke(builder); diff --git a/src/StateR/Store.cs b/src/StateR/Store.cs index 8d1e219..ad627df 100644 --- a/src/StateR/Store.cs +++ b/src/StateR/Store.cs @@ -13,7 +13,14 @@ public Store(IServiceProvider serviceProvider, IDispatcher dispatcher) _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); } - public Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) where TAction : IAction + public Task DispatchAsync(TAction action, CancellationToken cancellationToken = default) + where TAction : IAction + where TState : StateBase + { + return _dispatcher.DispatchAsync(action, cancellationToken); + } + + public Task DispatchAsync(object action, CancellationToken cancellationToken) { return _dispatcher.DispatchAsync(action, cancellationToken); } diff --git a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs b/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs deleted file mode 100644 index bcc066a..0000000 --- a/src/StateR/Updaters/Hooks/IAfterUpdateHook.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StateR.Updaters.Hooks; - -public interface IAfterUpdateHook -{ - Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; -} diff --git a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs b/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs deleted file mode 100644 index 7bd32a7..0000000 --- a/src/StateR/Updaters/Hooks/IBeforeUpdateHook.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StateR.Updaters.Hooks; - -public interface IBeforeUpdateHook -{ - Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; -} diff --git a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs deleted file mode 100644 index 1503e95..0000000 --- a/src/StateR/Updaters/Hooks/IUpdateHooksCollection.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace StateR.Updaters.Hooks; - -public interface IUpdateHooksCollection -{ - Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; - Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase; -} diff --git a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs b/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs deleted file mode 100644 index e8c56d8..0000000 --- a/src/StateR/Updaters/Hooks/UpdateHooksCollection.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace StateR.Updaters.Hooks; - -public class UpdateHooksCollection : IUpdateHooksCollection -{ - private readonly IEnumerable _beforeUpdateHooks; - private readonly IEnumerable _afterUpdateHooks; - public UpdateHooksCollection(IEnumerable beforeUpdateHooks, IEnumerable afterUpdateHooks) - { - _beforeUpdateHooks = beforeUpdateHooks ?? throw new ArgumentNullException(nameof(beforeUpdateHooks)); - _afterUpdateHooks = afterUpdateHooks ?? throw new ArgumentNullException(nameof(afterUpdateHooks)); - } - - public async Task BeforeUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase - { - foreach (var hook in _beforeUpdateHooks) - { - await hook.BeforeUpdateAsync(context, state, updater, cancellationToken); - } - } - - public async Task AfterUpdateAsync(IDispatchContext context, IState state, IUpdater updater, CancellationToken cancellationToken) - where TAction : IAction - where TState : StateBase - { - foreach (var hook in _afterUpdateHooks) - { - await hook.AfterUpdateAsync(context, state, updater, cancellationToken); - } - } -} diff --git a/src/StateR/Updaters/IUpdater.cs b/src/StateR/Updaters/IUpdater.cs index ddeacdc..6882cd0 100644 --- a/src/StateR/Updaters/IUpdater.cs +++ b/src/StateR/Updaters/IUpdater.cs @@ -1,7 +1,7 @@ namespace StateR.Updaters; public interface IUpdater - where TAction : IAction + where TAction : IAction where TState : StateBase { TState Update(TAction action, TState state); diff --git a/src/StateR/Updaters/UpdaterActionHandler.cs b/src/StateR/Updaters/UpdaterActionHandler.cs deleted file mode 100644 index 33efa7b..0000000 --- a/src/StateR/Updaters/UpdaterActionHandler.cs +++ /dev/null @@ -1,40 +0,0 @@ -using StateR.ActionHandlers; -using StateR.Updaters.Hooks; - -namespace StateR.Updaters; - -public class UpdaterActionHandler : IActionHandler - where TState : StateBase - where TAction : IAction -{ - private readonly IUpdateHooksCollection _hooks; - private readonly IEnumerable> _updaters; - private readonly IState _state; - - public UpdaterActionHandler(IState state, IEnumerable> updaters, IUpdateHooksCollection hooks) - { - _state = state ?? throw new ArgumentNullException(nameof(state)); - _updaters = updaters ?? throw new ArgumentNullException(nameof(updaters)); - _hooks = hooks ?? throw new ArgumentNullException(nameof(hooks)); - } - - public async Task HandleAsync(IDispatchContext context, CancellationToken cancellationToken) - { - foreach (var updater in _updaters) - { - if (cancellationToken.IsCancellationRequested) - { - break; - } - await _hooks.BeforeUpdateAsync(context, _state, updater, cancellationToken); - if (cancellationToken.IsCancellationRequested) - { - break; - } - _state.Set(updater.Update(context.Action, _state.Current)); - await _hooks.AfterUpdateAsync(context, _state, updater, cancellationToken); - } - _state.Notify(); - cancellationToken.ThrowIfCancellationRequested(); - } -} diff --git a/src/StateR/Updaters/UpdaterMiddleware.cs b/src/StateR/Updaters/UpdaterMiddleware.cs new file mode 100644 index 0000000..60183a3 --- /dev/null +++ b/src/StateR/Updaters/UpdaterMiddleware.cs @@ -0,0 +1,34 @@ +using StateR.Pipeline; + +namespace StateR.Updaters; + +public class UpdaterMiddleware : IActionFilter + where TState : StateBase + where TAction : IAction +{ + private readonly IEnumerable> _updaters; + private readonly IState _state; + + public UpdaterMiddleware(IState state, IEnumerable> updaters) + { + _state = state ?? throw new ArgumentNullException(nameof(state)); + _updaters = updaters ?? throw new ArgumentNullException(nameof(updaters)); + } + + public Task InvokeAsync(IDispatchContext context, ActionDelegate? next, CancellationToken cancellationToken) + { + foreach (var updater in _updaters) + { + if (cancellationToken.IsCancellationRequested) + { + break; + } + _state.Set(updater.Update(context.Action, _state.Current)); + } + _state.Notify(); + cancellationToken.ThrowIfCancellationRequested(); + + next?.Invoke(context, cancellationToken); + return Task.CompletedTask; + } +} diff --git a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs index cd65dba..fbca015 100644 --- a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs +++ b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs @@ -10,7 +10,7 @@ public class UpdaterActionHandlerTest private readonly Mock> _stateMock = new(); private readonly List> _updaters = new(); private readonly Mock _hooksMock = new(); - private readonly UpdaterActionHandler sut; + private readonly UpdaterMiddleware sut; private readonly Queue _operationQueue = new(); private readonly TestAction _action = new(); private readonly DispatchContext _context; From 03f5c53a860ac9b4b334b9cad00e1a2c2a9e95ae Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Tue, 1 Mar 2022 19:56:37 -0500 Subject: [PATCH 41/58] wip --- .../CounterApp/CounterApp/CounterApp.csproj | 1 + .../CounterApp/CounterApp/Features/Counter.cs | 46 +++++++------- samples/CounterApp/CounterApp/Program.cs | 10 +-- .../AsyncLogic/AsyncError.cs | 4 +- .../StateR.Experiments.csproj | 18 ++++++ .../FluentValidation/StartupExtensions.cs | 4 +- .../StateValidationDecorator.cs | 62 +++++++++---------- ...tionInterceptor.cs => ValidationFilter.cs} | 32 +++++++--- src/StateR/IDispatchManager.cs | 8 --- src/StateR/IStatorBuilder.cs | 7 +++ src/StateR/Internal/StatorBuilder.cs | 6 ++ src/StateR/StatorStartupExtensions.cs | 13 +++- 12 files changed, 128 insertions(+), 83 deletions(-) rename src/StateR.Experiments/Validations/FluentValidation/{ValidationInterceptor.cs => ValidationFilter.cs} (69%) delete mode 100644 src/StateR/IDispatchManager.cs diff --git a/samples/CounterApp/CounterApp/CounterApp.csproj b/samples/CounterApp/CounterApp/CounterApp.csproj index 98b8158..74fa298 100644 --- a/samples/CounterApp/CounterApp/CounterApp.csproj +++ b/samples/CounterApp/CounterApp/CounterApp.csproj @@ -20,6 +20,7 @@ + diff --git a/samples/CounterApp/CounterApp/Features/Counter.cs b/samples/CounterApp/CounterApp/Features/Counter.cs index e9ce5cc..3532ade 100644 --- a/samples/CounterApp/CounterApp/Features/Counter.cs +++ b/samples/CounterApp/CounterApp/Features/Counter.cs @@ -1,4 +1,4 @@ -//using FluentValidation; +using FluentValidation; using StateR; //using StateR.Blazor.Persistance; using StateR.Updaters; @@ -32,28 +32,28 @@ public State Update(SetNegative action, State state) => state with { Count = action.Count }; } - //public class SetPositiveValidator : AbstractValidator - //{ - // public SetPositiveValidator() - // { - // RuleFor(x => x.Count).GreaterThan(0); - // } - //} + public class SetPositiveValidator : AbstractValidator + { + public SetPositiveValidator() + { + RuleFor(x => x.Count).GreaterThan(0); + } + } - //public class SetNegativeValidator : AbstractValidator - //{ - // public SetNegativeValidator() - // { - // RuleFor(x => x.Count).LessThan(0); - // } - //} + public class SetNegativeValidator : AbstractValidator + { + public SetNegativeValidator() + { + RuleFor(x => x.Count).LessThan(0); + } + } - //public class StateValidator : AbstractValidator - //{ - // public StateValidator() - // { - // RuleFor(x => x.Count).GreaterThan(-100); - // RuleFor(x => x.Count).LessThan(100); - // } - //} + public class StateValidator : AbstractValidator + { + public StateValidator() + { + RuleFor(x => x.Count).GreaterThan(-100); + RuleFor(x => x.Count).LessThan(100); + } + } } \ No newline at end of file diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 36530f0..9ee5a23 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -6,7 +6,7 @@ //using StateR.Blazor.ReduxDevTools; using ForEvolve.Blazor.WebStorage; //using StateR.Experiments.AsyncLogic; -//using StateR.Validations.FluentValidation; +using StateR.Validations.FluentValidation; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); @@ -42,11 +42,11 @@ public static void RegisterServices(this IServiceCollection services) .AddStateR(appAssembly) //.AddAsyncOperations() //.AddReduxDevTools() - //.AddFluentValidation(appAssembly) - .Apply(/*buidler => buidler - .AddPersistence() + .AddFluentValidation(appAssembly) + .Apply(buidler => buidler + //.AddPersistence() .AddStateValidation() - */) + ) ; services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(sp.GetRequiredService().BaseAddress) }); } diff --git a/src/StateR.Experiments/AsyncLogic/AsyncError.cs b/src/StateR.Experiments/AsyncLogic/AsyncError.cs index a160565..5a97a8b 100644 --- a/src/StateR.Experiments/AsyncLogic/AsyncError.cs +++ b/src/StateR.Experiments/AsyncLogic/AsyncError.cs @@ -6,7 +6,7 @@ public class AsyncError { public record State : StateBase { - public IAction? Action { get; init; } + public IAction? Action { get; init; } public AsyncState? InitialState { get; init; } public AsyncState? ActualState { get; init; } public Exception? Exception { get; init; } @@ -21,7 +21,7 @@ public class InitialState : IInitialState public State Value => new(); } - public record Occured(IAction Action, AsyncState InitialState, AsyncState ActualState, Exception Exception) : IAction; + public record Occured(IAction Action, AsyncState InitialState, AsyncState ActualState, Exception Exception) : IAction; public class Updaters : IUpdater { diff --git a/src/StateR.Experiments/StateR.Experiments.csproj b/src/StateR.Experiments/StateR.Experiments.csproj index 95009eb..0cf5587 100644 --- a/src/StateR.Experiments/StateR.Experiments.csproj +++ b/src/StateR.Experiments/StateR.Experiments.csproj @@ -5,6 +5,24 @@ StateR + + + + + + + + + + + + + + + + + + diff --git a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs index d01fa92..18df8c8 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs @@ -20,10 +20,12 @@ public static IStatorBuilder AddFluentValidation(this IStatorBuilder builder, pa typeof(ValidationUpdaters), typeof(ValidationInitialState), typeof(ValidationState), + typeof(ValidationFilter<,>), }); + builder.AddMiddlewares(new[] { typeof(ValidationFilter<,>) }); // Validation interceptor and state - builder.Services.TryAddSingleton(typeof(IActionFilter<,>), typeof(ValidationInterceptor<>)); + builder.Services.TryAddSingleton(typeof(IActionFilter<,>), typeof(ValidationFilter<,>)); // Scan for validators builder.Services.AddValidatorsFromAssemblies(assembliesToScan, ServiceLifetime.Singleton); diff --git a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs index 91a8a2b..9468541 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs @@ -1,10 +1,7 @@ using FluentValidation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using StateR.ActionHandlers; using StateR.Internal; -using System; -using System.Reflection; namespace StateR.Validations.FluentValidation; @@ -54,15 +51,16 @@ public static class StateValidatorStartupExtensions public static IStatorBuilder AddStateValidation(this IStatorBuilder builder) { RegisterStateDecorator(builder.Services, builder.All); - ActionHandlerDecorator(builder.Services); + //ActionHandlerDecorator(builder.Services); return builder; } - private static void ActionHandlerDecorator(IServiceCollection services) - { - Console.WriteLine("- Decorate, ValidationExceptionActionHandlersManagerDecorator>()"); - services.Decorate(); - } + //private static void ActionHandlerDecorator(IServiceCollection services) + //{ + // Console.WriteLine("- Decorate, ValidationExceptionActionHandlersManagerDecorator>()"); + // services.Decorate(); + //} + private static void RegisterStateDecorator(IServiceCollection services, IEnumerable types) { var states = TypeScanner.FindStates(types); @@ -79,27 +77,27 @@ private static void RegisterStateDecorator(IServiceCollection services, IEnumera } } -public class ValidationExceptionActionHandlersManagerDecorator : IActionHandlersManager -{ - private readonly IActionHandlersManager _next; - public ValidationExceptionActionHandlersManagerDecorator(IActionHandlersManager next) - { - _next = next ?? throw new ArgumentNullException(nameof(next)); - } +//public class ValidationExceptionActionHandlersManagerDecorator : IActionFilter +//{ +// private readonly IActionHandlersManager _next; +// public ValidationExceptionActionHandlersManagerDecorator(IActionHandlersManager next) +// { +// _next = next ?? throw new ArgumentNullException(nameof(next)); +// } - public async Task DispatchAsync(IDispatchContext dispatchContext) - where TAction : IAction - { - try - { - await _next.DispatchAsync(dispatchContext); - } - catch (ValidationException ex) - { - await dispatchContext.Dispatcher.DispatchAsync( - new AddValidationErrors(ex.Errors), - dispatchContext.CancellationToken - ); - } - } -} +// public async Task DispatchAsync(IDispatchContext dispatchContext) +// where TAction : IAction +// { +// try +// { +// await _next.DispatchAsync(dispatchContext); +// } +// catch (ValidationException ex) +// { +// await dispatchContext.Dispatcher.DispatchAsync( +// new AddValidationErrors(ex.Errors), +// dispatchContext.CancellationToken +// ); +// } +// } +//} diff --git a/src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs b/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs similarity index 69% rename from src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs rename to src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs index 9a140b9..9c836c3 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/ValidationInterceptor.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs @@ -1,22 +1,25 @@ using FluentValidation; using FluentValidation.Results; -using StateR.Interceptors; +using StateR.Pipeline; using StateR.Updaters; using System.Collections.Immutable; namespace StateR.Validations.FluentValidation; -public class ValidationInterceptor : IInterceptor - where TAction : IAction +public class ValidationFilter : IActionFilter + where TAction : IAction + where TState : StateBase { private readonly IEnumerable> _validators; - public ValidationInterceptor(IEnumerable> validators) + public ValidationFilter(IEnumerable> validators) { _validators = validators; } - public async Task InterceptAsync(IDispatchContext context, CancellationToken cancellationToken) + public async Task InvokeAsync(IDispatchContext context, ActionDelegate? next, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(next, nameof(next)); + var result = _validators .Select(validator => validator.Validate(context.Action)); if (result?.Any(validator => !validator.IsValid) ?? false) @@ -27,6 +30,17 @@ public async Task InterceptAsync(IDispatchContext context, Cancellation await context.Dispatcher.DispatchAsync(new AddValidationErrors(errors), cancellationToken); context.Cancel(); } + try + { + await next(context, cancellationToken); + } + catch (ValidationException ex) + { + await context.Dispatcher.DispatchAsync( + new AddValidationErrors(ex.Errors), + context.CancellationToken + ); + } } } @@ -39,10 +53,10 @@ public class ValidationInitialState : IInitialState public ValidationState Value => new(ImmutableList.Create()); } -public record class AddValidationErrors(IEnumerable Errors) : IAction; -public record class ReplaceValidationErrors(IEnumerable Errors) : IAction; -public record class CleanValidationError() : IAction; -public record class RemoveValidationError(ValidationFailure Error) : IAction; +public record class AddValidationErrors(IEnumerable Errors) : IAction; +public record class ReplaceValidationErrors(IEnumerable Errors) : IAction; +public record class CleanValidationError() : IAction; +public record class RemoveValidationError(ValidationFailure Error) : IAction; public class ValidationUpdaters : IUpdater, diff --git a/src/StateR/IDispatchManager.cs b/src/StateR/IDispatchManager.cs deleted file mode 100644 index 70b1c50..0000000 --- a/src/StateR/IDispatchManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace StateR; - -public interface IDispatchManager -{ - Task DispatchAsync(IDispatchContext dispatchContext) - where TAction : IAction - where TState : StateBase; -} diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index f7605af..3c1b3b3 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -22,4 +22,11 @@ public interface IStatorBuilder IStatorBuilder AddMiddlewares(IEnumerable types); List Middlewares { get; } + + IStatorBuilder AddState() + where TState : StateBase; + + //IStatorBuilder AddAction() + // where TState : StateBase + // where TAction : IAction; } diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 9148111..4898d31 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -39,4 +39,10 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types list.AddRange(distinctTypes); return this; } + + public IStatorBuilder AddState() where TState : StateBase + { + States.Add(typeof(TState)); + return this; + } } diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 5c64773..cac8df4 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -28,12 +28,17 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) return new StatorBuilder(services); } - public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScan) + public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) { var builder = services.AddStateR(); - var allTypes = assembliesToScan + var allTypes = assembliesToScanForStates .SelectMany(a => a.GetTypes()); - return builder.AddTypes(allTypes); + //builder.AddTypes(allTypes); + + var states = TypeScanner.FindStates(allTypes); + builder.AddStates(states); + + return builder; } //public static IStatorBuilder AddMiddleware(this IStatorBuilder builder) @@ -43,6 +48,8 @@ public static IStatorBuilder AddStateR(this IServiceCollection services, params public static IServiceCollection Apply(this IStatorBuilder builder, Action? postConfiguration = null) { + return builder.Services; + // Extract types builder.ScanTypes(); From c8d121d01ac7059b709d8855393012aa27d6f5f6 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Tue, 1 Mar 2022 20:00:34 -0500 Subject: [PATCH 42/58] Remove old tests --- .../ActionHandlerManagerTest.cs | 116 ------------ .../Hooks/ActionHandlerHooksCollectionTest.cs | 60 ------ .../AfterEffects/AfterEffectsManagerTest.cs | 116 ------------ .../Hooks/AfterEffectHooksCollectionTest.cs | 61 ------ test/StateR.Tests/DispatcherTest.cs | 82 -------- .../Hooks/InterceptorsHooksCollectionTest.cs | 60 ------ .../Interceptors/InterceptorsManagerTest.cs | 114 ----------- .../Hooks/UpdateHooksCollectionTest.cs | 65 ------- .../Reducers/UpdaterActionHandlerTest.cs | 178 ------------------ 9 files changed, 852 deletions(-) delete mode 100644 test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs delete mode 100644 test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs delete mode 100644 test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs delete mode 100644 test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs delete mode 100644 test/StateR.Tests/DispatcherTest.cs delete mode 100644 test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs delete mode 100644 test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs delete mode 100644 test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs delete mode 100644 test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs diff --git a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs b/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs deleted file mode 100644 index ff259c1..0000000 --- a/test/StateR.Tests/ActionHandlers/ActionHandlerManagerTest.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Moq; -using StateR.ActionHandlers.Hooks; -using Xunit; - -namespace StateR.ActionHandlers; - -public class ActionHandlerManagerTest -{ - private readonly Mock _hooksCollectionMock = new(); - - protected ActionHandlersManager CreateUpdatersManager(Action configureServices) - { - var services = new ServiceCollection(); - configureServices?.Invoke(services); - var serviceProvider = services.BuildServiceProvider(); - return new ActionHandlersManager(_hooksCollectionMock.Object, serviceProvider); - } - - public class DispatchAsync : ActionHandlerManagerTest - { - [Fact] - public async Task Should_call_all_action_handlers() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var handler1 = new Mock>(); - var handler2 = new Mock>(); - var sut = CreateUpdatersManager(services => - { - services.AddSingleton(handler1.Object); - services.AddSingleton(handler2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - handler1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); - handler2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); - } - - [Fact] - public async Task Should_break_handlers_when_Cancel() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) - => context.Cancel()); - var afterEffect2 = new Mock>(); - var sut = CreateUpdatersManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await Assert.ThrowsAsync(() - => sut.DispatchAsync(context)); - - // Assert - afterEffect1.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Once); - afterEffect2.Verify(x => x.HandleAsync(context, cancellationTokenSource.Token), Times.Never); - } - - [Fact] - public async Task Should_call_middleware_and_handlers_in_order() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var operationQueue = new Queue(); - var actionHandler1 = new Mock>(); - actionHandler1.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("actionHandler1.HandleAsync")); - var actionHandler2 = new Mock>(); - actionHandler2.Setup(x => x.HandleAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("actionHandler2.HandleAsync")); - _hooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); - _hooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - - var sut = CreateUpdatersManager(services => - { - services.AddSingleton(actionHandler1.Object); - services.AddSingleton(actionHandler2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - Assert.Collection(operationQueue, - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("actionHandler1.HandleAsync", op), - op => Assert.Equal("AfterHandlerAsync", op), - - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("actionHandler2.HandleAsync", op), - op => Assert.Equal("AfterHandlerAsync", op) - ); - } - } - - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs b/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs deleted file mode 100644 index 50a60fb..0000000 --- a/test/StateR.Tests/ActionHandlers/Hooks/ActionHandlerHooksCollectionTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Moq; -using Xunit; - -namespace StateR.ActionHandlers.Hooks; - -public class ActionHandlerHooksCollectionTest -{ - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); - - private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly ActionHandlerHooksCollection sut; - - public ActionHandlerHooksCollectionTest() - { - dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - public class BeforeHandlerAsync : ActionHandlerHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - } - } - - public class AfterHandlerAsync : ActionHandlerHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - } - } - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs b/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs deleted file mode 100644 index bd9a9b1..0000000 --- a/test/StateR.Tests/AfterEffects/AfterEffectsManagerTest.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Moq; -using StateR.AfterEffects.Hooks; -using Xunit; - -namespace StateR.AfterEffects; - -public class AfterEffectsManagerTest -{ - private readonly Mock _afterEffectHooksCollectionMock = new(); - protected AfterEffectsManager CreateAfterEffectsManager(Action configureServices) - { - var services = new ServiceCollection(); - configureServices?.Invoke(services); - services.TryAddSingleton(_afterEffectHooksCollectionMock.Object); - var serviceProvider = services.BuildServiceProvider(); - var afterEffectHooksCollection = serviceProvider.GetService(); - return new AfterEffectsManager(afterEffectHooksCollection, serviceProvider); - } - - public class DispatchAsync : AfterEffectsManagerTest - { - [Fact] - public async Task Should_handle_all_after_effects() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var afterEffect1 = new Mock>(); - var afterEffect2 = new Mock>(); - var sut = CreateAfterEffectsManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); - afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); - } - - [Fact] - public async Task Should_break_after_effects_when_Cancel() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); - var afterEffect2 = new Mock>(); - var sut = CreateAfterEffectsManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await Assert.ThrowsAsync(() - => sut.DispatchAsync(context)); - - // Assert - afterEffect1.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Once); - afterEffect2.Verify(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token), Times.Never); - } - - [Fact] - public async Task Should_call_hooks_and_after_effects_methods_in_order() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var operationQueue = new Queue(); - var afterEffect1 = new Mock>(); - afterEffect1.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("afterEffect1.HandleAfterEffectAsync")); - var afterEffect2 = new Mock>(); - afterEffect2.Setup(x => x.HandleAfterEffectAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("afterEffect2.HandleAfterEffectAsync")); - _afterEffectHooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); - _afterEffectHooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - var sut = CreateAfterEffectsManager(services => - { - services.AddSingleton(afterEffect1.Object); - services.AddSingleton(afterEffect2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - Assert.Collection(operationQueue, - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("afterEffect1.HandleAfterEffectAsync", op), - op => Assert.Equal("AfterHandlerAsync", op), - - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("afterEffect2.HandleAfterEffectAsync", op), - op => Assert.Equal("AfterHandlerAsync", op) - ); - } - } - - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs b/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs deleted file mode 100644 index baa3fea..0000000 --- a/test/StateR.Tests/AfterEffects/Hooks/AfterEffectHooksCollectionTest.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Moq; -using Xunit; - -namespace StateR.AfterEffects.Hooks; - -public class AfterEffectHooksCollectionTest -{ - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); - - private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext _dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly AfterEffectHooksCollection sut; - - public AfterEffectHooksCollectionTest() - { - _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - public class BeforeHandlerAsync : AfterEffectHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - } - - } - - public class AfterHandlerAsync : AfterEffectHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - } - } - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/DispatcherTest.cs b/test/StateR.Tests/DispatcherTest.cs deleted file mode 100644 index 42c81d3..0000000 --- a/test/StateR.Tests/DispatcherTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.Extensions.Logging; -using Moq; -using StateR.ActionHandlers; -using StateR.AfterEffects; -using StateR.Interceptors; -using Xunit; - -namespace StateR; - -public class DispatcherTest -{ - private readonly Mock _dispatchContextFactory = new(); - private readonly Mock _interceptorsManager = new(); - private readonly Mock _actionHandlersManager = new(); - private readonly Mock _afterEffectsManager = new(); - private readonly Mock> _loggerMock = new(); - private readonly Dispatcher sut; - - public DispatcherTest() - { - sut = new(_dispatchContextFactory.Object, _interceptorsManager.Object, _actionHandlersManager.Object, _afterEffectsManager.Object, _loggerMock.Object); - } - - public class DispatchAsync : DispatcherTest - { - [Fact] - public async Task Should_create_DispatchContext_using_dispatchContextFactory() - { - var action = new TestAction(); - await sut.DispatchAsync(action, CancellationToken.None); - _dispatchContextFactory - .Verify(x => x.Create(action, sut, It.IsAny()), Times.Once); - } - - [Fact] - public async Task Should_send_the_same_DispatchContext_to_all_managers() - { - // Arrange - var action = new TestAction(); - var context = new DispatchContext(action, new Mock().Object, new CancellationTokenSource()); - _dispatchContextFactory - .Setup(x => x.Create(action, sut, It.IsAny())) - .Returns(context); - - // Act - await sut.DispatchAsync(action, CancellationToken.None); - - // Assert - _interceptorsManager.Verify(x => x.DispatchAsync(context), Times.Once); - _actionHandlersManager.Verify(x => x.DispatchAsync(context), Times.Once); - _afterEffectsManager.Verify(x => x.DispatchAsync(context), Times.Once); - } - [Fact] - public async Task Should_call_managers_in_the_expected_order() - { - // Arrange - var action = new TestAction(); - var operationQueue = new Queue(); - _interceptorsManager - .Setup(x => x.DispatchAsync(It.IsAny>())) - .Callback(() => operationQueue.Enqueue("Interceptors")); - _actionHandlersManager - .Setup(x => x.DispatchAsync(It.IsAny>())) - .Callback(() => operationQueue.Enqueue("Updaters")); - _afterEffectsManager - .Setup(x => x.DispatchAsync(It.IsAny>())) - .Callback(() => operationQueue.Enqueue("AfterEffects")); - - // Act - await sut.DispatchAsync(action, CancellationToken.None); - - // Assert - Assert.Collection(operationQueue, - operation => Assert.Equal("Interceptors", operation), - operation => Assert.Equal("Updaters", operation), - operation => Assert.Equal("AfterEffects", operation) - ); - } - } - - private record TestAction : IAction; -} diff --git a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs b/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs deleted file mode 100644 index b9cb112..0000000 --- a/test/StateR.Tests/Interceptors/Hooks/InterceptorsHooksCollectionTest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Moq; -using Xunit; - -namespace StateR.Interceptors.Hooks; - -public class InterceptorsHooksCollectionTest -{ - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); - - private readonly Mock> _afterEffectMock = new(); - private readonly IDispatchContext _dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly InterceptorsHooksCollection sut; - - public InterceptorsHooksCollectionTest() - { - _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - - public class BeforeHandlerAsync : InterceptorsHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - } - } - public class AfterHandlerAsync : InterceptorsHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterHandlerAsync(_dispatchContext, _afterEffectMock.Object, _cancellationToken), Times.Once); - } - } - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs b/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs deleted file mode 100644 index 09e4521..0000000 --- a/test/StateR.Tests/Interceptors/InterceptorsManagerTest.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Moq; -using StateR.Interceptors.Hooks; -using Xunit; - -namespace StateR.Interceptors; - -public class InterceptorsManagerTest -{ - private readonly Mock _hooksCollectionMock = new(); - protected InterceptorsManager CreateInterceptorsManager(Action configureServices) - { - var services = new ServiceCollection(); - configureServices?.Invoke(services); - services.TryAddSingleton(_hooksCollectionMock.Object); - var serviceProvider = services.BuildServiceProvider(); - return new InterceptorsManager(_hooksCollectionMock.Object, serviceProvider); - } - - public class DispatchAsync : InterceptorsManagerTest - { - [Fact] - public async Task Should_dispatch_to_all_interceptors() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var interceptor1 = new Mock>(); - var interceptor2 = new Mock>(); - var sut = CreateInterceptorsManager(services => - { - services.AddSingleton(interceptor1.Object); - services.AddSingleton(interceptor2.Object); - }); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - // Act - await sut.DispatchAsync(context); - - // Assert - interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); - interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); - } - - [Fact] - public async Task Should_call_middleware_and_interceptors_methods_in_order() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var operationQueue = new Queue(); - var interceptor1 = new Mock>(); - interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("interceptor1.InterceptAsync")); - var interceptor2 = new Mock>(); - interceptor2.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("interceptor2.InterceptAsync")); - _hooksCollectionMock - .Setup(x => x.BeforeHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("BeforeHandlerAsync")); - _hooksCollectionMock - .Setup(x => x.AfterHandlerAsync(context, It.IsAny>(), cancellationTokenSource.Token)) - .Callback(() => operationQueue.Enqueue("AfterHandlerAsync")); - var sut = CreateInterceptorsManager(services => - { - services.AddSingleton(interceptor1.Object); - services.AddSingleton(interceptor2.Object); - }); - - // Act - await sut.DispatchAsync(context); - - // Assert - Assert.Collection(operationQueue, - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("interceptor1.InterceptAsync", op), - op => Assert.Equal("AfterHandlerAsync", op), - - op => Assert.Equal("BeforeHandlerAsync", op), - op => Assert.Equal("interceptor2.InterceptAsync", op), - op => Assert.Equal("AfterHandlerAsync", op) - ); - } - - [Fact] - public async Task Should_break_interception_when_Cancel() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - - var interceptor1 = new Mock>(); - interceptor1.Setup(x => x.InterceptAsync(context, cancellationTokenSource.Token)) - .Callback((IDispatchContext context, CancellationToken cancellationToken) => context.Cancel()); - var interceptor2 = new Mock>(); - var sut = CreateInterceptorsManager(services => - { - services.AddSingleton(interceptor1.Object); - services.AddSingleton(interceptor2.Object); - }); - - // Act - await Assert.ThrowsAsync(() - => sut.DispatchAsync(context)); - - // Assert - interceptor1.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Once); - interceptor2.Verify(x => x.InterceptAsync(context, cancellationTokenSource.Token), Times.Never); - } - } - - public record TestAction : IAction; -} diff --git a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs b/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs deleted file mode 100644 index fd771ad..0000000 --- a/test/StateR.Tests/Reducers/Hooks/UpdateHooksCollectionTest.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Moq; -using Xunit; - -namespace StateR.Updaters.Hooks; - -public class UpdateHooksCollectionTest -{ - private readonly Mock _before1Mock = new(); - private readonly Mock _before2Mock = new(); - private readonly Mock _after1Mock = new(); - private readonly Mock _after2Mock = new(); - - private readonly Mock> _stateMock = new(); - private readonly Mock> _updater = new(); - - private readonly IDispatchContext _dispatchContext; - private readonly CancellationToken _cancellationToken = CancellationToken.None; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly UpdateHooksCollection sut; - - public UpdateHooksCollectionTest() - { - _dispatchContext = new DispatchContext(new TestAction(), new Mock().Object, _cancellationTokenSource); - sut = new UpdateHooksCollection( - new[] { _before1Mock.Object, _before2Mock.Object }, - new[] { _after1Mock.Object, _after2Mock.Object } - ); - } - - public class BeforeUpdateAsync : UpdateHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - } - } - - public class AfterUpdateAsync : UpdateHooksCollectionTest - { - [Fact] - public async Task Should_call_all_hooks() - { - // Act - await sut.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken); - - // Assert - _before1Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - _before2Mock.Verify(x => x.BeforeUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Never); - _after1Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - _after2Mock.Verify(x => x.AfterUpdateAsync(_dispatchContext, _stateMock.Object, _updater.Object, _cancellationToken), Times.Once); - } - } - - public record TestAction : IAction; - public record TestState : StateBase; -} diff --git a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs b/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs deleted file mode 100644 index fbca015..0000000 --- a/test/StateR.Tests/Reducers/UpdaterActionHandlerTest.cs +++ /dev/null @@ -1,178 +0,0 @@ -using Moq; -using StateR.Updaters.Hooks; -using Xunit; - -namespace StateR.Updaters; - -public class UpdaterActionHandlerTest -{ - private readonly TestState _state = new(); - private readonly Mock> _stateMock = new(); - private readonly List> _updaters = new(); - private readonly Mock _hooksMock = new(); - private readonly UpdaterMiddleware sut; - private readonly Queue _operationQueue = new(); - private readonly TestAction _action = new(); - private readonly DispatchContext _context; - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - - private readonly Mock> _updater1Mock = new(); - private readonly Mock> _updater2Mock = new(); - - public UpdaterActionHandlerTest() - { - _context = new(_action, new Mock().Object, _cancellationTokenSource); - - _stateMock.Setup(x => x.Current).Returns(_state); - _stateMock.Setup(x => x.Notify()) - .Callback(() => _operationQueue.Enqueue("state.Notify")); - - _updater1Mock - .Setup(x => x.Update(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("updater1.Update")); - _updaters.Add(_updater1Mock.Object); - _updater2Mock - .Setup(x => x.Update(_action, _state)) - .Returns(_state) - .Callback(() => _operationQueue.Enqueue("updater2.Update")); - _updaters.Add(_updater2Mock.Object); - - sut = new UpdaterActionHandler( - _stateMock.Object, - _updaters, - _hooksMock.Object - ); - } - - public class HandleAsync : UpdaterActionHandlerTest - { - [Fact] - public async Task Should_call_updaters_then_notify() - { - // Act - await sut.HandleAsync(_context, CancellationToken.None); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("updater2.Update", op), - op => Assert.Equal("state.Notify", op) - ); - } - - [Fact] - public async Task Should_break_updates_when_Cancel_is_called_in_a_BeforeUpdateAsync_hook() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => - { - _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2"); - _cancellationTokenSource.Cancel(); - }); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await Assert.ThrowsAsync(() - => sut.HandleAsync(_context, _cancellationTokenSource.Token)); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), - - op => Assert.Equal("state.Notify", op) - ); - } - - [Fact] - public async Task Should_break_updates_when_Cancel_is_called_in_an_AfterUpdateAsync_hook() - { - // Arrange - var cancellationTokenSource = new CancellationTokenSource(); - var context = new DispatchContext(new TestAction(), new Mock().Object, cancellationTokenSource); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => - { - _operationQueue.Enqueue("AfterUpdaterAsync:Updater1"); - _cancellationTokenSource.Cancel(); - }); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, _cancellationTokenSource.Token)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await Assert.ThrowsAsync(() - => sut.HandleAsync(_context, _cancellationTokenSource.Token)); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("state.Notify", op) - ); - } - - [Fact] - public async Task Should_call_hooks_methods_in_order() - { - // Arrange - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.BeforeUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("BeforeUpdaterAsync:Updater2")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater1Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater1")); - _hooksMock - .Setup(x => x.AfterUpdateAsync(_context, _stateMock.Object, _updater2Mock.Object, CancellationToken.None)) - .Callback(() => _operationQueue.Enqueue("AfterUpdaterAsync:Updater2")); - - // Act - await sut.HandleAsync(_context, CancellationToken.None); - - // Assert - Assert.Collection(_operationQueue, - op => Assert.Equal("BeforeUpdaterAsync:Updater1", op), - op => Assert.Equal("updater1.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater1", op), - - op => Assert.Equal("BeforeUpdaterAsync:Updater2", op), - op => Assert.Equal("updater2.Update", op), - op => Assert.Equal("AfterUpdaterAsync:Updater2", op), - - op => Assert.Equal("state.Notify", op) - ); - } - } - - - public record TestAction : IAction; - public record TestState : StateBase; -} From 8d523713318f0a0c0e860eaf92431d63736a6eb1 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 19:39:19 -0500 Subject: [PATCH 43/58] Register IState --- src/StateR/IStatorBuilder.cs | 19 +++-- src/StateR/Internal/StatorBuilder.cs | 59 ++++++++++++- src/StateR/Pipeline/IActionFilterFactory.cs | 1 - src/StateR/StatorStartupExtensions.cs | 22 +++++ .../Internal/StatorBuilderTest.cs | 82 +++++++++++++++++++ .../StatorStartupExtensionsTest.cs | 41 +++++++++- test/StateR.Tests/TestTypes.cs | 20 +++++ 7 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 test/StateR.Tests/TestTypes.cs diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 3c1b3b3..426cf89 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -1,12 +1,23 @@ using Microsoft.Extensions.DependencyInjection; +using System.Collections.ObjectModel; namespace StateR; -public interface IStatorBuilder +public interface IStatorBuilder : IOldStatorBuilder { IServiceCollection Services { get; } + ReadOnlyCollection States { get; } + ReadOnlyCollection InitialStates { get; } + + IStatorBuilder AddState() + where TState : StateBase + where TInitialState : IInitialState; + IStatorBuilder AddState(Type state, Type initialState); +} + +public interface IOldStatorBuilder +{ List Actions { get; } - List States { get; } //List Interceptors { get; } List ActionHandlers { get; } //List AfterEffects { get; } @@ -22,10 +33,6 @@ public interface IStatorBuilder IStatorBuilder AddMiddlewares(IEnumerable types); List Middlewares { get; } - - IStatorBuilder AddState() - where TState : StateBase; - //IStatorBuilder AddAction() // where TState : StateBase // where TAction : IAction; diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 4898d31..d1b3fef 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -1,18 +1,25 @@ using Microsoft.Extensions.DependencyInjection; +using System.Collections.ObjectModel; namespace StateR.Internal; public class StatorBuilder : IStatorBuilder { + private readonly List _states = new(); + private readonly List _initialStates = new(); + public StatorBuilder(IServiceCollection services) { Services = services ?? throw new ArgumentNullException(nameof(services)); } + + #region IOldStatorBuilder + public IStatorBuilder AddTypes(IEnumerable types) => AddDistinctTypes(All, types); public IStatorBuilder AddStates(IEnumerable types) - => AddDistinctTypes(States, types); + => AddDistinctTypes(_states, types); public IStatorBuilder AddActions(IEnumerable types) => AddDistinctTypes(Actions, types); public IStatorBuilder AddUpdaters(IEnumerable types) @@ -22,7 +29,6 @@ public IStatorBuilder AddActionHandlers(IEnumerable types) public IServiceCollection Services { get; } public List Actions { get; } = new List(); - public List States { get; } = new List(); public List Interceptors { get; } = new List(); public List ActionHandlers { get; } = new List(); public List AfterEffects { get; } = new List(); @@ -40,9 +46,54 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types return this; } - public IStatorBuilder AddState() where TState : StateBase + #endregion + + public ReadOnlyCollection States => new(_states); + public ReadOnlyCollection InitialStates => new(_initialStates); + + public IStatorBuilder AddState() + where TState : StateBase + where TInitialState : IInitialState { - States.Add(typeof(TState)); + _states.Add(typeof(TState)); + _initialStates.Add(typeof(TInitialState)); return this; } + + public IStatorBuilder AddState(Type state, Type initialState) + { + if (!state.IsAssignableTo(typeof(StateBase))) + { + throw new InvalidStateException(state); + } + if (!initialState.IsAssignableTo(typeof(IInitialState<>).MakeGenericType(state))) + { + throw new InvalidInitialStateException(state, initialState); + } + _states.Add(state); + return this; + } +} + +public class InvalidStateException : Exception +{ + public InvalidStateException(Type stateType) + { + StateType = stateType; + } + + public Type StateType { get; } +} + +public class InvalidInitialStateException : Exception +{ + public InvalidInitialStateException(Type stateType, Type initialState) + : base($"The type {initialState.Name} is not a valid IInitialState<{stateType.Name}>.") + { + StateType = stateType; + InitialStateType = initialState; + } + + public Type StateType { get; } + public Type InitialStateType { get; } } diff --git a/src/StateR/Pipeline/IActionFilterFactory.cs b/src/StateR/Pipeline/IActionFilterFactory.cs index 10cb79a..4e17058 100644 --- a/src/StateR/Pipeline/IActionFilterFactory.cs +++ b/src/StateR/Pipeline/IActionFilterFactory.cs @@ -22,5 +22,4 @@ public IActionFilter Create(IDispatchContext where TState : StateBase => _serviceProvider.GetRequiredService>(); - } \ No newline at end of file diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index cac8df4..feb8aca 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -48,6 +48,28 @@ public static IStatorBuilder AddStateR(this IServiceCollection services, params public static IServiceCollection Apply(this IStatorBuilder builder, Action? postConfiguration = null) { + // Register States + foreach (var state in builder.States) + { + Console.WriteLine($"state: {state.FullName}"); + + // Equivalent to: AddSingleton, State>(); + var stateServiceType = typeof(IState<>).MakeGenericType(state); + var stateImplementationType = typeof(State<>).MakeGenericType(state); + builder.Services.AddSingleton(stateServiceType, stateImplementationType); + } + + // Register Initial States + builder.Services.Scan(s => s + .AddTypes(builder.InitialStates) + + // Equivalent to: AddSingleton, Implementation>(); + .AddClasses(classes => classes.AssignableTo(typeof(IInitialState<>))) + .AsImplementedInterfaces() + .WithSingletonLifetime() + ); + + return builder.Services; // Extract types diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 208f67b..5eb581b 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -5,6 +5,88 @@ namespace StateR.Internal; public class StatorBuilderTest { + public class AddState_TState : StatorBuilderTest + { + [Fact] + public void Should_add_TState_to_States_and_TInitialState_to_InitialStates() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act + sut.AddState(); + + // Assert + Assert.Collection(sut.States, + type => Assert.Equal(typeof(TestState1), type) + ); + Assert.Collection(sut.InitialStates, + type => Assert.Equal(typeof(InitialTestState1), type) + ); + } + } + public class AddState_Type : StatorBuilderTest + { + [Fact] + public void Should_add_a_valid_state_type_to_States() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act + sut.AddState(typeof(TestState1), typeof(InitialTestState1)); + + // Assert + Assert.Collection(sut.States, + type => Assert.Equal(typeof(TestState1), type) + ); + } + + [Fact] + public void Should_throw_an_InvalidStateException_when_the_state_type_does_not_inherit_StateBase() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var stateType = typeof(NotAState); + var initialStateType = typeof(InitialTestState1); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); + Assert.Same(stateType, ex.StateType); + } + + [Fact] + public void Should_throw_an_InvalidInitialStateException_when_the_initialState_does_not_implement_IInitialState() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var stateType = typeof(TestState1); + var initialStateType = typeof(NotAState); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); + Assert.Same(initialStateType, ex.InitialStateType); + } + + [Fact] + public void Should_throw_an_InvalidInitialStateException_when_the_initialState_does_not_initialize_state() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var stateType = typeof(TestState1); + var initialStateType = typeof(InitialTestState2); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); + Assert.Same(initialStateType, ex.InitialStateType); + } + } + public class AddTypes : StatorBuilderTest { [Fact] diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 6e042f6..10fed5d 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -1,9 +1,48 @@ -namespace StateR; +using Microsoft.Extensions.DependencyInjection; +using StateR.Internal; +using System; +using Xunit; +namespace StateR; public class StatorStartupExtensionsTest { public class AddStateR : StatorStartupExtensionsTest { + [Fact(Skip = "TODO: implement tests")] + public void Should_be_tested() + { + // Arrange + + // Act + + + // Assert + throw new NotImplementedException(); + } + } + + public class Apply : StatorStartupExtensionsTest + { + [Fact] + public void Should_add_IState_to_the_ServiceCollection() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services) + .AddState() + .AddState() + .AddState() + ; + + // Act + sut.Apply(); + + // Assert + var sp = services.BuildServiceProvider(); + sp.GetRequiredService>(); + sp.GetRequiredService>(); + sp.GetRequiredService>(); + } } } diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs new file mode 100644 index 0000000..45c9e8b --- /dev/null +++ b/test/StateR.Tests/TestTypes.cs @@ -0,0 +1,20 @@ +namespace StateR; + +public record class TestState1 : StateBase; +public record class TestState2 : StateBase; +public record class TestState3 : StateBase; + +public record class InitialTestState1 : IInitialState +{ + public TestState1 Value => new(); +} +public record class InitialTestState2 : IInitialState +{ + public TestState2 Value => new(); +} +public record class InitialTestState3 : IInitialState +{ + public TestState3 Value => new(); +} + +public class NotAState { } From 778eeba04151a701dc6ba0f443f3685a5c5b88f3 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 19:42:05 -0500 Subject: [PATCH 44/58] Make Store internal --- src/StateR/{ => Internal}/Store.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/StateR/{ => Internal}/Store.cs (98%) diff --git a/src/StateR/Store.cs b/src/StateR/Internal/Store.cs similarity index 98% rename from src/StateR/Store.cs rename to src/StateR/Internal/Store.cs index ad627df..2a8d67a 100644 --- a/src/StateR/Store.cs +++ b/src/StateR/Internal/Store.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; -namespace StateR; +namespace StateR.Internal; public class Store : IStore { From 9b13046dbb8c9f8137fb50b6d239a6f8c9b0d46f Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 20:16:51 -0500 Subject: [PATCH 45/58] Cleanup --- src/StateR/Internal/TypeScannerBuilderExtensions.cs | 1 + src/StateR/Pipeline/IActionFilterFactory.cs | 1 - src/StateR/StatorStartupExtensions.cs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR/Internal/TypeScannerBuilderExtensions.cs index 628dbd0..1365f31 100644 --- a/src/StateR/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR/Internal/TypeScannerBuilderExtensions.cs @@ -53,6 +53,7 @@ public static IEnumerable FindUpdaters(IEnumerable types) ); return updaters; } + public static IEnumerable FindMiddlewares(IEnumerable types) { var handlers = types diff --git a/src/StateR/Pipeline/IActionFilterFactory.cs b/src/StateR/Pipeline/IActionFilterFactory.cs index 4e17058..19da883 100644 --- a/src/StateR/Pipeline/IActionFilterFactory.cs +++ b/src/StateR/Pipeline/IActionFilterFactory.cs @@ -1,6 +1,5 @@  using Microsoft.Extensions.DependencyInjection; -using System; namespace StateR.Pipeline; public interface IActionFilterFactory diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index feb8aca..2b51bca 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -153,7 +153,7 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); foreach (var @interface in interfaces) { - // Equivalent to: AddSingleton, UpdaterMiddleware> + // Equivalent to: AddSingleton, UpdaterMiddleware> var actionType = @interface.GenericTypeArguments[0]; var stateType = @interface.GenericTypeArguments[1]; var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); From 17a7be7b0d8d59e5d603063a7346f4e1bba8187e Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 20:17:22 -0500 Subject: [PATCH 46/58] Add actions to StatorBuilder --- src/StateR/IStatorBuilder.cs | 7 ++- src/StateR/Internal/StatorBuilder.cs | 42 +++++++++++++- .../Internal/StatorBuilderTest.cs | 58 ++++++++++++++++--- test/StateR.Tests/TestTypes.cs | 5 ++ 4 files changed, 100 insertions(+), 12 deletions(-) diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 426cf89..a7c51bd 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -8,16 +8,21 @@ public interface IStatorBuilder : IOldStatorBuilder IServiceCollection Services { get; } ReadOnlyCollection States { get; } ReadOnlyCollection InitialStates { get; } + ReadOnlyCollection Actions { get; } IStatorBuilder AddState() where TState : StateBase where TInitialState : IInitialState; IStatorBuilder AddState(Type state, Type initialState); + + IStatorBuilder AddAction() + where TAction : IAction + where TState : StateBase; + IStatorBuilder AddAction(Type actionType); } public interface IOldStatorBuilder { - List Actions { get; } //List Interceptors { get; } List ActionHandlers { get; } //List AfterEffects { get; } diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index d1b3fef..6dd02bb 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -7,6 +7,7 @@ public class StatorBuilder : IStatorBuilder { private readonly List _states = new(); private readonly List _initialStates = new(); + private readonly List _actions = new(); public StatorBuilder(IServiceCollection services) { @@ -21,14 +22,13 @@ public IStatorBuilder AddTypes(IEnumerable types) public IStatorBuilder AddStates(IEnumerable types) => AddDistinctTypes(_states, types); public IStatorBuilder AddActions(IEnumerable types) - => AddDistinctTypes(Actions, types); + => AddDistinctTypes(_actions, types); public IStatorBuilder AddUpdaters(IEnumerable types) => AddDistinctTypes(Updaters, types); public IStatorBuilder AddActionHandlers(IEnumerable types) => AddDistinctTypes(ActionHandlers, types); public IServiceCollection Services { get; } - public List Actions { get; } = new List(); public List Interceptors { get; } = new List(); public List ActionHandlers { get; } = new List(); public List AfterEffects { get; } = new List(); @@ -50,6 +50,7 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types public ReadOnlyCollection States => new(_states); public ReadOnlyCollection InitialStates => new(_initialStates); + public ReadOnlyCollection Actions => new(_actions); public IStatorBuilder AddState() where TState : StateBase @@ -73,6 +74,32 @@ public IStatorBuilder AddState(Type state, Type initialState) _states.Add(state); return this; } + + public IStatorBuilder AddAction() + where TAction : IAction + where TState : StateBase + { + _actions.Add(typeof(TAction)); + return this; + } + + public IStatorBuilder AddAction(Type actionType) + { + if(!IsAction(actionType)) + { + throw new InvalidActionException(actionType); + } + _actions.Add(actionType); + return this; + } + + private static readonly Type _iActionType = typeof(IAction<>); + private static bool IsAction(Type actionType) + { + var interfaces = actionType.GetInterfaces() + .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iActionType); + return interfaces > 0; + } } public class InvalidStateException : Exception @@ -97,3 +124,14 @@ public InvalidInitialStateException(Type stateType, Type initialState) public Type StateType { get; } public Type InitialStateType { get; } } + +public class InvalidActionException : Exception +{ + public InvalidActionException(Type actionType) + : base($"The type {actionType.Name} is not a valid IAction.") + { + ActionType = actionType; + } + + public Type ActionType { get; } +} \ No newline at end of file diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 5eb581b..f503298 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using System; using Xunit; namespace StateR.Internal; @@ -58,35 +59,74 @@ public void Should_throw_an_InvalidStateException_when_the_state_type_does_not_i Assert.Same(stateType, ex.StateType); } - [Fact] - public void Should_throw_an_InvalidInitialStateException_when_the_initialState_does_not_implement_IInitialState() + [Theory] + [InlineData(typeof(TestState1), typeof(InitialTestState2))] + [InlineData(typeof(TestState1), typeof(NotAState))] + public void Should_throw_an_InvalidInitialStateException_when_the_initialState_is_invalid(Type stateType, Type initialStateType) { // Arrange var services = new ServiceCollection(); var sut = new StatorBuilder(services); - var stateType = typeof(TestState1); - var initialStateType = typeof(NotAState); // Act & Assert var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); Assert.Same(initialStateType, ex.InitialStateType); } + } + public class AddAction_TAction_TState + { [Fact] - public void Should_throw_an_InvalidInitialStateException_when_the_initialState_does_not_initialize_state() + public void Should_add_TAction_to_Actions() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act + sut.AddAction(); + + // Assert + Assert.Collection(sut.Actions, + type => Assert.Equal(typeof(TestAction1), type) + ); + } + } + + public class AddAction_Type + { + [Fact] + public void Should_add_TAction_to_Actions() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var actionType = typeof(TestAction1); + + // Act + sut.AddAction(actionType); + + // Assert + Assert.Collection(sut.Actions, + type => Assert.Same(actionType, type) + ); + } + + [Theory] + [InlineData(typeof(NotAnAction))] + public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Type actionType) { // Arrange var services = new ServiceCollection(); var sut = new StatorBuilder(services); - var stateType = typeof(TestState1); - var initialStateType = typeof(InitialTestState2); // Act & Assert - var ex = Assert.Throws(() => sut.AddState(stateType, initialStateType)); - Assert.Same(initialStateType, ex.InitialStateType); + var ex = Assert.Throws(() => sut.AddAction(actionType)); + Assert.Same(actionType, ex.ActionType); } } + public class AddTypes : StatorBuilderTest { [Fact] diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index 45c9e8b..ff81e41 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -18,3 +18,8 @@ public record class InitialTestState3 : IInitialState } public class NotAState { } +public class NotAnAction { } + +public record TestAction1 : IAction; +public record TestAction2 : IAction; +public record TestAction3 : IAction; From b59e0134ddc037672a36ce785d4e93b04e45e8c3 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 20:45:23 -0500 Subject: [PATCH 47/58] Register updaters --- src/StateR/IStatorBuilder.cs | 9 ++- src/StateR/Internal/StatorBuilder.cs | 47 +++++++++++- src/StateR/StatorStartupExtensions.cs | 76 +++++++++++++------ .../Internal/StatorBuilderTest.cs | 56 ++++++++++++++ .../StatorStartupExtensionsTest.cs | 22 ++++++ test/StateR.Tests/TestTypes.cs | 11 ++- 6 files changed, 191 insertions(+), 30 deletions(-) diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index a7c51bd..8dadfa7 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using StateR.Updaters; using System.Collections.ObjectModel; namespace StateR; @@ -9,6 +10,7 @@ public interface IStatorBuilder : IOldStatorBuilder ReadOnlyCollection States { get; } ReadOnlyCollection InitialStates { get; } ReadOnlyCollection Actions { get; } + ReadOnlyCollection Updaters { get; } IStatorBuilder AddState() where TState : StateBase @@ -19,6 +21,12 @@ IStatorBuilder AddAction() where TAction : IAction where TState : StateBase; IStatorBuilder AddAction(Type actionType); + + IStatorBuilder AddUpdater() + where TUpdater : IUpdater + where TAction : IAction + where TState : StateBase; + IStatorBuilder AddUpdater(Type updaterType); } public interface IOldStatorBuilder @@ -26,7 +34,6 @@ public interface IOldStatorBuilder //List Interceptors { get; } List ActionHandlers { get; } //List AfterEffects { get; } - List Updaters { get; } List All { get; } IStatorBuilder AddTypes(IEnumerable types); diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 6dd02bb..0638d98 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using StateR.Updaters; using System.Collections.ObjectModel; namespace StateR.Internal; @@ -8,6 +9,7 @@ public class StatorBuilder : IStatorBuilder private readonly List _states = new(); private readonly List _initialStates = new(); private readonly List _actions = new(); + private readonly List _updaters = new(); public StatorBuilder(IServiceCollection services) { @@ -24,7 +26,7 @@ public IStatorBuilder AddStates(IEnumerable types) public IStatorBuilder AddActions(IEnumerable types) => AddDistinctTypes(_actions, types); public IStatorBuilder AddUpdaters(IEnumerable types) - => AddDistinctTypes(Updaters, types); + => AddDistinctTypes(_updaters, types); public IStatorBuilder AddActionHandlers(IEnumerable types) => AddDistinctTypes(ActionHandlers, types); @@ -32,7 +34,6 @@ public IStatorBuilder AddActionHandlers(IEnumerable types) public List Interceptors { get; } = new List(); public List ActionHandlers { get; } = new List(); public List AfterEffects { get; } = new List(); - public List Updaters { get; } = new List(); public List All { get; } = new List(); public IStatorBuilder AddMiddlewares(IEnumerable types) @@ -51,6 +52,7 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types public ReadOnlyCollection States => new(_states); public ReadOnlyCollection InitialStates => new(_initialStates); public ReadOnlyCollection Actions => new(_actions); + public ReadOnlyCollection Updaters => new(_updaters); public IStatorBuilder AddState() where TState : StateBase @@ -93,6 +95,25 @@ public IStatorBuilder AddAction(Type actionType) return this; } + public IStatorBuilder AddUpdater() + where TUpdater : IUpdater + where TAction : IAction + where TState : StateBase + { + _updaters.Add(typeof(TUpdater)); + return this; + } + + public IStatorBuilder AddUpdater(Type updaterType) + { + if(!IsUpdater(updaterType)) + { + throw new InvalidUpdaterException(updaterType); + } + _updaters.Add(updaterType); + return this; + } + private static readonly Type _iActionType = typeof(IAction<>); private static bool IsAction(Type actionType) { @@ -100,6 +121,15 @@ private static bool IsAction(Type actionType) .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iActionType); return interfaces > 0; } + + private static readonly Type _iUpdaterType = typeof(IUpdater<,>); + private static bool IsUpdater(Type updaterType) + { + var interfaces = updaterType.GetInterfaces() + .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iUpdaterType); + return interfaces > 0; + } + } public class InvalidStateException : Exception @@ -128,10 +158,21 @@ public InvalidInitialStateException(Type stateType, Type initialState) public class InvalidActionException : Exception { public InvalidActionException(Type actionType) - : base($"The type {actionType.Name} is not a valid IAction.") + : base($"The type {actionType.Name} is not a valid IAction.") { ActionType = actionType; } public Type ActionType { get; } +} + +public class InvalidUpdaterException : Exception +{ + public InvalidUpdaterException(Type updaterType) + : base($"The type {updaterType.Name} is not a valid IUpdater.") + { + UpdaterType = updaterType; + } + + public Type UpdaterType { get; } } \ No newline at end of file diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR/StatorStartupExtensions.cs index 2b51bca..5df5292 100644 --- a/src/StateR/StatorStartupExtensions.cs +++ b/src/StateR/StatorStartupExtensions.cs @@ -69,6 +69,32 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action); + var updaterHandler = typeof(UpdaterMiddleware<,>); + var handlerType = typeof(IActionFilter<,>); + foreach (var updater in builder.Updaters) + { + Console.WriteLine($"updater: {updater.FullName}"); + var interfaces = updater.GetInterfaces() + .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); + foreach (var @interface in interfaces) + { + // Equivalent to: AddSingleton, UpdaterMiddleware> + var actionType = @interface.GenericTypeArguments[0]; + var stateType = @interface.GenericTypeArguments[1]; + var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); + var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); + builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); + + // Equivalent to: AddSingleton, Updater>(); + builder.Services.AddSingleton(@interface, updater); + + Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); + Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); + } + } + return builder.Services; @@ -142,31 +168,31 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action); - var updaterHandler = typeof(UpdaterMiddleware<,>); - var handlerType = typeof(IActionFilter<,>); - foreach (var updater in builder.Updaters) - { - Console.WriteLine($"updater: {updater.FullName}"); - var interfaces = updater.GetInterfaces() - .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); - foreach (var @interface in interfaces) - { - // Equivalent to: AddSingleton, UpdaterMiddleware> - var actionType = @interface.GenericTypeArguments[0]; - var stateType = @interface.GenericTypeArguments[1]; - var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); - var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); - builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); - - // Equivalent to: AddSingleton, Updater>(); - builder.Services.AddSingleton(@interface, updater); - - Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); - Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); - } - } + //// Register Updaters and their respective IMiddleware + //var iUpdaterType = typeof(IUpdater<,>); + //var updaterHandler = typeof(UpdaterMiddleware<,>); + //var handlerType = typeof(IActionFilter<,>); + //foreach (var updater in builder.Updaters) + //{ + // Console.WriteLine($"updater: {updater.FullName}"); + // var interfaces = updater.GetInterfaces() + // .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); + // foreach (var @interface in interfaces) + // { + // // Equivalent to: AddSingleton, UpdaterMiddleware> + // var actionType = @interface.GenericTypeArguments[0]; + // var stateType = @interface.GenericTypeArguments[1]; + // var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); + // var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); + // builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); + + // // Equivalent to: AddSingleton, Updater>(); + // builder.Services.AddSingleton(@interface, updater); + + // Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); + // Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); + // } + //} // Register Middleware foreach (var middleware in builder.Middlewares.AsEnumerable().Reverse()) diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index f503298..010a904 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -126,6 +126,62 @@ public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Ty } } + public class AddUpdater_TUpdater_TAction_TState + { + [Fact] + public void Should_add_TUpdater_to_Updaters() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act + sut.AddUpdater(); + + // Assert + Assert.Collection(sut.Updaters, + type => Assert.Equal(typeof(TestUpdaters), type) + ); + } + } + + public class AddUpdater_Type + { + [Fact] + public void Should_add_updaterType_to_Updaters() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var updaterType = typeof(TestUpdaters); + + // Act + sut.AddUpdater(updaterType); + + // Assert + Assert.Collection(sut.Updaters, + type => Assert.Same(updaterType, type) + ); + } + + [Theory] + [InlineData(typeof(NotAnUpdater))] + public void Should_throw_an_InvalidUpdaterException_when_updaterType_is_invalid(Type updaterType) + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddUpdater(updaterType)); + Assert.Same(updaterType, ex.UpdaterType); + } + + //where TUpdater : IUpdater + //where TAction : IAction + //where TState : StateBase + + } public class AddTypes : StatorBuilderTest { diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 10fed5d..8ac6e9e 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -1,5 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using StateR.Internal; +using StateR.Pipeline; +using StateR.Updaters; using System; using Xunit; namespace StateR; @@ -44,5 +46,25 @@ public void Should_add_IState_to_the_ServiceCollection() sp.GetRequiredService>(); sp.GetRequiredService>(); } + + [Fact] + public void Should_add_IUpdater_and_IActionFilter_to_the_ServiceCollection() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services) + .AddState() + .AddAction() + .AddUpdater() + ; + + // Act + sut.Apply(); + + // Assert + var sp = services.BuildServiceProvider(); + sp.GetRequiredService>(); + sp.GetRequiredService>(); + } } } diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index ff81e41..46100df 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -1,4 +1,6 @@ -namespace StateR; +using StateR.Updaters; + +namespace StateR; public record class TestState1 : StateBase; public record class TestState2 : StateBase; @@ -19,7 +21,14 @@ public record class InitialTestState3 : IInitialState public class NotAState { } public class NotAnAction { } +public class NotAnUpdater { } public record TestAction1 : IAction; public record TestAction2 : IAction; public record TestAction3 : IAction; + +public class TestUpdaters : IUpdater +{ + public TestState1 Update(TestAction1 action, TestState1 state) + => new(); +} \ No newline at end of file From ce5710303338f3c2a912653994d4368f49ee1e08 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:20:26 -0500 Subject: [PATCH 48/58] Remove generic AddAction and AddUpdater methods --- src/StateR/IStatorBuilder.cs | 16 ++-- src/StateR/Internal/StatorBuilder.cs | 32 ++++---- .../Internal/StatorBuilderTest.cs | 80 +++++++++---------- .../StatorStartupExtensionsTest.cs | 4 +- test/StateR.Tests/TestTypes.cs | 2 - 5 files changed, 66 insertions(+), 68 deletions(-) diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 8dadfa7..cd31de8 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -17,16 +17,16 @@ IStatorBuilder AddState() where TInitialState : IInitialState; IStatorBuilder AddState(Type state, Type initialState); - IStatorBuilder AddAction() - where TAction : IAction - where TState : StateBase; + //IStatorBuilder AddAction() + // where TAction : IAction + // where TState : StateBase; IStatorBuilder AddAction(Type actionType); - IStatorBuilder AddUpdater() - where TUpdater : IUpdater - where TAction : IAction - where TState : StateBase; - IStatorBuilder AddUpdater(Type updaterType); + //IStatorBuilder AddUpdater() + // where TUpdater : IUpdater + // where TAction : IAction + // where TState : StateBase; + IStatorBuilder AddUpdaters(Type updaterType); } public interface IOldStatorBuilder diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 0638d98..ef47bbc 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -77,13 +77,13 @@ public IStatorBuilder AddState(Type state, Type initialState) return this; } - public IStatorBuilder AddAction() - where TAction : IAction - where TState : StateBase - { - _actions.Add(typeof(TAction)); - return this; - } + //public IStatorBuilder AddAction() + // where TAction : IAction + // where TState : StateBase + //{ + // _actions.Add(typeof(TAction)); + // return this; + //} public IStatorBuilder AddAction(Type actionType) { @@ -95,16 +95,16 @@ public IStatorBuilder AddAction(Type actionType) return this; } - public IStatorBuilder AddUpdater() - where TUpdater : IUpdater - where TAction : IAction - where TState : StateBase - { - _updaters.Add(typeof(TUpdater)); - return this; - } + //public IStatorBuilder AddUpdater() + // where TUpdater : IUpdater + // where TAction : IAction + // where TState : StateBase + //{ + // _updaters.Add(typeof(TUpdater)); + // return this; + //} - public IStatorBuilder AddUpdater(Type updaterType) + public IStatorBuilder AddUpdaters(Type updaterType) { if(!IsUpdater(updaterType)) { diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 010a904..51b6560 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -74,24 +74,24 @@ public void Should_throw_an_InvalidInitialStateException_when_the_initialState_i } } - public class AddAction_TAction_TState - { - [Fact] - public void Should_add_TAction_to_Actions() - { - // Arrange - var services = new ServiceCollection(); - var sut = new StatorBuilder(services); - - // Act - sut.AddAction(); - - // Assert - Assert.Collection(sut.Actions, - type => Assert.Equal(typeof(TestAction1), type) - ); - } - } + //public class AddAction_TAction_TState + //{ + // [Fact] + // public void Should_add_TAction_to_Actions() + // { + // // Arrange + // var services = new ServiceCollection(); + // var sut = new StatorBuilder(services); + + // // Act + // sut.AddAction(); + + // // Assert + // Assert.Collection(sut.Actions, + // type => Assert.Equal(typeof(TestAction1), type) + // ); + // } + //} public class AddAction_Type { @@ -126,26 +126,26 @@ public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Ty } } - public class AddUpdater_TUpdater_TAction_TState - { - [Fact] - public void Should_add_TUpdater_to_Updaters() - { - // Arrange - var services = new ServiceCollection(); - var sut = new StatorBuilder(services); - - // Act - sut.AddUpdater(); - - // Assert - Assert.Collection(sut.Updaters, - type => Assert.Equal(typeof(TestUpdaters), type) - ); - } - } - - public class AddUpdater_Type + //public class AddUpdater_TUpdater_TAction_TState + //{ + // [Fact] + // public void Should_add_TUpdater_to_Updaters() + // { + // // Arrange + // var services = new ServiceCollection(); + // var sut = new StatorBuilder(services); + + // // Act + // sut.AddUpdater(); + + // // Assert + // Assert.Collection(sut.Updaters, + // type => Assert.Equal(typeof(TestUpdaters), type) + // ); + // } + //} + + public class AddUpdaters_Type { [Fact] public void Should_add_updaterType_to_Updaters() @@ -156,7 +156,7 @@ public void Should_add_updaterType_to_Updaters() var updaterType = typeof(TestUpdaters); // Act - sut.AddUpdater(updaterType); + sut.AddUpdaters(updaterType); // Assert Assert.Collection(sut.Updaters, @@ -173,7 +173,7 @@ public void Should_throw_an_InvalidUpdaterException_when_updaterType_is_invalid( var sut = new StatorBuilder(services); // Act & Assert - var ex = Assert.Throws(() => sut.AddUpdater(updaterType)); + var ex = Assert.Throws(() => sut.AddUpdaters(updaterType)); Assert.Same(updaterType, ex.UpdaterType); } diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 8ac6e9e..7c08996 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -54,8 +54,8 @@ public void Should_add_IUpdater_and_IActionFilter_to_the_ServiceCollection() var services = new ServiceCollection(); var sut = new StatorBuilder(services) .AddState() - .AddAction() - .AddUpdater() + .AddAction(typeof(TestAction1)) + .AddUpdaters(typeof(TestUpdaters)) ; // Act diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index 46100df..df2ecb1 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -24,8 +24,6 @@ public class NotAnAction { } public class NotAnUpdater { } public record TestAction1 : IAction; -public record TestAction2 : IAction; -public record TestAction3 : IAction; public class TestUpdaters : IUpdater { From 12e256904fc053f8db291a3ff6bc97c36c12fc6d Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:20:40 -0500 Subject: [PATCH 49/58] Add a counter integration test --- test/StateR.Tests/IntegrationTest.cs | 84 +++++++++++++++++++++++++++ test/StateR.Tests/StateR.Tests.csproj | 1 + 2 files changed, 85 insertions(+) create mode 100644 test/StateR.Tests/IntegrationTest.cs diff --git a/test/StateR.Tests/IntegrationTest.cs b/test/StateR.Tests/IntegrationTest.cs new file mode 100644 index 0000000..82acb87 --- /dev/null +++ b/test/StateR.Tests/IntegrationTest.cs @@ -0,0 +1,84 @@ +using Microsoft.Extensions.DependencyInjection; +using StateR.Internal; +using StateR.Updaters; +using System; +using Xunit; + +namespace StateR; +public class IntegrationTest +{ + public record class CounterState(int Count) : StateBase; + public record class InitialCounterState : IInitialState + { + public CounterState Value => new(0); + } + public record class Increment(int Amount) : IAction; + public record class Decrement(int Amount) : IAction; + public class CounterUpdaters : + IUpdater, + IUpdater + { + public CounterState Update(Increment action, CounterState state) + => state with { Count = state.Count + action.Amount }; + public CounterState Update(Decrement action, CounterState state) + => state with { Count = state.Count - action.Amount }; + } + + public class IncrementTest : IntegrationTest + { + [Fact] + public async Task Should_increment_the_CounterState_by_the_Increment_action_Amount() + { + // Arrange + var services = Initialize(); + var state = services.GetRequiredService>(); + var initialCount = state.Current.Count; + Assert.Equal(0, initialCount); + + var cancellationToken = CancellationToken.None; + var dispatcher = services.GetRequiredService(); + + // Act + await dispatcher.DispatchAsync(new Increment(2), cancellationToken); + + // Assert + Assert.Equal(2, state.Current.Count); + } + } + + public class DecrementTest : IntegrationTest + { + [Fact] + public async Task Should_decrement_the_CounterState_by_the_Increment_action_Amount() + { + // Arrange + var services = Initialize(); + var state = services.GetRequiredService>(); + var initialCount = state.Current.Count; + Assert.Equal(0, initialCount); + + var cancellationToken = CancellationToken.None; + var dispatcher = services.GetRequiredService(); + + // Act + await dispatcher.DispatchAsync(new Decrement(5), cancellationToken); + + // Assert + Assert.Equal(-5, state.Current.Count); + } + } + + private IServiceProvider Initialize() + { + var services = new ServiceCollection(); + services.AddLogging(); + return services.AddStateR() + .AddState() + .AddAction(typeof(Increment)) + .AddAction(typeof(Decrement)) + .AddUpdaters(typeof(CounterUpdaters)) + .Apply() + .BuildServiceProvider() + ; + } +} diff --git a/test/StateR.Tests/StateR.Tests.csproj b/test/StateR.Tests/StateR.Tests.csproj index 7709cc4..24856d1 100644 --- a/test/StateR.Tests/StateR.Tests.csproj +++ b/test/StateR.Tests/StateR.Tests.csproj @@ -12,6 +12,7 @@ + From 36644f7b43a276482fb33de85e45c602571ab55d Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:21:59 -0500 Subject: [PATCH 50/58] Cleanup comments --- src/StateR/IStatorBuilder.cs | 8 ---- src/StateR/Internal/StatorBuilder.cs | 17 -------- .../Internal/StatorBuilderTest.cs | 43 ------------------- 3 files changed, 68 deletions(-) diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index cd31de8..297e31f 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -17,15 +17,7 @@ IStatorBuilder AddState() where TInitialState : IInitialState; IStatorBuilder AddState(Type state, Type initialState); - //IStatorBuilder AddAction() - // where TAction : IAction - // where TState : StateBase; IStatorBuilder AddAction(Type actionType); - - //IStatorBuilder AddUpdater() - // where TUpdater : IUpdater - // where TAction : IAction - // where TState : StateBase; IStatorBuilder AddUpdaters(Type updaterType); } diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index ef47bbc..d09710f 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -77,14 +77,6 @@ public IStatorBuilder AddState(Type state, Type initialState) return this; } - //public IStatorBuilder AddAction() - // where TAction : IAction - // where TState : StateBase - //{ - // _actions.Add(typeof(TAction)); - // return this; - //} - public IStatorBuilder AddAction(Type actionType) { if(!IsAction(actionType)) @@ -95,15 +87,6 @@ public IStatorBuilder AddAction(Type actionType) return this; } - //public IStatorBuilder AddUpdater() - // where TUpdater : IUpdater - // where TAction : IAction - // where TState : StateBase - //{ - // _updaters.Add(typeof(TUpdater)); - // return this; - //} - public IStatorBuilder AddUpdaters(Type updaterType) { if(!IsUpdater(updaterType)) diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 51b6560..f966ca6 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -74,25 +74,6 @@ public void Should_throw_an_InvalidInitialStateException_when_the_initialState_i } } - //public class AddAction_TAction_TState - //{ - // [Fact] - // public void Should_add_TAction_to_Actions() - // { - // // Arrange - // var services = new ServiceCollection(); - // var sut = new StatorBuilder(services); - - // // Act - // sut.AddAction(); - - // // Assert - // Assert.Collection(sut.Actions, - // type => Assert.Equal(typeof(TestAction1), type) - // ); - // } - //} - public class AddAction_Type { [Fact] @@ -126,25 +107,6 @@ public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Ty } } - //public class AddUpdater_TUpdater_TAction_TState - //{ - // [Fact] - // public void Should_add_TUpdater_to_Updaters() - // { - // // Arrange - // var services = new ServiceCollection(); - // var sut = new StatorBuilder(services); - - // // Act - // sut.AddUpdater(); - - // // Assert - // Assert.Collection(sut.Updaters, - // type => Assert.Equal(typeof(TestUpdaters), type) - // ); - // } - //} - public class AddUpdaters_Type { [Fact] @@ -176,11 +138,6 @@ public void Should_throw_an_InvalidUpdaterException_when_updaterType_is_invalid( var ex = Assert.Throws(() => sut.AddUpdaters(updaterType)); Assert.Same(updaterType, ex.UpdaterType); } - - //where TUpdater : IUpdater - //where TAction : IAction - //where TState : StateBase - } public class AddTypes : StatorBuilderTest From 53bd1567002b25debeb96a90538f75bb7f8d976b Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:42:43 -0500 Subject: [PATCH 51/58] Extract DI from main project --- StateR.sln | 15 ++++++++++ .../CounterApp/CounterApp/CounterApp.csproj | 1 + .../StateValidationDecorator.cs | 28 +++++++++---------- .../Internal/TypeScannerBuilderExtensions.cs | 0 ...soft.Extensions.DependencyInjection.csproj | 17 +++++++++++ .../StatorStartupExtensions.cs | 0 src/StateR/StateR.csproj | 1 - test/StateR.Tests/StateR.Tests.csproj | 2 +- 8 files changed, 48 insertions(+), 16 deletions(-) rename src/{StateR => StateR.Microsoft.Extensions.DependencyInjection}/Internal/TypeScannerBuilderExtensions.cs (100%) create mode 100644 src/StateR.Microsoft.Extensions.DependencyInjection/StateR.Microsoft.Extensions.DependencyInjection.csproj rename src/{StateR => StateR.Microsoft.Extensions.DependencyInjection}/StatorStartupExtensions.cs (100%) diff --git a/StateR.sln b/StateR.sln index 4dad392..78e4f50 100644 --- a/StateR.sln +++ b/StateR.sln @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CounterApp", "samples\Count EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CounterApp.Tests", "samples\CounterApp\CounterApp.Tests\CounterApp.Tests.csproj", "{FBDEBA94-7F63-4CB5-AC13-4D0874730316}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateR.Microsoft.Extensions.DependencyInjection", "src\StateR.Microsoft.Extensions.DependencyInjection\StateR.Microsoft.Extensions.DependencyInjection.csproj", "{5C432129-E637-4895-895D-1FDFDC61C049}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -119,6 +121,18 @@ Global {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x64.Build.0 = Release|Any CPU {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x86.ActiveCfg = Release|Any CPU {FBDEBA94-7F63-4CB5-AC13-4D0874730316}.Release|x86.Build.0 = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|x64.ActiveCfg = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|x64.Build.0 = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|x86.ActiveCfg = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Debug|x86.Build.0 = Debug|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|Any CPU.Build.0 = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|x64.ActiveCfg = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|x64.Build.0 = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|x86.ActiveCfg = Release|Any CPU + {5C432129-E637-4895-895D-1FDFDC61C049}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -132,6 +146,7 @@ Global {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} = {22B777AC-AFAE-422A-B4CD-48C907251D9E} {99926EB0-84F9-4906-8F7E-4E1873A403EB} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} {FBDEBA94-7F63-4CB5-AC13-4D0874730316} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} + {5C432129-E637-4895-895D-1FDFDC61C049} = {F0F6A2CA-0972-43BD-B777-B5656DFE20C3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6ADD1933-C449-475E-9409-AD333C1C48A0} diff --git a/samples/CounterApp/CounterApp/CounterApp.csproj b/samples/CounterApp/CounterApp/CounterApp.csproj index 74fa298..5317fde 100644 --- a/samples/CounterApp/CounterApp/CounterApp.csproj +++ b/samples/CounterApp/CounterApp/CounterApp.csproj @@ -21,6 +21,7 @@ + diff --git a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs index 9468541..5769a29 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StateValidationDecorator.cs @@ -50,7 +50,7 @@ public static class StateValidatorStartupExtensions { public static IStatorBuilder AddStateValidation(this IStatorBuilder builder) { - RegisterStateDecorator(builder.Services, builder.All); + //RegisterStateDecorator(builder.Services, builder.All); //ActionHandlerDecorator(builder.Services); return builder; } @@ -61,20 +61,20 @@ public static IStatorBuilder AddStateValidation(this IStatorBuilder builder) // services.Decorate(); //} - private static void RegisterStateDecorator(IServiceCollection services, IEnumerable types) - { - var states = TypeScanner.FindStates(types); - Console.WriteLine("StateValidator:"); - foreach (var state in states) - { - Console.WriteLine($"- Decorate, StateValidationDecorator<{state.GetStatorName()}>>()"); + //private static void RegisterStateDecorator(IServiceCollection services, IEnumerable types) + //{ + // var states = TypeScanner.FindStates(types); + // Console.WriteLine("StateValidator:"); + // foreach (var state in states) + // { + // Console.WriteLine($"- Decorate, StateValidationDecorator<{state.GetStatorName()}>>()"); - // Equivalent to: Decorate, StateValidationDecorator>(); - var stateType = typeof(IState<>).MakeGenericType(state); - var stateSessionDecoratorType = typeof(StateValidationDecorator<>).MakeGenericType(state); - services.Decorate(stateType, stateSessionDecoratorType); - } - } + // // Equivalent to: Decorate, StateValidationDecorator>(); + // var stateType = typeof(IState<>).MakeGenericType(state); + // var stateSessionDecoratorType = typeof(StateValidationDecorator<>).MakeGenericType(state); + // services.Decorate(stateType, stateSessionDecoratorType); + // } + //} } //public class ValidationExceptionActionHandlersManagerDecorator : IActionFilter diff --git a/src/StateR/Internal/TypeScannerBuilderExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs similarity index 100% rename from src/StateR/Internal/TypeScannerBuilderExtensions.cs rename to src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StateR.Microsoft.Extensions.DependencyInjection.csproj b/src/StateR.Microsoft.Extensions.DependencyInjection/StateR.Microsoft.Extensions.DependencyInjection.csproj new file mode 100644 index 0000000..b289158 --- /dev/null +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StateR.Microsoft.Extensions.DependencyInjection.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + StateR + + + + + + + + + + diff --git a/src/StateR/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs similarity index 100% rename from src/StateR/StatorStartupExtensions.cs rename to src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs diff --git a/src/StateR/StateR.csproj b/src/StateR/StateR.csproj index 37d1c0a..ad8857a 100644 --- a/src/StateR/StateR.csproj +++ b/src/StateR/StateR.csproj @@ -9,7 +9,6 @@ - diff --git a/test/StateR.Tests/StateR.Tests.csproj b/test/StateR.Tests/StateR.Tests.csproj index 24856d1..06ff3ba 100644 --- a/test/StateR.Tests/StateR.Tests.csproj +++ b/test/StateR.Tests/StateR.Tests.csproj @@ -27,7 +27,7 @@ - + From 504c9c21dedef8e013df9910144036044cc8c333 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 21:45:26 -0500 Subject: [PATCH 52/58] AddState now add initialState properly --- src/StateR/Internal/StatorBuilder.cs | 1 + test/StateR.Tests/Internal/StatorBuilderTest.cs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index d09710f..35c383d 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -74,6 +74,7 @@ public IStatorBuilder AddState(Type state, Type initialState) throw new InvalidInitialStateException(state, initialState); } _states.Add(state); + _initialStates.Add(initialState); return this; } diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index f966ca6..9379f75 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -30,7 +30,7 @@ public void Should_add_TState_to_States_and_TInitialState_to_InitialStates() public class AddState_Type : StatorBuilderTest { [Fact] - public void Should_add_a_valid_state_type_to_States() + public void Should_add_a_valid_state_type_to_States_and_valid_initialState_to_InitialStates() { // Arrange var services = new ServiceCollection(); @@ -43,6 +43,9 @@ public void Should_add_a_valid_state_type_to_States() Assert.Collection(sut.States, type => Assert.Equal(typeof(TestState1), type) ); + Assert.Collection(sut.InitialStates, + type => Assert.Equal(typeof(InitialTestState1), type) + ); } [Fact] From 104b83ba4bc04b3c33af38556dc22fdc1ea5df06 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Wed, 2 Mar 2022 22:03:56 -0500 Subject: [PATCH 53/58] Add ActionFilter to StatorBuilder --- samples/CounterApp/CounterApp/Program.cs | 2 +- .../StatorStartupExtensions.cs | 20 ++++----- src/StateR/IStatorBuilder.cs | 3 +- src/StateR/Internal/StatorBuilder.cs | 43 ++++++++++++++----- .../Internal/StatorBuilderTest.cs | 37 +++++++++++++++- test/StateR.Tests/TestTypes.cs | 16 ++++++- 6 files changed, 95 insertions(+), 26 deletions(-) diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 9ee5a23..4586b09 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -39,7 +39,7 @@ public static void RegisterServices(this IServiceCollection services) { var appAssembly = typeof(App).Assembly; services - .AddStateR(appAssembly) + .AddStateR() //.AddAsyncOperations() //.AddReduxDevTools() .AddFluentValidation(appAssembly) diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs index 5df5292..949b8b2 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs @@ -28,18 +28,18 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) return new StatorBuilder(services); } - public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) - { - var builder = services.AddStateR(); - var allTypes = assembliesToScanForStates - .SelectMany(a => a.GetTypes()); - //builder.AddTypes(allTypes); + //public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) + //{ + // var builder = services.AddStateR(); + // var allTypes = assembliesToScanForStates + // .SelectMany(a => a.GetTypes()); + // //builder.AddTypes(allTypes); - var states = TypeScanner.FindStates(allTypes); - builder.AddStates(states); + // var states = TypeScanner.FindStates(allTypes); + // builder.AddStates(states); - return builder; - } + // return builder; + //} //public static IStatorBuilder AddMiddleware(this IStatorBuilder builder) //{ diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index 297e31f..a841bb3 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using StateR.Updaters; using System.Collections.ObjectModel; namespace StateR; @@ -11,6 +10,7 @@ public interface IStatorBuilder : IOldStatorBuilder ReadOnlyCollection InitialStates { get; } ReadOnlyCollection Actions { get; } ReadOnlyCollection Updaters { get; } + ReadOnlyCollection ActionFilters { get; } IStatorBuilder AddState() where TState : StateBase @@ -19,6 +19,7 @@ IStatorBuilder AddState() IStatorBuilder AddAction(Type actionType); IStatorBuilder AddUpdaters(Type updaterType); + IStatorBuilder AddActionFilter(Type actionFilterType); } public interface IOldStatorBuilder diff --git a/src/StateR/Internal/StatorBuilder.cs b/src/StateR/Internal/StatorBuilder.cs index 35c383d..22585e8 100644 --- a/src/StateR/Internal/StatorBuilder.cs +++ b/src/StateR/Internal/StatorBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using StateR.Pipeline; using StateR.Updaters; using System.Collections.ObjectModel; @@ -10,6 +11,7 @@ public class StatorBuilder : IStatorBuilder private readonly List _initialStates = new(); private readonly List _actions = new(); private readonly List _updaters = new(); + private readonly List _actionFilters = new(); public StatorBuilder(IServiceCollection services) { @@ -53,6 +55,7 @@ private IStatorBuilder AddDistinctTypes(List list, IEnumerable types public ReadOnlyCollection InitialStates => new(_initialStates); public ReadOnlyCollection Actions => new(_actions); public ReadOnlyCollection Updaters => new(_updaters); + public ReadOnlyCollection ActionFilters => new(_actionFilters); public IStatorBuilder AddState() where TState : StateBase @@ -80,7 +83,7 @@ public IStatorBuilder AddState(Type state, Type initialState) public IStatorBuilder AddAction(Type actionType) { - if(!IsAction(actionType)) + if (!IsAction(actionType)) { throw new InvalidActionException(actionType); } @@ -90,7 +93,7 @@ public IStatorBuilder AddAction(Type actionType) public IStatorBuilder AddUpdaters(Type updaterType) { - if(!IsUpdater(updaterType)) + if (!IsUpdater(updaterType)) { throw new InvalidUpdaterException(updaterType); } @@ -98,22 +101,29 @@ public IStatorBuilder AddUpdaters(Type updaterType) return this; } - private static readonly Type _iActionType = typeof(IAction<>); - private static bool IsAction(Type actionType) + public IStatorBuilder AddActionFilter(Type actionFilterType) { - var interfaces = actionType.GetInterfaces() - .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iActionType); - return interfaces > 0; + if (!IsActionFilter(actionFilterType)) + { + throw new InvalidActionFilterException(actionFilterType); + } + _actionFilters.Add(actionFilterType); + return this; } - private static readonly Type _iUpdaterType = typeof(IUpdater<,>); + private static bool IsAction(Type actionType) + => HasGenericInterface(actionType, typeof(IAction<>)); private static bool IsUpdater(Type updaterType) + => HasGenericInterface(updaterType, typeof(IUpdater<,>)); + private static bool IsActionFilter(Type actionFilterType) + => HasGenericInterface(actionFilterType, typeof(IActionFilter<,>)); + + private static bool HasGenericInterface(Type type, Type interfaceType) { - var interfaces = updaterType.GetInterfaces() - .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == _iUpdaterType); + var interfaces = type.GetInterfaces() + .Count(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType); return interfaces > 0; } - } public class InvalidStateException : Exception @@ -159,4 +169,15 @@ public InvalidUpdaterException(Type updaterType) } public Type UpdaterType { get; } +} + +public class InvalidActionFilterException : Exception +{ + public InvalidActionFilterException(Type actionFilterType) + : base($"The type {actionFilterType.Name} is not a valid IActionFilter.") + { + ActionFilterType = actionFilterType; + } + + public Type ActionFilterType { get; } } \ No newline at end of file diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 9379f75..3726384 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -77,7 +77,7 @@ public void Should_throw_an_InvalidInitialStateException_when_the_initialState_i } } - public class AddAction_Type + public class AddAction : StatorBuilderTest { [Fact] public void Should_add_TAction_to_Actions() @@ -110,7 +110,7 @@ public void Should_throw_an_InvalidActionException_when_actionType_is_invalid(Ty } } - public class AddUpdaters_Type + public class AddUpdaters : StatorBuilderTest { [Fact] public void Should_add_updaterType_to_Updaters() @@ -143,6 +143,39 @@ public void Should_throw_an_InvalidUpdaterException_when_updaterType_is_invalid( } } + public class AddActionFilter : StatorBuilderTest + { + [Fact] + public void Should_add_actionFilterType_to_ActionFilters() + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + var actionFilterType = typeof(TestActionFilter); + + // Act + sut.AddActionFilter(actionFilterType); + + // Assert + Assert.Collection(sut.ActionFilters, + type => Assert.Same(actionFilterType, type) + ); + } + + [Theory] + [InlineData(typeof(NotAnActionFilter))] + public void Should_throw_an_InvalidActionFilterException_when_actionFilterType_is_invalid(Type actionFilterType) + { + // Arrange + var services = new ServiceCollection(); + var sut = new StatorBuilder(services); + + // Act & Assert + var ex = Assert.Throws(() => sut.AddActionFilter(actionFilterType)); + Assert.Same(actionFilterType, ex.ActionFilterType); + } + } + public class AddTypes : StatorBuilderTest { [Fact] diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index df2ecb1..530365d 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -1,4 +1,5 @@ -using StateR.Updaters; +using StateR.Pipeline; +using StateR.Updaters; namespace StateR; @@ -22,11 +23,24 @@ public record class InitialTestState3 : IInitialState public class NotAState { } public class NotAnAction { } public class NotAnUpdater { } +public class NotAnActionFilter { } public record TestAction1 : IAction; +public record TestAction2 : IAction; public class TestUpdaters : IUpdater { public TestState1 Update(TestAction1 action, TestState1 state) => new(); +} + +public class TestActionFilter : IActionFilter +{ + public Task InvokeAsync( + IDispatchContext context, + ActionDelegate next, + CancellationToken cancellationToken) + { + return Task.CompletedTask; + } } \ No newline at end of file From 7461853a5a20e342e8c897e93861e4e647fd2382 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Thu, 3 Mar 2022 00:11:57 -0500 Subject: [PATCH 54/58] Add support for pipeline action filters --- .../Internal/TypeScannerBuilderExtensions.cs | 11 +++++ .../StatorStartupExtensions.cs | 46 +++++++++++++++---- src/StateR/Dispatcher.cs | 14 +++--- src/StateR/Pipeline/IActionFilterFactory.cs | 24 ---------- src/StateR/Pipeline/IPipelineFactory.cs | 35 ++++++++++++++ test/StateR.Tests/IntegrationTest.cs | 36 +++++++++++++++ .../Internal/StatorBuilderTest.cs | 4 +- .../StatorStartupExtensionsTest.cs | 29 +++++++++++- test/StateR.Tests/TestTypes.cs | 20 +++++++- 9 files changed, 175 insertions(+), 44 deletions(-) delete mode 100644 src/StateR/Pipeline/IActionFilterFactory.cs create mode 100644 src/StateR/Pipeline/IPipelineFactory.cs diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs index 1365f31..c2d9121 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs @@ -32,6 +32,17 @@ public static IEnumerable FindStates(IEnumerable types) return states; } + public static IEnumerable FindInitialStates(IEnumerable types) + { + var initialStates = types + .Where(type => !type.IsAbstract && type + .GetTypeInfo() + .GetInterfaces() + .Any(i => i == typeof(IInitialState<>)) + ); + return initialStates; + } + public static IEnumerable FindActions(IEnumerable types) { var actions = types diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs index 949b8b2..0c47f7b 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs @@ -14,18 +14,24 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); + return new StatorBuilder(services); + } - //services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); + public static IStatorBuilder ScanAndAddStates(this IStatorBuilder builder, params Assembly[] assembliesToScan) + { + var allTypes = assembliesToScan + .SelectMany(a => a.GetTypes()); + var initialStates = TypeScanner.FindInitialStates(allTypes); - return new StatorBuilder(services); + foreach (var initialState in initialStates) + { + var state = initialState.GenericTypeArguments[0]; + builder.AddState(state, initialState); + } + + return builder; } //public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) @@ -95,6 +101,28 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action); + foreach (var filter in builder.ActionFilters) + { + var interfaces = filter.GetInterfaces() + .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iActionFilterType); + foreach (var @interface in interfaces) + { + var actionType = @interface.GenericTypeArguments[0]; + var stateType = @interface.GenericTypeArguments[1]; + var filterType = iActionFilterType.MakeGenericType(actionType, stateType); + + builder.Services.AddSingleton(@interface, filter); + } + } + + /* + + public ActionDelegate Create(IDispatchContext context, CancellationToken cancellationToken) + { + + } + */ return builder.Services; diff --git a/src/StateR/Dispatcher.cs b/src/StateR/Dispatcher.cs index 63eff9d..2d3836b 100644 --- a/src/StateR/Dispatcher.cs +++ b/src/StateR/Dispatcher.cs @@ -7,13 +7,13 @@ namespace StateR; public class Dispatcher : IDispatcher { private readonly IDispatchContextFactory _dispatchContextFactory; - private readonly IActionFilterFactory _actionFilterFactory; + private readonly IPipelineFactory _pipelineFactory; private readonly ILogger _logger; - public Dispatcher(IDispatchContextFactory dispatchContextFactory, IActionFilterFactory actionFilterFactory, ILogger logger) + public Dispatcher(IDispatchContextFactory dispatchContextFactory, IPipelineFactory actionFilterFactory, ILogger logger) { _dispatchContextFactory = dispatchContextFactory ?? throw new ArgumentNullException(nameof(dispatchContextFactory)); - _actionFilterFactory = actionFilterFactory ?? throw new ArgumentNullException(nameof(actionFilterFactory)); + _pipelineFactory = actionFilterFactory ?? throw new ArgumentNullException(nameof(actionFilterFactory)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -23,10 +23,10 @@ public async Task DispatchAsync(TAction action, CancellationTok { using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var dispatchContext = _dispatchContextFactory.Create(action, this, cancellationTokenSource); - var actionFilter = _actionFilterFactory.Create(dispatchContext); + var pipeline = _pipelineFactory.Create(dispatchContext); try { - await actionFilter.InvokeAsync(dispatchContext, null, cancellationToken); + await pipeline.Invoke(dispatchContext, cancellationToken).ConfigureAwait(false); } catch (DispatchCancelledException ex) { @@ -46,7 +46,9 @@ public Task DispatchAsync(object action, CancellationToken cancellationToken) throw new InvalidOperationException($"The action must implement the {typeof(IAction<>).Name} interface."); } var stateType = actionInterface.GetGenericArguments()[0]; - var method = GetType().GetMethods().FirstOrDefault(m => m.IsGenericMethod && m.Name == nameof(DispatchAsync)); + var method = GetType() + .GetMethods() + .FirstOrDefault(m => m.IsGenericMethod && m.Name == nameof(DispatchAsync)); if(method == null) { throw new MissingMethodException(nameof(Dispatcher), nameof(DispatchAsync)); diff --git a/src/StateR/Pipeline/IActionFilterFactory.cs b/src/StateR/Pipeline/IActionFilterFactory.cs deleted file mode 100644 index 19da883..0000000 --- a/src/StateR/Pipeline/IActionFilterFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ - -using Microsoft.Extensions.DependencyInjection; -namespace StateR.Pipeline; - -public interface IActionFilterFactory -{ - IActionFilter Create(IDispatchContext context) - where TAction : IAction - where TState : StateBase; -} - -public class ActionFilterFactory : IActionFilterFactory -{ - private readonly IServiceProvider _serviceProvider; - public ActionFilterFactory(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - } - - public IActionFilter Create(IDispatchContext context) - where TAction : IAction - where TState : StateBase - => _serviceProvider.GetRequiredService>(); -} \ No newline at end of file diff --git a/src/StateR/Pipeline/IPipelineFactory.cs b/src/StateR/Pipeline/IPipelineFactory.cs new file mode 100644 index 0000000..942a36e --- /dev/null +++ b/src/StateR/Pipeline/IPipelineFactory.cs @@ -0,0 +1,35 @@ + +using Microsoft.Extensions.DependencyInjection; +namespace StateR.Pipeline; + +public interface IPipelineFactory +{ + ActionDelegate Create(IDispatchContext context) + where TAction : IAction + where TState : StateBase; +} + +public class PipelineFactory : IPipelineFactory +{ + private readonly IServiceProvider _serviceProvider; + public PipelineFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + public ActionDelegate Create(IDispatchContext context) + where TAction : IAction + where TState : StateBase + { + var filters = _serviceProvider.GetServices>(); + var enumerator = filters.GetEnumerator(); + enumerator.MoveNext(); + return MakeDelegate(enumerator.Current); + + ActionDelegate MakeDelegate(IActionFilter filter) + { + var hasNext = enumerator.MoveNext(); + return new((a, s) => filter.InvokeAsync(a, hasNext ? MakeDelegate(enumerator.Current) : null, s)); + } + } +} \ No newline at end of file diff --git a/test/StateR.Tests/IntegrationTest.cs b/test/StateR.Tests/IntegrationTest.cs index 82acb87..4bc6853 100644 --- a/test/StateR.Tests/IntegrationTest.cs +++ b/test/StateR.Tests/IntegrationTest.cs @@ -1,7 +1,9 @@ using Microsoft.Extensions.DependencyInjection; using StateR.Internal; +using StateR.Pipeline; using StateR.Updaters; using System; +using System.Threading.Tasks; using Xunit; namespace StateR; @@ -23,6 +25,21 @@ public CounterState Update(Increment action, CounterState state) public CounterState Update(Decrement action, CounterState state) => state with { Count = state.Count - action.Amount }; } + public class ValidateIncrementFilter : IActionFilter + { + public Task InvokeAsync( + IDispatchContext context, + ActionDelegate next, + CancellationToken cancellationToken) + { + if (context.Action.Amount <= 0) + { + throw new ValidationException(); + } + return next?.Invoke(context, cancellationToken); + } + } + public class ValidationException : Exception { } public class IncrementTest : IntegrationTest { @@ -68,6 +85,24 @@ public async Task Should_decrement_the_CounterState_by_the_Increment_action_Amou } } + public class ValidateIncrementFilterTest : IntegrationTest + { + [Theory] + [InlineData(0)] + [InlineData(-1)] + public async Task Should_throw_a_ValidationException_when_Increment_is_smaller_or_equal_to_zero(int amount) + { + // Arrange + var services = Initialize(); + var cancellationToken = CancellationToken.None; + var dispatcher = services.GetRequiredService(); + + // Act & Assert + await Assert.ThrowsAsync(() => dispatcher + .DispatchAsync(new Increment(amount), cancellationToken)); + } + } + private IServiceProvider Initialize() { var services = new ServiceCollection(); @@ -77,6 +112,7 @@ private IServiceProvider Initialize() .AddAction(typeof(Increment)) .AddAction(typeof(Decrement)) .AddUpdaters(typeof(CounterUpdaters)) + .AddActionFilter(typeof(ValidateIncrementFilter)) .Apply() .BuildServiceProvider() ; diff --git a/test/StateR.Tests/Internal/StatorBuilderTest.cs b/test/StateR.Tests/Internal/StatorBuilderTest.cs index 3726384..91d1a18 100644 --- a/test/StateR.Tests/Internal/StatorBuilderTest.cs +++ b/test/StateR.Tests/Internal/StatorBuilderTest.cs @@ -118,7 +118,7 @@ public void Should_add_updaterType_to_Updaters() // Arrange var services = new ServiceCollection(); var sut = new StatorBuilder(services); - var updaterType = typeof(TestUpdaters); + var updaterType = typeof(TestUpdater1); // Act sut.AddUpdaters(updaterType); @@ -151,7 +151,7 @@ public void Should_add_actionFilterType_to_ActionFilters() // Arrange var services = new ServiceCollection(); var sut = new StatorBuilder(services); - var actionFilterType = typeof(TestActionFilter); + var actionFilterType = typeof(TestActionFilter1); // Act sut.AddActionFilter(actionFilterType); diff --git a/test/StateR.Tests/StatorStartupExtensionsTest.cs b/test/StateR.Tests/StatorStartupExtensionsTest.cs index 7c08996..f733f1a 100644 --- a/test/StateR.Tests/StatorStartupExtensionsTest.cs +++ b/test/StateR.Tests/StatorStartupExtensionsTest.cs @@ -55,7 +55,7 @@ public void Should_add_IUpdater_and_IActionFilter_to_the_ServiceCollection() var sut = new StatorBuilder(services) .AddState() .AddAction(typeof(TestAction1)) - .AddUpdaters(typeof(TestUpdaters)) + .AddUpdaters(typeof(TestUpdater1)) ; // Act @@ -66,5 +66,32 @@ public void Should_add_IUpdater_and_IActionFilter_to_the_ServiceCollection() sp.GetRequiredService>(); sp.GetRequiredService>(); } + + [Fact] + public void Should_add_IActionFilter_to_the_ServiceCollection() + { + // Arrange + var services = new ServiceCollection(); + var sp = new StatorBuilder(services) + .AddState() + .AddAction(typeof(TestAction2)) + .AddUpdaters(typeof(TestUpdater2)) + .AddActionFilter(typeof(TestActionFilter1)) + .AddActionFilter(typeof(TestActionFilter2)) + .Apply() + .BuildServiceProvider(); + ; + + // Act + var actionFilters = sp.GetServices>(); + + // Assert + Assert.Collection(actionFilters, + filter => Assert.IsType>(filter), + filter => Assert.IsType(filter), + filter => Assert.IsType(filter) + ); + } + } } diff --git a/test/StateR.Tests/TestTypes.cs b/test/StateR.Tests/TestTypes.cs index 530365d..a49465b 100644 --- a/test/StateR.Tests/TestTypes.cs +++ b/test/StateR.Tests/TestTypes.cs @@ -28,13 +28,29 @@ public class NotAnActionFilter { } public record TestAction1 : IAction; public record TestAction2 : IAction; -public class TestUpdaters : IUpdater +public class TestUpdater1 : IUpdater { public TestState1 Update(TestAction1 action, TestState1 state) => new(); } -public class TestActionFilter : IActionFilter +public class TestUpdater2 : IUpdater +{ + public TestState2 Update(TestAction2 action, TestState2 state) + => new(); +} + +public class TestActionFilter1 : IActionFilter +{ + public Task InvokeAsync( + IDispatchContext context, + ActionDelegate next, + CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} +public class TestActionFilter2 : IActionFilter { public Task InvokeAsync( IDispatchContext context, From 710b14815ff7b6996c17290f09428178641e069e Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Thu, 3 Mar 2022 00:31:59 -0500 Subject: [PATCH 55/58] Statically fix the test program counter and experimental FluentValidation registration --- samples/CounterApp/CounterApp/Program.cs | 9 ++ .../FluentValidation/StartupExtensions.cs | 19 ++- .../FluentValidation/ValidationFilter.cs | 3 +- .../Internal/TypeScannerBuilderExtensions.cs | 30 ++-- .../StatorStartupExtensions.cs | 133 ------------------ src/StateR/IStatorBuilder.cs | 23 +-- src/StateR/Pipeline/IPipelineFactory.cs | 3 + 7 files changed, 38 insertions(+), 182 deletions(-) diff --git a/samples/CounterApp/CounterApp/Program.cs b/samples/CounterApp/CounterApp/Program.cs index 4586b09..0742e60 100644 --- a/samples/CounterApp/CounterApp/Program.cs +++ b/samples/CounterApp/CounterApp/Program.cs @@ -40,6 +40,15 @@ public static void RegisterServices(this IServiceCollection services) var appAssembly = typeof(App).Assembly; services .AddStateR() + + // TODO: scan for types instead + .AddState() + .AddAction(typeof(CounterApp.Features.Counter.Increment)) + .AddAction(typeof(CounterApp.Features.Counter.Decrement)) + .AddAction(typeof(CounterApp.Features.Counter.SetPositive)) + .AddAction(typeof(CounterApp.Features.Counter.SetNegative)) + .AddUpdaters(typeof(CounterApp.Features.Counter.Updaters)) + //.AddAsyncOperations() //.AddReduxDevTools() .AddFluentValidation(appAssembly) diff --git a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs index 18df8c8..c7994a1 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/StartupExtensions.cs @@ -13,18 +13,15 @@ public static IStatorBuilder AddFluentValidation(this IStatorBuilder builder, pa ArgumentNullException.ThrowIfNull(builder, nameof(builder)); ArgumentNullException.ThrowIfNull(assembliesToScan, nameof(assembliesToScan)); - // Validation action - builder.AddTypes(new[] { - typeof(AddValidationErrors), - typeof(ReplaceValidationErrors), - typeof(ValidationUpdaters), - typeof(ValidationInitialState), - typeof(ValidationState), - typeof(ValidationFilter<,>), - }); - builder.AddMiddlewares(new[] { typeof(ValidationFilter<,>) }); + // Add state, actions, and updaters + builder + .AddState() + .AddAction(typeof(AddValidationErrors)) + .AddAction(typeof(ReplaceValidationErrors)) + .AddUpdaters(typeof(ValidationUpdaters)) + ; - // Validation interceptor and state + // Validation interceptor builder.Services.TryAddSingleton(typeof(IActionFilter<,>), typeof(ValidationFilter<,>)); // Scan for validators diff --git a/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs b/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs index 9c836c3..fa14a1c 100644 --- a/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs +++ b/src/StateR.Experiments/Validations/FluentValidation/ValidationFilter.cs @@ -18,7 +18,7 @@ public ValidationFilter(IEnumerable> validators) public async Task InvokeAsync(IDispatchContext context, ActionDelegate? next, CancellationToken cancellationToken) { - ArgumentNullException.ThrowIfNull(next, nameof(next)); + ArgumentNullException.ThrowIfNull(next); var result = _validators .Select(validator => validator.Validate(context.Action)); @@ -36,6 +36,7 @@ public async Task InvokeAsync(IDispatchContext context, ActionD } catch (ValidationException ex) { + Console.WriteLine(ex.Message); await context.Dispatcher.DispatchAsync( new AddValidationErrors(ex.Errors), context.CancellationToken diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs index c2d9121..d32f824 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs @@ -4,25 +4,25 @@ namespace StateR.Internal; -public static class TypeScannerBuilderExtensions -{ - public static IStatorBuilder ScanTypes(this IStatorBuilder builder) - { - var states = TypeScanner.FindStates(builder.All); - builder.AddStates(states); +//public static class TypeScannerBuilderExtensions +//{ +// public static IStatorBuilder ScanTypes(this IStatorBuilder builder) +// { +// var states = TypeScanner.FindStates(builder.All); +// builder.AddStates(states); - var actions = TypeScanner.FindActions(builder.All); - builder.AddActions(actions); +// var actions = TypeScanner.FindActions(builder.All); +// builder.AddActions(actions); - var updaters = TypeScanner.FindUpdaters(builder.All); - builder.AddUpdaters(updaters); +// var updaters = TypeScanner.FindUpdaters(builder.All); +// builder.AddUpdaters(updaters); - var actionHandlers = TypeScanner.FindMiddlewares(builder.All); - builder.AddUpdaters(actionHandlers); +// var actionHandlers = TypeScanner.FindMiddlewares(builder.All); +// builder.AddUpdaters(actionHandlers); - return builder; - } -} +// return builder; +// } +//} public static class TypeScanner { public static IEnumerable FindStates(IEnumerable types) diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs index 0c47f7b..6a7d712 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs @@ -34,24 +34,6 @@ public static IStatorBuilder ScanAndAddStates(this IStatorBuilder builder, param return builder; } - //public static IStatorBuilder AddStateR(this IServiceCollection services, params Assembly[] assembliesToScanForStates) - //{ - // var builder = services.AddStateR(); - // var allTypes = assembliesToScanForStates - // .SelectMany(a => a.GetTypes()); - // //builder.AddTypes(allTypes); - - // var states = TypeScanner.FindStates(allTypes); - // builder.AddStates(states); - - // return builder; - //} - - //public static IStatorBuilder AddMiddleware(this IStatorBuilder builder) - //{ - - //} - public static IServiceCollection Apply(this IStatorBuilder builder, Action? postConfiguration = null) { // Register States @@ -116,121 +98,6 @@ public static IServiceCollection Apply(this IStatorBuilder builder, Action Create(IDispatchContext context, CancellationToken cancellationToken) - { - - } - */ - - return builder.Services; - - // Extract types - builder.ScanTypes(); - - // Scan - builder.Services.Scan(s => s - .AddTypes(builder.All) - - // Equivalent to: AddSingleton, Implementation>(); - .AddClasses(classes => classes.AssignableTo(typeof(IInitialState<>))) - .AsImplementedInterfaces() - .WithSingletonLifetime() - - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeInterceptorHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterInterceptorHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeAfterEffectHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterAfterEffectHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeActionHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterActionHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IBeforeUpdateHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - //// Equivalent to: AddSingleton(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterUpdateHook))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton, Implementation>(); - //.AddClasses(classes => classes.AssignableTo(typeof(IInterceptor<>))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - - //// Equivalent to: AddSingleton, Implementation>(); - //.AddClasses(classes => classes.AssignableTo(typeof(IAfterEffects<>))) - //.AsImplementedInterfaces() - //.WithSingletonLifetime() - ); - - // Register States - foreach (var state in builder.States) - { - Console.WriteLine($"state: {state.FullName}"); - - // Equivalent to: AddSingleton, State>(); - var stateServiceType = typeof(IState<>).MakeGenericType(state); - var stateImplementationType = typeof(State<>).MakeGenericType(state); - builder.Services.AddSingleton(stateServiceType, stateImplementationType); - } - - //// Register Updaters and their respective IMiddleware - //var iUpdaterType = typeof(IUpdater<,>); - //var updaterHandler = typeof(UpdaterMiddleware<,>); - //var handlerType = typeof(IActionFilter<,>); - //foreach (var updater in builder.Updaters) - //{ - // Console.WriteLine($"updater: {updater.FullName}"); - // var interfaces = updater.GetInterfaces() - // .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == iUpdaterType); - // foreach (var @interface in interfaces) - // { - // // Equivalent to: AddSingleton, UpdaterMiddleware> - // var actionType = @interface.GenericTypeArguments[0]; - // var stateType = @interface.GenericTypeArguments[1]; - // var iMiddlewareServiceType = handlerType.MakeGenericType(actionType, stateType); - // var updaterMiddlewareImplementationType = updaterHandler.MakeGenericType(stateType, actionType); - // builder.Services.AddSingleton(iMiddlewareServiceType, updaterMiddlewareImplementationType); - - // // Equivalent to: AddSingleton, Updater>(); - // builder.Services.AddSingleton(@interface, updater); - - // Console.WriteLine($"- AddSingleton<{iMiddlewareServiceType.GetStatorName()}, {updaterMiddlewareImplementationType.GetStatorName()}>()"); - // Console.WriteLine($"- AddSingleton<{@interface.GetStatorName()}, {updater.GetStatorName()}>()"); - // } - //} - - // Register Middleware - foreach (var middleware in builder.Middlewares.AsEnumerable().Reverse()) - { - builder.Services.Decorate(typeof(IActionFilter<,>), middleware); - } - - // Run post-configuration - postConfiguration?.Invoke(builder); - return builder.Services; } } diff --git a/src/StateR/IStatorBuilder.cs b/src/StateR/IStatorBuilder.cs index a841bb3..25beaf9 100644 --- a/src/StateR/IStatorBuilder.cs +++ b/src/StateR/IStatorBuilder.cs @@ -3,7 +3,7 @@ namespace StateR; -public interface IStatorBuilder : IOldStatorBuilder +public interface IStatorBuilder { IServiceCollection Services { get; } ReadOnlyCollection States { get; } @@ -21,24 +21,3 @@ IStatorBuilder AddState() IStatorBuilder AddUpdaters(Type updaterType); IStatorBuilder AddActionFilter(Type actionFilterType); } - -public interface IOldStatorBuilder -{ - //List Interceptors { get; } - List ActionHandlers { get; } - //List AfterEffects { get; } - List All { get; } - - IStatorBuilder AddTypes(IEnumerable types); - IStatorBuilder AddStates(IEnumerable states); - IStatorBuilder AddActions(IEnumerable states); - IStatorBuilder AddUpdaters(IEnumerable states); - IStatorBuilder AddActionHandlers(IEnumerable types); - - IStatorBuilder AddMiddlewares(IEnumerable types); - List Middlewares { get; } - - //IStatorBuilder AddAction() - // where TState : StateBase - // where TAction : IAction; -} diff --git a/src/StateR/Pipeline/IPipelineFactory.cs b/src/StateR/Pipeline/IPipelineFactory.cs index 942a36e..0158880 100644 --- a/src/StateR/Pipeline/IPipelineFactory.cs +++ b/src/StateR/Pipeline/IPipelineFactory.cs @@ -1,5 +1,7 @@  using Microsoft.Extensions.DependencyInjection; +using StateR.Internal; + namespace StateR.Pipeline; public interface IPipelineFactory @@ -29,6 +31,7 @@ public ActionDelegate Create(IDispatchContext< ActionDelegate MakeDelegate(IActionFilter filter) { var hasNext = enumerator.MoveNext(); + Console.WriteLine($"ActionDelegate: {filter.GetType().GetStatorName()}"); return new((a, s) => filter.InvokeAsync(a, hasNext ? MakeDelegate(enumerator.Current) : null, s)); } } From 11e85601b7a0a68ea066aa64108a98ced5e648d6 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Thu, 3 Mar 2022 18:41:20 -0500 Subject: [PATCH 56/58] Make TypeScanner extension methods --- ...nerBuilderExtensions.cs => TypeScanner.cs} | 31 ++++--------------- .../StatorStartupExtensions.cs | 7 +++-- 2 files changed, 10 insertions(+), 28 deletions(-) rename src/StateR.Microsoft.Extensions.DependencyInjection/Internal/{TypeScannerBuilderExtensions.cs => TypeScanner.cs} (57%) diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScanner.cs similarity index 57% rename from src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs rename to src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScanner.cs index d32f824..7a0801a 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScannerBuilderExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/Internal/TypeScanner.cs @@ -4,35 +4,16 @@ namespace StateR.Internal; -//public static class TypeScannerBuilderExtensions -//{ -// public static IStatorBuilder ScanTypes(this IStatorBuilder builder) -// { -// var states = TypeScanner.FindStates(builder.All); -// builder.AddStates(states); - -// var actions = TypeScanner.FindActions(builder.All); -// builder.AddActions(actions); - -// var updaters = TypeScanner.FindUpdaters(builder.All); -// builder.AddUpdaters(updaters); - -// var actionHandlers = TypeScanner.FindMiddlewares(builder.All); -// builder.AddUpdaters(actionHandlers); - -// return builder; -// } -//} -public static class TypeScanner +public static class TypeScannerExtensions { - public static IEnumerable FindStates(IEnumerable types) + public static IEnumerable FindStates(this IEnumerable types) { var states = types .Where(type => !type.IsAbstract && type.IsSubclassOf(typeof(StateBase))); return states; } - public static IEnumerable FindInitialStates(IEnumerable types) + public static IEnumerable FindInitialStates(this IEnumerable types) { var initialStates = types .Where(type => !type.IsAbstract && type @@ -43,7 +24,7 @@ public static IEnumerable FindInitialStates(IEnumerable types) return initialStates; } - public static IEnumerable FindActions(IEnumerable types) + public static IEnumerable FindActions(this IEnumerable types) { var actions = types .Where(type => !type.IsAbstract && type @@ -54,7 +35,7 @@ public static IEnumerable FindActions(IEnumerable types) return actions; } - public static IEnumerable FindUpdaters(IEnumerable types) + public static IEnumerable FindUpdaters(this IEnumerable types) { var updaters = types .Where(type => !type.IsAbstract && type @@ -65,7 +46,7 @@ public static IEnumerable FindUpdaters(IEnumerable types) return updaters; } - public static IEnumerable FindMiddlewares(IEnumerable types) + public static IEnumerable FindActionFilters(this IEnumerable types) { var handlers = types .Where(type => !type.IsAbstract && type diff --git a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs index 6a7d712..03a8b60 100644 --- a/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs +++ b/src/StateR.Microsoft.Extensions.DependencyInjection/StatorStartupExtensions.cs @@ -21,9 +21,10 @@ public static IStatorBuilder AddStateR(this IServiceCollection services) public static IStatorBuilder ScanAndAddStates(this IStatorBuilder builder, params Assembly[] assembliesToScan) { - var allTypes = assembliesToScan - .SelectMany(a => a.GetTypes()); - var initialStates = TypeScanner.FindInitialStates(allTypes); + var initialStates = assembliesToScan + .SelectMany(a => a.GetTypes()) + .FindInitialStates() + ; foreach (var initialState in initialStates) { From 6aab37f112932dd1d1aec10b67659b262c1636e8 Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 4 Mar 2022 10:13:23 -0500 Subject: [PATCH 57/58] Upgrade version to 1.0.0-ci.{height} --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 70fb031..c75410e 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.1", + "version": "1.0.0-ci.{height}", "publicReleaseRefSpec": ["^refs/heads/master-disabled$"], "cloudBuild": { "buildNumber": { From 9b92a84ce8caea170db00967c26cca3749f4ae9d Mon Sep 17 00:00:00 2001 From: Carl-Hugo Marcotte Date: Fri, 4 Mar 2022 10:20:13 -0500 Subject: [PATCH 58/58] Remove StateR.Blazor.Experiments from the solution --- StateR.sln | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/StateR.sln b/StateR.sln index 78e4f50..cd5d6f6 100644 --- a/StateR.sln +++ b/StateR.sln @@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateR.Tests", "test\StateR EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateR.Experiments", "src\StateR.Experiments\StateR.Experiments.csproj", "{0EB20F7F-8AA1-48E2-9489-6975566469AF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateR.Blazor.Experiments", "src\StateR.Blazor.Experiments\StateR.Blazor.Experiments.csproj", "{5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{22B777AC-AFAE-422A-B4CD-48C907251D9E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CounterApp", "CounterApp", "{E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9}" @@ -85,18 +83,6 @@ Global {0EB20F7F-8AA1-48E2-9489-6975566469AF}.Release|x64.Build.0 = Release|Any CPU {0EB20F7F-8AA1-48E2-9489-6975566469AF}.Release|x86.ActiveCfg = Release|Any CPU {0EB20F7F-8AA1-48E2-9489-6975566469AF}.Release|x86.Build.0 = Release|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Debug|x64.ActiveCfg = Debug|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Debug|x64.Build.0 = Debug|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Debug|x86.ActiveCfg = Debug|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Debug|x86.Build.0 = Debug|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Release|Any CPU.Build.0 = Release|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Release|x64.ActiveCfg = Release|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Release|x64.Build.0 = Release|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Release|x86.ActiveCfg = Release|Any CPU - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D}.Release|x86.Build.0 = Release|Any CPU {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {99926EB0-84F9-4906-8F7E-4E1873A403EB}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -142,7 +128,6 @@ Global {EA4C22B6-78A9-481A-85BF-62FF1EA70F1B} = {F0F6A2CA-0972-43BD-B777-B5656DFE20C3} {3276327A-63D9-4BEA-BBB8-D3AFF841CC67} = {372F8647-9F67-4176-B0C1-B0E5CAD87C9E} {0EB20F7F-8AA1-48E2-9489-6975566469AF} = {F0F6A2CA-0972-43BD-B777-B5656DFE20C3} - {5B16AF66-5F00-4E5F-AAEF-DE2CF549E21D} = {F0F6A2CA-0972-43BD-B777-B5656DFE20C3} {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} = {22B777AC-AFAE-422A-B4CD-48C907251D9E} {99926EB0-84F9-4906-8F7E-4E1873A403EB} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9} {FBDEBA94-7F63-4CB5-AC13-4D0874730316} = {E72B8B55-3F7B-4EE2-955B-B5FAB222EBD9}