From b21e7f9e5fd81c8ab83ac1c2752e4e5e0f0d4cb6 Mon Sep 17 00:00:00 2001 From: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Date: Mon, 16 May 2022 12:57:28 -0500 Subject: [PATCH] refactor: migrate flutter forest to new bloc api (#481) Co-authored-by: Tom Arra --- .../flutter_forest_bonus_behavior.dart | 46 ++--- .../flutter_forest/flutter_forest.dart | 67 ++++--- .../components/game_bloc_status_listener.dart | 13 +- .../behaviors/google_word_bonus_behavior.dart | 2 +- .../dash_bumper_ball_contact_behavior.dart | 2 +- .../dash_bumper/cubit/dash_bumper_cubit.dart | 17 -- .../dash_bumper/cubit/dash_bumper_state.dart | 10 - .../dash_bumper/cubit/dash_bumpers_cubit.dart | 26 +++ .../dash_bumper/cubit/dash_bumpers_state.dart | 21 ++ .../components/dash_bumper/dash_bumper.dart | 70 ++++--- .../lib/src/components/signpost/signpost.dart | 52 ++--- .../flutter_forest/dash_bumper_a_game.dart | 10 +- .../flutter_forest/dash_bumper_b_game.dart | 10 +- .../flutter_forest/dash_bumper_main_game.dart | 8 +- .../stories/flutter_forest/signpost_game.dart | 22 ++- ...ash_bumper_ball_contact_behavior_test.dart | 24 ++- .../cubit/dash_bumper_cubit_test.dart | 24 --- .../cubit/dash_bumpers_cubit_test.dart | 37 ++++ .../cubit/dash_bumpers_state_test.dart | 76 ++++++++ .../dash_nest_bumper/dash_bumper_test.dart | 144 ++++++++------ .../src/components/google_letter_test.dart | 2 +- .../components/signpost/signpost_test.dart | 179 +++++++++++++----- .../flutter_forest_bonus_behavior_test.dart | 147 +++++++------- .../game_bloc_status_listener_test.dart | 91 +++------ .../google_word_bonus_behavior_test.dart | 4 + 25 files changed, 678 insertions(+), 426 deletions(-) delete mode 100644 packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_cubit.dart delete mode 100644 packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_state.dart create mode 100644 packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumpers_cubit.dart create mode 100644 packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumpers_state.dart delete mode 100644 packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumper_cubit_test.dart create mode 100644 packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumpers_cubit_test.dart create mode 100644 packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumpers_state_test.dart diff --git a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart index d13297f0..cc13d6ac 100644 --- a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart +++ b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart @@ -10,37 +10,21 @@ import 'package:pinball_flame/pinball_flame.dart'; /// When all [DashBumper]s are hit at least once three times, the [Signpost] /// progresses. When the [Signpost] fully progresses, the [GameBonus.dashNest] /// is awarded, and the [DashBumper.main] releases a new [Ball]. -class FlutterForestBonusBehavior extends Component - with - ParentIsA, - HasGameRef, - FlameBlocReader { +class FlutterForestBonusBehavior extends Component { @override - void onMount() { - super.onMount(); - - final bumpers = parent.children.whereType(); - final signpost = parent.firstChild()!; - - for (final bumper in bumpers) { - bumper.bloc.stream.listen((state) { - final activatedAllBumpers = bumpers.every( - (bumper) => bumper.bloc.state == DashBumperState.active, - ); - - if (activatedAllBumpers) { - signpost.bloc.onProgressed(); - for (final bumper in bumpers) { - bumper.bloc.onReset(); - } - - if (signpost.bloc.isFullyProgressed()) { - bloc.add(const BonusActivated(GameBonus.dashNest)); - add(BonusBallSpawningBehavior()); - signpost.bloc.onProgressed(); - } - } - }); - } + Future onLoad() async { + await super.onLoad(); + await add( + FlameBlocListener( + listenWhen: (_, state) => state == SignpostState.active3, + onNewState: (_) { + readBloc() + .add(const BonusActivated(GameBonus.dashNest)); + readBloc().onProgressed(); + readBloc().onReset(); + add(BonusBallSpawningBehavior()); + }, + ), + ); } } diff --git a/lib/game/components/flutter_forest/flutter_forest.dart b/lib/game/components/flutter_forest/flutter_forest.dart index 9717ee8b..d38645aa 100644 --- a/lib/game/components/flutter_forest/flutter_forest.dart +++ b/lib/game/components/flutter_forest/flutter_forest.dart @@ -1,6 +1,7 @@ // ignore_for_file: avoid_renaming_method_parameters import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart'; @@ -16,36 +17,48 @@ class FlutterForest extends Component with ZIndex { FlutterForest() : super( children: [ - Signpost( - children: [ - ScoringContactBehavior(points: Points.fiveThousand), - BumperNoiseBehavior(), - ], - )..initialPosition = Vector2(7.95, -58.35), - DashBumper.main( - children: [ - ScoringContactBehavior(points: Points.twoHundredThousand), - BumperNoiseBehavior(), - ], - )..initialPosition = Vector2(18.55, -59.35), - DashBumper.a( - children: [ - ScoringContactBehavior(points: Points.twentyThousand), - BumperNoiseBehavior(), - ], - )..initialPosition = Vector2(8.95, -51.95), - DashBumper.b( - children: [ - ScoringContactBehavior(points: Points.twentyThousand), - BumperNoiseBehavior(), + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider( + create: SignpostCubit.new, + ), + FlameBlocProvider( + create: DashBumpersCubit.new, + ), ], - )..initialPosition = Vector2(21.8, -46.75), - DashAnimatronic( children: [ - AnimatronicLoopingBehavior(animationCoolDown: 11), + Signpost( + children: [ + ScoringContactBehavior(points: Points.fiveThousand), + BumperNoiseBehavior(), + ], + )..initialPosition = Vector2(7.95, -58.35), + DashBumper.main( + children: [ + ScoringContactBehavior(points: Points.twoHundredThousand), + BumperNoiseBehavior(), + ], + )..initialPosition = Vector2(18.55, -59.35), + DashBumper.a( + children: [ + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoiseBehavior(), + ], + )..initialPosition = Vector2(8.95, -51.95), + DashBumper.b( + children: [ + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoiseBehavior(), + ], + )..initialPosition = Vector2(21.8, -46.75), + DashAnimatronic( + children: [ + AnimatronicLoopingBehavior(animationCoolDown: 11), + ], + )..position = Vector2(20, -66), + FlutterForestBonusBehavior(), ], - )..position = Vector2(20, -66), - FlutterForestBonusBehavior(), + ), ], ) { zIndex = ZIndexes.flutterForest; diff --git a/lib/game/components/game_bloc_status_listener.dart b/lib/game/components/game_bloc_status_listener.dart index fb5fc485..701c6f48 100644 --- a/lib/game/components/game_bloc_status_listener.dart +++ b/lib/game/components/game_bloc_status_listener.dart @@ -62,9 +62,16 @@ class GameBlocStatusListener extends Component .onReset(); gameRef .descendants() - .whereType() - .forEach((bumper) => bumper.bloc.onReset()); - gameRef.descendants().whereType().single.bloc.onReset(); + .whereType>() + .single + .bloc + .onReset(); + gameRef + .descendants() + .whereType>() + .single + .bloc + .onReset(); } void _addPlungerBehaviors(Plunger plunger) => plunger diff --git a/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart b/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart index 2313e921..45e8eca6 100644 --- a/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart +++ b/lib/game/components/google_gallery/behaviors/google_word_bonus_behavior.dart @@ -14,7 +14,7 @@ class GoogleWordBonusBehavior extends Component { FlameBlocListener( listenWhen: (_, state) => state.letterSpriteStates.values .every((element) => element == GoogleLetterSpriteState.lit), - onNewState: (state) { + onNewState: (_) { readBloc() .add(const BonusActivated(GameBonus.googleWord)); readBloc().onReset(); diff --git a/packages/pinball_components/lib/src/components/dash_bumper/behaviors/dash_bumper_ball_contact_behavior.dart b/packages/pinball_components/lib/src/components/dash_bumper/behaviors/dash_bumper_ball_contact_behavior.dart index d147515c..9d37c2b0 100644 --- a/packages/pinball_components/lib/src/components/dash_bumper/behaviors/dash_bumper_ball_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/dash_bumper/behaviors/dash_bumper_ball_contact_behavior.dart @@ -7,6 +7,6 @@ class DashBumperBallContactBehavior extends ContactBehavior { void beginContact(Object other, Contact contact) { super.beginContact(other, contact); if (other is! Ball) return; - parent.bloc.onBallContacted(); + readBloc().onBallContacted(parent.id); } } diff --git a/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_cubit.dart b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_cubit.dart deleted file mode 100644 index 84e626c4..00000000 --- a/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_cubit.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:bloc/bloc.dart'; - -part 'dash_bumper_state.dart'; - -class DashBumperCubit extends Cubit { - DashBumperCubit() : super(DashBumperState.inactive); - - /// Event added when the bumper contacts with a ball. - void onBallContacted() { - emit(DashBumperState.active); - } - - /// Event added when the bumper should return to its initial configuration. - void onReset() { - emit(DashBumperState.inactive); - } -} diff --git a/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_state.dart b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_state.dart deleted file mode 100644 index f15d2e57..00000000 --- a/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumper_state.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of 'dash_bumper_cubit.dart'; - -/// Indicates the [DashBumperCubit]'s current state. -enum DashBumperState { - /// A lit up bumper. - active, - - /// A dimmed bumper. - inactive, -} diff --git a/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumpers_cubit.dart b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumpers_cubit.dart new file mode 100644 index 00000000..1bd7ec62 --- /dev/null +++ b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumpers_cubit.dart @@ -0,0 +1,26 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:pinball_components/pinball_components.dart'; + +part 'dash_bumpers_state.dart'; + +class DashBumpersCubit extends Cubit { + DashBumpersCubit() : super(DashBumpersState.initial()); + + /// Event added when a ball contacts with a bumper. + void onBallContacted(DashBumperId id) { + final spriteStatesMap = {...state.bumperSpriteStates}; + emit( + DashBumpersState( + bumperSpriteStates: spriteStatesMap + ..update(id, (_) => DashBumperSpriteState.active), + ), + ); + } + + /// Event added when the bumpers should return to their initial + /// configurations. + void onReset() { + emit(DashBumpersState.initial()); + } +} diff --git a/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumpers_state.dart b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumpers_state.dart new file mode 100644 index 00000000..7620a2e2 --- /dev/null +++ b/packages/pinball_components/lib/src/components/dash_bumper/cubit/dash_bumpers_state.dart @@ -0,0 +1,21 @@ +part of 'dash_bumpers_cubit.dart'; + +class DashBumpersState extends Equatable { + const DashBumpersState({required this.bumperSpriteStates}); + + DashBumpersState.initial() + : this( + bumperSpriteStates: { + for (var id in DashBumperId.values) + id: DashBumperSpriteState.inactive + }, + ); + + final Map bumperSpriteStates; + + bool get isFullyActivated => bumperSpriteStates.values + .every((spriteState) => spriteState == DashBumperSpriteState.active); + + @override + List get props => [...bumperSpriteStates.values]; +} diff --git a/packages/pinball_components/lib/src/components/dash_bumper/dash_bumper.dart b/packages/pinball_components/lib/src/components/dash_bumper/dash_bumper.dart index 1b960610..1f3a8a2e 100644 --- a/packages/pinball_components/lib/src/components/dash_bumper/dash_bumper.dart +++ b/packages/pinball_components/lib/src/components/dash_bumper/dash_bumper.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -8,7 +9,18 @@ import 'package:pinball_components/src/components/bumping_behavior.dart'; import 'package:pinball_components/src/components/dash_bumper/behaviors/behaviors.dart'; import 'package:pinball_flame/pinball_flame.dart'; -export 'cubit/dash_bumper_cubit.dart'; +export 'cubit/dash_bumpers_cubit.dart'; + +enum DashBumperSpriteState { + active, + inactive, +} + +enum DashBumperId { + main, + a, + b, +} /// {@template dash_bumper} /// Bumper for the flutter forest. @@ -16,23 +28,23 @@ export 'cubit/dash_bumper_cubit.dart'; class DashBumper extends BodyComponent with InitialPosition { /// {@macro dash_bumper} DashBumper._({ + required this.id, required double majorRadius, required double minorRadius, required String activeAssetPath, required String inactiveAssetPath, required Vector2 spritePosition, Iterable? children, - required this.bloc, }) : _majorRadius = majorRadius, _minorRadius = minorRadius, super( renderBody: false, children: [ - _DashBumperSpriteGroupComponent( + DashBumperSpriteGroupComponent( + id: id, activeAssetPath: activeAssetPath, inactiveAssetPath: inactiveAssetPath, position: spritePosition, - current: bloc.state, ), DashBumperBallContactBehavior(), ...?children, @@ -46,12 +58,12 @@ class DashBumper extends BodyComponent with InitialPosition { DashBumper.main({ Iterable? children, }) : this._( + id: DashBumperId.main, majorRadius: 5.1, minorRadius: 3.75, activeAssetPath: Assets.images.dash.bumper.main.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName, spritePosition: Vector2(0, -0.3), - bloc: DashBumperCubit(), children: [ ...?children, BumpingBehavior(strength: 20), @@ -65,12 +77,12 @@ class DashBumper extends BodyComponent with InitialPosition { DashBumper.a({ Iterable? children, }) : this._( + id: DashBumperId.a, majorRadius: 3, minorRadius: 2.2, activeAssetPath: Assets.images.dash.bumper.a.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName, spritePosition: Vector2(0.3, -1.3), - bloc: DashBumperCubit(), children: [ ...?children, BumpingBehavior(strength: 20), @@ -84,12 +96,12 @@ class DashBumper extends BodyComponent with InitialPosition { DashBumper.b({ Iterable? children, }) : this._( + id: DashBumperId.b, majorRadius: 3.1, minorRadius: 2.2, activeAssetPath: Assets.images.dash.bumper.b.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName, spritePosition: Vector2(0.4, -1.2), - bloc: DashBumperCubit(), children: [ ...?children, BumpingBehavior(strength: 20), @@ -100,22 +112,14 @@ class DashBumper extends BodyComponent with InitialPosition { /// /// This can be used for testing [DashBumper]'s behaviors in isolation. @visibleForTesting - DashBumper.test({required this.bloc}) + DashBumper.test({required this.id}) : _majorRadius = 3, _minorRadius = 2.5; + final DashBumperId id; final double _majorRadius; final double _minorRadius; - // ignore: public_member_api_docs - final DashBumperCubit bloc; - - @override - void onRemove() { - bloc.close(); - super.onRemove(); - } - @override Body createBody() { final shape = EllipseShape( @@ -131,37 +135,49 @@ class DashBumper extends BodyComponent with InitialPosition { } } -class _DashBumperSpriteGroupComponent - extends SpriteGroupComponent - with HasGameRef, ParentIsA { - _DashBumperSpriteGroupComponent({ +@visibleForTesting +class DashBumperSpriteGroupComponent + extends SpriteGroupComponent + with HasGameRef, FlameBlocListenable { + DashBumperSpriteGroupComponent({ + required DashBumperId id, required String activeAssetPath, required String inactiveAssetPath, required Vector2 position, - required DashBumperState current, - }) : _activeAssetPath = activeAssetPath, + }) : _id = id, + _activeAssetPath = activeAssetPath, _inactiveAssetPath = inactiveAssetPath, super( anchor: Anchor.center, position: position, - current: current, ); + final DashBumperId _id; final String _activeAssetPath; final String _inactiveAssetPath; + @override + bool listenWhen(DashBumpersState previousState, DashBumpersState newState) { + return previousState.bumperSpriteStates[_id] != + newState.bumperSpriteStates[_id]; + } + + @override + void onNewState(DashBumpersState state) => + current = state.bumperSpriteStates[_id]; + @override Future onLoad() async { await super.onLoad(); - parent.bloc.stream.listen((state) => current = state); final sprites = { - DashBumperState.active: + DashBumperSpriteState.active: Sprite(gameRef.images.fromCache(_activeAssetPath)), - DashBumperState.inactive: + DashBumperSpriteState.inactive: Sprite(gameRef.images.fromCache(_inactiveAssetPath)), }; this.sprites = sprites; + current = DashBumperSpriteState.inactive; size = sprites[current]!.originalSize / 10; } } diff --git a/packages/pinball_components/lib/src/components/signpost/signpost.dart b/packages/pinball_components/lib/src/components/signpost/signpost.dart index 3ba486c1..714bea07 100644 --- a/packages/pinball_components/lib/src/components/signpost/signpost.dart +++ b/packages/pinball_components/lib/src/components/signpost/signpost.dart @@ -1,6 +1,6 @@ import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flutter/foundation.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -13,40 +13,29 @@ export 'cubit/signpost_cubit.dart'; /// {@endtemplate} class Signpost extends BodyComponent with InitialPosition { /// {@macro signpost} - Signpost({ - Iterable? children, - }) : this._( - children: children, - bloc: SignpostCubit(), - ); - Signpost._({ + Signpost({ Iterable? children, - required this.bloc, }) : super( renderBody: false, children: [ - _SignpostSpriteComponent( - current: bloc.state, - ), + _SignpostSpriteComponent(), ...?children, ], ); - /// Creates a [Signpost] without any children. - /// - /// This can be used for testing [Signpost]'s behaviors in isolation. - @visibleForTesting - Signpost.test({ - required this.bloc, - }); - - final SignpostCubit bloc; - @override - void onRemove() { - bloc.close(); - super.onRemove(); + Future onLoad() async { + await super.onLoad(); + await add( + FlameBlocListener( + listenWhen: (_, state) => state.isFullyActivated, + onNewState: (_) { + readBloc().onProgressed(); + readBloc().onReset(); + }, + ), + ); } @override @@ -61,20 +50,19 @@ class Signpost extends BodyComponent with InitialPosition { } class _SignpostSpriteComponent extends SpriteGroupComponent - with HasGameRef, ParentIsA { - _SignpostSpriteComponent({ - required SignpostState current, - }) : super( + with HasGameRef, FlameBlocListenable { + _SignpostSpriteComponent() + : super( anchor: Anchor.bottomCenter, position: Vector2(0.65, 0.45), - current: current, ); + @override + void onNewState(SignpostState state) => current = state; + @override Future onLoad() async { await super.onLoad(); - parent.bloc.stream.listen((state) => current = state); - final sprites = {}; this.sprites = sprites; for (final spriteState in SignpostState.values) { diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_a_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_a_game.dart index d81540b0..4d437f76 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_a_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_a_game.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; @@ -24,7 +25,14 @@ class DashBumperAGame extends BallGame { await super.onLoad(); camera.followVector2(Vector2.zero()); - await add(DashBumper.a()..priority = 1); + await add( + FlameBlocProvider( + create: DashBumpersCubit.new, + children: [ + DashBumper.a()..priority = 1, + ], + ), + ); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_b_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_b_game.dart index 05664a3a..64af8c59 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_b_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_b_game.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; @@ -24,7 +25,14 @@ class DashBumperBGame extends BallGame { await super.onLoad(); camera.followVector2(Vector2.zero()); - await add(DashBumper.b()..priority = 1); + await add( + FlameBlocProvider( + create: DashBumpersCubit.new, + children: [ + DashBumper.b()..priority = 1, + ], + ), + ); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_main_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_main_game.dart index 6a927eb9..773ead8d 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_main_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/dash_bumper_main_game.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; @@ -25,7 +26,12 @@ class DashBumperMainGame extends BallGame { camera.followVector2(Vector2.zero()); await add( - DashBumper.main()..priority = 1, + FlameBlocProvider( + create: DashBumpersCubit.new, + children: [ + DashBumper.main()..priority = 1, + ], + ), ); await traceAllBodies(); } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart index 020311d3..3174f6f7 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flame/input.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart'; @@ -22,18 +23,35 @@ class SignpostGame extends BallGame { - Tap to progress the sprite. '''; + late final SignpostCubit _bloc; + @override Future onLoad() async { await super.onLoad(); + _bloc = SignpostCubit(); camera.followVector2(Vector2.zero()); - await add(Signpost()); + await add( + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: _bloc, + ), + FlameBlocProvider( + create: DashBumpersCubit.new, + ), + ], + children: [ + Signpost(), + ], + ), + ); await traceAllBodies(); } @override void onTap() { super.onTap(); - firstChild()!.bloc.onProgressed(); + _bloc.onProgressed(); } } diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_bumper_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_bumper_ball_contact_behavior_test.dart index 3c8f51db..e80b743e 100644 --- a/packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_bumper_ball_contact_behavior_test.dart +++ b/packages/pinball_components/test/src/components/dash_nest_bumper/behaviors/dash_bumper_ball_contact_behavior_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: cascade_invocations import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -10,7 +11,7 @@ import 'package:pinball_components/src/components/dash_bumper/behaviors/behavior import '../../../../helpers/helpers.dart'; -class _MockDashBumperCubit extends Mock implements DashBumperCubit {} +class _MockDashBumpersCubit extends Mock implements DashBumpersCubit {} class _MockBall extends Mock implements Ball {} @@ -31,23 +32,30 @@ void main() { }); flameTester.test( - 'beginContact emits onBallContacted when contacts with a ball', + 'beginContact emits onBallContacted with the bumper ID ' + 'when contacts with a ball', (game) async { final behavior = DashBumperBallContactBehavior(); - final bloc = _MockDashBumperCubit(); + final bloc = _MockDashBumpersCubit(); + const id = DashBumperId.main; whenListen( bloc, - const Stream.empty(), - initialState: DashBumperState.active, + const Stream.empty(), + initialState: DashBumperSpriteState.active, ); - final bumper = DashBumper.test(bloc: bloc); + final bumper = DashBumper.test(id: id); await bumper.add(behavior); - await game.ensureAdd(bumper); + await game.ensureAdd( + FlameBlocProvider.value( + value: bloc, + children: [bumper], + ), + ); behavior.beginContact(_MockBall(), _MockContact()); - verify(bumper.bloc.onBallContacted).called(1); + verify(() => bloc.onBallContacted(id)).called(1); }, ); }, diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumper_cubit_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumper_cubit_test.dart deleted file mode 100644 index 1b255cd5..00000000 --- a/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumper_cubit_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:pinball_components/pinball_components.dart'; - -void main() { - group( - 'DashBumperCubit', - () { - blocTest( - 'onBallContacted emits active', - build: DashBumperCubit.new, - act: (bloc) => bloc.onBallContacted(), - expect: () => [DashBumperState.active], - ); - - blocTest( - 'onReset emits inactive', - build: DashBumperCubit.new, - act: (bloc) => bloc.onReset(), - expect: () => [DashBumperState.inactive], - ); - }, - ); -} diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumpers_cubit_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumpers_cubit_test.dart new file mode 100644 index 00000000..2de127c5 --- /dev/null +++ b/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumpers_cubit_test.dart @@ -0,0 +1,37 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group( + 'DashBumperCubit', + () { + blocTest( + 'onBallContacted emits active for contacted bumper', + build: DashBumpersCubit.new, + act: (bloc) => bloc.onBallContacted(DashBumperId.main), + expect: () => [ + const DashBumpersState( + bumperSpriteStates: { + DashBumperId.main: DashBumperSpriteState.active, + DashBumperId.a: DashBumperSpriteState.inactive, + DashBumperId.b: DashBumperSpriteState.inactive, + }, + ), + ], + ); + + blocTest( + 'onReset emits initial state', + build: DashBumpersCubit.new, + seed: () => DashBumpersState( + bumperSpriteStates: { + for (var id in DashBumperId.values) id: DashBumperSpriteState.active + }, + ), + act: (bloc) => bloc.onReset(), + expect: () => [DashBumpersState.initial()], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumpers_state_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumpers_state_test.dart new file mode 100644 index 00000000..dbce9e1e --- /dev/null +++ b/packages/pinball_components/test/src/components/dash_nest_bumper/cubit/dash_bumpers_state_test.dart @@ -0,0 +1,76 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group('DashBumpersState', () { + test('supports value equality', () { + expect( + DashBumpersState( + bumperSpriteStates: const { + DashBumperId.main: DashBumperSpriteState.active, + DashBumperId.a: DashBumperSpriteState.active, + DashBumperId.b: DashBumperSpriteState.active, + }, + ), + equals( + DashBumpersState( + bumperSpriteStates: const { + DashBumperId.main: DashBumperSpriteState.active, + DashBumperId.a: DashBumperSpriteState.active, + DashBumperId.b: DashBumperSpriteState.active, + }, + ), + ), + ); + }); + + group('constructor', () { + test('can be instantiated', () { + expect( + const DashBumpersState(bumperSpriteStates: {}), + isNotNull, + ); + }); + + test('initial has all inactive sprite states', () { + const initialState = DashBumpersState( + bumperSpriteStates: { + DashBumperId.main: DashBumperSpriteState.inactive, + DashBumperId.a: DashBumperSpriteState.inactive, + DashBumperId.b: DashBumperSpriteState.inactive, + }, + ); + + expect(DashBumpersState.initial(), equals(initialState)); + }); + }); + + group('isFullyActivated', () { + test('returns true when all bumpers have an active state', () { + const fullyActivatedState = DashBumpersState( + bumperSpriteStates: { + DashBumperId.main: DashBumperSpriteState.active, + DashBumperId.a: DashBumperSpriteState.active, + DashBumperId.b: DashBumperSpriteState.active, + }, + ); + + expect(fullyActivatedState.isFullyActivated, isTrue); + }); + + test('returns false when not all bumpers have an active state', () { + const notFullyActivatedState = DashBumpersState( + bumperSpriteStates: { + DashBumperId.main: DashBumperSpriteState.active, + DashBumperId.a: DashBumperSpriteState.active, + DashBumperId.b: DashBumperSpriteState.inactive, + }, + ); + + expect(notFullyActivatedState.isFullyActivated, isFalse); + }); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper/dash_bumper_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper/dash_bumper_test.dart index a8ad8410..034e5664 100644 --- a/packages/pinball_components/test/src/components/dash_nest_bumper/dash_bumper_test.dart +++ b/packages/pinball_components/test/src/components/dash_nest_bumper/dash_bumper_test.dart @@ -1,75 +1,65 @@ // ignore_for_file: cascade_invocations -import 'package:bloc_test/bloc_test.dart'; import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/src/components/bumping_behavior.dart'; import 'package:pinball_components/src/components/dash_bumper/behaviors/behaviors.dart'; -import '../../../helpers/helpers.dart'; - -class _MockDashBumperCubit extends Mock implements DashBumperCubit {} +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + ]); + } + + Future pump(DashBumper child) async { + await ensureAdd( + FlameBlocProvider.value( + value: DashBumpersCubit(), + children: [child], + ), + ); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('DashBumper', () { - final flameTester = FlameTester( - () => TestGame( - [ - Assets.images.dash.bumper.main.active.keyName, - Assets.images.dash.bumper.main.inactive.keyName, - Assets.images.dash.bumper.a.active.keyName, - Assets.images.dash.bumper.a.inactive.keyName, - Assets.images.dash.bumper.b.active.keyName, - Assets.images.dash.bumper.b.inactive.keyName, - ], - ), - ); + final flameTester = FlameTester(_TestGame.new); - flameTester.test('"main" loads correctly', (game) async { + group('DashBumper', () { + flameTester.test('"main" can be added', (game) async { final bumper = DashBumper.main(); - await game.ensureAdd(bumper); - expect(game.contains(bumper), isTrue); + await game.pump(bumper); + expect(game.descendants().contains(bumper), isTrue); }); - flameTester.test('"a" loads correctly', (game) async { + flameTester.test('"a" can be added', (game) async { final bumper = DashBumper.a(); - await game.ensureAdd(bumper); - - expect(game.contains(bumper), isTrue); + await game.pump(bumper); + expect(game.descendants().contains(bumper), isTrue); }); - flameTester.test('"b" loads correctly', (game) async { + flameTester.test('"b" can be added', (game) async { final bumper = DashBumper.b(); - await game.ensureAdd(bumper); - expect(game.contains(bumper), isTrue); + await game.pump(bumper); + expect(game.descendants().contains(bumper), isTrue); }); - // ignore: public_member_api_docs - flameTester.test('closes bloc when removed', (game) async { - final bloc = _MockDashBumperCubit(); - whenListen( - bloc, - const Stream.empty(), - initialState: DashBumperState.inactive, - ); - when(bloc.close).thenAnswer((_) async {}); - final bumper = DashBumper.test(bloc: bloc); - - await game.ensureAdd(bumper); - game.remove(bumper); - await game.ready(); - - verify(bloc.close).called(1); - }); - - flameTester.test('adds a bumperBallContactBehavior', (game) async { + flameTester.test('adds a DashBumperBallContactBehavior', (game) async { final bumper = DashBumper.a(); - await game.ensureAdd(bumper); + await game.pump(bumper); expect( bumper.children.whereType().single, isNotNull, @@ -82,13 +72,13 @@ void main() { final bumper = DashBumper.main( children: [component], ); - await game.ensureAdd(bumper); + await game.pump(bumper); expect(bumper.children, contains(component)); }); flameTester.test('a BumpingBehavior', (game) async { final bumper = DashBumper.main(); - await game.ensureAdd(bumper); + await game.pump(bumper); expect( bumper.children.whereType().single, isNotNull, @@ -102,13 +92,13 @@ void main() { final bumper = DashBumper.a( children: [component], ); - await game.ensureAdd(bumper); + await game.pump(bumper); expect(bumper.children, contains(component)); }); flameTester.test('a BumpingBehavior', (game) async { final bumper = DashBumper.a(); - await game.ensureAdd(bumper); + await game.pump(bumper); expect( bumper.children.whereType().single, isNotNull, @@ -122,18 +112,64 @@ void main() { final bumper = DashBumper.b( children: [component], ); - await game.ensureAdd(bumper); + await game.pump(bumper); expect(bumper.children, contains(component)); }); flameTester.test('a BumpingBehavior', (game) async { final bumper = DashBumper.b(); - await game.ensureAdd(bumper); + await game.pump(bumper); expect( bumper.children.whereType().single, isNotNull, ); }); }); + + group('SpriteGroupComponent', () { + const mainBumperActivatedState = DashBumpersState( + bumperSpriteStates: { + DashBumperId.main: DashBumperSpriteState.active, + DashBumperId.a: DashBumperSpriteState.inactive, + DashBumperId.b: DashBumperSpriteState.inactive, + }, + ); + + group('listenWhen', () { + flameTester.test( + 'is true when the sprite state for the given ID has changed', + (game) async { + final bumper = DashBumper.main(); + await game.pump(bumper); + + final listenWhen = + bumper.firstChild()!.listenWhen( + DashBumpersState.initial(), + mainBumperActivatedState, + ); + + expect(listenWhen, isTrue); + }, + ); + + flameTester.test( + 'onNewState updates the current sprite', + (game) async { + final bumper = DashBumper.main(); + await game.pump(bumper); + + final spriteGroupComponent = + bumper.firstChild()!; + final originalSprite = spriteGroupComponent.current; + + spriteGroupComponent.onNewState(mainBumperActivatedState); + await game.ready(); + + final newSprite = spriteGroupComponent.current; + expect(newSprite, isNot(equals(originalSprite))); + }, + ); + }); + }); }); } diff --git a/packages/pinball_components/test/src/components/google_letter_test.dart b/packages/pinball_components/test/src/components/google_letter_test.dart index 7deea645..116c4d97 100644 --- a/packages/pinball_components/test/src/components/google_letter_test.dart +++ b/packages/pinball_components/test/src/components/google_letter_test.dart @@ -151,7 +151,7 @@ void main() { await game.ready(); final newSprite = googleLetter.current; - expect(newSprite != originalSprite, isTrue); + expect(newSprite, isNot(equals(originalSprite))); }, ); }); diff --git a/packages/pinball_components/test/src/components/signpost/signpost_test.dart b/packages/pinball_components/test/src/components/signpost/signpost_test.dart index 6aecd0bd..0874a8bd 100644 --- a/packages/pinball_components/test/src/components/signpost/signpost_test.dart +++ b/packages/pinball_components/test/src/components/signpost/signpost_test.dart @@ -1,35 +1,72 @@ // ignore_for_file: cascade_invocations +import 'dart:async'; + import 'package:bloc_test/bloc_test.dart'; import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball_components/pinball_components.dart'; -import '../../../helpers/helpers.dart'; +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]); + } + + Future pump( + Signpost child, { + SignpostCubit? signpostBloc, + DashBumpersCubit? dashBumpersBloc, + }) async { + await onLoad(); + await ensureAdd( + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: signpostBloc ?? SignpostCubit(), + ), + FlameBlocProvider.value( + value: dashBumpersBloc ?? DashBumpersCubit(), + ), + ], + children: [child], + ), + ); + } +} class _MockSignpostCubit extends Mock implements SignpostCubit {} +class _MockDashBumpersCubit extends Mock implements DashBumpersCubit {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.signpost.inactive.keyName, - Assets.images.signpost.active1.keyName, - Assets.images.signpost.active2.keyName, - Assets.images.signpost.active3.keyName, - ]; - final flameTester = FlameTester(() => TestGame(assets)); + + final flameTester = FlameTester(_TestGame.new); group('Signpost', () { const goldenPath = '../golden/signpost/'; + test('can be instantiated', () { + expect(Signpost(), isA()); + }); + flameTester.test( - 'loads correctly', + 'can be added', (game) async { final signpost = Signpost(); - await game.ensureAdd(signpost); - expect(game.contains(signpost), isTrue); + await game.pump(signpost); + expect(game.descendants().contains(signpost), isTrue); }, ); @@ -37,13 +74,12 @@ void main() { flameTester.testGameWidget( 'inactive sprite', setUp: (game, tester) async { - await game.images.loadAll(assets); final signpost = Signpost(); - await game.ensureAdd(signpost); + await game.pump(signpost); await tester.pump(); expect( - signpost.bloc.state, + signpost.firstChild()!.current, equals(SignpostState.inactive), ); @@ -51,7 +87,7 @@ void main() { }, verify: (game, tester) async { await expectLater( - find.byGame(), + find.byGame<_TestGame>(), matchesGoldenFile('${goldenPath}inactive.png'), ); }, @@ -60,14 +96,14 @@ void main() { flameTester.testGameWidget( 'active1 sprite', setUp: (game, tester) async { - await game.images.loadAll(assets); final signpost = Signpost(); - await game.ensureAdd(signpost); - signpost.bloc.onProgressed(); + final bloc = SignpostCubit(); + await game.pump(signpost, signpostBloc: bloc); + bloc.onProgressed(); await tester.pump(); expect( - signpost.bloc.state, + signpost.firstChild()!.current, equals(SignpostState.active1), ); @@ -75,7 +111,7 @@ void main() { }, verify: (game, tester) async { await expectLater( - find.byGame(), + find.byGame<_TestGame>(), matchesGoldenFile('${goldenPath}active1.png'), ); }, @@ -84,16 +120,16 @@ void main() { flameTester.testGameWidget( 'active2 sprite', setUp: (game, tester) async { - await game.images.loadAll(assets); final signpost = Signpost(); - await game.ensureAdd(signpost); - signpost.bloc + final bloc = SignpostCubit(); + await game.pump(signpost, signpostBloc: bloc); + bloc ..onProgressed() ..onProgressed(); await tester.pump(); expect( - signpost.bloc.state, + signpost.firstChild()!.current, equals(SignpostState.active2), ); @@ -101,7 +137,7 @@ void main() { }, verify: (game, tester) async { await expectLater( - find.byGame(), + find.byGame<_TestGame>(), matchesGoldenFile('${goldenPath}active2.png'), ); }, @@ -110,18 +146,17 @@ void main() { flameTester.testGameWidget( 'active3 sprite', setUp: (game, tester) async { - await game.images.loadAll(assets); final signpost = Signpost(); - await game.ensureAdd(signpost); - - signpost.bloc + final bloc = SignpostCubit(); + await game.pump(signpost, signpostBloc: bloc); + bloc ..onProgressed() ..onProgressed() ..onProgressed(); await tester.pump(); expect( - signpost.bloc.state, + signpost.firstChild()!.current, equals(SignpostState.active3), ); @@ -129,37 +164,83 @@ void main() { }, verify: (game, tester) async { await expectLater( - find.byGame(), + find.byGame<_TestGame>(), matchesGoldenFile('${goldenPath}active3.png'), ); }, ); }); + flameTester.testGameWidget( + 'listenWhen is true when all dash bumpers are activated', + setUp: (game, tester) async { + final activatedBumpersState = DashBumpersState( + bumperSpriteStates: { + for (var id in DashBumperId.values) id: DashBumperSpriteState.active + }, + ); + final signpost = Signpost(); + final dashBumpersBloc = _MockDashBumpersCubit(); + whenListen( + dashBumpersBloc, + const Stream.empty(), + initialState: DashBumpersState.initial(), + ); + await game.pump(signpost, dashBumpersBloc: dashBumpersBloc); + + final signpostListener = game + .descendants() + .whereType>() + .single; + final listenWhen = signpostListener.listenWhen( + DashBumpersState.initial(), + activatedBumpersState, + ); + + expect(listenWhen, isTrue); + }, + ); + + flameTester.test( + 'onNewState calls onProgressed and onReset', + (game) async { + final signpost = Signpost(); + final signpostBloc = _MockSignpostCubit(); + whenListen( + signpostBloc, + const Stream.empty(), + initialState: SignpostState.inactive, + ); + final dashBumpersBloc = _MockDashBumpersCubit(); + whenListen( + dashBumpersBloc, + const Stream.empty(), + initialState: DashBumpersState.initial(), + ); + await game.pump( + signpost, + signpostBloc: signpostBloc, + dashBumpersBloc: dashBumpersBloc, + ); + + game + .descendants() + .whereType>() + .single + .onNewState(DashBumpersState.initial()); + + verify(signpostBloc.onProgressed).called(1); + verify(dashBumpersBloc.onReset).called(1); + }, + ); + flameTester.test('adds new children', (game) async { final component = Component(); final signpost = Signpost( children: [component], ); - await game.ensureAdd(signpost); + await game.pump(signpost); expect(signpost.children, contains(component)); }); - - flameTester.test('closes bloc when removed', (game) async { - final bloc = _MockSignpostCubit(); - whenListen( - bloc, - const Stream.empty(), - initialState: SignpostState.inactive, - ); - when(bloc.close).thenAnswer((_) async {}); - final component = Signpost.test(bloc: bloc); - - await game.ensureAdd(component); - game.remove(component); - await game.ready(); - - verify(bloc.close).called(1); - }); }); } diff --git a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart index fdef2f39..8c59a62c 100644 --- a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart +++ b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart @@ -1,5 +1,8 @@ // ignore_for_file: cascade_invocations +import 'dart:async'; + +import 'package:bloc_test/bloc_test.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame_test/flame_test.dart'; @@ -22,10 +25,22 @@ class _TestGame extends Forge2DGame { Future pump( FlutterForest child, { required GameBloc gameBloc, + required SignpostCubit signpostBloc, + DashBumpersCubit? dashBumpersBloc, }) async { await ensureAdd( - FlameBlocProvider.value( - value: gameBloc, + FlameMultiBlocProvider( + providers: [ + FlameBlocProvider.value( + value: gameBloc, + ), + FlameBlocProvider.value( + value: signpostBloc, + ), + FlameBlocProvider.value( + value: dashBumpersBloc ?? DashBumpersCubit(), + ), + ], children: [ ZCanvasComponent( children: [child], @@ -38,6 +53,10 @@ class _TestGame extends Forge2DGame { class _MockGameBloc extends Mock implements GameBloc {} +class _MockSignpostCubit extends Mock implements SignpostCubit {} + +class _MockDashBumpersCubit extends Mock implements DashBumpersCubit {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -50,31 +69,33 @@ void main() { final flameTester = FlameTester(_TestGame.new); - void _contactedBumper(DashBumper bumper) => bumper.bloc.onBallContacted(); + test('can be instantiated', () { + expect(FlutterForestBonusBehavior(), isA()); + }); flameTester.testGameWidget( 'adds GameBonus.dashNest to the game ' - 'when bumpers are activated three times', + 'when signpost becomes fully activated', setUp: (game, tester) async { - await game.onLoad(); final behavior = FlutterForestBonusBehavior(); final parent = FlutterForest.test(); - final bumpers = [ - DashBumper.test(bloc: DashBumperCubit()), - DashBumper.test(bloc: DashBumperCubit()), - DashBumper.test(bloc: DashBumperCubit()), - ]; - final signpost = Signpost.test(bloc: SignpostCubit()); - await game.pump(parent, gameBloc: gameBloc); - await parent.ensureAddAll([...bumpers, signpost]); + final signpostBloc = _MockSignpostCubit(); + final streamController = StreamController(); + + whenListen( + signpostBloc, + streamController.stream, + initialState: SignpostState.inactive, + ); + + await game.pump( + parent, + gameBloc: gameBloc, + signpostBloc: signpostBloc, + ); await parent.ensureAdd(behavior); - expect(game.descendants().whereType(), equals(bumpers)); - bumpers.forEach(_contactedBumper); - await tester.pump(); - bumpers.forEach(_contactedBumper); - await tester.pump(); - bumpers.forEach(_contactedBumper); + streamController.add(SignpostState.active3); await tester.pump(); verify( @@ -84,76 +105,66 @@ void main() { ); flameTester.testGameWidget( - 'adds BonusBallSpawningBehavior to the game ' - 'when bumpers are activated three times', + 'calls onProgressed and onReset ' + 'when signpost becomes fully activated', setUp: (game, tester) async { - await game.onLoad(); final behavior = FlutterForestBonusBehavior(); final parent = FlutterForest.test(); - final bumpers = [ - DashBumper.test(bloc: DashBumperCubit()), - DashBumper.test(bloc: DashBumperCubit()), - DashBumper.test(bloc: DashBumperCubit()), - ]; - final signpost = Signpost.test(bloc: SignpostCubit()); - await game.pump(parent, gameBloc: gameBloc); - await parent.ensureAddAll([...bumpers, signpost]); + final dashBumpersBloc = _MockDashBumpersCubit(); + final signpostBloc = _MockSignpostCubit(); + final streamController = StreamController(); + + whenListen( + signpostBloc, + streamController.stream, + initialState: SignpostState.inactive, + ); + + await game.pump( + parent, + gameBloc: gameBloc, + signpostBloc: signpostBloc, + dashBumpersBloc: dashBumpersBloc, + ); await parent.ensureAdd(behavior); - expect(game.descendants().whereType(), equals(bumpers)); - bumpers.forEach(_contactedBumper); - await tester.pump(); - bumpers.forEach(_contactedBumper); - await tester.pump(); - bumpers.forEach(_contactedBumper); + streamController.add(SignpostState.active3); await tester.pump(); - await game.ready(); - expect( - game.descendants().whereType().length, - equals(1), - ); + verify(signpostBloc.onProgressed).called(1); + verify(dashBumpersBloc.onReset).called(1); }, ); flameTester.testGameWidget( - 'progress the signpost ' - 'when bumpers are activated', + 'adds BonusBallSpawningBehavior to the game ' + 'when signpost becomes fully activated', setUp: (game, tester) async { - await game.onLoad(); final behavior = FlutterForestBonusBehavior(); final parent = FlutterForest.test(); - final bumpers = [ - DashBumper.test(bloc: DashBumperCubit()), - DashBumper.test(bloc: DashBumperCubit()), - DashBumper.test(bloc: DashBumperCubit()), - ]; - final signpost = Signpost.test(bloc: SignpostCubit()); - await game.pump(parent, gameBloc: gameBloc); - await parent.ensureAddAll([...bumpers, signpost]); - await parent.ensureAdd(behavior); - - expect(game.descendants().whereType(), equals(bumpers)); + final signpostBloc = _MockSignpostCubit(); + final streamController = StreamController(); - bumpers.forEach(_contactedBumper); - await tester.pump(); - expect( - signpost.bloc.state, - equals(SignpostState.active1), + whenListen( + signpostBloc, + streamController.stream, + initialState: SignpostState.inactive, ); - bumpers.forEach(_contactedBumper); - await tester.pump(); - expect( - signpost.bloc.state, - equals(SignpostState.active2), + await game.pump( + parent, + gameBloc: gameBloc, + signpostBloc: signpostBloc, ); + await parent.ensureAdd(behavior); - bumpers.forEach(_contactedBumper); + streamController.add(SignpostState.active3); await tester.pump(); + await game.ready(); + expect( - signpost.bloc.state, - equals(SignpostState.inactive), + game.descendants().whereType().length, + equals(1), ); }, ); diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index 4f7f7222..05d70ad0 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -37,6 +37,8 @@ class _TestGame extends Forge2DGame with HasTappables { Iterable children, { PinballAudioPlayer? pinballAudioPlayer, GoogleWordCubit? googleWordBloc, + DashBumpersCubit? dashBumpersBloc, + SignpostCubit? signpostBloc, }) async { return ensureAdd( FlameMultiBlocProvider( @@ -50,6 +52,12 @@ class _TestGame extends Forge2DGame with HasTappables { FlameBlocProvider.value( value: googleWordBloc ?? GoogleWordCubit(), ), + FlameBlocProvider.value( + value: dashBumpersBloc ?? DashBumpersCubit(), + ), + FlameBlocProvider.value( + value: signpostBloc ?? SignpostCubit(), + ), ], children: [ MultiFlameProvider( @@ -83,7 +91,7 @@ class _MockPlungerCubit extends Mock implements PlungerCubit {} class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {} -class _MockDashBumperCubit extends Mock implements DashBumperCubit {} +class _MockDashBumpersCubit extends Mock implements DashBumpersCubit {} class _MockSignpostCubit extends Mock implements SignpostCubit {} @@ -334,12 +342,7 @@ void main() { final audioPlayer = _MockPinballAudioPlayer(); final component = GameBlocStatusListener(); await game.pump( - [ - component, - Signpost.test( - bloc: _MockSignpostCubit(), - ), - ], + [component], pinballAudioPlayer: audioPlayer, ); @@ -360,12 +363,7 @@ void main() { final googleWordBloc = _MockGoogleWordCubit(); final component = GameBlocStatusListener(); await game.pump( - [ - component, - Signpost.test( - bloc: _MockSignpostCubit(), - ), - ], + [component], googleWordBloc: googleWordBloc, ); @@ -377,27 +375,19 @@ void main() { ); flameTester.test( - 'resets the DashBumperCubits', + 'resets the DashBumpersCubit', (game) async { - final dashBumper1Bloc = _MockDashBumperCubit(); - final dashBumper2Bloc = _MockDashBumperCubit(); - final dashBumper1 = DashBumper.test(bloc: dashBumper1Bloc); - final dashBumper2 = DashBumper.test(bloc: dashBumper2Bloc); + final dashBumpersBloc = _MockDashBumpersCubit(); final component = GameBlocStatusListener(); - await game.pump([ - component, - dashBumper1, - dashBumper2, - Signpost.test( - bloc: _MockSignpostCubit(), - ), - ]); + await game.pump( + [component], + dashBumpersBloc: dashBumpersBloc, + ); expect(state.status, equals(GameStatus.playing)); component.onNewState(state); - verify(dashBumper1Bloc.onReset).called(1); - verify(dashBumper2Bloc.onReset).called(1); + verify(dashBumpersBloc.onReset).called(1); }, ); @@ -405,9 +395,8 @@ void main() { 'resets the SignpostCubit', (game) async { final signpostBloc = _MockSignpostCubit(); - final signpost = Signpost.test(bloc: signpostBloc); final component = GameBlocStatusListener(); - await game.pump([component, signpost]); + await game.pump([component], signpostBloc: signpostBloc); expect(state.status, equals(GameStatus.playing)); component.onNewState(state); @@ -429,14 +418,7 @@ void main() { ); final flipper = Flipper.test(side: BoardSide.left); - await game.pump([ - component, - backbox, - flipper, - Signpost.test( - bloc: _MockSignpostCubit(), - ), - ]); + await game.pump([component, backbox, flipper]); await flipper.ensureAdd( FlameBlocProvider( create: _MockFlipperCubit.new, @@ -468,16 +450,7 @@ void main() { entries: const [], ); final plunger = Plunger.test(); - await game.pump( - [ - component, - backbox, - plunger, - Signpost.test( - bloc: _MockSignpostCubit(), - ), - ], - ); + await game.pump([component, backbox, plunger]); await plunger.ensureAdd( FlameBlocProvider( create: _MockPlungerCubit.new, @@ -511,16 +484,7 @@ void main() { entries: const [], ); final plunger = Plunger.test(); - await game.pump( - [ - component, - backbox, - plunger, - Signpost.test( - bloc: _MockSignpostCubit(), - ), - ], - ); + await game.pump([component, backbox, plunger]); await plunger.ensureAdd( FlameBlocProvider( create: _MockPlungerCubit.new, @@ -551,16 +515,7 @@ void main() { entries: const [], ); final plunger = Plunger.test(); - await game.pump( - [ - component, - backbox, - plunger, - Signpost.test( - bloc: _MockSignpostCubit(), - ), - ], - ); + await game.pump([component, backbox, plunger]); await plunger.ensureAdd( FlameBlocProvider( create: _MockPlungerCubit.new, diff --git a/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart b/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart index 17726156..9d180cc9 100644 --- a/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart +++ b/test/game/components/google_gallery/behaviors/google_word_bonus_behavior_test.dart @@ -73,6 +73,10 @@ void main() { final flameTester = FlameTester(_TestGame.new); + test('can be instantiated', () { + expect(GoogleWordBonusBehavior(), isA()); + }); + flameTester.testGameWidget( 'adds GameBonus.googleWord to the game when all letters ' 'in google word are activated and calls onReset',