runStateMachineTestCases<STATE extends Object, ACTION extends Object, MOCKS extends Object> function

void runStateMachineTestCases<STATE extends Object, ACTION extends Object, MOCKS extends Object>({
  1. required String name,
  2. required MOCKS createMocks(),
  3. required StateMachineFactory<STATE, ACTION, MOCKS> createStateMachine,
  4. required List<STATE> states,
  5. required List<ACTION> actions,
  6. required StateMachineTestCases<STATE, ACTION, MOCKS> cases,
  7. StateMachineTestHook? beforeEach,
  8. StateMachineTestHook? afterEach,
})

Registers exhaustive Given-When-Then transition tests for a StateMachine.

The runner creates a fresh real StateMachine for each state/action pair, dispatches the action, verifies the immediate state, waits for the expected state stream emissions, and finally verifies the settled state.

It also registers a coverage test that fails when any states x actions pair is missing from cases, or when unexpected cases exist.

afterEach is invoked for every generated transition test even when setup, execution, or earlier cleanup fails.

Implementation

void runStateMachineTestCases<STATE extends Object, ACTION extends Object,
    MOCKS extends Object>({
  required String name,
  required MOCKS Function() createMocks,
  required StateMachineFactory<STATE, ACTION, MOCKS> createStateMachine,
  required List<STATE> states,
  required List<ACTION> actions,
  required StateMachineTestCases<STATE, ACTION, MOCKS> cases,
  StateMachineTestHook? beforeEach,
  StateMachineTestHook? afterEach,
}) {
  group(name, () {
    test('covers every state/action pair', () {
      _expectAllTransitionsCovered(
        states: states,
        actions: actions,
        cases: cases,
      );
    });

    for (final stateCase in cases) {
      group(_stateLabel(stateCase._initial), () {
        for (final actionCase in stateCase._actions) {
          final description = actionCase._describe(stateCase._initial);
          test(description, () async {
            fsm.StateMachine<STATE, ACTION>? stateMachine;
            _StateStreamProbe<STATE>? stateStreamProbe;

            try {
              await beforeEach?.call();
              final mocks = createMocks();
              final arrange = actionCase._arrange;
              if (arrange != null) {
                await arrange(mocks);
              }
              stateMachine = createStateMachine(mocks, stateCase._initial);
              stateStreamProbe = _StateStreamProbe<STATE>(
                stateMachine.stateStream,
              );
              final context = _failureContext(
                beforeState: stateCase._initial,
                action: actionCase._dispatch,
                description: description,
              );
              stateMachine.dispatch(actionCase._dispatch);

              expect(
                stateMachine.state,
                actionCase._afterState(stateCase._initial),
                reason: context,
              );
              await stateStreamProbe.expectEmittedStates(
                actionCase._emittedStates(stateCase._initial),
                context,
              );
              expect(
                stateMachine.state,
                actionCase._finallyState(stateCase._initial),
                reason: context,
              );
              final verify = actionCase._verify;
              if (verify != null) {
                await verify(mocks);
              }
            } finally {
              try {
                await stateStreamProbe?.cancel();
              } finally {
                try {
                  stateMachine?.close();
                } finally {
                  await afterEach?.call();
                }
              }
            }
          });
        }
      });
    }
  });
}