Skip to content

Commit

Permalink
Merge pull request #499 from dotnet-state-machine/dev
Browse files Browse the repository at this point in the history
Release 5.13.0
  • Loading branch information
mclift authored Dec 29, 2022
2 parents 8d1016c + e0dd5c9 commit d5f6563
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 4 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
3 changes: 1 addition & 2 deletions src/Stateless/Reflection/StateMachineInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ internal StateMachineInfo(IEnumerable<StateInfo> states, Type stateType, Type tr
/// <summary>
/// Exposes the initial state of this state machine.
/// </summary>

public StateInfo InitialState { get; }

/// <summary>
/// Exposes the states, transitions, and actions of this machine.
/// </summary>

public IEnumerable<StateInfo> States { get; }

/// <summary>
Expand Down
16 changes: 15 additions & 1 deletion src/Stateless/StateMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public partial class StateMachine<TState, TTrigger>
private UnhandledTriggerAction _unhandledTriggerAction;
private readonly OnTransitionedEvent _onTransitionedEvent;
private readonly OnTransitionedEvent _onTransitionCompletedEvent;
private readonly TState _initialState;
private readonly FiringMode _firingMode;

private class QueuedTrigger
Expand Down Expand Up @@ -69,6 +70,7 @@ public StateMachine(Func<TState> stateAccessor, Action<TState> stateMutator, Fir
_stateAccessor = stateAccessor ?? throw new ArgumentNullException(nameof(stateAccessor));
_stateMutator = stateMutator ?? throw new ArgumentNullException(nameof(stateMutator));

_initialState = stateAccessor();
_firingMode = firingMode;
}

Expand All @@ -83,6 +85,7 @@ public StateMachine(TState initialState, FiringMode firingMode) : this()
_stateAccessor = () => reference.State;
_stateMutator = s => reference.State = s;

_initialState = initialState;
_firingMode = firingMode;
}

Expand Down Expand Up @@ -131,6 +134,17 @@ public IEnumerable<TTrigger> GetPermittedTriggers(params object[] args)
return CurrentRepresentation.GetPermittedTriggers(args);
}

#if !NETSTANDARD1_0
/// <summary>
/// Gets the currently-permissible triggers with any configured parameters.
/// </summary>
public IEnumerable<TriggerDetails<TState, TTrigger>> GetDetailedPermittedTriggers(params object[] args)
{
return CurrentRepresentation.GetPermittedTriggers(args)
.Select(trigger => new TriggerDetails<TState, TTrigger>(trigger, _triggerConfiguration));
}
#endif

StateRepresentation CurrentRepresentation
{
get
Expand All @@ -144,7 +158,7 @@ StateRepresentation CurrentRepresentation
/// </summary>
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);

Expand Down
2 changes: 1 addition & 1 deletion src/Stateless/Stateless.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<Description>Create state machines and lightweight state machine-based workflows directly in .NET code</Description>
<Copyright>Copyright © Stateless Contributors 2009-2019</Copyright>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>5.12.0</VersionPrefix>
<VersionPrefix>5.13.0</VersionPrefix>
<Authors>Stateless Contributors</Authors>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
38 changes: 38 additions & 0 deletions src/Stateless/TriggerDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Collections.Generic;

namespace Stateless
{
/// <summary>
/// Represents a trigger with details of any configured trigger parameters.
/// </summary>
public sealed class TriggerDetails<TState, TTrigger>
{
/// <summary>
/// Creates a new instance of <see cref="TriggerDetails{TState, TTrigger}"/>.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <param name="triggerConfiguration">The trigger configurations dictionary.</param>
internal TriggerDetails(TTrigger trigger, IDictionary<TTrigger, StateMachine<TState, TTrigger>.TriggerWithParameters> triggerConfiguration)
{
Trigger = trigger;
HasParameters = triggerConfiguration.ContainsKey(trigger);
Parameters = HasParameters ? triggerConfiguration[trigger] : null;
}

/// <summary>
/// Gets the trigger.
/// </summary>
public TTrigger Trigger { get; }

/// <summary>
/// Gets a value indicating whether the trigger has been configured with parameters.
/// </summary>
public bool HasParameters { get; }

/// <summary>
/// When <see cref="HasParameters"/> is <code>true</code>, returns the parameters required by
/// this trigger; otherwise, returns <code>null</code>.
/// </summary>
public StateMachine<TState, TTrigger>.TriggerWithParameters Parameters { get; }
}
}
6 changes: 6 additions & 0 deletions src/Stateless/TriggerWithParameters.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace Stateless
{
Expand All @@ -23,6 +24,11 @@ public TriggerWithParameters(TTrigger underlyingTrigger, params Type[] argumentT
_argumentTypes = argumentTypes ?? throw new ArgumentNullException(nameof(argumentTypes));
}

/// <summary>
/// Gets the arguments types expected by this trigger.
/// </summary>
public IEnumerable<Type> ArgumentTypes => _argumentTypes;

/// <summary>
/// Gets the underlying trigger value that has been configured.
/// </summary>
Expand Down
21 changes: 21 additions & 0 deletions test/Stateless.Tests/DotGraphFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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, Trigger>(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; }
Expand Down
31 changes: 31 additions & 0 deletions test/Stateless.Tests/StateMachineFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1066,5 +1066,36 @@ public void CanFire_GetEmptyUnmetGuardDescriptionsIfGuardPasses()
Assert.True(unmetGuards?.Count == 0);
}

[Fact]
public void GetDetailedPermittedTriggers_ReturnsTriggerWithoutParameters()
{
var sm = new StateMachine<State, Trigger>(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, Trigger>(State.B);

var pt = sm.SetTriggerParameters<int>(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);
}

}
}
31 changes: 31 additions & 0 deletions test/Stateless.Tests/TriggerWithParametersFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace Stateless.Tests
Expand Down Expand Up @@ -73,5 +75,34 @@ public void ParameterListOfCorrectTypeAreAccepted()
var twp = new StateMachine<State, Trigger>.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<State, Trigger>.TriggerWithParameters<int>(Trigger.X);
Assert.Single(twp.ArgumentTypes);
Assert.Equal(typeof(int), twp.ArgumentTypes.First());
}

[Fact]
public void Arguments_Returs_Two_Arguments()
{
var twp = new StateMachine<State, Trigger>.TriggerWithParameters<int, string>(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<State, Trigger>.TriggerWithParameters<int, string, List<bool>>(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<bool>), args[2]);
}
}
}

0 comments on commit d5f6563

Please sign in to comment.