Skip to content

Commit 867b03e

Browse files
committed
Add immediate transitions. Add Start() to avoid faffing about with InitialStateOrDefault().
1 parent d89659b commit 867b03e

11 files changed

Lines changed: 653 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
namespace FunctionalStateMachine.Core.Tests;
2+
3+
public class StateMachineImmediateTransitionTests
4+
{
5+
[Fact]
6+
public void Start_AppliesEntryAndImmediateTransition()
7+
{
8+
var machine = StateMachine<State, Trigger, Data, Command>.Create()
9+
.StartWith(State.Starting)
10+
.For(State.Starting)
11+
.OnEntry(data => new Command("enter-starting"))
12+
.OnExit(data => new Command("exit-starting"))
13+
.Immediately()
14+
.TransitionTo(State.Waiting)
15+
.Done()
16+
.For(State.Waiting)
17+
.OnEntry(data => new Command("enter-waiting"))
18+
.Build();
19+
20+
var (state, _, commands) = machine.Start(Data.Initial);
21+
22+
Assert.Equal(State.Waiting, state);
23+
Assert.Equal(
24+
["enter-starting", "exit-starting", "enter-waiting"],
25+
commands.Select(command => command.Message).ToArray());
26+
}
27+
28+
[Fact]
29+
public void Fire_AppliesImmediateTransitionsAfterEntry()
30+
{
31+
var machine = StateMachine<State, Trigger, Data, Command>.Create()
32+
.StartWith(State.Starting)
33+
.For(State.Starting)
34+
.On(Trigger.Next)
35+
.TransitionTo(State.Waiting)
36+
.For(State.Waiting)
37+
.OnEntry(data => new Command("enter-waiting"))
38+
.Immediately()
39+
.TransitionTo(State.Ready)
40+
.Done()
41+
.For(State.Ready)
42+
.OnEntry(data => new Command("enter-ready"))
43+
.Build();
44+
45+
var (nextState, _, commands) = machine.Fire(Trigger.Next, State.Starting, Data.Initial);
46+
47+
Assert.Equal(State.Ready, nextState);
48+
Assert.Equal(
49+
["enter-waiting", "enter-ready"],
50+
commands.Select(command => command.Message).ToArray());
51+
}
52+
53+
[Fact]
54+
public void ImmediateTransitionGuard_CanBlockTransition()
55+
{
56+
var machine = StateMachine<State, Trigger, Data, Command>.Create()
57+
.StartWith(State.Starting)
58+
.For(State.Starting)
59+
.Immediately()
60+
.Guard(data => data.Value == "go")
61+
.TransitionTo(State.Waiting)
62+
.Done()
63+
.For(State.Waiting)
64+
.Build();
65+
66+
var (state, _, commands) = machine.Start(new Data("stop"));
67+
68+
Assert.Equal(State.Starting, state);
69+
Assert.Empty(commands);
70+
}
71+
72+
[Fact]
73+
public void ImmediateTransitionLoop_Throws()
74+
{
75+
var machine = StateMachine<State, Trigger, Data, Command>.Create()
76+
.StartWith(State.Starting)
77+
.For(State.Starting)
78+
.Immediately()
79+
.TransitionTo(State.Waiting)
80+
.Done()
81+
.For(State.Waiting)
82+
.Immediately()
83+
.TransitionTo(State.Starting)
84+
.Done()
85+
.Build();
86+
87+
var exception = Assert.Throws<InvalidOperationException>(() => machine.Start(Data.Initial));
88+
89+
Assert.Contains("Immediate transition loop", exception.Message);
90+
}
91+
92+
private enum State
93+
{
94+
Starting,
95+
Waiting,
96+
Ready
97+
}
98+
99+
private enum Trigger
100+
{
101+
Next
102+
}
103+
104+
private sealed record Data(string Value)
105+
{
106+
public static Data Initial => new(string.Empty);
107+
}
108+
109+
private sealed record Command(string Message);
110+
}

FunctionalStateMachine.Core/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Features:
2626
- Fluent syntax
2727
- No external dependencies
2828
- Enter and Exit actions
29+
- Immediate transitions
2930
- Hierarchical states
3031
- Fast, efficient rehydration
3132

0 commit comments

Comments
 (0)