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]
/// 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<FlutterForest>,
HasGameRef,
FlameBlocReader<GameBloc, GameState> {
class FlutterForestBonusBehavior extends Component {
@override
void onMount() {
super.onMount();
final bumpers = parent.children.whereType<DashBumper>();
final signpost = parent.firstChild<Signpost>()!;
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<void> onLoad() async {
await super.onLoad();
await add(
FlameBlocListener<SignpostCubit, SignpostState>(
listenWhen: (_, state) => state == SignpostState.active3,
onNewState: (_) {
readBloc<GameBloc, GameState>()
.add(const BonusActivated(GameBonus.dashNest));
readBloc<SignpostCubit, SignpostState>().onProgressed();
readBloc<DashBumpersCubit, DashBumpersState>().onReset();
add(BonusBallSpawningBehavior());
},
),
);
}
}

@ -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<SignpostCubit, SignpostState>(
create: SignpostCubit.new,
),
FlameBlocProvider<DashBumpersCubit, DashBumpersState>(
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;

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

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

@ -7,6 +7,6 @@ class DashBumperBallContactBehavior extends ContactBehavior<DashBumper> {
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
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 '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<Component>? 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<Component>? 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<Component>? 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<Component>? 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<DashBumperState>
with HasGameRef, ParentIsA<DashBumper> {
_DashBumperSpriteGroupComponent({
@visibleForTesting
class DashBumperSpriteGroupComponent
extends SpriteGroupComponent<DashBumperSpriteState>
with HasGameRef, FlameBlocListenable<DashBumpersCubit, DashBumpersState> {
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<void> 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;
}
}

@ -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<Component>? children,
}) : this._(
children: children,
bloc: SignpostCubit(),
);
Signpost._({
Signpost({
Iterable<Component>? 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<void> onLoad() async {
await super.onLoad();
await add(
FlameBlocListener<DashBumpersCubit, DashBumpersState>(
listenWhen: (_, state) => state.isFullyActivated,
onNewState: (_) {
readBloc<SignpostCubit, SignpostState>().onProgressed();
readBloc<DashBumpersCubit, DashBumpersState>().onReset();
},
),
);
}
@override
@ -61,20 +50,19 @@ class Signpost extends BodyComponent with InitialPosition {
}
class _SignpostSpriteComponent extends SpriteGroupComponent<SignpostState>
with HasGameRef, ParentIsA<Signpost> {
_SignpostSpriteComponent({
required SignpostState current,
}) : super(
with HasGameRef, FlameBlocListenable<SignpostCubit, SignpostState> {
_SignpostSpriteComponent()
: super(
anchor: Anchor.bottomCenter,
position: Vector2(0.65, 0.45),
current: current,
);
@override
void onNewState(SignpostState state) => current = state;
@override
Future<void> onLoad() async {
await super.onLoad();
parent.bloc.stream.listen((state) => current = state);
final sprites = <SignpostState, Sprite>{};
this.sprites = sprites;
for (final spriteState in SignpostState.values) {

@ -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<DashBumpersCubit, DashBumpersState>(
create: DashBumpersCubit.new,
children: [
DashBumper.a()..priority = 1,
],
),
);
await traceAllBodies();
}
}

@ -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<DashBumpersCubit, DashBumpersState>(
create: DashBumpersCubit.new,
children: [
DashBumper.b()..priority = 1,
],
),
);
await traceAllBodies();
}
}

@ -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<DashBumpersCubit, DashBumpersState>(
create: DashBumpersCubit.new,
children: [
DashBumper.main()..priority = 1,
],
),
);
await traceAllBodies();
}

@ -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<void> onLoad() async {
await super.onLoad();
_bloc = SignpostCubit();
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();
}
@override
void onTap() {
super.onTap();
firstChild<Signpost>()!.bloc.onProgressed();
_bloc.onProgressed();
}
}

@ -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<DashBumperState>.empty(),
initialState: DashBumperState.active,
const Stream<DashBumperSpriteState>.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<DashBumpersCubit, DashBumpersState>.value(
value: bloc,
children: [bumper],
),
);
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
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<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() {
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<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 {
flameTester.test('adds a DashBumperBallContactBehavior', (game) async {
final bumper = DashBumper.a();
await game.ensureAdd(bumper);
await game.pump(bumper);
expect(
bumper.children.whereType<DashBumperBallContactBehavior>().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<BumpingBehavior>().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<BumpingBehavior>().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<BumpingBehavior>().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<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();
final newSprite = googleLetter.current;
expect(newSprite != originalSprite, isTrue);
expect(newSprite, isNot(equals(originalSprite)));
},
);
});

@ -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<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 _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<Signpost>());
});
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<SpriteGroupComponent>()!.current,
equals(SignpostState.inactive),
);
@ -51,7 +87,7 @@ void main() {
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
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<SpriteGroupComponent>()!.current,
equals(SignpostState.active1),
);
@ -75,7 +111,7 @@ void main() {
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
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<SpriteGroupComponent>()!.current,
equals(SignpostState.active2),
);
@ -101,7 +137,7 @@ void main() {
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
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<SpriteGroupComponent>()!.current,
equals(SignpostState.active3),
);
@ -129,37 +164,83 @@ void main() {
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
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<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 {
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<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
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<void> pump(
FlutterForest child, {
required GameBloc gameBloc,
required SignpostCubit signpostBloc,
DashBumpersCubit? dashBumpersBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
FlameMultiBlocProvider(
providers: [
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
),
FlameBlocProvider<SignpostCubit, SignpostState>.value(
value: signpostBloc,
),
FlameBlocProvider<DashBumpersCubit, DashBumpersState>.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<FlutterForestBonusBehavior>());
});
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<SignpostState>();
whenListen(
signpostBloc,
streamController.stream,
initialState: SignpostState.inactive,
);
await game.pump(
parent,
gameBloc: gameBloc,
signpostBloc: signpostBloc,
);
await parent.ensureAdd(behavior);
expect(game.descendants().whereType<DashBumper>(), 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<SignpostState>();
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<DashBumper>(), 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<BonusBallSpawningBehavior>().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<DashBumper>(), equals(bumpers));
final signpostBloc = _MockSignpostCubit();
final streamController = StreamController<SignpostState>();
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<BonusBallSpawningBehavior>().length,
equals(1),
);
},
);

@ -37,6 +37,8 @@ class _TestGame extends Forge2DGame with HasTappables {
Iterable<Component> children, {
PinballAudioPlayer? pinballAudioPlayer,
GoogleWordCubit? googleWordBloc,
DashBumpersCubit? dashBumpersBloc,
SignpostCubit? signpostBloc,
}) async {
return ensureAdd(
FlameMultiBlocProvider(
@ -50,6 +52,12 @@ class _TestGame extends Forge2DGame with HasTappables {
FlameBlocProvider<GoogleWordCubit, GoogleWordState>.value(
value: googleWordBloc ?? GoogleWordCubit(),
),
FlameBlocProvider<DashBumpersCubit, DashBumpersState>.value(
value: dashBumpersBloc ?? DashBumpersCubit(),
),
FlameBlocProvider<SignpostCubit, SignpostState>.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<FlipperCubit, FlipperState>(
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<PlungerCubit, PlungerState>(
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<PlungerCubit, PlungerState>(
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<PlungerCubit, PlungerState>(
create: _MockPlungerCubit.new,

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

Loading…
Cancel
Save