From 4f63723331066c4b05ed61eacc5ef9256c314264 Mon Sep 17 00:00:00 2001 From: Peter Morris Date: Sat, 17 Dec 2022 17:36:08 +0000 Subject: [PATCH] Allow action filtering on redux dev tools (fixes #383) (#389) * Support action filtering in Redux Dev Tools * Add unit tests --- Docs/releases.md | 6 +- Source/Fluxor.sln | 19 ++++- Source/Lib/Directory.Build.props | 11 +-- .../CallbackObjects/BaseCallbackObject.cs | 13 ---- .../Fluxor.Blazor.Web.ReduxDevTools.csproj | 17 +++- .../CallbackObjects/BaseCallbackObject.cs | 13 ++++ .../CallbackObjects/BasePayload.cs | 4 +- .../CallbackObjects/JumpToStateCallback.cs | 4 +- .../CallbackObjects/JumpToStatePayload.cs | 4 +- .../{ => Internal}/ReduxDevToolsInterop.cs | 35 ++++++--- .../{ => Internal}/ReduxDevToolsMiddleware.cs | 27 +++---- .../OptionsReduxDevToolsExtensions.cs | 4 +- .../ReduxDevToolsMiddlewareOptions.cs | 36 ++++++++- .../Fluxor.Blazor.Web.csproj | 11 +-- Source/Tests/Directory.Build.props | 16 ++++ ....Blazor.Web.ReduxDevTools.UnitTests.csproj | 29 +++++++ .../AfterDispatchTests.cs | 78 +++++++++++++++++++ .../Fluxor.Blazor.Web.UnitTests.csproj | 42 +++++----- Source/Tests/Fluxor.UnitTests/AssemblyInfo.cs | 3 - .../Tests/Fluxor.UnitTests/IsExternalInit.cs | 9 +++ .../ReduxDevToolsTutorial/Client/Program.cs | 1 + 21 files changed, 288 insertions(+), 94 deletions(-) delete mode 100644 Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/BaseCallbackObject.cs create mode 100644 Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/BaseCallbackObject.cs rename Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/{ => Internal}/CallbackObjects/BasePayload.cs (59%) rename Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/{ => Internal}/CallbackObjects/JumpToStateCallback.cs (54%) rename Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/{ => Internal}/CallbackObjects/JumpToStatePayload.cs (60%) rename Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/{ => Internal}/ReduxDevToolsInterop.cs (84%) rename Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/{ => Internal}/ReduxDevToolsMiddleware.cs (84%) create mode 100644 Source/Tests/Directory.Build.props create mode 100644 Source/Tests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests.csproj create mode 100644 Source/Tests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests/ReduxDevToolsInteropTests/AfterDispatchTests.cs delete mode 100644 Source/Tests/Fluxor.UnitTests/AssemblyInfo.cs create mode 100644 Source/Tests/Fluxor.UnitTests/IsExternalInit.cs diff --git a/Docs/releases.md b/Docs/releases.md index 5c810c72..e824f252 100644 --- a/Docs/releases.md +++ b/Docs/releases.md @@ -1,13 +1,13 @@ # Releases +## New in 5.10 +* Support Action Filtering in Redux Dev Tools ([#383](https://github.com/mrpmorris/Fluxor/issues/383)) ## New in 5.9 * Adds additional useful information to exception thrown by `DisposableAction` ([#425](https://github.com/mrpmorris/Fluxor/issues/425)) * Fix deadlock scenario when dispatching actions from an effect triggered by store activation ([#426](https://github.com/mrpmorris/Fluxor/issues/426)) ## New in 5.8 -* Fixes potential for deadlock ([#407](https://github.com/mrpmorris/Fluxor/issues/407)) - -## New in 5.7 +* Fixes potential for deadlock ([#407](https://github.com/mrpmorris/Fluxor/issues/407))## New in 5.7 * Fixes memory leak when using `ActionSubscriber` or `SubscribeToAction` ([#378](https://github.com/mrpmorris/Fluxor/issues/378)) ## New in 5.6 diff --git a/Source/Fluxor.sln b/Source/Fluxor.sln index 6a13895c..4eef33ad 100644 --- a/Source/Fluxor.sln +++ b/Source/Fluxor.sln @@ -6,11 +6,13 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tutorials", "Tutorials", "{1A6663AC-4F1F-40AD-8D03-0B0977ED466D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{2279CD95-5886-4777-B019-E66683CE32F1}" + ProjectSection(SolutionItems) = preProject + Tests\Directory.Build.props = Tests\Directory.Build.props + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C2B1D7B3-E943-4EA3-88B0-94433B49080C}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - Lib\Directory.Build.props = Lib\Directory.Build.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxor", "Lib\Fluxor\Fluxor.csproj", "{863909D3-7E81-4240-8C0A-6F57768D28FF}" @@ -119,6 +121,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluxorBlazorWeb.EffectsTuto EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluxorBlazorWeb.ActionSubscriberTutorial.Contracts", "Tutorials\02-Blazor\02E-ActionSubscriber\FluxorBlazorWeb.ActionSubscriberTutorial\Shared\FluxorBlazorWeb.ActionSubscriberTutorial.Contracts.csproj", "{AE3CD639-5DE8-43ED-9CE4-8498548ADD28}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxor.Blazor.Web.ReduxDevTools.UnitTests", "Tests\Fluxor.Blazor.Web.ReduxDevTools.UnitTests\Fluxor.Blazor.Web.ReduxDevTools.UnitTests.csproj", "{FD3207E5-1DE9-4526-9C2A-4C18129992D9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -393,6 +397,18 @@ Global {AE3CD639-5DE8-43ED-9CE4-8498548ADD28}.Release|iPhone.Build.0 = Release|Any CPU {AE3CD639-5DE8-43ED-9CE4-8498548ADD28}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {AE3CD639-5DE8-43ED-9CE4-8498548ADD28}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Debug|iPhone.Build.0 = Debug|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Release|Any CPU.Build.0 = Release|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Release|iPhone.ActiveCfg = Release|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Release|iPhone.Build.0 = Release|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {FD3207E5-1DE9-4526-9C2A-4C18129992D9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -431,6 +447,7 @@ Global {3E8452CA-14A3-432E-8378-99EAC13E5EED} = {B5E9B3FC-1304-4A74-B0A9-607282AB2FD0} {7A6B9736-6DE1-4B52-8A88-D433C8917731} = {730B26CD-CADC-4CF0-8A1D-3BB174692A40} {AE3CD639-5DE8-43ED-9CE4-8498548ADD28} = {B5E9B3FC-1304-4A74-B0A9-607282AB2FD0} + {FD3207E5-1DE9-4526-9C2A-4C18129992D9} = {2279CD95-5886-4777-B019-E66683CE32F1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B1F2E0DF-C651-48A3-83E3-3B77D34EC3A2} diff --git a/Source/Lib/Directory.Build.props b/Source/Lib/Directory.Build.props index 5855d4c9..49897432 100644 --- a/Source/Lib/Directory.Build.props +++ b/Source/Lib/Directory.Build.props @@ -21,22 +21,15 @@ true ..\..\..\..\..\MrPMorris.snk - true false - true + true + true true - NU5118 - - - - - - \ No newline at end of file diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/BaseCallbackObject.cs b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/BaseCallbackObject.cs deleted file mode 100644 index 14ecf205..00000000 --- a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/BaseCallbackObject.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Fluxor.Blazor.Web.ReduxDevTools.CallbackObjects -{ - internal class BaseCallbackObject - where TPayload: BasePayload - { -#pragma warning disable IDE1006 // Naming Styles - public string type { get; set; } - public TPayload payload { get; set; } -#pragma warning restore IDE1006 // Naming Styles - } - - internal class BaseCallbackObject : BaseCallbackObject { } -} diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Fluxor.Blazor.Web.ReduxDevTools.csproj b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Fluxor.Blazor.Web.ReduxDevTools.csproj index 8830c541..fb3f813e 100644 --- a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Fluxor.Blazor.Web.ReduxDevTools.csproj +++ b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Fluxor.Blazor.Web.ReduxDevTools.csproj @@ -1,5 +1,6 @@  + 11 ReduxDevTools for Fluxor Blazor (Web) Middleware link from a Blazor (Web) Fluxor store to the Redux Dev Tools browser extension for Chrome. fluxor-blazor-reduxdevtools-small.png @@ -7,12 +8,20 @@ True - - 3.0 - + + + + + + + + + + + - + diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/BaseCallbackObject.cs b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/BaseCallbackObject.cs new file mode 100644 index 00000000..183fa834 --- /dev/null +++ b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/BaseCallbackObject.cs @@ -0,0 +1,13 @@ +namespace Fluxor.Blazor.Web.ReduxDevTools.Internal.CallbackObjects +{ + public class BaseCallbackObject + where TPayload : BasePayload + { +#pragma warning disable IDE1006 // Naming Styles + public string type { get; set; } + public TPayload payload { get; set; } +#pragma warning restore IDE1006 // Naming Styles + } + + public class BaseCallbackObject : BaseCallbackObject { } +} diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/BasePayload.cs b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/BasePayload.cs similarity index 59% rename from Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/BasePayload.cs rename to Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/BasePayload.cs index d0464062..ab612d65 100644 --- a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/BasePayload.cs +++ b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/BasePayload.cs @@ -1,6 +1,6 @@ -namespace Fluxor.Blazor.Web.ReduxDevTools.CallbackObjects +namespace Fluxor.Blazor.Web.ReduxDevTools.Internal.CallbackObjects { - internal class BasePayload + public class BasePayload { #pragma warning disable IDE1006 // Naming Styles public string type { get; set; } diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/JumpToStateCallback.cs b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/JumpToStateCallback.cs similarity index 54% rename from Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/JumpToStateCallback.cs rename to Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/JumpToStateCallback.cs index dd85b873..bb9e4582 100644 --- a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/JumpToStateCallback.cs +++ b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/JumpToStateCallback.cs @@ -1,8 +1,8 @@ using System.Dynamic; -namespace Fluxor.Blazor.Web.ReduxDevTools.CallbackObjects +namespace Fluxor.Blazor.Web.ReduxDevTools.Internal.CallbackObjects { - internal class JumpToStateCallback: BaseCallbackObject + public class JumpToStateCallback : BaseCallbackObject { #pragma warning disable IDE1006 // Naming Styles public string state { get; set; } diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/JumpToStatePayload.cs b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/JumpToStatePayload.cs similarity index 60% rename from Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/JumpToStatePayload.cs rename to Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/JumpToStatePayload.cs index 0e712047..7885d348 100644 --- a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/CallbackObjects/JumpToStatePayload.cs +++ b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/CallbackObjects/JumpToStatePayload.cs @@ -1,6 +1,6 @@ -namespace Fluxor.Blazor.Web.ReduxDevTools.CallbackObjects +namespace Fluxor.Blazor.Web.ReduxDevTools.Internal.CallbackObjects { - internal class JumpToStatePayload : BasePayload + public class JumpToStatePayload : BasePayload { #pragma warning disable IDE1006 // Naming Styles public int index { get; set; } diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsInterop.cs b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/ReduxDevToolsInterop.cs similarity index 84% rename from Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsInterop.cs rename to Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/ReduxDevToolsInterop.cs index 7d430ec9..7408ae04 100644 --- a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsInterop.cs +++ b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/ReduxDevToolsInterop.cs @@ -1,20 +1,33 @@ -using Fluxor.Blazor.Web.ReduxDevTools.CallbackObjects; +using Fluxor.Blazor.Web.ReduxDevTools.Internal.CallbackObjects; using Microsoft.JSInterop; using System; using System.Collections.Generic; using System.Threading.Tasks; -namespace Fluxor.Blazor.Web.ReduxDevTools +namespace Fluxor.Blazor.Web.ReduxDevTools.Internal { + public interface IReduxDevToolsInterop + { + bool DevToolsBrowserPluginDetected { get; } + Func OnJumpToState { get; set; } + Func OnCommit { get; set; } + ValueTask InitializeAsync(IDictionary state); + Task DispatchAsync( + object action, + IDictionary state, + string stackTrace); + Task DevToolsCallback(string messageAsJson); + } + /// /// Interop for dev tools /// - internal sealed class ReduxDevToolsInterop : IDisposable + internal sealed class ReduxDevToolsInterop : IDisposable, IReduxDevToolsInterop { public const string DevToolsCallbackId = "DevToolsCallback"; public bool DevToolsBrowserPluginDetected { get; private set; } - public Func OnJumpToState; - public Func OnCommit; + public Func OnJumpToState { get; set; } + public Func OnCommit { get; set; } private const string FluxorDevToolsId = "__FluxorDevTools__"; private const string FromJsDevToolsDetectedActionTypeName = "detected"; @@ -39,7 +52,7 @@ public ReduxDevToolsInterop( DotNetRef = DotNetObjectReference.Create(this); } - internal async ValueTask InitializeAsync(IDictionary state) + public async ValueTask InitializeAsync(IDictionary state) { IsInitializing = true; try @@ -52,7 +65,7 @@ internal async ValueTask InitializeAsync(IDictionary state) } } - internal async Task DispatchAsync( + public async Task DispatchAsync( object action, IDictionary state, string stackTrace) @@ -113,9 +126,9 @@ void IDisposable.Dispose() } private static bool IsDotNetReferenceObject(object x) => - (x is not null) - && (x.GetType().IsGenericType) - && (x.GetType().GetGenericTypeDefinition() == typeof(DotNetObjectReference<>)); + x is not null + && x.GetType().IsGenericType + && x.GetType().GetGenericTypeDefinition() == typeof(DotNetObjectReference<>); private ValueTask InvokeFluxorDevToolsMethodAsync(string identifier, params object[] args) { @@ -167,7 +180,7 @@ internal static string GetClientScripts(ReduxDevToolsMiddlewareOptions options) // Notify Fluxor of the presence of the browser plugin const detectedMessage = {{ payload: {{ - type: '{ReduxDevToolsInterop.FromJsDevToolsDetectedActionTypeName}' + type: '{FromJsDevToolsDetectedActionTypeName}' }} }}; const detectedMessageAsJson = JSON.stringify(detectedMessage); diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsMiddleware.cs b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/ReduxDevToolsMiddleware.cs similarity index 84% rename from Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsMiddleware.cs rename to Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/ReduxDevToolsMiddleware.cs index 74660d98..5d45ee9a 100644 --- a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsMiddleware.cs +++ b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/Internal/ReduxDevToolsMiddleware.cs @@ -1,10 +1,8 @@ -using Fluxor.Blazor.Web.ReduxDevTools.CallbackObjects; -using Fluxor.Extensions; -using System; +using Fluxor.Blazor.Web.ReduxDevTools.Internal; +using Fluxor.Blazor.Web.ReduxDevTools.Internal.CallbackObjects; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace Fluxor.Blazor.Web.ReduxDevTools @@ -12,7 +10,7 @@ namespace Fluxor.Blazor.Web.ReduxDevTools /// /// Middleware for interacting with the Redux Devtools extension for Chrome /// - internal sealed class ReduxDevToolsMiddleware : WebMiddleware + public sealed class ReduxDevToolsMiddleware : WebMiddleware { private readonly object SyncRoot = new(); private Task TailTask = Task.CompletedTask; @@ -20,21 +18,21 @@ internal sealed class ReduxDevToolsMiddleware : WebMiddleware private int SequenceNumberOfLatestState = 0; private readonly ReduxDevToolsMiddlewareOptions Options; private IStore Store; - private readonly ReduxDevToolsInterop ReduxDevToolsInterop; + private readonly IReduxDevToolsInterop ToolsInterop; private readonly IJsonSerialization JsonSerialization; /// /// Creates a new instance of the middleware /// public ReduxDevToolsMiddleware( - ReduxDevToolsInterop reduxDevToolsInterop, + IReduxDevToolsInterop reduxDevToolsInterop, ReduxDevToolsMiddlewareOptions options, IJsonSerialization jsonSerialization = null) { Options = options; - ReduxDevToolsInterop = reduxDevToolsInterop; - ReduxDevToolsInterop.OnJumpToState = OnJumpToState; - ReduxDevToolsInterop.OnCommit = OnCommit; + ToolsInterop = reduxDevToolsInterop; + ToolsInterop.OnJumpToState = OnJumpToState; + ToolsInterop.OnCommit = OnCommit; JsonSerialization = jsonSerialization ?? new Serialization.NewtonsoftJsonAdapter(); } @@ -45,7 +43,7 @@ public ReduxDevToolsMiddleware( public async override Task InitializeAsync(IDispatcher dispatcher, IStore store) { Store = store; - await ReduxDevToolsInterop.InitializeAsync(GetState()); + await ToolsInterop.InitializeAsync(GetState()); } /// @@ -55,6 +53,9 @@ public override bool MayDispatchAction(object action) => /// public override void AfterDispatch(object action) { + if (Options.ActionFilters.Length > 0 && !Options.ActionFilters.All(filter => filter(action))) + return; + string stackTrace = null; int maxItems = Options.StackTraceLimit == 0 ? int.MaxValue : Options.StackTraceLimit; if (Options.StackTraceEnabled) @@ -69,7 +70,7 @@ public override void AfterDispatch(object action) { IDictionary state = GetState(); TailTask = TailTask - .ContinueWith(_ => ReduxDevToolsInterop.DispatchAsync(action, state, stackTrace)).Unwrap(); + .ContinueWith(_ => ToolsInterop.DispatchAsync(action, state, stackTrace)).Unwrap(); } // As actions can only be executed if not in a historical state (yes, "a" historical, pronounce your H!) @@ -91,7 +92,7 @@ private async Task OnCommit() // Wait for fire+forget state notifications to ReduxDevTools to dequeue await TailTask.ConfigureAwait(false); - await ReduxDevToolsInterop.InitializeAsync(GetState()); + await ToolsInterop.InitializeAsync(GetState()); SequenceNumberOfCurrentState = SequenceNumberOfLatestState; } diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/OptionsReduxDevToolsExtensions.cs b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/OptionsReduxDevToolsExtensions.cs index fc6ca23b..449281fa 100644 --- a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/OptionsReduxDevToolsExtensions.cs +++ b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/OptionsReduxDevToolsExtensions.cs @@ -1,10 +1,10 @@ -using Fluxor.Blazor.Web.ReduxDevTools; +using Fluxor.Blazor.Web.ReduxDevTools.Internal; using Fluxor.DependencyInjection; using Fluxor.Extensions; using Microsoft.Extensions.DependencyInjection; using System; -namespace Fluxor +namespace Fluxor.Blazor.Web.ReduxDevTools { public static class OptionsReduxDevToolsExtensions { diff --git a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsMiddlewareOptions.cs b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsMiddlewareOptions.cs index b2e307f9..3684a279 100644 --- a/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsMiddlewareOptions.cs +++ b/Source/Lib/Fluxor.Blazor.Web.ReduxDevTools/ReduxDevToolsMiddlewareOptions.cs @@ -3,41 +3,61 @@ using Fluxor.Extensions; using Newtonsoft.Json; using System; +using System.Collections.Immutable; using System.Text.Json; using System.Text.RegularExpressions; namespace Fluxor.Blazor.Web.ReduxDevTools { /// - /// Options class for Redux Dev Tools integration + /// Used to determine if an action should be logged to Redux Dev Tools + /// or filtered out and ignored. + /// + /// + /// + public delegate bool ActionFilter(object action); + + /// + /// Options class for Redux Dev Tools integration. /// public class ReduxDevToolsMiddlewareOptions { private readonly FluxorOptions FluxorOptions; /// - /// The name to display in the Redux Dev Tools window + /// The name to display in the Redux Dev Tools window. /// public string Name { get; set; } = "Fluxor"; + /// /// How often the Redux Dev Tools actions are updated. /// public TimeSpan Latency { get; set; } = TimeSpan.FromMilliseconds(50); + /// /// How many actions to keep in the Redux Dev Tools history (maxAge setting). /// Default is 50. /// public ushort MaximumHistoryLength { get; set; } = 50; + /// /// When enabled, the stack trace that led to the dispatch of an action will /// be displayed in Redux Dev Tools. /// public bool StackTraceEnabled { get; private set; } + /// /// Specifies how many stack frames to show in Redux Dev Tools for each action. /// Less than or equal to zero means show all. /// public int StackTraceLimit { get; private set; } + + /// + /// Allows the developer to decide whether an action is logged via Redux Dev Tools + /// or filtered out and ignored. + /// + public ImmutableArray ActionFilters { get; private set; } = ImmutableArray.Create(); + internal Regex StackTraceFilterRegex { get; private set; } @@ -46,6 +66,18 @@ public ReduxDevToolsMiddlewareOptions(FluxorOptions fluxorOptions) FluxorOptions = fluxorOptions; } + /// + /// Enables action + /// + /// Accepts the action object, should return `true` to log the action or `false` to ignore it. + /// + public ReduxDevToolsMiddlewareOptions AddActionFilter(ActionFilter filter) + { + ArgumentNullException.ThrowIfNull(filter); + ActionFilters = ActionFilters.Add(filter); + return this; + } + /// /// Enables stack trace in Redux Dev Tools diff --git a/Source/Lib/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj b/Source/Lib/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj index c2d66ff3..ee56a2b5 100644 --- a/Source/Lib/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj +++ b/Source/Lib/Fluxor.Blazor.Web/Fluxor.Blazor.Web.csproj @@ -1,10 +1,10 @@  + 11 Fluxor for Blazor (Web) A zero boilerplate Redux/Flux framework for Blazor fluxor-blazor-logo-small.png Redux Flux DotNet CSharp Blazor RazorComponents - True @@ -12,10 +12,11 @@ - - - - + + + + + diff --git a/Source/Tests/Directory.Build.props b/Source/Tests/Directory.Build.props new file mode 100644 index 00000000..3a23e557 --- /dev/null +++ b/Source/Tests/Directory.Build.props @@ -0,0 +1,16 @@ + + + net6.0;net7.0 + true + 11 + ..\..\..\..\..\MrPMorris.snk + true + false + false + false + + + + true + + \ No newline at end of file diff --git a/Source/Tests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests.csproj b/Source/Tests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests.csproj new file mode 100644 index 00000000..ce36d39a --- /dev/null +++ b/Source/Tests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests.csproj @@ -0,0 +1,29 @@ + + + + enable + enable + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/Source/Tests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests/ReduxDevToolsInteropTests/AfterDispatchTests.cs b/Source/Tests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests/ReduxDevToolsInteropTests/AfterDispatchTests.cs new file mode 100644 index 00000000..25b7603b --- /dev/null +++ b/Source/Tests/Fluxor.Blazor.Web.ReduxDevTools.UnitTests/ReduxDevToolsInteropTests/AfterDispatchTests.cs @@ -0,0 +1,78 @@ +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed +using Fluxor.Blazor.Web.ReduxDevTools.Internal; +using Fluxor.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace Fluxor.Blazor.Web.ReduxDevTools.UnitTests.ReduxDevToolsInteropTests; + +public class AfterDispatchTests +{ + private readonly Mock MockReduxDevToolsInterop; + private ReduxDevToolsMiddlewareOptions Options; + private readonly Mock MockJsonSerialization; + private Lazy Subject; + + [Fact] + public void WhenUsingActionFiltering_AndFilterReturnsFalse_ThenActionShouldNotBeLogged() + { + Options.AddActionFilter(_ => false); + Options.AddActionFilter(_ => true); + Subject.Value.AfterDispatch(this); + MockReduxDevToolsInterop.Verify( + x => x.DispatchAsync( + this, + It.IsAny>(), + It.IsAny()), + Times.Never, + "Should have logged action"); + } + + [Fact] + public void WhenUsingActionFiltering_AndFilterReturnsTrue_ThenActionShouldBeLogged() + { + Options.AddActionFilter(_ => true); + Subject.Value.AfterDispatch(this); + MockReduxDevToolsInterop.Verify( + x => x.DispatchAsync( + this, + It.IsAny>(), + It.IsAny()), + Times.Once, + "Should have logged action"); + } + + [Fact] + public void WhenNotUsingActionFiltering_ThenActionShouldBeLogged() + { + Subject.Value.AfterDispatch(this); + MockReduxDevToolsInterop.Verify( + x => x.DispatchAsync( + this, + It.IsAny>(), + It.IsAny()), + Times.Once, + "Should have logged action"); + } + + public AfterDispatchTests() + { + MockReduxDevToolsInterop = new Mock(); + Options = new ReduxDevToolsMiddlewareOptions(new FluxorOptions(new ServiceCollection())); + MockJsonSerialization = new Mock(); + Subject = new(() => + { + + var result = new ReduxDevToolsMiddleware( + MockReduxDevToolsInterop.Object, + Options, + MockJsonSerialization.Object); + result.InitializeAsync(Mock.Of(), new Store(Mock.Of())); + return result; + }); + + } +} + +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed \ No newline at end of file diff --git a/Source/Tests/Fluxor.Blazor.Web.UnitTests/Fluxor.Blazor.Web.UnitTests.csproj b/Source/Tests/Fluxor.Blazor.Web.UnitTests/Fluxor.Blazor.Web.UnitTests.csproj index c9c87f31..22de5652 100644 --- a/Source/Tests/Fluxor.Blazor.Web.UnitTests/Fluxor.Blazor.Web.UnitTests.csproj +++ b/Source/Tests/Fluxor.Blazor.Web.UnitTests/Fluxor.Blazor.Web.UnitTests.csproj @@ -1,26 +1,24 @@  - - net6.0 - false - 9 - + + false + - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - - + + + + diff --git a/Source/Tests/Fluxor.UnitTests/AssemblyInfo.cs b/Source/Tests/Fluxor.UnitTests/AssemblyInfo.cs deleted file mode 100644 index 4b5e7765..00000000 --- a/Source/Tests/Fluxor.UnitTests/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly:InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/Source/Tests/Fluxor.UnitTests/IsExternalInit.cs b/Source/Tests/Fluxor.UnitTests/IsExternalInit.cs new file mode 100644 index 00000000..a55d4830 --- /dev/null +++ b/Source/Tests/Fluxor.UnitTests/IsExternalInit.cs @@ -0,0 +1,9 @@ +#if NETCOREAPP3_1 +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + [EditorBrowsable(EditorBrowsableState.Never)] + internal class IsExternalInit { } +} +#endif \ No newline at end of file diff --git a/Source/Tutorials/02-Blazor/02D-ReduxDevToolsTutorial/ReduxDevToolsTutorial/Client/Program.cs b/Source/Tutorials/02-Blazor/02D-ReduxDevToolsTutorial/ReduxDevToolsTutorial/Client/Program.cs index bc2bde14..6205aeda 100644 --- a/Source/Tutorials/02-Blazor/02D-ReduxDevToolsTutorial/ReduxDevToolsTutorial/Client/Program.cs +++ b/Source/Tutorials/02-Blazor/02D-ReduxDevToolsTutorial/ReduxDevToolsTutorial/Client/Program.cs @@ -4,6 +4,7 @@ using Fluxor; using System.Net.Http; using System; +using Fluxor.Blazor.Web.ReduxDevTools; namespace FluxorBlazorWeb.ReduxDevToolsTutorial.Client {