refactor: migrate flutter forest to new bloc api (#481)

Co-authored-by: Tom Arra <tarra3@gmail.com>
main
Allison Ryan 2 years ago committed by GitHub
parent f04808dc36
commit b21e7f9e5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,37 +10,21 @@ import 'package:pinball_flame/pinball_flame.dart';
/// When all [DashBumper]s are hit at least once three times, the [Signpost] /// When all [DashBumper]s are hit at least once three times, the [Signpost]
/// progresses. When the [Signpost] fully progresses, the [GameBonus.dashNest] /// progresses. When the [Signpost] fully progresses, the [GameBonus.dashNest]
/// is awarded, and the [DashBumper.main] releases a new [Ball]. /// is awarded, and the [DashBumper.main] releases a new [Ball].
class FlutterForestBonusBehavior extends Component class FlutterForestBonusBehavior extends Component {
with
ParentIsA<FlutterForest>,
HasGameRef,
FlameBlocReader<GameBloc, GameState> {
@override @override
void onMount() { Future<void> onLoad() async {
super.onMount(); await super.onLoad();
await add(
final bumpers = parent.children.whereType<DashBumper>(); FlameBlocListener<SignpostCubit, SignpostState>(
final signpost = parent.firstChild<Signpost>()!; listenWhen: (_, state) => state == SignpostState.active3,
onNewState: (_) {
for (final bumper in bumpers) { readBloc<GameBloc, GameState>()
bumper.bloc.stream.listen((state) { .add(const BonusActivated(GameBonus.dashNest));
final activatedAllBumpers = bumpers.every( readBloc<SignpostCubit, SignpostState>().onProgressed();
(bumper) => bumper.bloc.state == DashBumperState.active, readBloc<DashBumpersCubit, DashBumpersState>().onReset();
); add(BonusBallSpawningBehavior());
},
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();
}
}
});
}
} }
} }

@ -1,6 +1,7 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart'; import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart';
@ -16,36 +17,48 @@ class FlutterForest extends Component with ZIndex {
FlutterForest() FlutterForest()
: super( : super(
children: [ children: [
Signpost( FlameMultiBlocProvider(
children: [ providers: [
ScoringContactBehavior(points: Points.fiveThousand), FlameBlocProvider<SignpostCubit, SignpostState>(
BumperNoiseBehavior(), create: SignpostCubit.new,
], ),
)..initialPosition = Vector2(7.95, -58.35), FlameBlocProvider<DashBumpersCubit, DashBumpersState>(
DashBumper.main( create: DashBumpersCubit.new,
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: [ 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; zIndex = ZIndexes.flutterForest;

@ -62,9 +62,16 @@ class GameBlocStatusListener extends Component
.onReset(); .onReset();
gameRef gameRef
.descendants() .descendants()
.whereType<DashBumper>() .whereType<FlameBlocProvider<DashBumpersCubit, DashBumpersState>>()
.forEach((bumper) => bumper.bloc.onReset()); .single
gameRef.descendants().whereType<Signpost>().single.bloc.onReset(); .bloc
.onReset();
gameRef
.descendants()
.whereType<FlameBlocProvider<SignpostCubit, SignpostState>>()
.single
.bloc
.onReset();
} }
void _addPlungerBehaviors(Plunger plunger) => plunger void _addPlungerBehaviors(Plunger plunger) => plunger

@ -14,7 +14,7 @@ class GoogleWordBonusBehavior extends Component {
FlameBlocListener<GoogleWordCubit, GoogleWordState>( FlameBlocListener<GoogleWordCubit, GoogleWordState>(
listenWhen: (_, state) => state.letterSpriteStates.values listenWhen: (_, state) => state.letterSpriteStates.values
.every((element) => element == GoogleLetterSpriteState.lit), .every((element) => element == GoogleLetterSpriteState.lit),
onNewState: (state) { onNewState: (_) {
readBloc<GameBloc, GameState>() readBloc<GameBloc, GameState>()
.add(const BonusActivated(GameBonus.googleWord)); .add(const BonusActivated(GameBonus.googleWord));
readBloc<GoogleWordCubit, GoogleWordState>().onReset(); readBloc<GoogleWordCubit, GoogleWordState>().onReset();

@ -7,6 +7,6 @@ class DashBumperBallContactBehavior extends ContactBehavior<DashBumper> {
void beginContact(Object other, Contact contact) { void beginContact(Object other, Contact contact) {
super.beginContact(other, contact); super.beginContact(other, contact);
if (other is! Ball) return; if (other is! Ball) return;
parent.bloc.onBallContacted(); readBloc<DashBumpersCubit, DashBumpersState>().onBallContacted(parent.id);
} }
} }

@ -1,17 +0,0 @@
import 'package:bloc/bloc.dart';
part 'dash_bumper_state.dart';
class DashBumperCubit extends Cubit<DashBumperState> {
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);
}
}

@ -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,
}

@ -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<DashBumpersState> {
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());
}
}

@ -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<DashBumperId, DashBumperSpriteState> bumperSpriteStates;
bool get isFullyActivated => bumperSpriteStates.values
.every((spriteState) => spriteState == DashBumperSpriteState.active);
@override
List<Object> get props => [...bumperSpriteStates.values];
}

@ -1,6 +1,7 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.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_components/src/components/dash_bumper/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.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} /// {@template dash_bumper}
/// Bumper for the flutter forest. /// Bumper for the flutter forest.
@ -16,23 +28,23 @@ export 'cubit/dash_bumper_cubit.dart';
class DashBumper extends BodyComponent with InitialPosition { class DashBumper extends BodyComponent with InitialPosition {
/// {@macro dash_bumper} /// {@macro dash_bumper}
DashBumper._({ DashBumper._({
required this.id,
required double majorRadius, required double majorRadius,
required double minorRadius, required double minorRadius,
required String activeAssetPath, required String activeAssetPath,
required String inactiveAssetPath, required String inactiveAssetPath,
required Vector2 spritePosition, required Vector2 spritePosition,
Iterable<Component>? children, Iterable<Component>? children,
required this.bloc,
}) : _majorRadius = majorRadius, }) : _majorRadius = majorRadius,
_minorRadius = minorRadius, _minorRadius = minorRadius,
super( super(
renderBody: false, renderBody: false,
children: [ children: [
_DashBumperSpriteGroupComponent( DashBumperSpriteGroupComponent(
id: id,
activeAssetPath: activeAssetPath, activeAssetPath: activeAssetPath,
inactiveAssetPath: inactiveAssetPath, inactiveAssetPath: inactiveAssetPath,
position: spritePosition, position: spritePosition,
current: bloc.state,
), ),
DashBumperBallContactBehavior(), DashBumperBallContactBehavior(),
...?children, ...?children,
@ -46,12 +58,12 @@ class DashBumper extends BodyComponent with InitialPosition {
DashBumper.main({ DashBumper.main({
Iterable<Component>? children, Iterable<Component>? children,
}) : this._( }) : this._(
id: DashBumperId.main,
majorRadius: 5.1, majorRadius: 5.1,
minorRadius: 3.75, minorRadius: 3.75,
activeAssetPath: Assets.images.dash.bumper.main.active.keyName, activeAssetPath: Assets.images.dash.bumper.main.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName,
spritePosition: Vector2(0, -0.3), spritePosition: Vector2(0, -0.3),
bloc: DashBumperCubit(),
children: [ children: [
...?children, ...?children,
BumpingBehavior(strength: 20), BumpingBehavior(strength: 20),
@ -65,12 +77,12 @@ class DashBumper extends BodyComponent with InitialPosition {
DashBumper.a({ DashBumper.a({
Iterable<Component>? children, Iterable<Component>? children,
}) : this._( }) : this._(
id: DashBumperId.a,
majorRadius: 3, majorRadius: 3,
minorRadius: 2.2, minorRadius: 2.2,
activeAssetPath: Assets.images.dash.bumper.a.active.keyName, activeAssetPath: Assets.images.dash.bumper.a.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName,
spritePosition: Vector2(0.3, -1.3), spritePosition: Vector2(0.3, -1.3),
bloc: DashBumperCubit(),
children: [ children: [
...?children, ...?children,
BumpingBehavior(strength: 20), BumpingBehavior(strength: 20),
@ -84,12 +96,12 @@ class DashBumper extends BodyComponent with InitialPosition {
DashBumper.b({ DashBumper.b({
Iterable<Component>? children, Iterable<Component>? children,
}) : this._( }) : this._(
id: DashBumperId.b,
majorRadius: 3.1, majorRadius: 3.1,
minorRadius: 2.2, minorRadius: 2.2,
activeAssetPath: Assets.images.dash.bumper.b.active.keyName, activeAssetPath: Assets.images.dash.bumper.b.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName,
spritePosition: Vector2(0.4, -1.2), spritePosition: Vector2(0.4, -1.2),
bloc: DashBumperCubit(),
children: [ children: [
...?children, ...?children,
BumpingBehavior(strength: 20), BumpingBehavior(strength: 20),
@ -100,22 +112,14 @@ class DashBumper extends BodyComponent with InitialPosition {
/// ///
/// This can be used for testing [DashBumper]'s behaviors in isolation. /// This can be used for testing [DashBumper]'s behaviors in isolation.
@visibleForTesting @visibleForTesting
DashBumper.test({required this.bloc}) DashBumper.test({required this.id})
: _majorRadius = 3, : _majorRadius = 3,
_minorRadius = 2.5; _minorRadius = 2.5;
final DashBumperId id;
final double _majorRadius; final double _majorRadius;
final double _minorRadius; final double _minorRadius;
// ignore: public_member_api_docs
final DashBumperCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
@override @override
Body createBody() { Body createBody() {
final shape = EllipseShape( final shape = EllipseShape(
@ -131,37 +135,49 @@ class DashBumper extends BodyComponent with InitialPosition {
} }
} }
class _DashBumperSpriteGroupComponent @visibleForTesting
extends SpriteGroupComponent<DashBumperState> class DashBumperSpriteGroupComponent
with HasGameRef, ParentIsA<DashBumper> { extends SpriteGroupComponent<DashBumperSpriteState>
_DashBumperSpriteGroupComponent({ with HasGameRef, FlameBlocListenable<DashBumpersCubit, DashBumpersState> {
DashBumperSpriteGroupComponent({
required DashBumperId id,
required String activeAssetPath, required String activeAssetPath,
required String inactiveAssetPath, required String inactiveAssetPath,
required Vector2 position, required Vector2 position,
required DashBumperState current, }) : _id = id,
}) : _activeAssetPath = activeAssetPath, _activeAssetPath = activeAssetPath,
_inactiveAssetPath = inactiveAssetPath, _inactiveAssetPath = inactiveAssetPath,
super( super(
anchor: Anchor.center, anchor: Anchor.center,
position: position, position: position,
current: current,
); );
final DashBumperId _id;
final String _activeAssetPath; final String _activeAssetPath;
final String _inactiveAssetPath; 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 @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
parent.bloc.stream.listen((state) => current = state);
final sprites = { final sprites = {
DashBumperState.active: DashBumperSpriteState.active:
Sprite(gameRef.images.fromCache(_activeAssetPath)), Sprite(gameRef.images.fromCache(_activeAssetPath)),
DashBumperState.inactive: DashBumperSpriteState.inactive:
Sprite(gameRef.images.fromCache(_inactiveAssetPath)), Sprite(gameRef.images.fromCache(_inactiveAssetPath)),
}; };
this.sprites = sprites; this.sprites = sprites;
current = DashBumperSpriteState.inactive;
size = sprites[current]!.originalSize / 10; size = sprites[current]!.originalSize / 10;
} }
} }

@ -1,6 +1,6 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
@ -13,40 +13,29 @@ export 'cubit/signpost_cubit.dart';
/// {@endtemplate} /// {@endtemplate}
class Signpost extends BodyComponent with InitialPosition { class Signpost extends BodyComponent with InitialPosition {
/// {@macro signpost} /// {@macro signpost}
Signpost({
Iterable<Component>? children,
}) : this._(
children: children,
bloc: SignpostCubit(),
);
Signpost._({ Signpost({
Iterable<Component>? children, Iterable<Component>? children,
required this.bloc,
}) : super( }) : super(
renderBody: false, renderBody: false,
children: [ children: [
_SignpostSpriteComponent( _SignpostSpriteComponent(),
current: bloc.state,
),
...?children, ...?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 @override
void onRemove() { Future<void> onLoad() async {
bloc.close(); await super.onLoad();
super.onRemove(); await add(
FlameBlocListener<DashBumpersCubit, DashBumpersState>(
listenWhen: (_, state) => state.isFullyActivated,
onNewState: (_) {
readBloc<SignpostCubit, SignpostState>().onProgressed();
readBloc<DashBumpersCubit, DashBumpersState>().onReset();
},
),
);
} }
@override @override
@ -61,20 +50,19 @@ class Signpost extends BodyComponent with InitialPosition {
} }
class _SignpostSpriteComponent extends SpriteGroupComponent<SignpostState> class _SignpostSpriteComponent extends SpriteGroupComponent<SignpostState>
with HasGameRef, ParentIsA<Signpost> { with HasGameRef, FlameBlocListenable<SignpostCubit, SignpostState> {
_SignpostSpriteComponent({ _SignpostSpriteComponent()
required SignpostState current, : super(
}) : super(
anchor: Anchor.bottomCenter, anchor: Anchor.bottomCenter,
position: Vector2(0.65, 0.45), position: Vector2(0.65, 0.45),
current: current,
); );
@override
void onNewState(SignpostState state) => current = state;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
parent.bloc.stream.listen((state) => current = state);
final sprites = <SignpostState, Sprite>{}; final sprites = <SignpostState, Sprite>{};
this.sprites = sprites; this.sprites = sprites;
for (final spriteState in SignpostState.values) { for (final spriteState in SignpostState.values) {

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart';
@ -24,7 +25,14 @@ class DashBumperAGame extends BallGame {
await super.onLoad(); await super.onLoad();
camera.followVector2(Vector2.zero()); camera.followVector2(Vector2.zero());
await add(DashBumper.a()..priority = 1); await add(
FlameBlocProvider<DashBumpersCubit, DashBumpersState>(
create: DashBumpersCubit.new,
children: [
DashBumper.a()..priority = 1,
],
),
);
await traceAllBodies(); await traceAllBodies();
} }
} }

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart';
@ -24,7 +25,14 @@ class DashBumperBGame extends BallGame {
await super.onLoad(); await super.onLoad();
camera.followVector2(Vector2.zero()); camera.followVector2(Vector2.zero());
await add(DashBumper.b()..priority = 1); await add(
FlameBlocProvider<DashBumpersCubit, DashBumpersState>(
create: DashBumpersCubit.new,
children: [
DashBumper.b()..priority = 1,
],
),
);
await traceAllBodies(); await traceAllBodies();
} }
} }

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart';
@ -25,7 +26,12 @@ class DashBumperMainGame extends BallGame {
camera.followVector2(Vector2.zero()); camera.followVector2(Vector2.zero());
await add( await add(
DashBumper.main()..priority = 1, FlameBlocProvider<DashBumpersCubit, DashBumpersState>(
create: DashBumpersCubit.new,
children: [
DashBumper.main()..priority = 1,
],
),
); );
await traceAllBodies(); await traceAllBodies();
} }

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart';
@ -22,18 +23,35 @@ class SignpostGame extends BallGame {
- Tap to progress the sprite. - Tap to progress the sprite.
'''; ''';
late final SignpostCubit _bloc;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
_bloc = SignpostCubit();
camera.followVector2(Vector2.zero()); camera.followVector2(Vector2.zero());
await add(Signpost()); await add(
FlameMultiBlocProvider(
providers: [
FlameBlocProvider<SignpostCubit, SignpostState>.value(
value: _bloc,
),
FlameBlocProvider<DashBumpersCubit, DashBumpersState>(
create: DashBumpersCubit.new,
),
],
children: [
Signpost(),
],
),
);
await traceAllBodies(); await traceAllBodies();
} }
@override @override
void onTap() { void onTap() {
super.onTap(); super.onTap();
firstChild<Signpost>()!.bloc.onProgressed(); _bloc.onProgressed();
} }
} }

@ -1,6 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_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'; import '../../../../helpers/helpers.dart';
class _MockDashBumperCubit extends Mock implements DashBumperCubit {} class _MockDashBumpersCubit extends Mock implements DashBumpersCubit {}
class _MockBall extends Mock implements Ball {} class _MockBall extends Mock implements Ball {}
@ -31,23 +32,30 @@ void main() {
}); });
flameTester.test( flameTester.test(
'beginContact emits onBallContacted when contacts with a ball', 'beginContact emits onBallContacted with the bumper ID '
'when contacts with a ball',
(game) async { (game) async {
final behavior = DashBumperBallContactBehavior(); final behavior = DashBumperBallContactBehavior();
final bloc = _MockDashBumperCubit(); final bloc = _MockDashBumpersCubit();
const id = DashBumperId.main;
whenListen( whenListen(
bloc, bloc,
const Stream<DashBumperState>.empty(), const Stream<DashBumperSpriteState>.empty(),
initialState: DashBumperState.active, initialState: DashBumperSpriteState.active,
); );
final bumper = DashBumper.test(bloc: bloc); final bumper = DashBumper.test(id: id);
await bumper.add(behavior); await bumper.add(behavior);
await game.ensureAdd(bumper); await game.ensureAdd(
FlameBlocProvider<DashBumpersCubit, DashBumpersState>.value(
value: bloc,
children: [bumper],
),
);
behavior.beginContact(_MockBall(), _MockContact()); behavior.beginContact(_MockBall(), _MockContact());
verify(bumper.bloc.onBallContacted).called(1); verify(() => bloc.onBallContacted(id)).called(1);
}, },
); );
}, },

@ -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<DashBumperCubit, DashBumperState>(
'onBallContacted emits active',
build: DashBumperCubit.new,
act: (bloc) => bloc.onBallContacted(),
expect: () => [DashBumperState.active],
);
blocTest<DashBumperCubit, DashBumperState>(
'onReset emits inactive',
build: DashBumperCubit.new,
act: (bloc) => bloc.onReset(),
expect: () => [DashBumperState.inactive],
);
},
);
}

@ -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<DashBumpersCubit, DashBumpersState>(
'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<DashBumpersCubit, DashBumpersState>(
'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()],
);
},
);
}

@ -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);
});
});
});
}

@ -1,75 +1,65 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.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:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_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/pinball_components.dart';
import 'package:pinball_components/src/components/bumping_behavior.dart'; import 'package:pinball_components/src/components/bumping_behavior.dart';
import 'package:pinball_components/src/components/dash_bumper/behaviors/behaviors.dart'; import 'package:pinball_components/src/components/dash_bumper/behaviors/behaviors.dart';
import '../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
@override
class _MockDashBumperCubit extends Mock implements DashBumperCubit {} Future<void> 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<void> pump(DashBumper child) async {
await ensureAdd(
FlameBlocProvider<DashBumpersCubit, DashBumpersState>.value(
value: DashBumpersCubit(),
children: [child],
),
);
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
group('DashBumper', () { final flameTester = FlameTester(_TestGame.new);
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,
],
),
);
flameTester.test('"main" loads correctly', (game) async { group('DashBumper', () {
flameTester.test('"main" can be added', (game) async {
final bumper = DashBumper.main(); final bumper = DashBumper.main();
await game.ensureAdd(bumper); await game.pump(bumper);
expect(game.contains(bumper), isTrue); 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(); final bumper = DashBumper.a();
await game.ensureAdd(bumper); await game.pump(bumper);
expect(game.descendants().contains(bumper), isTrue);
expect(game.contains(bumper), isTrue);
}); });
flameTester.test('"b" loads correctly', (game) async { flameTester.test('"b" can be added', (game) async {
final bumper = DashBumper.b(); final bumper = DashBumper.b();
await game.ensureAdd(bumper); await game.pump(bumper);
expect(game.contains(bumper), isTrue); expect(game.descendants().contains(bumper), isTrue);
}); });
// ignore: public_member_api_docs flameTester.test('adds a DashBumperBallContactBehavior', (game) async {
flameTester.test('closes bloc when removed', (game) async {
final bloc = _MockDashBumperCubit();
whenListen(
bloc,
const Stream<DashBumperState>.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 {
final bumper = DashBumper.a(); final bumper = DashBumper.a();
await game.ensureAdd(bumper); await game.pump(bumper);
expect( expect(
bumper.children.whereType<DashBumperBallContactBehavior>().single, bumper.children.whereType<DashBumperBallContactBehavior>().single,
isNotNull, isNotNull,
@ -82,13 +72,13 @@ void main() {
final bumper = DashBumper.main( final bumper = DashBumper.main(
children: [component], children: [component],
); );
await game.ensureAdd(bumper); await game.pump(bumper);
expect(bumper.children, contains(component)); expect(bumper.children, contains(component));
}); });
flameTester.test('a BumpingBehavior', (game) async { flameTester.test('a BumpingBehavior', (game) async {
final bumper = DashBumper.main(); final bumper = DashBumper.main();
await game.ensureAdd(bumper); await game.pump(bumper);
expect( expect(
bumper.children.whereType<BumpingBehavior>().single, bumper.children.whereType<BumpingBehavior>().single,
isNotNull, isNotNull,
@ -102,13 +92,13 @@ void main() {
final bumper = DashBumper.a( final bumper = DashBumper.a(
children: [component], children: [component],
); );
await game.ensureAdd(bumper); await game.pump(bumper);
expect(bumper.children, contains(component)); expect(bumper.children, contains(component));
}); });
flameTester.test('a BumpingBehavior', (game) async { flameTester.test('a BumpingBehavior', (game) async {
final bumper = DashBumper.a(); final bumper = DashBumper.a();
await game.ensureAdd(bumper); await game.pump(bumper);
expect( expect(
bumper.children.whereType<BumpingBehavior>().single, bumper.children.whereType<BumpingBehavior>().single,
isNotNull, isNotNull,
@ -122,18 +112,64 @@ void main() {
final bumper = DashBumper.b( final bumper = DashBumper.b(
children: [component], children: [component],
); );
await game.ensureAdd(bumper); await game.pump(bumper);
expect(bumper.children, contains(component)); expect(bumper.children, contains(component));
}); });
flameTester.test('a BumpingBehavior', (game) async { flameTester.test('a BumpingBehavior', (game) async {
final bumper = DashBumper.b(); final bumper = DashBumper.b();
await game.ensureAdd(bumper); await game.pump(bumper);
expect( expect(
bumper.children.whereType<BumpingBehavior>().single, bumper.children.whereType<BumpingBehavior>().single,
isNotNull, 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<DashBumperSpriteGroupComponent>()!.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<DashBumperSpriteGroupComponent>()!;
final originalSprite = spriteGroupComponent.current;
spriteGroupComponent.onNewState(mainBumperActivatedState);
await game.ready();
final newSprite = spriteGroupComponent.current;
expect(newSprite, isNot(equals(originalSprite)));
},
);
});
});
}); });
} }

@ -151,7 +151,7 @@ void main() {
await game.ready(); await game.ready();
final newSprite = googleLetter.current; final newSprite = googleLetter.current;
expect(newSprite != originalSprite, isTrue); expect(newSprite, isNot(equals(originalSprite)));
}, },
); );
}); });

@ -1,35 +1,72 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'dart:async';
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.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:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import '../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
@override
Future<void> 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<void> pump(
Signpost child, {
SignpostCubit? signpostBloc,
DashBumpersCubit? dashBumpersBloc,
}) async {
await onLoad();
await ensureAdd(
FlameMultiBlocProvider(
providers: [
FlameBlocProvider<SignpostCubit, SignpostState>.value(
value: signpostBloc ?? SignpostCubit(),
),
FlameBlocProvider<DashBumpersCubit, DashBumpersState>.value(
value: dashBumpersBloc ?? DashBumpersCubit(),
),
],
children: [child],
),
);
}
}
class _MockSignpostCubit extends Mock implements SignpostCubit {} class _MockSignpostCubit extends Mock implements SignpostCubit {}
class _MockDashBumpersCubit extends Mock implements DashBumpersCubit {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.signpost.inactive.keyName, final flameTester = FlameTester(_TestGame.new);
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group('Signpost', () { group('Signpost', () {
const goldenPath = '../golden/signpost/'; const goldenPath = '../golden/signpost/';
test('can be instantiated', () {
expect(Signpost(), isA<Signpost>());
});
flameTester.test( flameTester.test(
'loads correctly', 'can be added',
(game) async { (game) async {
final signpost = Signpost(); final signpost = Signpost();
await game.ensureAdd(signpost); await game.pump(signpost);
expect(game.contains(signpost), isTrue); expect(game.descendants().contains(signpost), isTrue);
}, },
); );
@ -37,13 +74,12 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'inactive sprite', 'inactive sprite',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
final signpost = Signpost(); final signpost = Signpost();
await game.ensureAdd(signpost); await game.pump(signpost);
await tester.pump(); await tester.pump();
expect( expect(
signpost.bloc.state, signpost.firstChild<SpriteGroupComponent>()!.current,
equals(SignpostState.inactive), equals(SignpostState.inactive),
); );
@ -51,7 +87,7 @@ void main() {
}, },
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('${goldenPath}inactive.png'), matchesGoldenFile('${goldenPath}inactive.png'),
); );
}, },
@ -60,14 +96,14 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'active1 sprite', 'active1 sprite',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
final signpost = Signpost(); final signpost = Signpost();
await game.ensureAdd(signpost); final bloc = SignpostCubit();
signpost.bloc.onProgressed(); await game.pump(signpost, signpostBloc: bloc);
bloc.onProgressed();
await tester.pump(); await tester.pump();
expect( expect(
signpost.bloc.state, signpost.firstChild<SpriteGroupComponent>()!.current,
equals(SignpostState.active1), equals(SignpostState.active1),
); );
@ -75,7 +111,7 @@ void main() {
}, },
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('${goldenPath}active1.png'), matchesGoldenFile('${goldenPath}active1.png'),
); );
}, },
@ -84,16 +120,16 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'active2 sprite', 'active2 sprite',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
final signpost = Signpost(); final signpost = Signpost();
await game.ensureAdd(signpost); final bloc = SignpostCubit();
signpost.bloc await game.pump(signpost, signpostBloc: bloc);
bloc
..onProgressed() ..onProgressed()
..onProgressed(); ..onProgressed();
await tester.pump(); await tester.pump();
expect( expect(
signpost.bloc.state, signpost.firstChild<SpriteGroupComponent>()!.current,
equals(SignpostState.active2), equals(SignpostState.active2),
); );
@ -101,7 +137,7 @@ void main() {
}, },
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('${goldenPath}active2.png'), matchesGoldenFile('${goldenPath}active2.png'),
); );
}, },
@ -110,18 +146,17 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'active3 sprite', 'active3 sprite',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
final signpost = Signpost(); final signpost = Signpost();
await game.ensureAdd(signpost); final bloc = SignpostCubit();
await game.pump(signpost, signpostBloc: bloc);
signpost.bloc bloc
..onProgressed() ..onProgressed()
..onProgressed() ..onProgressed()
..onProgressed(); ..onProgressed();
await tester.pump(); await tester.pump();
expect( expect(
signpost.bloc.state, signpost.firstChild<SpriteGroupComponent>()!.current,
equals(SignpostState.active3), equals(SignpostState.active3),
); );
@ -129,37 +164,83 @@ void main() {
}, },
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('${goldenPath}active3.png'), 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<DashBumpersState>.empty(),
initialState: DashBumpersState.initial(),
);
await game.pump(signpost, dashBumpersBloc: dashBumpersBloc);
final signpostListener = game
.descendants()
.whereType<FlameBlocListener<DashBumpersCubit, DashBumpersState>>()
.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<SignpostState>.empty(),
initialState: SignpostState.inactive,
);
final dashBumpersBloc = _MockDashBumpersCubit();
whenListen(
dashBumpersBloc,
const Stream<DashBumpersState>.empty(),
initialState: DashBumpersState.initial(),
);
await game.pump(
signpost,
signpostBloc: signpostBloc,
dashBumpersBloc: dashBumpersBloc,
);
game
.descendants()
.whereType<FlameBlocListener<DashBumpersCubit, DashBumpersState>>()
.single
.onNewState(DashBumpersState.initial());
verify(signpostBloc.onProgressed).called(1);
verify(dashBumpersBloc.onReset).called(1);
},
);
flameTester.test('adds new children', (game) async { flameTester.test('adds new children', (game) async {
final component = Component(); final component = Component();
final signpost = Signpost( final signpost = Signpost(
children: [component], children: [component],
); );
await game.ensureAdd(signpost); await game.pump(signpost);
expect(signpost.children, contains(component)); expect(signpost.children, contains(component));
}); });
flameTester.test('closes bloc when removed', (game) async {
final bloc = _MockSignpostCubit();
whenListen(
bloc,
const Stream<SignpostCubit>.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);
});
}); });
} }

