From 4f01d9dc75081f2fc3dbaf05f876318cead1b65a Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Tue, 8 Nov 2022 18:05:43 +0000 Subject: [PATCH 1/4] Add method to get permitted triggers with parameters; allow enumeration of trigger parameter argument types. --- src/Stateless/StateMachine.cs | 11 ++++++ src/Stateless/TriggerDetails.cs | 38 +++++++++++++++++++ src/Stateless/TriggerWithParameters.cs | 6 +++ test/Stateless.Tests/StateMachineFixture.cs | 31 +++++++++++++++ .../TriggerWithParametersFixture.cs | 31 +++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 src/Stateless/TriggerDetails.cs diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 803ed7ba..051040a8 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -131,6 +131,17 @@ public IEnumerable GetPermittedTriggers(params object[] args) return CurrentRepresentation.GetPermittedTriggers(args); } +#if !NETSTANDARD1_0 + /// + /// Gets the currently-permissible triggers with any configured parameters. + /// + public IEnumerable> GetDetailedPermittedTriggers(params object[] args) + { + return CurrentRepresentation.GetPermittedTriggers(args) + .Select(trigger => new TriggerDetails(trigger, _triggerConfiguration)); + } +#endif + StateRepresentation CurrentRepresentation { get diff --git a/src/Stateless/TriggerDetails.cs b/src/Stateless/TriggerDetails.cs new file mode 100644 index 00000000..6de1070d --- /dev/null +++ b/src/Stateless/TriggerDetails.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; + +namespace Stateless +{ + /// + /// Represents a trigger with details of any configured trigger parameters. + /// + public sealed class TriggerDetails + { + /// + /// Creates a new instance of . + /// + /// The trigger. + /// The trigger configurations dictionary. + internal TriggerDetails(TTrigger trigger, IDictionary.TriggerWithParameters> triggerConfiguration) + { + Trigger = trigger; + HasParameters = triggerConfiguration.ContainsKey(trigger); + Parameters = HasParameters ? triggerConfiguration[trigger] : null; + } + + /// + /// Gets the trigger. + /// + public TTrigger Trigger { get; } + + /// + /// Gets a value indicating whether the trigger has been configured with parameters. + /// + public bool HasParameters { get; } + + /// + /// When is true, returns the parameters required by + /// this trigger; otherwise, returns false. + /// + public StateMachine.TriggerWithParameters Parameters { get; } + } +} diff --git a/src/Stateless/TriggerWithParameters.cs b/src/Stateless/TriggerWithParameters.cs index 3dd35ef4..5bffcc3b 100644 --- a/src/Stateless/TriggerWithParameters.cs +++ b/src/Stateless/TriggerWithParameters.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace Stateless { @@ -23,6 +24,11 @@ public TriggerWithParameters(TTrigger underlyingTrigger, params Type[] argumentT _argumentTypes = argumentTypes ?? throw new ArgumentNullException(nameof(argumentTypes)); } + /// + /// Gets the arguments types expected by this trigger. + /// + public IEnumerable ArgumentTypes => _argumentTypes; + /// /// Gets the underlying trigger value that has been configured. /// diff --git a/test/Stateless.Tests/StateMachineFixture.cs b/test/Stateless.Tests/StateMachineFixture.cs index e5f93e36..5f2803b9 100644 --- a/test/Stateless.Tests/StateMachineFixture.cs +++ b/test/Stateless.Tests/StateMachineFixture.cs @@ -1066,5 +1066,36 @@ public void CanFire_GetEmptyUnmetGuardDescriptionsIfGuardPasses() Assert.True(unmetGuards?.Count == 0); } + [Fact] + public void GetDetailedPermittedTriggers_ReturnsTriggerWithoutParameters() + { + var sm = new StateMachine(State.B); + + sm.Configure(State.B) + .Permit(Trigger.X, State.A); + + var permitted = sm.GetDetailedPermittedTriggers().ToList(); + Assert.Single(permitted); + var triggerDetails = permitted.First(); + Assert.False(triggerDetails.HasParameters); + Assert.Null(triggerDetails.Parameters); + } + + [Fact] + public void GetDetailedPermittedTriggers_ReturnsTriggerWithParameters() + { + var sm = new StateMachine(State.B); + + var pt = sm.SetTriggerParameters(Trigger.X); + sm.Configure(State.B) + .Permit(Trigger.X, State.A); + + var permitted = sm.GetDetailedPermittedTriggers().ToList(); + Assert.Single(permitted); + var triggerDetails = permitted.First(); + Assert.True(triggerDetails.HasParameters); + Assert.Equal(pt, triggerDetails.Parameters); + } + } } \ No newline at end of file diff --git a/test/Stateless.Tests/TriggerWithParametersFixture.cs b/test/Stateless.Tests/TriggerWithParametersFixture.cs index 3c2890f2..8d92f147 100644 --- a/test/Stateless.Tests/TriggerWithParametersFixture.cs +++ b/test/Stateless.Tests/TriggerWithParametersFixture.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Xunit; namespace Stateless.Tests @@ -73,5 +75,34 @@ public void ParameterListOfCorrectTypeAreAccepted() var twp = new StateMachine.TriggerWithParameters(Trigger.X, new Type[] { typeof(int), typeof(string) }); twp.ValidateParameters(new object[] { 123, "arg" }); } + + [Fact] + public void Arguments_Returs_Single_Argument() + { + var twp = new StateMachine.TriggerWithParameters(Trigger.X); + Assert.Single(twp.ArgumentTypes); + Assert.Equal(typeof(int), twp.ArgumentTypes.First()); + } + + [Fact] + public void Arguments_Returs_Two_Arguments() + { + var twp = new StateMachine.TriggerWithParameters(Trigger.X); + var args = twp.ArgumentTypes.ToList(); + Assert.Equal(2, args.Count); + Assert.Equal(typeof(int), args[0]); + Assert.Equal(typeof(string), args[1]); + } + + [Fact] + public void Arguments_Returs_Three_Arguments() + { + var twp = new StateMachine.TriggerWithParameters>(Trigger.X); + var args = twp.ArgumentTypes.ToList(); + Assert.Equal(3, args.Count); + Assert.Equal(typeof(int), args[0]); + Assert.Equal(typeof(string), args[1]); + Assert.Equal(typeof(List), args[2]); + } } } From 77685bc1f2e6f122fe468fbe4ef0422cb1dcf4a2 Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Mon, 14 Nov 2022 14:18:43 +0000 Subject: [PATCH 2/4] Fix typo in comment --- src/Stateless/TriggerDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stateless/TriggerDetails.cs b/src/Stateless/TriggerDetails.cs index 6de1070d..af7b4175 100644 --- a/src/Stateless/TriggerDetails.cs +++ b/src/Stateless/TriggerDetails.cs @@ -31,7 +31,7 @@ internal TriggerDetails(TTrigger trigger, IDictionary /// When is true, returns the parameters required by - /// this trigger; otherwise, returns false. + /// this trigger; otherwise, returns null. /// public StateMachine.TriggerWithParameters Parameters { get; } } From 01c605fd69d843dc3f51a9d1b43a59c3088cc214 Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Fri, 18 Nov 2022 17:45:48 +0000 Subject: [PATCH 3/4] Fix incorrect initial state in dotgraph after trigger fired --- src/Stateless/Reflection/StateMachineInfo.cs | 3 +-- src/Stateless/StateMachine.cs | 5 ++++- test/Stateless.Tests/DotGraphFixture.cs | 21 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Stateless/Reflection/StateMachineInfo.cs b/src/Stateless/Reflection/StateMachineInfo.cs index 53d8cd25..ff4a1f44 100644 --- a/src/Stateless/Reflection/StateMachineInfo.cs +++ b/src/Stateless/Reflection/StateMachineInfo.cs @@ -20,12 +20,11 @@ internal StateMachineInfo(IEnumerable states, Type stateType, Type tr /// /// Exposes the initial state of this state machine. /// - public StateInfo InitialState { get; } + /// /// Exposes the states, transitions, and actions of this machine. /// - public IEnumerable States { get; } /// diff --git a/src/Stateless/StateMachine.cs b/src/Stateless/StateMachine.cs index 051040a8..2e374bb5 100644 --- a/src/Stateless/StateMachine.cs +++ b/src/Stateless/StateMachine.cs @@ -30,6 +30,7 @@ public partial class StateMachine private UnhandledTriggerAction _unhandledTriggerAction; private readonly OnTransitionedEvent _onTransitionedEvent; private readonly OnTransitionedEvent _onTransitionCompletedEvent; + private readonly TState _initialState; private readonly FiringMode _firingMode; private class QueuedTrigger @@ -69,6 +70,7 @@ public StateMachine(Func stateAccessor, Action stateMutator, Fir _stateAccessor = stateAccessor ?? throw new ArgumentNullException(nameof(stateAccessor)); _stateMutator = stateMutator ?? throw new ArgumentNullException(nameof(stateMutator)); + _initialState = stateAccessor(); _firingMode = firingMode; } @@ -83,6 +85,7 @@ public StateMachine(TState initialState, FiringMode firingMode) : this() _stateAccessor = () => reference.State; _stateMutator = s => reference.State = s; + _initialState = initialState; _firingMode = firingMode; } @@ -155,7 +158,7 @@ StateRepresentation CurrentRepresentation /// public StateMachineInfo GetInfo() { - var initialState = StateInfo.CreateStateInfo(new StateRepresentation(State)); + var initialState = StateInfo.CreateStateInfo(new StateRepresentation(_initialState)); var representations = _stateConfiguration.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); diff --git a/test/Stateless.Tests/DotGraphFixture.cs b/test/Stateless.Tests/DotGraphFixture.cs index ab3aabe1..b16234b4 100644 --- a/test/Stateless.Tests/DotGraphFixture.cs +++ b/test/Stateless.Tests/DotGraphFixture.cs @@ -537,6 +537,27 @@ public void TransitionWithIgnoreAndEntry() Assert.Equal(expected, dotGraph); } + [Fact] + public void Initial_State_Not_Changed_After_Trigger_Fired() + { + var expected = Prefix(Style.UML) + Box(Style.UML, "A") + Box(Style.UML, "B") + Line("A", "B", "X") + suffix; + + var sm = new StateMachine(State.A); + + sm.Configure(State.A) + .Permit(Trigger.X, State.B); + + sm.Fire(Trigger.X); + + string dotGraph = UmlDotGraph.Format(sm.GetInfo()); + +#if WRITE_DOTS_TO_FOLDER + System.IO.File.WriteAllText(DestinationFolder + "SimpleTransition.dot", dotGraph); +#endif + + Assert.Equal(expected, dotGraph); + } + private void TestEntryAction() { } private void TestEntryActionString(string val) { } private State DestinationSelector() { return State.A; } From 3a281f65154097bee3e2de5000fcd40a60fc5f00 Mon Sep 17 00:00:00 2001 From: Mike Clift Date: Thu, 29 Dec 2022 12:12:58 +0000 Subject: [PATCH 4/4] Release 5.13.0 --- CHANGELOG.md | 6 ++++++ src/Stateless/Stateless.csproj | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8619fcf9..07a1e42f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 5.13.0 - 2022.12.29 +### Added + - Add method to get permitted triggers with parameter information [#494] +### Fixed + - Fix incorrect initial state in dotgraph after trigger fired [#495] + ## 5.12.0 - 2022.10.17 ### Added - Add explicit .NET 6.0 framework support [#479] diff --git a/src/Stateless/Stateless.csproj b/src/Stateless/Stateless.csproj index 4e4b9c1d..da712780 100644 --- a/src/Stateless/Stateless.csproj +++ b/src/Stateless/Stateless.csproj @@ -8,7 +8,7 @@ Create state machines and lightweight state machine-based workflows directly in .NET code Copyright © Stateless Contributors 2009-2019 en-US - 5.12.0 + 5.13.0 Stateless Contributors true true