Skip to content

Commit

Permalink
feat: add OnExitFrom configuration method (closes #75)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Zombik authored and Jan Zombik committed May 17, 2024
1 parent 2a5b0eb commit d3fa7ca
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 0 deletions.
10 changes: 10 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ func (sc *StateConfiguration) OnExit(action ActionFunc) *StateConfiguration {
return sc
}

// OnExitWith specifies an action that will execute when transitioning from the configured state with a specific trigger.
func (sc *StateConfiguration) OnExitWith(trigger Trigger, action ActionFunc) *StateConfiguration {
sc.sr.ExitActions = append(sc.sr.ExitActions, actionBehaviour{
Action: action,
Description: newinvocationInfo(action),
Trigger: &trigger,
})
return sc
}

// SubstateOf sets the superstate that the configured state is a substate of.
// Substates inherit the allowed transitions of their superstate.
// When entering directly into a substate from outside of the superstate,
Expand Down
9 changes: 9 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ func Example() {

phoneCall.Configure(stateOnHold).
SubstateOf(stateConnected).
OnExitWith(triggerPhoneHurledAgainstWall, func(ctx context.Context, args ...any) error {
onWasted()
return nil
}).
Permit(triggerTakenOffHold, stateConnected).
Permit(triggerPhoneHurledAgainstWall, statePhoneDestroyed)

Expand All @@ -90,6 +94,7 @@ func Example() {
// Microphone muted!
// Microphone unmuted!
// Volume set to 11!
// Wasted!
// [Timer:] Call ended at 11:30am
// State is PhoneDestroyed

Expand All @@ -111,6 +116,10 @@ func onDialed(callee string) {
fmt.Printf("[Phone Call] placed for : [%s]\n", callee)
}

func onWasted() {
fmt.Println("Wasted!")
}

func startCallTimer(_ context.Context, _ ...any) error {
fmt.Println("[Timer:] Call started at 11:00am")
return nil
Expand Down
4 changes: 4 additions & 0 deletions graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ func phoneCall() *stateless.StateMachine {

phoneCall.Configure(stateOnHold).
SubstateOf(stateConnected).
OnExitWith(triggerPhoneHurledAgainstWall, func(ctx context.Context, args ...any) error {
onWasted()
return nil
}).
Permit(triggerTakenOffHold, stateConnected).
Permit(triggerPhoneHurledAgainstWall, statePhoneDestroyed)

Expand Down
48 changes: 48 additions & 0 deletions statemachine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,31 @@ func TestStateMachine_Fire_ParametersSuppliedToFireArePassedToEntryAction(t *tes
}
}

func TestStateMachine_Fire_ParametersSuppliedToFireArePassedToExitAction(t *testing.T) {
sm := NewStateMachine(stateB)
sm.SetTriggerParameters(triggerX, reflect.TypeOf(""), reflect.TypeOf(0))
sm.Configure(stateB).Permit(triggerX, stateC)

var (
entryArg1 string
entryArg2 int
)
sm.Configure(stateB).OnExitWith(triggerX, func(_ context.Context, args ...any) error {
entryArg1 = args[0].(string)
entryArg2 = args[1].(int)
return nil
})
suppliedArg1, suppliedArg2 := "something", 2
sm.Fire(triggerX, suppliedArg1, suppliedArg2)

if entryArg1 != suppliedArg1 {
t.Errorf("entryArg1 = %v, want %v", entryArg1, suppliedArg1)
}
if entryArg2 != suppliedArg2 {
t.Errorf("entryArg2 = %v, want %v", entryArg2, suppliedArg2)
}
}

func TestStateMachine_OnUnhandledTrigger_TheProvidedHandlerIsCalledWithStateAndTrigger(t *testing.T) {
sm := NewStateMachine(stateB)
var (
Expand Down Expand Up @@ -570,6 +595,29 @@ func TestStateMachine_Fire_IgnoreVsPermitReentryFrom(t *testing.T) {
}
}

func TestStateMachine_Fire_IgnoreVsPermitReentryExitWith(t *testing.T) {
sm := NewStateMachine(stateA)
var calls int
sm.Configure(stateA).
OnExitWith(triggerX, func(_ context.Context, _ ...any) error {
calls += 1
return nil
}).
OnExitWith(triggerY, func(_ context.Context, _ ...any) error {
calls += 1
return nil
}).
PermitReentry(triggerX).
Ignore(triggerY)

sm.Fire(triggerX)
sm.Fire(triggerY)

if calls != 1 {
t.Errorf("calls = %d, want %d", calls, 1)
}
}

func TestStateMachine_Fire_IfSelfTransitionPermited_ActionsFire_InSubstate(t *testing.T) {
sm := NewStateMachine(stateA)
var onEntryStateBfired, onExitStateBfired, onExitStateAfired bool
Expand Down

0 comments on commit d3fa7ca

Please sign in to comment.