Skip to content

Commit

Permalink
Merge pull request #9 from pierremrtn/async_transition
Browse files Browse the repository at this point in the history
Support for async transitions
  • Loading branch information
pierremrtn authored Oct 7, 2024
2 parents e45cb05 + 4748e69 commit 511b183
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 13 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/run-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Run tests

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
strategy:
matrix:
working-directory: [
packages/state_machine_bloc,
examples/flutter_timer_state_machine,
examples/infinite_list_state_machine
]

runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ matrix.working-directory }}

steps:
- uses: actions/checkout@v4

- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable

- name: Install dependencies
run: flutter pub get

- name: Verify formatting
run: dart format --output=none --set-exit-if-changed .

- name: Run tests
run: flutter test
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,10 @@
"languageVersion": "3.0"
}
],
"generated": "2024-08-28T12:34:53.161436Z",
"generated": "2024-10-07T02:53:31.718067Z",
"generator": "pub",
"generatorVersion": "3.5.1",
"generatorVersion": "3.5.3",
"flutterRoot": "file:///Users/pm/Dev/flutter",
"flutterVersion": "3.24.1",
"flutterVersion": "3.24.3",
"pubCache": "file:///Users/pm/.pub-cache"
}
2 changes: 1 addition & 1 deletion examples/infinite_list_state_machine/.dart_tool/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.24.1
3.24.3
12 changes: 6 additions & 6 deletions packages/state_machine_bloc/lib/src/state_definition.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ part of 'state_machine.dart';
/// Signature of a function that may or may not emit a new state
/// base on it's current state an an external event
typedef EventTransition<Event, SuperState, CurrentState extends SuperState>
= SuperState? Function(Event, CurrentState);
= FutureOr<SuperState?> Function(Event, CurrentState);

/// Signature of a callback function called by the state machine
/// in various contexts that hasn't the ability to emit new state
Expand All @@ -26,7 +26,7 @@ class _StateEventHandler<SuperEvent, SuperState,

final EventTransition<DefinedEvent, SuperState, DefinedState> transition;

SuperState? handle(SuperEvent e, SuperState s) =>
FutureOr<SuperState?> handle(SuperEvent e, SuperState s) =>
transition(e as DefinedEvent, s as DefinedState);
}

Expand Down Expand Up @@ -90,20 +90,20 @@ class _StateDefinition<Event, SuperState, DefinedState extends SuperState> {
_nestedStateDefinition(state)?.onExit(state);
}

SuperState? add(
FutureOr<SuperState?> add(
Event event,
DefinedState state,
) {
) async {
final stateHandlers = _handlers.where(
(handler) => handler.isType(event),
);
for (final handler in stateHandlers) {
final nextState = handler.handle(event, state) as SuperState?;
final nextState = (await handler.handle(event, state)) as SuperState?;
if (nextState != null) return nextState;
}
final nestedDefinition = _nestedStateDefinition(state);
if (nestedDefinition != null) {
return nestedDefinition.add(event, state);
return nestedDefinition.add(event, state) as FutureOr<SuperState?>;
}
return null;
}
Expand Down
6 changes: 4 additions & 2 deletions packages/state_machine_bloc/lib/src/state_machine.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';
import 'package:meta/meta.dart';
Expand Down Expand Up @@ -140,10 +142,10 @@ abstract class StateMachine<Event, State> extends Bloc<Event, State> {
throw "Invalid use of StateMachine.on(). You should use StateMachine.define() instead.";
}

void _mapEventToState(Event event, Emitter emit) {
void _mapEventToState(Event event, Emitter emit) async {
final definition = _stateDefinitions.firstWhere((def) => def.isType(state));

final nextState = definition.add(event, state) as State?;
final nextState = (await definition.add(event, state)) as State?;
if (nextState != null) {
emit(nextState);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/state_machine_bloc/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: state_machine_bloc
description: state_machine_bloc is an extension to the bloc state management library which provide a convenient way to create finite state machines based blocs.
version: 0.0.1
version: 0.0.2
homepage: https://github.com/Pierre2tm/state_machine_bloc
repository: https://github.com/Pierre2tm/state_machine_bloc

Expand Down
76 changes: 76 additions & 0 deletions packages/state_machine_bloc/test/async_transition_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'package:state_machine_bloc/state_machine_bloc.dart';
import 'package:test/test.dart';

import 'utils.dart';

abstract class Event {}

class EventA extends Event {}

class EventB extends Event {}

abstract class State {
const State();
}

class StateA extends State {
const StateA();
}

class StateB extends State {
const StateB();
}

class StateC extends State {
const StateC();
}

class StateD extends State {
const StateD();
}

class DummyStateMachine extends StateMachine<Event, State> {
DummyStateMachine([State? initial]) : super(initial ?? const StateA()) {
define<StateA>(($) => $
..on((EventA e, s) async {
await Future.delayed(Duration(milliseconds: 300));
return const StateB();
})
..on((EventB e, s) {
return const StateC();
}));
define<StateB>();
define<StateC>();
}
}

void main() {
group("event receiving tests", () {
test("Transitions are awaited", () async {
final sm = DummyStateMachine(const StateA());
sm.add(EventA());

await wait();

expect(sm.state, const StateA());

await Future.delayed(Duration(seconds: 1));

expect(sm.state, const StateB());
});
});

test("Transitions are evaluated sequentially", () async {
final sm = DummyStateMachine(const StateA());
sm.add(EventA());
sm.add(EventB());

await wait();

expect(sm.state, const StateA());

await Future.delayed(Duration(seconds: 1));

expect(sm.state, const StateB());
});
}
79 changes: 79 additions & 0 deletions packages/state_machine_bloc/test/nested_async_transition_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'package:state_machine_bloc/state_machine_bloc.dart';
import 'package:test/test.dart';

import 'utils.dart';

abstract class Event {}

class EventA extends Event {}

class EventB extends Event {}

abstract class State {
const State();
}

class StateA extends State {
const StateA();
}

class StateB extends StateA {
const StateB();
}

class StateC extends State {
const StateC();
}

class StateD extends State {
const StateD();
}

class DummyStateMachine extends StateMachine<Event, State> {
DummyStateMachine([State? initial]) : super(initial ?? const StateA()) {
define<StateA>(
($) => $
..define<StateB>(($) => $
..on((EventA e, s) async {
await Future.delayed(Duration(milliseconds: 300));
return const StateC();
})
..on((EventB e, s) {
return const StateD();
})),
);
define<StateC>();
define<StateD>();
}
}

void main() {
group("event receiving tests", () {
test("Nested transitions are awaited", () async {
final sm = DummyStateMachine(const StateB());
sm.add(EventA());

await wait();

expect(sm.state, const StateB());

await Future.delayed(Duration(seconds: 1));

expect(sm.state, const StateC());
});
});

test("Nested transitions are evaluated sequentially", () async {
final sm = DummyStateMachine(const StateB());
sm.add(EventA());
sm.add(EventB());

await wait();

expect(sm.state, const StateB());

await Future.delayed(Duration(seconds: 1));

expect(sm.state, const StateC());
});
}

0 comments on commit 511b183

Please sign in to comment.