@ -1,5 +1,8 @@
// ignore_for_file: cascade_invocations // 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_bloc/flame_bloc.dart';
import 'package:flame_forge2d/forge2d_game.dart'; import 'package:flame_forge2d/forge2d_game.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
@ -22,10 +25,22 @@ class _TestGame extends Forge2DGame {
Future<void> pump( Future<void> pump(
FlutterForest child, { FlutterForest child, {
required GameBloc gameBloc, required GameBloc gameBloc,
required SignpostCubit signpostBloc,
DashBumpersCubit? dashBumpersBloc,
}) async { }) async {
await ensureAdd( await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value( FlameMultiBlocProvider(
value: gameBloc, providers: [
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
),
FlameBlocProvider<SignpostCubit, SignpostState>.value(
value: signpostBloc,
),
FlameBlocProvider<DashBumpersCubit, DashBumpersState>.value(
value: dashBumpersBloc ?? DashBumpersCubit(),
),
],
children: [ children: [
ZCanvasComponent( ZCanvasComponent(
children: [child], children: [child],
@ -38,6 +53,10 @@ class _TestGame extends Forge2DGame {
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
class _MockSignpostCubit extends Mock implements SignpostCubit {}
class _MockDashBumpersCubit extends Mock implements DashBumpersCubit {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
@ -50,31 +69,33 @@ void main() {
final flameTester = FlameTester(_TestGame.new); final flameTester = FlameTester(_TestGame.new);
void _contactedBumper(DashBumper bumper) => bumper.bloc.onBallContacted(); test('can be instantiated', () {
expect(FlutterForestBonusBehavior(), isA<FlutterForestBonusBehavior>());
});
flameTester.testGameWidget( flameTester.testGameWidget(
'adds GameBonus.dashNest to the game ' 'adds GameBonus.dashNest to the game '
'when bumpers are activated three times', 'when signpost becomes fully activated',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.onLoad();
final behavior = FlutterForestBonusBehavior(); final behavior = FlutterForestBonusBehavior();
final parent = FlutterForest.test(); final parent = FlutterForest.test();
final bumpers = [ final signpostBloc = _MockSignpostCubit();
DashBumper.test(bloc: DashBumperCubit()), final streamController = StreamController<SignpostState>();
DashBumper.test(bloc: DashBumperCubit()),
DashBumper.test(bloc: DashBumperCubit()), whenListen(
]; signpostBloc,
final signpost = Signpost.test(bloc: SignpostCubit()); streamController.stream,
await game.pump(parent, gameBloc: gameBloc); initialState: SignpostState.inactive,
await parent.ensureAddAll([...bumpers, signpost]); );
await game.pump(
parent,
gameBloc: gameBloc,
signpostBloc: signpostBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
expect(game.descendants().whereType<DashBumper>(), equals(bumpers)); streamController.add(SignpostState.active3);
bumpers.forEach(_contactedBumper);
await tester.pump();
bumpers.forEach(_contactedBumper);
await tester.pump();
bumpers.forEach(_contactedBumper);
await tester.pump(); await tester.pump();
verify( verify(
@ -84,76 +105,66 @@ void main() {
); );
flameTester.testGameWidget( flameTester.testGameWidget(
'adds BonusBallSpawningBehavior to the game ' 'calls onProgressed and onReset '
'when bumpers are activated three times', 'when signpost becomes fully activated',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.onLoad();
final behavior = FlutterForestBonusBehavior(); final behavior = FlutterForestBonusBehavior();
final parent = FlutterForest.test(); final parent = FlutterForest.test();
final bumpers = [ final dashBumpersBloc = _MockDashBumpersCubit();
DashBumper.test(bloc: DashBumperCubit()), final signpostBloc = _MockSignpostCubit();
DashBumper.test(bloc: DashBumperCubit()), final streamController = StreamController<SignpostState>();
DashBumper.test(bloc: DashBumperCubit()),
]; whenListen(
final signpost = Signpost.test(bloc: SignpostCubit()); signpostBloc,
await game.pump(parent, gameBloc: gameBloc); streamController.stream,
await parent.ensureAddAll([...bumpers, signpost]); initialState: SignpostState.inactive,
);
await game.pump(
parent,
gameBloc: gameBloc,
signpostBloc: signpostBloc,
dashBumpersBloc: dashBumpersBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
expect(game.descendants().whereType<DashBumper>(), equals(bumpers)); streamController.add(SignpostState.active3);
bumpers.forEach(_contactedBumper);
await tester.pump();
bumpers.forEach(_contactedBumper);
await tester.pump();
bumpers.forEach(_contactedBumper);
await tester.pump(); await tester.pump();
await game.ready(); verify(signpostBloc.onProgressed).called(1);
expect( verify(dashBumpersBloc.onReset).called(1);
game.descendants().whereType<BonusBallSpawningBehavior>().length,
equals(1),
);
}, },
); );
flameTester.testGameWidget( flameTester.testGameWidget(
'progress the signpost ' 'adds BonusBallSpawningBehavior to the game '
'when bumpers are activated', 'when signpost becomes fully activated',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.onLoad();
final behavior = FlutterForestBonusBehavior(); final behavior = FlutterForestBonusBehavior();
final parent = FlutterForest.test(); final parent = FlutterForest.test();
final bumpers = [ final signpostBloc = _MockSignpostCubit();
DashBumper.test(bloc: DashBumperCubit()), final streamController = StreamController<SignpostState>();
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<DashBumper>(), equals(bumpers));
bumpers.forEach(_contactedBumper); whenListen(
await tester.pump(); signpostBloc,
expect( streamController.stream,
signpost.bloc.state, initialState: SignpostState.inactive,
equals(SignpostState.active1),
); );
bumpers.forEach(_contactedBumper); await game.pump(
await tester.pump(); parent,
expect( gameBloc: gameBloc,
signpost.bloc.state, signpostBloc: signpostBloc,
equals(SignpostState.active2),
); );
await parent.ensureAdd(behavior);
bumpers.forEach(_contactedBumper); streamController.add(SignpostState.active3);
await tester.pump(); await tester.pump();
await game.ready();
expect( expect(
signpost.bloc.state, game.descendants().whereType<BonusBallSpawningBehavior>().length,
equals(SignpostState.inactive), equals(1),
); );
}, },
); );

@ -37,6 +37,8 @@ class _TestGame extends Forge2DGame with HasTappables {
Iterable<Component> children, { Iterable<Component> children, {
PinballAudioPlayer? pinballAudioPlayer, PinballAudioPlayer? pinballAudioPlayer,
GoogleWordCubit? googleWordBloc, GoogleWordCubit? googleWordBloc,
DashBumpersCubit? dashBumpersBloc,
SignpostCubit? signpostBloc,
}) async { }) async {
return ensureAdd( return ensureAdd(
FlameMultiBlocProvider( FlameMultiBlocProvider(
@ -50,6 +52,12 @@ class _TestGame extends Forge2DGame with HasTappables {
FlameBlocProvider<GoogleWordCubit, GoogleWordState>.value( FlameBlocProvider<GoogleWordCubit, GoogleWordState>.value(
value: googleWordBloc ?? GoogleWordCubit(), value: googleWordBloc ?? GoogleWordCubit(),
), ),
FlameBlocProvider<DashBumpersCubit, DashBumpersState>.value(
value: dashBumpersBloc ?? DashBumpersCubit(),
),
FlameBlocProvider<SignpostCubit, SignpostState>.value(
value: signpostBloc ?? SignpostCubit(),
),
], ],
children: [ children: [
MultiFlameProvider( MultiFlameProvider(
@ -83,7 +91,7 @@ class _MockPlungerCubit extends Mock implements PlungerCubit {}
class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {} class _MockGoogleWordCubit extends Mock implements GoogleWordCubit {}
class _MockDashBumperCubit extends Mock implements DashBumperCubit {} class _MockDashBumpersCubit extends Mock implements DashBumpersCubit {}
class _MockSignpostCubit extends Mock implements SignpostCubit {} class _MockSignpostCubit extends Mock implements SignpostCubit {}
@ -334,12 +342,7 @@ void main() {
final audioPlayer = _MockPinballAudioPlayer(); final audioPlayer = _MockPinballAudioPlayer();
final component = GameBlocStatusListener(); final component = GameBlocStatusListener();
await game.pump( await game.pump(
[ [component],
component,
Signpost.test(
bloc: _MockSignpostCubit(),
),
],
pinballAudioPlayer: audioPlayer, pinballAudioPlayer: audioPlayer,
); );
@ -360,12 +363,7 @@ void main() {
final googleWordBloc = _MockGoogleWordCubit(); final googleWordBloc = _MockGoogleWordCubit();
final component = GameBlocStatusListener(); final component = GameBlocStatusListener();
await game.pump( await game.pump(
[ [component],
component,
Signpost.test(
bloc: _MockSignpostCubit(),
),
],
googleWordBloc: googleWordBloc, googleWordBloc: googleWordBloc,
); );
@ -377,27 +375,19 @@ void main() {
); );
flameTester.test( flameTester.test(
'resets the DashBumperCubits', 'resets the DashBumpersCubit',
(game) async { (game) async {
final dashBumper1Bloc = _MockDashBumperCubit(); final dashBumpersBloc = _MockDashBumpersCubit();
final dashBumper2Bloc = _MockDashBumperCubit();
final dashBumper1 = DashBumper.test(bloc: dashBumper1Bloc);
final dashBumper2 = DashBumper.test(bloc: dashBumper2Bloc);
final component = GameBlocStatusListener(); final component = GameBlocStatusListener();
await game.pump([ await game.pump(
component, [component],
dashBumper1, dashBumpersBloc: dashBumpersBloc,
dashBumper2, );
Signpost.test(
bloc: _MockSignpostCubit(),
),
]);
expect(state.status, equals(GameStatus.playing)); expect(state.status, equals(GameStatus.playing));
component.onNewState(state); component.onNewState(state);
verify(dashBumper1Bloc.onReset).called(1); verify(dashBumpersBloc.onReset).called(1);
verify(dashBumper2Bloc.onReset).called(1);
}, },
); );
@ -405,9 +395,8 @@ void main() {
'resets the SignpostCubit', 'resets the SignpostCubit',
(game) async { (game) async {
final signpostBloc = _MockSignpostCubit(); final signpostBloc = _MockSignpostCubit();
final signpost = Signpost.test(bloc: signpostBloc);
final component = GameBlocStatusListener(); final component = GameBlocStatusListener();
await game.pump([component, signpost]); await game.pump([component], signpostBloc: signpostBloc);
expect(state.status, equals(GameStatus.playing)); expect(state.status, equals(GameStatus.playing));
component.onNewState(state); component.onNewState(state);
@ -429,14 +418,7 @@ void main() {
); );
final flipper = Flipper.test(side: BoardSide.left); final flipper = Flipper.test(side: BoardSide.left);
await game.pump([ await game.pump([component, backbox, flipper]);
component,
backbox,
flipper,
Signpost.test(
bloc: _MockSignpostCubit(),
),
]);
await flipper.ensureAdd( await flipper.ensureAdd(
FlameBlocProvider<FlipperCubit, FlipperState>( FlameBlocProvider<FlipperCubit, FlipperState>(
create: _MockFlipperCubit.new, create: _MockFlipperCubit.new,
@ -468,16 +450,7 @@ void main() {
entries: const [], entries: const [],
); );
final plunger = Plunger.test(); final plunger = Plunger.test();
await game.pump( await game.pump([component, backbox, plunger]);
[
component,
backbox,
plunger,
Signpost.test(
bloc: _MockSignpostCubit(),
),
],
);
await plunger.ensureAdd( await plunger.ensureAdd(
FlameBlocProvider<PlungerCubit, PlungerState>( FlameBlocProvider<PlungerCubit, PlungerState>(
create: _MockPlungerCubit.new, create: _MockPlungerCubit.new,
@ -511,16 +484,7 @@ void main() {
entries: const [], entries: const [],
); );
final plunger = Plunger.test(); final plunger = Plunger.test();
await game.pump( await game.pump([component, backbox, plunger]);
[
component,
backbox,
plunger,
Signpost.test(
bloc: _MockSignpostCubit(),
),
],
);
await plunger.ensureAdd( await plunger.ensureAdd(
FlameBlocProvider<PlungerCubit, PlungerState>( FlameBlocProvider<PlungerCubit, PlungerState>(
create: _MockPlungerCubit.new, create: _MockPlungerCubit.new,
@ -551,16 +515,7 @@ void main() {
entries: const [], entries: const [],
); );
final plunger = Plunger.test(); final plunger = Plunger.test();
await game.pump( await game.pump([component, backbox, plunger]);
[
component,
backbox,
plunger,
Signpost.test(
bloc: _MockSignpostCubit(),
),
],
);
await plunger.ensureAdd( await plunger.ensureAdd(
FlameBlocProvider<PlungerCubit, PlungerState>( FlameBlocProvider<PlungerCubit, PlungerState>(
create: _MockPlungerCubit.new, create: _MockPlungerCubit.new,

@ -73,6 +73,10 @@ void main() {
final flameTester = FlameTester(_TestGame.new); final flameTester = FlameTester(_TestGame.new);
test('can be instantiated', () {
expect(GoogleWordBonusBehavior(), isA<GoogleWordBonusBehavior>());
});
flameTester.testGameWidget( flameTester.testGameWidget(
'adds GameBonus.googleWord to the game when all letters ' 'adds GameBonus.googleWord to the game when all letters '
'in google word are activated and calls onReset', 'in google word are activated and calls onReset',

Loading…
Cancel
Save