chore: migrating to `flame_bloc` 1.4.0 (#357)

* refactor: migrating to flame_bloc

* test: passed all tests

* test: updeted ControlledFlipperTest

* test: tested FlameProvider

* test: awaited loading

* test: waited loading ScoreBehavior

* refactor: removed unused files

* refactor: removed FlameBlocTester

* refactor: moved helper

* test: fixed typo name

* refactor: renamed file name to bumper_noise_behavior_test.dart

* refactor: renamed to TestDebugPinballGame

* refactor: removed uncessary KeybordHandler mixin

* refactor: removed unecessary imports

* test: removed testGameWidgets
pull/362/head
Alejandro Santiago 3 years ago committed by GitHub
parent 0f12e00aff
commit b3136e5857
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,11 +3,12 @@ import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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';
import 'package:pinball_theme/pinball_theme.dart';
/// Spawns a new [Ball] into the game when all balls are lost and still /// Spawns a new [Ball] into the game when all balls are lost and still
/// [GameStatus.playing]. /// [GameStatus.playing].
class BallSpawningBehavior extends Component class BallSpawningBehavior extends Component
with ParentIsA<PinballGame>, BlocComponent<GameBloc, GameState> { with FlameBlocListenable<GameBloc, GameState>, HasGameRef {
@override @override
bool listenWhen(GameState? previousState, GameState newState) { bool listenWhen(GameState? previousState, GameState newState) {
if (!newState.status.isPlaying) return false; if (!newState.status.isPlaying) return false;
@ -20,9 +21,10 @@ class BallSpawningBehavior extends Component
@override @override
void onNewState(GameState state) { void onNewState(GameState state) {
final plunger = parent.descendants().whereType<Plunger>().single; final plunger = gameRef.descendants().whereType<Plunger>().single;
final canvas = parent.descendants().whereType<ZCanvasComponent>().single; final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
final ball = ControlledBall.launch(characterTheme: parent.characterTheme) final characterTheme = readProvider<CharacterTheme>();
final ball = ControlledBall.launch(characterTheme: characterTheme)
..initialPosition = Vector2( ..initialPosition = Vector2(
plunger.body.position.x, plunger.body.position.x,
plunger.body.position.y - Ball.size.y, plunger.body.position.y - Ball.size.y,

@ -1,15 +1,13 @@
// ignore_for_file: public_member_api_docs // ignore_for_file: public_member_api_docs
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/pinball_game.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
class BumperNoiseBehavior extends ContactBehavior with HasGameRef<PinballGame> { class BumperNoiseBehavior extends ContactBehavior {
@override @override
void beginContact(Object other, Contact contact) { void beginContact(Object other, Contact contact) {
super.beginContact(other, contact); super.beginContact(other, contact);
gameRef.player.play(PinballAudio.bumper); readProvider<PinballPlayer>().play(PinballAudio.bumper);
} }
} }

@ -3,7 +3,6 @@ import 'package:flame/game.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template focus_data} /// {@template focus_data}
/// Defines a [Camera] focus point. /// Defines a [Camera] focus point.
@ -24,7 +23,7 @@ class FocusData {
/// Changes the game focus when the [GameBloc] status changes. /// Changes the game focus when the [GameBloc] status changes.
class CameraFocusingBehavior extends Component class CameraFocusingBehavior extends Component
with ParentIsA<FlameGame>, BlocComponent<GameBloc, GameState> { with FlameBlocListenable<GameBloc, GameState>, HasGameRef {
late final Map<String, FocusData> _foci; late final Map<String, FocusData> _foci;
@override @override
@ -51,15 +50,15 @@ class CameraFocusingBehavior extends Component
await super.onLoad(); await super.onLoad();
_foci = { _foci = {
'game': FocusData( 'game': FocusData(
zoom: parent.size.y / 16, zoom: gameRef.size.y / 16,
position: Vector2(0, -7.8), position: Vector2(0, -7.8),
), ),
'waiting': FocusData( 'waiting': FocusData(
zoom: parent.size.y / 18, zoom: gameRef.size.y / 18,
position: Vector2(0, -112), position: Vector2(0, -112),
), ),
'backbox': FocusData( 'backbox': FocusData(
zoom: parent.size.y / 10, zoom: gameRef.size.y / 10,
position: Vector2(0, -111), position: Vector2(0, -111),
), ),
}; };
@ -68,7 +67,7 @@ class CameraFocusingBehavior extends Component
} }
void _snap(FocusData data) { void _snap(FocusData data) {
parent.camera gameRef.camera
..speed = 100 ..speed = 100
..followVector2(data.position) ..followVector2(data.position)
..zoom = data.zoom; ..zoom = data.zoom;
@ -77,7 +76,7 @@ class CameraFocusingBehavior extends Component
void _zoom(FocusData data) { void _zoom(FocusData data) {
final zoom = CameraZoom(value: data.zoom); final zoom = CameraZoom(value: data.zoom);
zoom.completed.then((_) { zoom.completed.then((_) {
parent.camera.moveTo(data.position); gameRef.camera.moveTo(data.position);
}); });
add(zoom); add(zoom);
} }

@ -2,6 +2,7 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/effects.dart'; import 'package:flame/effects.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -12,7 +13,8 @@ import 'package:pinball_flame/pinball_flame.dart';
/// ///
/// The behavior removes itself after the duration. /// The behavior removes itself after the duration.
/// {@endtemplate} /// {@endtemplate}
class ScoringBehavior extends Component with HasGameRef<PinballGame> { class ScoringBehavior extends Component
with HasGameRef, FlameBlocReader<GameBloc, GameState> {
/// {@macto scoring_behavior} /// {@macto scoring_behavior}
ScoringBehavior({ ScoringBehavior({
required Points points, required Points points,
@ -39,7 +41,8 @@ class ScoringBehavior extends Component with HasGameRef<PinballGame> {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
gameRef.read<GameBloc>().add(Scored(points: _points.value)); await super.onLoad();
bloc.add(Scored(points: _points.value));
final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single; final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
await canvas.add( await canvas.add(
ScoreComponent( ScoreComponent(
@ -54,8 +57,7 @@ class ScoringBehavior extends Component with HasGameRef<PinballGame> {
/// {@template scoring_contact_behavior} /// {@template scoring_contact_behavior}
/// Adds points to the score when the [Ball] contacts the [parent]. /// Adds points to the score when the [Ball] contacts the [parent].
/// {@endtemplate} /// {@endtemplate}
class ScoringContactBehavior extends ContactBehavior class ScoringContactBehavior extends ContactBehavior {
with HasGameRef<PinballGame> {
/// {@macro scoring_contact_behavior} /// {@macro scoring_contact_behavior}
ScoringContactBehavior({ ScoringContactBehavior({
required Points points, required Points points,

@ -1,11 +1,12 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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';
/// Adds a [GameBonus.androidSpaceship] when [AndroidSpaceship] has a bonus. /// Adds a [GameBonus.androidSpaceship] when [AndroidSpaceship] has a bonus.
class AndroidSpaceshipBonusBehavior extends Component class AndroidSpaceshipBonusBehavior extends Component
with HasGameRef<PinballGame>, ParentIsA<AndroidAcres> { with ParentIsA<AndroidAcres>, FlameBlocReader<GameBloc, GameState> {
@override @override
void onMount() { void onMount() {
super.onMount(); super.onMount();
@ -18,9 +19,7 @@ class AndroidSpaceshipBonusBehavior extends Component
final listenWhen = state == AndroidSpaceshipState.withBonus; final listenWhen = state == AndroidSpaceshipState.withBonus;
if (!listenWhen) return; if (!listenWhen) return;
gameRef bloc.add(const BonusActivated(GameBonus.androidSpaceship));
.read<GameBloc>()
.add(const BonusActivated(GameBonus.androidSpaceship));
androidSpaceship.bloc.onBonusAwarded(); androidSpaceship.bloc.onBonusAwarded();
}); });
} }

@ -3,15 +3,13 @@ import 'dart:async';
import 'package:flame/components.dart'; import 'package:flame/components.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/game.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';
/// {@template ramp_bonus_behavior} /// {@template ramp_bonus_behavior}
/// Increases the score when a [Ball] is shot 10 times into the [SpaceshipRamp]. /// Increases the score when a [Ball] is shot 10 times into the [SpaceshipRamp].
/// {@endtemplate} /// {@endtemplate}
class RampBonusBehavior extends Component class RampBonusBehavior extends Component with ParentIsA<SpaceshipRamp> {
with ParentIsA<SpaceshipRamp>, HasGameRef<PinballGame> {
/// {@macro ramp_bonus_behavior} /// {@macro ramp_bonus_behavior}
RampBonusBehavior({ RampBonusBehavior({
required Points points, required Points points,

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
@ -11,7 +12,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// Increases the score when a [Ball] is shot into the [SpaceshipRamp]. /// Increases the score when a [Ball] is shot into the [SpaceshipRamp].
/// {@endtemplate} /// {@endtemplate}
class RampShotBehavior extends Component class RampShotBehavior extends Component
with ParentIsA<SpaceshipRamp>, HasGameRef<PinballGame> { with ParentIsA<SpaceshipRamp>, FlameBlocReader<GameBloc, GameState> {
/// {@macro ramp_shot_behavior} /// {@macro ramp_shot_behavior}
RampShotBehavior({ RampShotBehavior({
required Points points, required Points points,
@ -43,7 +44,7 @@ class RampShotBehavior extends Component
final achievedOneMillionPoints = state.hits % 10 == 0; final achievedOneMillionPoints = state.hits % 10 == 0;
if (!achievedOneMillionPoints) { if (!achievedOneMillionPoints) {
gameRef.read<GameBloc>().add(const MultiplierIncreased()); bloc.add(const MultiplierIncreased());
parent.add( parent.add(
ScoringBehavior( ScoringBehavior(

@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart'; import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart';
import 'package:pinball/game/components/backbox/displays/displays.dart'; import 'package:pinball/game/components/backbox/displays/displays.dart';
import 'package:pinball/game/pinball_game.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';
import 'package:pinball_theme/pinball_theme.dart' hide Assets; import 'package:pinball_theme/pinball_theme.dart' hide Assets;
@ -13,7 +12,7 @@ import 'package:pinball_theme/pinball_theme.dart' hide Assets;
/// {@template backbox} /// {@template backbox}
/// The [Backbox] of the pinball machine. /// The [Backbox] of the pinball machine.
/// {@endtemplate} /// {@endtemplate}
class Backbox extends PositionComponent with HasGameRef<PinballGame>, ZIndex { class Backbox extends PositionComponent with ZIndex {
/// {@macro backbox} /// {@macro backbox}
Backbox({ Backbox({
required LeaderboardRepository leaderboardRepository, required LeaderboardRepository leaderboardRepository,

@ -4,7 +4,7 @@ import 'dart:math';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/l10n/l10n.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';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
@ -103,8 +103,7 @@ class InitialsInputDisplay extends Component with HasGameRef {
} }
} }
class _ScoreLabelTextComponent extends TextComponent class _ScoreLabelTextComponent extends TextComponent {
with HasGameRef<PinballGame> {
_ScoreLabelTextComponent() _ScoreLabelTextComponent()
: super( : super(
anchor: Anchor.centerLeft, anchor: Anchor.centerLeft,
@ -119,7 +118,7 @@ class _ScoreLabelTextComponent extends TextComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
text = gameRef.l10n.score; text = readProvider<AppLocalizations>().score;
} }
} }
@ -133,8 +132,7 @@ class _ScoreTextComponent extends TextComponent {
); );
} }
class _NameLabelTextComponent extends TextComponent class _NameLabelTextComponent extends TextComponent {
with HasGameRef<PinballGame> {
_NameLabelTextComponent() _NameLabelTextComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
@ -149,7 +147,7 @@ class _NameLabelTextComponent extends TextComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
text = gameRef.l10n.name; text = readProvider<AppLocalizations>().name;
} }
} }
@ -300,8 +298,7 @@ class _InstructionsComponent extends PositionComponent with HasGameRef {
); );
} }
class _EnterInitialsTextComponent extends TextComponent class _EnterInitialsTextComponent extends TextComponent {
with HasGameRef<PinballGame> {
_EnterInitialsTextComponent() _EnterInitialsTextComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
@ -312,11 +309,11 @@ class _EnterInitialsTextComponent extends TextComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
text = gameRef.l10n.enterInitials; text = readProvider<AppLocalizations>().enterInitials;
} }
} }
class _ArrowsTextComponent extends TextComponent with HasGameRef<PinballGame> { class _ArrowsTextComponent extends TextComponent {
_ArrowsTextComponent() _ArrowsTextComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
@ -331,12 +328,11 @@ class _ArrowsTextComponent extends TextComponent with HasGameRef<PinballGame> {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
text = gameRef.l10n.arrows; text = readProvider<AppLocalizations>().arrows;
} }
} }
class _AndPressTextComponent extends TextComponent class _AndPressTextComponent extends TextComponent {
with HasGameRef<PinballGame> {
_AndPressTextComponent() _AndPressTextComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
@ -347,12 +343,11 @@ class _AndPressTextComponent extends TextComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
text = gameRef.l10n.andPress; text = readProvider<AppLocalizations>().andPress;
} }
} }
class _EnterReturnTextComponent extends TextComponent class _EnterReturnTextComponent extends TextComponent {
with HasGameRef<PinballGame> {
_EnterReturnTextComponent() _EnterReturnTextComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
@ -367,12 +362,11 @@ class _EnterReturnTextComponent extends TextComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
text = gameRef.l10n.enterReturn; text = readProvider<AppLocalizations>().enterReturn;
} }
} }
class _ToSubmitTextComponent extends TextComponent class _ToSubmitTextComponent extends TextComponent {
with HasGameRef<PinballGame> {
_ToSubmitTextComponent() _ToSubmitTextComponent()
: super( : super(
anchor: Anchor.center, anchor: Anchor.center,
@ -383,6 +377,6 @@ class _ToSubmitTextComponent extends TextComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
text = gameRef.l10n.toSubmit; text = readProvider<AppLocalizations>().toSubmit;
} }
} }

@ -15,8 +15,7 @@ final _bodyTextPaint = TextPaint(
/// {@template initials_submission_failure_display} /// {@template initials_submission_failure_display}
/// [Backbox] display for when a failure occurs during initials submission. /// [Backbox] display for when a failure occurs during initials submission.
/// {@endtemplate} /// {@endtemplate}
class InitialsSubmissionFailureDisplay extends TextComponent class InitialsSubmissionFailureDisplay extends TextComponent {
with HasGameRef<PinballGame> {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();

@ -15,8 +15,7 @@ final _bodyTextPaint = TextPaint(
/// {@template initials_submission_success_display} /// {@template initials_submission_success_display}
/// [Backbox] display for initials successfully submitted. /// [Backbox] display for initials successfully submitted.
/// {@endtemplate} /// {@endtemplate}
class InitialsSubmissionSuccessDisplay extends TextComponent class InitialsSubmissionSuccessDisplay extends TextComponent {
with HasGameRef<PinballGame> {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();

@ -1,7 +1,8 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/l10n/l10n.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_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
final _bodyTextPaint = TextPaint( final _bodyTextPaint = TextPaint(
@ -15,7 +16,7 @@ final _bodyTextPaint = TextPaint(
/// {@template loading_display} /// {@template loading_display}
/// Display used to show the loading animation. /// Display used to show the loading animation.
/// {@endtemplate} /// {@endtemplate}
class LoadingDisplay extends TextComponent with HasGameRef<PinballGame> { class LoadingDisplay extends TextComponent {
/// {@template loading_display} /// {@template loading_display}
LoadingDisplay(); LoadingDisplay();
@ -27,7 +28,7 @@ class LoadingDisplay extends TextComponent with HasGameRef<PinballGame> {
position = Vector2(0, -10); position = Vector2(0, -10);
anchor = Anchor.center; anchor = Anchor.center;
text = _label = gameRef.l10n.loading; text = _label = readProvider<AppLocalizations>().loading;
textRenderer = _bodyTextPaint; textRenderer = _bodyTextPaint;
await add( await add(

@ -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:pinball/game/game.dart'; import 'package:pinball/game/game.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';
@ -41,14 +42,14 @@ class ControlledBall extends Ball with Controls<BallController> {
/// Controller attached to a [Ball] that handles its game related logic. /// Controller attached to a [Ball] that handles its game related logic.
/// {@endtemplate} /// {@endtemplate}
class BallController extends ComponentController<Ball> class BallController extends ComponentController<Ball>
with HasGameRef<PinballGame> { with FlameBlocReader<GameBloc, GameState> {
/// {@macro ball_controller} /// {@macro ball_controller}
BallController(Ball ball) : super(ball); BallController(Ball ball) : super(ball);
/// Stops the [Ball] inside of the [SparkyComputer] while the turbo charge /// Stops the [Ball] inside of the [SparkyComputer] while the turbo charge
/// sequence runs, then boosts the ball out of the computer. /// sequence runs, then boosts the ball out of the computer.
Future<void> turboCharge() async { Future<void> turboCharge() async {
gameRef.read<GameBloc>().add(const SparkyTurboChargeActivated()); bloc.add(const SparkyTurboChargeActivated());
component.stop(); component.stop();
// TODO(alestiago): Refactor this hard coded duration once the following is // TODO(alestiago): Refactor this hard coded duration once the following is

@ -21,7 +21,7 @@ class ControlledFlipper extends Flipper with Controls<FlipperController> {
/// A [ComponentController] that controls a [Flipper]s movement. /// A [ComponentController] that controls a [Flipper]s movement.
/// {@endtemplate} /// {@endtemplate}
class FlipperController extends ComponentController<Flipper> class FlipperController extends ComponentController<Flipper>
with KeyboardHandler, BlocComponent<GameBloc, GameState> { with KeyboardHandler, FlameBlocReader<GameBloc, GameState> {
/// {@macro flipper_controller} /// {@macro flipper_controller}
FlipperController(Flipper flipper) FlipperController(Flipper flipper)
: _keys = flipper.side.flipperKeys, : _keys = flipper.side.flipperKeys,
@ -37,7 +37,7 @@ class FlipperController extends ComponentController<Flipper>
RawKeyEvent event, RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed, Set<LogicalKeyboardKey> keysPressed,
) { ) {
if (state?.status.isGameOver ?? false) return true; if (!bloc.state.status.isPlaying) return true;
if (!_keys.contains(event.logicalKey)) return true; if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) { if (event is RawKeyDownEvent) {

@ -24,13 +24,13 @@ class ControlledPlunger extends Plunger with Controls<PlungerController> {
} }
} }
/// A behavior attached to the plunger when it launches the ball /// A behavior attached to the plunger when it launches the ball which plays the
/// which plays the related sound effects. /// related sound effects.
class PlungerNoiseBehavior extends Component with HasGameRef<PinballGame> { class PlungerNoiseBehavior extends Component {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
gameRef.player.play(PinballAudio.launcher); readProvider<PinballPlayer>().play(PinballAudio.launcher);
} }
@override @override
@ -44,7 +44,7 @@ class PlungerNoiseBehavior extends Component with HasGameRef<PinballGame> {
/// A [ComponentController] that controls a [Plunger]s movement. /// A [ComponentController] that controls a [Plunger]s movement.
/// {@endtemplate} /// {@endtemplate}
class PlungerController extends ComponentController<Plunger> class PlungerController extends ComponentController<Plunger>
with KeyboardHandler, BlocComponent<GameBloc, GameState> { with KeyboardHandler, FlameBlocReader<GameBloc, GameState> {
/// {@macro plunger_controller} /// {@macro plunger_controller}
PlungerController(Plunger plunger) : super(plunger); PlungerController(Plunger plunger) : super(plunger);
@ -62,7 +62,7 @@ class PlungerController extends ComponentController<Plunger>
RawKeyEvent event, RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed, Set<LogicalKeyboardKey> keysPressed,
) { ) {
if (state?.status.isGameOver ?? false) return true; if (bloc.state.status.isGameOver) return true;
if (!_keys.contains(event.logicalKey)) return true; if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) { if (event is RawKeyDownEvent) {

@ -1,11 +1,12 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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';
/// Adds a [GameBonus.dinoChomp] when a [Ball] is chomped by the [ChromeDino]. /// Adds a [GameBonus.dinoChomp] when a [Ball] is chomped by the [ChromeDino].
class ChromeDinoBonusBehavior extends Component class ChromeDinoBonusBehavior extends Component
with HasGameRef<PinballGame>, ParentIsA<DinoDesert> { with ParentIsA<DinoDesert>, FlameBlocReader<GameBloc, GameState> {
@override @override
void onMount() { void onMount() {
super.onMount(); super.onMount();
@ -18,7 +19,7 @@ class ChromeDinoBonusBehavior extends Component
final listenWhen = state.status == ChromeDinoStatus.chomping; final listenWhen = state.status == ChromeDinoStatus.chomping;
if (!listenWhen) return; if (!listenWhen) return;
gameRef.read<GameBloc>().add(const BonusActivated(GameBonus.dinoChomp)); bloc.add(const BonusActivated(GameBonus.dinoChomp));
}); });
} }
} }

@ -1,12 +1,12 @@
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:pinball/game/game.dart'; import 'package:pinball/game/game.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';
/// Handles removing a [Ball] from the game. /// Handles removing a [Ball] from the game.
class DrainingBehavior extends ContactBehavior<Drain> class DrainingBehavior extends ContactBehavior<Drain> with HasGameRef {
with HasGameRef<PinballGame> {
@override @override
void beginContact(Object other, Contact contact) { void beginContact(Object other, Contact contact) {
super.beginContact(other, contact); super.beginContact(other, contact);
@ -15,7 +15,11 @@ class DrainingBehavior extends ContactBehavior<Drain>
other.removeFromParent(); other.removeFromParent();
final ballsLeft = gameRef.descendants().whereType<Ball>().length; final ballsLeft = gameRef.descendants().whereType<Ball>().length;
if (ballsLeft - 1 == 0) { if (ballsLeft - 1 == 0) {
gameRef.read<GameBloc>().add(const RoundLost()); ancestors()
.whereType<FlameBlocProvider<GameBloc, GameState>>()
.first
.bloc
.add(const RoundLost());
} }
} }
} }

@ -1,7 +1,9 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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';
import 'package:pinball_theme/pinball_theme.dart';
/// Bonus obtained at the [FlutterForest]. /// Bonus obtained at the [FlutterForest].
/// ///
@ -9,7 +11,10 @@ import 'package:pinball_flame/pinball_flame.dart';
/// progresses. When the [Signpost] fully progresses, the [GameBonus.dashNest] /// progresses. When the [Signpost] fully progresses, the [GameBonus.dashNest]
/// is awarded, and the [DashNestBumper.main] releases a new [Ball]. /// is awarded, and the [DashNestBumper.main] releases a new [Ball].
class FlutterForestBonusBehavior extends Component class FlutterForestBonusBehavior extends Component
with ParentIsA<FlutterForest>, HasGameRef<PinballGame> { with
ParentIsA<FlutterForest>,
HasGameRef,
FlameBlocReader<GameBloc, GameState> {
@override @override
void onMount() { void onMount() {
super.onMount(); super.onMount();
@ -35,12 +40,11 @@ class FlutterForestBonusBehavior extends Component
} }
if (signpost.bloc.isFullyProgressed()) { if (signpost.bloc.isFullyProgressed()) {
gameRef bloc.add(const BonusActivated(GameBonus.dashNest));
.read<GameBloc>()
.add(const BonusActivated(GameBonus.dashNest));
canvas.add( canvas.add(
ControlledBall.bonus(characterTheme: gameRef.characterTheme) ControlledBall.bonus(
..initialPosition = Vector2(29.5, -24.5), characterTheme: readProvider<CharacterTheme>(),
)..initialPosition = Vector2(29.5, -24.5),
); );
animatronic.playing = true; animatronic.playing = true;
signpost.bloc.onProgressed(); signpost.bloc.onProgressed();

@ -2,10 +2,12 @@ import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// Listens to the [GameBloc] and updates the game accordingly. /// Listens to the [GameBloc] and updates the game accordingly.
class GameBlocStatusListener extends Component class GameBlocStatusListener extends Component
with BlocComponent<GameBloc, GameState>, HasGameRef<PinballGame> { with FlameBlocListenable<GameBloc, GameState>, HasGameRef {
@override @override
bool listenWhen(GameState? previousState, GameState newState) { bool listenWhen(GameState? previousState, GameState newState) {
return previousState?.status != newState.status; return previousState?.status != newState.status;
@ -17,14 +19,14 @@ class GameBlocStatusListener extends Component
case GameStatus.waiting: case GameStatus.waiting:
break; break;
case GameStatus.playing: case GameStatus.playing:
gameRef.player.play(PinballAudio.backgroundMusic); readProvider<PinballPlayer>().play(PinballAudio.backgroundMusic);
gameRef.overlays.remove(PinballGame.playButtonOverlay); gameRef.overlays.remove(PinballGame.playButtonOverlay);
break; break;
case GameStatus.gameOver: case GameStatus.gameOver:
gameRef.player.play(PinballAudio.gameOverVoiceOver); readProvider<PinballPlayer>().play(PinballAudio.gameOverVoiceOver);
gameRef.descendants().whereType<Backbox>().first.requestInitials( gameRef.descendants().whereType<Backbox>().first.requestInitials(
score: state.displayScore, score: state.displayScore,
character: gameRef.characterTheme, character: readProvider<CharacterTheme>(),
); );
break; break;
} }

@ -1,4 +1,5 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -6,7 +7,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// Adds a [GameBonus.googleWord] when all [GoogleLetter]s are activated. /// Adds a [GameBonus.googleWord] when all [GoogleLetter]s are activated.
class GoogleWordBonusBehavior extends Component class GoogleWordBonusBehavior extends Component
with HasGameRef<PinballGame>, ParentIsA<GoogleWord> { with ParentIsA<GoogleWord>, FlameBlocReader<GameBloc, GameState> {
@override @override
void onMount() { void onMount() {
super.onMount(); super.onMount();
@ -21,10 +22,8 @@ class GoogleWordBonusBehavior extends Component
.every((letter) => letter.bloc.state == GoogleLetterState.lit); .every((letter) => letter.bloc.state == GoogleLetterState.lit);
if (achievedBonus) { if (achievedBonus) {
gameRef.player.play(PinballAudio.google); readProvider<PinballPlayer>().play(PinballAudio.google);
gameRef bloc.add(const BonusActivated(GameBonus.googleWord));
.read<GameBloc>()
.add(const BonusActivated(GameBonus.googleWord));
for (final letter in googleLetters) { for (final letter in googleLetters) {
letter.bloc.onReset(); letter.bloc.onReset();
} }

@ -6,10 +6,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// Toggle each [Multiball] when there is a bonus ball. /// Toggle each [Multiball] when there is a bonus ball.
class MultiballsBehavior extends Component class MultiballsBehavior extends Component
with with ParentIsA<Multiballs>, FlameBlocListenable<GameBloc, GameState> {
HasGameRef<PinballGame>,
ParentIsA<Multiballs>,
BlocComponent<GameBloc, GameState> {
@override @override
bool listenWhen(GameState? previousState, GameState newState) { bool listenWhen(GameState? previousState, GameState newState) {
final hasChanged = previousState?.bonusHistory != newState.bonusHistory; final hasChanged = previousState?.bonusHistory != newState.bonusHistory;

@ -6,10 +6,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// Toggle each [Multiplier] when GameState.multiplier changes. /// Toggle each [Multiplier] when GameState.multiplier changes.
class MultipliersBehavior extends Component class MultipliersBehavior extends Component
with with ParentIsA<Multipliers>, FlameBlocListenable<GameBloc, GameState> {
HasGameRef<PinballGame>,
ParentIsA<Multipliers>,
BlocComponent<GameBloc, GameState> {
@override @override
bool listenWhen(GameState? previousState, GameState newState) { bool listenWhen(GameState? previousState, GameState newState) {
return previousState?.multiplier != newState.multiplier; return previousState?.multiplier != newState.multiplier;

@ -17,13 +17,20 @@ import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart'; import 'package:pinball_theme/pinball_theme.dart';
class PinballGame extends PinballForge2DGame class PinballGame extends PinballForge2DGame
with FlameBloc, HasKeyboardHandlerComponents, MultiTouchTapDetector { with HasKeyboardHandlerComponents, MultiTouchTapDetector {
PinballGame({ PinballGame({
required this.characterTheme, required CharacterTheme characterTheme,
required this.leaderboardRepository, required this.leaderboardRepository,
required this.l10n, required GameBloc gameBloc,
required this.player, required AppLocalizations l10n,
}) : super(gravity: Vector2(0, 30)) { required PinballPlayer player,
}) : _gameBloc = gameBloc,
_player = player,
_characterTheme = characterTheme,
_l10n = l10n,
super(
gravity: Vector2(0, 30),
) {
images.prefix = ''; images.prefix = '';
} }
@ -33,63 +40,68 @@ class PinballGame extends PinballForge2DGame
@override @override
Color backgroundColor() => Colors.transparent; Color backgroundColor() => Colors.transparent;
final CharacterTheme characterTheme; final CharacterTheme _characterTheme;
final PinballPlayer player; final PinballPlayer _player;
final LeaderboardRepository leaderboardRepository; final LeaderboardRepository leaderboardRepository;
final AppLocalizations l10n; final AppLocalizations _l10n;
final GameBloc _gameBloc;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
final machine = [ await add(
BoardBackgroundSpriteComponent(), FlameBlocProvider<GameBloc, GameState>.value(
Boundaries(), value: _gameBloc,
Backbox(leaderboardRepository: leaderboardRepository),
];
final decals = [
GoogleWord(position: Vector2(-4.25, 1.8)),
Multipliers(),
Multiballs(),
SkillShot(
children: [ children: [
ScoringContactBehavior(points: Points.oneMillion), MultiFlameProvider(
providers: [
FlameProvider<PinballPlayer>.value(_player),
FlameProvider<CharacterTheme>.value(_characterTheme),
FlameProvider<LeaderboardRepository>.value(leaderboardRepository),
FlameProvider<AppLocalizations>.value(_l10n),
],
children: [
GameBlocStatusListener(),
BallSpawningBehavior(),
CameraFocusingBehavior(),
CanvasComponent(
onSpritePainted: (paint) {
if (paint.filterQuality != FilterQuality.medium) {
paint.filterQuality = FilterQuality.medium;
}
},
children: [
ZCanvasComponent(
children: [
BoardBackgroundSpriteComponent(),
Boundaries(),
Backbox(leaderboardRepository: leaderboardRepository),
GoogleWord(position: Vector2(-4.25, 1.8)),
Multipliers(),
Multiballs(),
SkillShot(
children: [
ScoringContactBehavior(points: Points.oneMillion),
],
),
AndroidAcres(),
DinoDesert(),
FlutterForest(),
SparkyScorch(),
Drain(),
BottomGroup(),
Launcher(),
],
),
],
),
],
),
], ],
), ),
];
final characterAreas = [
AndroidAcres(),
DinoDesert(),
FlutterForest(),
SparkyScorch(),
];
await addAll(
[
GameBlocStatusListener(),
BallSpawningBehavior(),
CameraFocusingBehavior(),
CanvasComponent(
onSpritePainted: (paint) {
if (paint.filterQuality != FilterQuality.medium) {
paint.filterQuality = FilterQuality.medium;
}
},
children: [
ZCanvasComponent(
children: [
...machine,
...decals,
...characterAreas,
Drain(),
BottomGroup(),
Launcher(),
],
),
],
),
],
); );
await super.onLoad(); await super.onLoad();
@ -149,11 +161,13 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
required LeaderboardRepository leaderboardRepository, required LeaderboardRepository leaderboardRepository,
required AppLocalizations l10n, required AppLocalizations l10n,
required PinballPlayer player, required PinballPlayer player,
required GameBloc gameBloc,
}) : super( }) : super(
characterTheme: characterTheme, characterTheme: characterTheme,
player: player, player: player,
leaderboardRepository: leaderboardRepository, leaderboardRepository: leaderboardRepository,
l10n: l10n, l10n: l10n,
gameBloc: gameBloc,
); );
Vector2? lineStart; Vector2? lineStart;

@ -40,33 +40,40 @@ class PinballGamePage extends StatelessWidget {
final player = context.read<PinballPlayer>(); final player = context.read<PinballPlayer>();
final leaderboardRepository = context.read<LeaderboardRepository>(); final leaderboardRepository = context.read<LeaderboardRepository>();
final game = isDebugMode return BlocProvider(
? DebugPinballGame( create: (_) => GameBloc(),
characterTheme: characterTheme, child: Builder(
player: player, builder: (context) {
leaderboardRepository: leaderboardRepository, final gameBloc = context.read<GameBloc>();
l10n: context.l10n, final game = isDebugMode
) ? DebugPinballGame(
: PinballGame( characterTheme: characterTheme,
characterTheme: characterTheme, player: player,
player: player, leaderboardRepository: leaderboardRepository,
leaderboardRepository: leaderboardRepository, l10n: context.l10n,
l10n: context.l10n, gameBloc: gameBloc,
); )
: PinballGame(
characterTheme: characterTheme,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: context.l10n,
gameBloc: gameBloc,
);
final loadables = [ final loadables = [
...game.preLoadAssets(), ...game.preLoadAssets(),
...player.load(), ...player.load(),
...BonusAnimation.loadAssets(), ...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(), ...SelectedCharacter.loadAssets(),
]; ];
return MultiBlocProvider( return BlocProvider(
providers: [ create: (_) => AssetsManagerCubit(loadables)..load(),
BlocProvider(create: (_) => GameBloc()), child: PinballGameView(game: game),
BlocProvider(create: (_) => AssetsManagerCubit(loadables)..load()), );
], },
child: PinballGameView(game: game), ),
); );
} }
} }

@ -1,20 +1,17 @@
// ignore_for_file: cascade_invocations, prefer_const_constructors // ignore_for_file: cascade_invocations, prefer_const_constructors
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.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
class _MockMultiplierCubit extends Mock implements MultiplierCubit {} Future<void> onLoad() async {
images.prefix = '';
void main() { await images.loadAll([
group('Multiplier', () {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.multiplier.x2.lit.keyName, Assets.images.multiplier.x2.lit.keyName,
Assets.images.multiplier.x2.dimmed.keyName, Assets.images.multiplier.x2.dimmed.keyName,
Assets.images.multiplier.x3.lit.keyName, Assets.images.multiplier.x3.lit.keyName,
@ -25,8 +22,16 @@ void main() {
Assets.images.multiplier.x5.dimmed.keyName, Assets.images.multiplier.x5.dimmed.keyName,
Assets.images.multiplier.x6.lit.keyName, Assets.images.multiplier.x6.lit.keyName,
Assets.images.multiplier.x6.dimmed.keyName, Assets.images.multiplier.x6.dimmed.keyName,
]; ]);
final flameTester = FlameTester(() => TestGame(assets)); }
}
class _MockMultiplierCubit extends Mock implements MultiplierCubit {}
void main() {
group('Multiplier', () {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(_TestGame.new);
late MultiplierCubit bloc; late MultiplierCubit bloc;
setUp(() { setUp(() {
@ -85,7 +90,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'lit when bloc state is lit', 'lit when bloc state is lit',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -116,7 +121,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x2-lit.png'), matchesGoldenFile('../golden/multipliers/x2-lit.png'),
); );
}, },
@ -125,7 +130,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'dimmed when bloc state is dimmed', 'dimmed when bloc state is dimmed',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -156,7 +161,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x2-dimmed.png'), matchesGoldenFile('../golden/multipliers/x2-dimmed.png'),
); );
}, },
@ -169,7 +174,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'lit when bloc state is lit', 'lit when bloc state is lit',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -200,7 +205,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x3-lit.png'), matchesGoldenFile('../golden/multipliers/x3-lit.png'),
); );
}, },
@ -209,7 +214,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'dimmed when bloc state is dimmed', 'dimmed when bloc state is dimmed',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -240,7 +245,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x3-dimmed.png'), matchesGoldenFile('../golden/multipliers/x3-dimmed.png'),
); );
}, },
@ -253,7 +258,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'lit when bloc state is lit', 'lit when bloc state is lit',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -284,7 +289,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x4-lit.png'), matchesGoldenFile('../golden/multipliers/x4-lit.png'),
); );
}, },
@ -293,7 +298,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'dimmed when bloc state is dimmed', 'dimmed when bloc state is dimmed',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -324,7 +329,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x4-dimmed.png'), matchesGoldenFile('../golden/multipliers/x4-dimmed.png'),
); );
}, },
@ -337,7 +342,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'lit when bloc state is lit', 'lit when bloc state is lit',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -368,7 +373,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x5-lit.png'), matchesGoldenFile('../golden/multipliers/x5-lit.png'),
); );
}, },
@ -377,7 +382,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'dimmed when bloc state is dimmed', 'dimmed when bloc state is dimmed',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -408,7 +413,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x5-dimmed.png'), matchesGoldenFile('../golden/multipliers/x5-dimmed.png'),
); );
}, },
@ -421,7 +426,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'lit when bloc state is lit', 'lit when bloc state is lit',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -452,7 +457,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x6-lit.png'), matchesGoldenFile('../golden/multipliers/x6-lit.png'),
); );
}, },
@ -461,7 +466,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'dimmed when bloc state is dimmed', 'dimmed when bloc state is dimmed',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
whenListen( whenListen(
bloc, bloc,
@ -492,7 +497,7 @@ void main() {
); );
await expectLater( await expectLater(
find.byGame<TestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/multipliers/x6-dimmed.png'), matchesGoldenFile('../golden/multipliers/x6-dimmed.png'),
); );
}, },

@ -3,6 +3,7 @@ library pinball_flame;
export 'src/canvas/canvas.dart'; export 'src/canvas/canvas.dart';
export 'src/component_controller.dart'; export 'src/component_controller.dart';
export 'src/contact_behavior.dart'; export 'src/contact_behavior.dart';
export 'src/flame_provider.dart';
export 'src/keyboard_input_controller.dart'; export 'src/keyboard_input_controller.dart';
export 'src/parent_is_a.dart'; export 'src/parent_is_a.dart';
export 'src/pinball_forge2d_game.dart'; export 'src/pinball_forge2d_game.dart';

@ -26,6 +26,7 @@ class ContactBehavior<T extends BodyComponent> extends Component
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad();
if (_fixturesUserData.isNotEmpty) { if (_fixturesUserData.isNotEmpty) {
for (final fixture in _targetedFixtures) { for (final fixture in _targetedFixtures) {
fixture.userData = _UserData.fromFixture(fixture)..add(this); fixture.userData = _UserData.fromFixture(fixture)..add(this);

@ -0,0 +1,65 @@
// ignore_for_file: public_member_api_docs
import 'package:flame/components.dart';
class FlameProvider<T> extends Component {
FlameProvider.value(
this.provider, {
Iterable<Component>? children,
}) : super(
children: children,
);
final T provider;
}
class MultiFlameProvider extends Component {
MultiFlameProvider({
required List<FlameProvider<dynamic>> providers,
Iterable<Component>? children,
}) : _providers = providers,
_initialChildren = children,
assert(providers.isNotEmpty, 'At least one provider must be given') {
_addProviders();
}
final List<FlameProvider<dynamic>> _providers;
final Iterable<Component>? _initialChildren;
FlameProvider<dynamic>? _lastProvider;
Future<void> _addProviders() async {
final _list = [..._providers];
var current = _list.removeAt(0);
while (_list.isNotEmpty) {
final provider = _list.removeAt(0);
await current.add(provider);
current = provider;
}
await add(_providers.first);
_lastProvider = current;
_initialChildren?.forEach(add);
}
@override
Future<void> add(Component component) async {
if (_lastProvider == null) {
await super.add(component);
}
await _lastProvider?.add(component);
}
}
extension ReadFlameProvider on Component {
T readProvider<T>() {
final providers = ancestors().whereType<FlameProvider<T>>();
assert(
providers.isNotEmpty,
'No FlameProvider<$T> available on the component tree',
);
return providers.first.provider;
}
}

@ -0,0 +1,103 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_flame/pinball_flame.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(FlameGame.new);
group(
'FlameProvider',
() {
test('can be instantiated', () {
expect(
FlameProvider<bool>.value(true),
isA<FlameProvider<bool>>(),
);
});
flameTester.test('can be loaded', (game) async {
final component = FlameProvider<bool>.value(true);
await game.ensureAdd(component);
expect(game.children, contains(component));
});
flameTester.test('adds children', (game) async {
final component = Component();
final provider = FlameProvider<bool>.value(
true,
children: [component],
);
await game.ensureAdd(provider);
expect(provider.children, contains(component));
});
},
);
group('MultiFlameProvider', () {
test('can be instantiated', () {
expect(
MultiFlameProvider(
providers: [
FlameProvider<bool>.value(true),
],
),
isA<MultiFlameProvider>(),
);
});
flameTester.test('adds multiple providers', (game) async {
final provider1 = FlameProvider<bool>.value(true);
final provider2 = FlameProvider<bool>.value(true);
final providers = MultiFlameProvider(
providers: [provider1, provider2],
);
await game.ensureAdd(providers);
expect(providers.children, contains(provider1));
expect(provider1.children, contains(provider2));
});
flameTester.test('adds children under provider', (game) async {
final component = Component();
final provider = FlameProvider<bool>.value(true);
final providers = MultiFlameProvider(
providers: [provider],
children: [component],
);
await game.ensureAdd(providers);
expect(provider.children, contains(component));
});
});
group(
'ReadFlameProvider',
() {
flameTester.test('loads provider', (game) async {
final component = Component();
final provider = FlameProvider<bool>.value(
true,
children: [component],
);
await game.ensureAdd(provider);
expect(component.readProvider<bool>(), isTrue);
});
flameTester.test(
'throws assertionError when no provider is found',
(game) async {
final component = Component();
await game.ensureAdd(component);
expect(
() => component.readProvider<bool>(),
throwsAssertionError,
);
},
);
},
);
}

@ -238,7 +238,7 @@ packages:
name: flame_bloc name: flame_bloc
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.4.0"
flame_forge2d: flame_forge2d:
dependency: "direct main" dependency: "direct main"
description: description:

@ -15,7 +15,7 @@ dependencies:
firebase_auth: ^3.3.16 firebase_auth: ^3.3.16
firebase_core: ^1.15.0 firebase_core: ^1.15.0
flame: ^1.1.1 flame: ^1.1.1
flame_bloc: ^1.2.0 flame_bloc: ^1.4.0
flame_forge2d: flame_forge2d:
git: git:
url: https://github.com/flame-engine/flame/ url: https://github.com/flame-engine/flame/

@ -1,3 +1,4 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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';
@ -7,6 +8,21 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import '../helpers/helpers.dart'; import '../helpers/helpers.dart';
bool _tapTextSpan(RichText richText, String text) {
final isTapped = !richText.text.visitChildren(
(visitor) => _findTextAndTap(visitor, text),
);
return isTapped;
}
bool _findTextAndTap(InlineSpan visitor, String text) {
if (visitor is TextSpan && visitor.text == text) {
(visitor.recognizer as TapGestureRecognizer?)?.onTap?.call();
return false;
}
return true;
}
class _MockUrlLauncher extends Mock class _MockUrlLauncher extends Mock
with MockPlatformInterfaceMixin with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {} implements UrlLauncherPlatform {}
@ -49,7 +65,7 @@ void main() {
).thenAnswer((_) async => true); ).thenAnswer((_) async => true);
await tester.pumpApp(const Footer()); await tester.pumpApp(const Footer());
final flutterTextFinder = find.byWidgetPredicate( final flutterTextFinder = find.byWidgetPredicate(
(widget) => widget is RichText && tapTextSpan(widget, 'Flutter'), (widget) => widget is RichText && _tapTextSpan(widget, 'Flutter'),
); );
await tester.tap(flutterTextFinder); await tester.tap(flutterTextFinder);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
@ -84,7 +100,7 @@ void main() {
).thenAnswer((_) async => true); ).thenAnswer((_) async => true);
await tester.pumpApp(const Footer()); await tester.pumpApp(const Footer());
final firebaseTextFinder = find.byWidgetPredicate( final firebaseTextFinder = find.byWidgetPredicate(
(widget) => widget is RichText && tapTextSpan(widget, 'Firebase'), (widget) => widget is RichText && _tapTextSpan(widget, 'Firebase'),
); );
await tester.tap(firebaseTextFinder); await tester.tap(firebaseTextFinder);
await tester.pumpAndSettle(); await tester.pumpAndSettle();

@ -6,14 +6,24 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../helpers/helpers.dart';
class _TestGame extends Forge2DGame {
Future<void> pump(_TestBodyComponent child, {required PinballPlayer player}) {
return ensureAdd(
FlameProvider<PinballPlayer>.value(
player,
children: [
child,
],
),
);
}
}
class _TestBodyComponent extends BodyComponent { class _TestBodyComponent extends BodyComponent {
@override @override
Body createBody() { Body createBody() => world.createBody(BodyDef());
return world.createBody(BodyDef());
}
} }
class _MockPinballPlayer extends Mock implements PinballPlayer {} class _MockPinballPlayer extends Mock implements PinballPlayer {}
@ -26,9 +36,7 @@ void main() {
group('BumperNoiseBehavior', () {}); group('BumperNoiseBehavior', () {});
late PinballPlayer player; late PinballPlayer player;
final flameTester = FlameTester( final flameTester = FlameTester(_TestGame.new);
() => EmptyPinballTestGame(player: player),
);
setUp(() { setUp(() {
player = _MockPinballPlayer(); player = _MockPinballPlayer();
@ -39,7 +47,7 @@ void main() {
setUp: (game, _) async { setUp: (game, _) async {
final behavior = BumperNoiseBehavior(); final behavior = BumperNoiseBehavior();
final parent = _TestBodyComponent(); final parent = _TestBodyComponent();
await game.ensureAdd(parent); await game.pump(parent, player: player);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
behavior.beginContact(Object(), _MockContact()); behavior.beginContact(Object(), _MockContact());
}, },

@ -1,22 +1,20 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:flame/game.dart';
import 'package:flame_bloc/flame_bloc.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:pinball/game/behaviors/camera_focusing_behavior.dart'; import 'package:pinball/game/behaviors/camera_focusing_behavior.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
group( group(
'CameraFocusingBehavior', 'CameraFocusingBehavior',
() { () {
final flameTester = FlameTester( final flameTester = FlameTester(FlameGame.new);
EmptyPinballTestGame.new,
);
test('can be instantiated', () { test('can be instantiated', () {
expect( expect(
@ -26,9 +24,14 @@ void main() {
}); });
flameTester.test('loads', (game) async { flameTester.test('loads', (game) async {
final behavior = CameraFocusingBehavior(); late final behavior = CameraFocusingBehavior();
await game.ensureAdd(behavior); await game.ensureAdd(
expect(game.contains(behavior), isTrue); FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [behavior],
),
);
expect(game.descendants(), contains(behavior));
}); });
flameTester.test( flameTester.test(
@ -38,7 +41,12 @@ void main() {
final previousZoom = game.camera.zoom; final previousZoom = game.camera.zoom;
expect(game.camera.follow, isNull); expect(game.camera.follow, isNull);
await game.ensureAdd(behavior); await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [behavior],
),
);
expect(game.camera.follow, isNotNull); expect(game.camera.follow, isNotNull);
expect(game.camera.zoom, isNot(equals(previousZoom))); expect(game.camera.zoom, isNot(equals(previousZoom)));
@ -77,8 +85,12 @@ void main() {
const GameState.initial().copyWith(status: GameStatus.playing); const GameState.initial().copyWith(status: GameStatus.playing);
final behavior = CameraFocusingBehavior(); final behavior = CameraFocusingBehavior();
await game.ensureAdd(behavior); await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [behavior],
),
);
behavior.onNewState(playing); behavior.onNewState(playing);
final previousPosition = game.camera.position.clone(); final previousPosition = game.camera.position.clone();
await game.ready(); await game.ready();
@ -103,7 +115,12 @@ void main() {
); );
final behavior = CameraFocusingBehavior(); final behavior = CameraFocusingBehavior();
await game.ensureAdd(behavior); await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [behavior],
),
);
behavior.onNewState(playing); behavior.onNewState(playing);
final previousPosition = game.camera.position.clone(); final previousPosition = game.camera.position.clone();

@ -1,6 +1,6 @@
// ignore_for_file: cascade_invocations // 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_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 +10,29 @@ import 'package:pinball/game/game.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';
import '../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
Assets.images.score.fiveThousand.keyName,
Assets.images.score.twentyThousand.keyName,
Assets.images.score.twoHundredThousand.keyName,
Assets.images.score.oneMillion.keyName,
]);
}
Future<void> pump(BodyComponent child, {GameBloc? gameBloc}) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc ?? GameBloc(),
children: [
ZCanvasComponent(children: [child])
],
),
);
}
}
class _TestBodyComponent extends BodyComponent { class _TestBodyComponent extends BodyComponent {
@override @override
@ -27,18 +49,13 @@ class _MockContact extends Mock implements Contact {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.score.fiveThousand.keyName,
Assets.images.score.twentyThousand.keyName,
Assets.images.score.twoHundredThousand.keyName,
Assets.images.score.oneMillion.keyName,
];
late GameBloc bloc; late GameBloc bloc;
late Ball ball; late Ball ball;
late BodyComponent parent; late BodyComponent parent;
setUp(() { setUp(() {
bloc = _MockGameBloc();
ball = _MockBall(); ball = _MockBall();
final ballBody = _MockBody(); final ballBody = _MockBody();
when(() => ball.body).thenReturn(ballBody); when(() => ball.body).thenReturn(ballBody);
@ -47,23 +64,7 @@ void main() {
parent = _TestBodyComponent(); parent = _TestBodyComponent();
}); });
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>( final flameBlocTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () {
bloc = _MockGameBloc();
const state = GameState(
totalScore: 0,
roundScore: 0,
multiplier: 1,
rounds: 3,
bonusHistory: [],
status: GameStatus.playing,
);
whenListen(bloc, Stream.value(state), initialState: state);
return bloc;
},
assets: assets,
);
group('ScoringBehavior', () { group('ScoringBehavior', () {
test('can be instantiated', () { test('can be instantiated', () {
@ -76,16 +77,16 @@ void main() {
); );
}); });
flameBlocTester.testGameWidget( flameBlocTester.test(
'can be loaded', 'can be loaded',
setUp: (game, tester) async { (game) async {
final canvas = ZCanvasComponent(children: [parent]); await game.pump(parent);
final behavior = ScoringBehavior( final behavior = ScoringBehavior(
points: Points.fiveThousand, points: Points.fiveThousand,
position: Vector2.zero(), position: Vector2.zero(),
); );
await parent.add(behavior); await parent.ensureAdd(behavior);
await game.ensureAdd(canvas);
expect( expect(
parent.firstChild<ScoringBehavior>(), parent.firstChild<ScoringBehavior>(),
@ -94,13 +95,12 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameBlocTester.test(
'emits Scored event with points when added', 'emits Scored event with points when added',
setUp: (game, tester) async { (game) async {
const points = Points.oneMillion; await game.pump(parent, gameBloc: bloc);
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
const points = Points.oneMillion;
final behavior = ScoringBehavior( final behavior = ScoringBehavior(
points: points, points: points,
position: Vector2(0, 0), position: Vector2(0, 0),
@ -115,11 +115,10 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameBlocTester.test(
'correctly renders text', 'correctly renders text',
setUp: (game, tester) async { (game) async {
final canvas = ZCanvasComponent(children: [parent]); await game.pump(parent);
await game.ensureAdd(canvas);
const points = Points.oneMillion; const points = Points.oneMillion;
final position = Vector2.all(1); final position = Vector2.all(1);
@ -145,8 +144,8 @@ void main() {
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
'is removed after duration', 'is removed after duration',
setUp: (game, tester) async { setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]); await game.onLoad();
await game.ensureAdd(canvas); await game.pump(parent);
const duration = 2.0; const duration = 2.0;
final behavior = ScoringBehavior( final behavior = ScoringBehavior(
@ -173,8 +172,8 @@ void main() {
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
'beginContact adds a ScoringBehavior', 'beginContact adds a ScoringBehavior',
setUp: (game, tester) async { setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]); await game.onLoad();
await game.ensureAdd(canvas); await game.pump(parent);
final behavior = ScoringContactBehavior(points: Points.oneMillion); final behavior = ScoringContactBehavior(points: Points.oneMillion);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
@ -192,8 +191,8 @@ void main() {
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
"beginContact positions text at contact's position", "beginContact positions text at contact's position",
setUp: (game, tester) async { setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]); await game.onLoad();
await game.ensureAdd(canvas); await game.pump(parent);
final behavior = ScoringContactBehavior(points: Points.oneMillion); final behavior = ScoringContactBehavior(points: Points.oneMillion);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);

@ -1,5 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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:pinball/game/behaviors/bumper_noise_behavior.dart'; import 'package:pinball/game/behaviors/bumper_noise_behavior.dart';
@ -7,50 +9,62 @@ import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.animatronic.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.android.bumper.a.lit.keyName,
Assets.images.android.bumper.a.dimmed.keyName,
Assets.images.android.bumper.b.lit.keyName,
Assets.images.android.bumper.b.dimmed.keyName,
Assets.images.android.bumper.cow.lit.keyName,
Assets.images.android.bumper.cow.dimmed.keyName,
]);
}
Future<void> pump(AndroidAcres child) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [child],
),
);
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.animatronic.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.android.bumper.a.lit.keyName,
Assets.images.android.bumper.a.dimmed.keyName,
Assets.images.android.bumper.b.lit.keyName,
Assets.images.android.bumper.b.dimmed.keyName,
Assets.images.android.bumper.cow.lit.keyName,
Assets.images.android.bumper.cow.dimmed.keyName,
];
group('AndroidAcres', () { group('AndroidAcres', () {
final flameTester = FlameTester( final flameTester = FlameTester(_TestGame.new);
() => EmptyPinballTestGame(assets: assets),
);
flameTester.test('loads correctly', (game) async { flameTester.test('loads correctly', (game) async {
final component = AndroidAcres(); final component = AndroidAcres();
await game.ensureAdd(component); await game.pump(component);
expect(game.contains(component), isTrue); expect(game.descendants(), contains(component));
}); });
group('loads', () { group('loads', () {
flameTester.test( flameTester.test(
'an AndroidSpaceship', 'an AndroidSpaceship',
(game) async { (game) async {
await game.ensureAdd(AndroidAcres()); await game.pump(AndroidAcres());
expect( expect(
game.descendants().whereType<AndroidSpaceship>().length, game.descendants().whereType<AndroidSpaceship>().length,
equals(1), equals(1),
@ -61,7 +75,7 @@ void main() {
flameTester.test( flameTester.test(
'an AndroidAnimatronic', 'an AndroidAnimatronic',
(game) async { (game) async {
await game.ensureAdd(AndroidAcres()); await game.pump(AndroidAcres());
expect( expect(
game.descendants().whereType<AndroidAnimatronic>().length, game.descendants().whereType<AndroidAnimatronic>().length,
equals(1), equals(1),
@ -72,7 +86,7 @@ void main() {
flameTester.test( flameTester.test(
'a SpaceshipRamp', 'a SpaceshipRamp',
(game) async { (game) async {
await game.ensureAdd(AndroidAcres()); await game.pump(AndroidAcres());
expect( expect(
game.descendants().whereType<SpaceshipRamp>().length, game.descendants().whereType<SpaceshipRamp>().length,
equals(1), equals(1),
@ -83,7 +97,7 @@ void main() {
flameTester.test( flameTester.test(
'a SpaceshipRail', 'a SpaceshipRail',
(game) async { (game) async {
await game.ensureAdd(AndroidAcres()); await game.pump(AndroidAcres());
expect( expect(
game.descendants().whereType<SpaceshipRail>().length, game.descendants().whereType<SpaceshipRail>().length,
equals(1), equals(1),
@ -94,7 +108,7 @@ void main() {
flameTester.test( flameTester.test(
'three AndroidBumper', 'three AndroidBumper',
(game) async { (game) async {
await game.ensureAdd(AndroidAcres()); await game.pump(AndroidAcres());
expect( expect(
game.descendants().whereType<AndroidBumper>().length, game.descendants().whereType<AndroidBumper>().length,
equals(3), equals(3),
@ -105,7 +119,7 @@ void main() {
flameTester.test( flameTester.test(
'three AndroidBumpers with BumperNoiseBehavior', 'three AndroidBumpers with BumperNoiseBehavior',
(game) async { (game) async {
await game.ensureAdd(AndroidAcres()); await game.pump(AndroidAcres());
final bumpers = game.descendants().whereType<AndroidBumper>(); final bumpers = game.descendants().whereType<AndroidBumper>();
for (final bumper in bumpers) { for (final bumper in bumpers) {
expect( expect(
@ -119,7 +133,7 @@ void main() {
flameTester.test('adds an AndroidSpaceshipBonusBehavior', (game) async { flameTester.test('adds an AndroidSpaceshipBonusBehavior', (game) async {
final androidAcres = AndroidAcres(); final androidAcres = AndroidAcres();
await game.ensureAdd(androidAcres); await game.pump(androidAcres);
expect( expect(
androidAcres.children.whereType<AndroidSpaceshipBonusBehavior>().single, androidAcres.children.whereType<AndroidSpaceshipBonusBehavior>().single,
isNotNull, isNotNull,

@ -1,7 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame/extensions.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';
@ -9,55 +9,63 @@ import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.animatronic.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.android.bumper.a.lit.keyName,
Assets.images.android.bumper.a.dimmed.keyName,
Assets.images.android.bumper.b.lit.keyName,
Assets.images.android.bumper.b.dimmed.keyName,
Assets.images.android.bumper.cow.lit.keyName,
Assets.images.android.bumper.cow.dimmed.keyName,
]);
}
Future<void> pump(
AndroidAcres child, {
required GameBloc gameBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
children: [child],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.animatronic.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.android.bumper.a.lit.keyName,
Assets.images.android.bumper.a.dimmed.keyName,
Assets.images.android.bumper.b.lit.keyName,
Assets.images.android.bumper.b.dimmed.keyName,
Assets.images.android.bumper.cow.lit.keyName,
Assets.images.android.bumper.cow.dimmed.keyName,
];
group('AndroidSpaceshipBonusBehavior', () { group('AndroidSpaceshipBonusBehavior', () {
late GameBloc gameBloc; late GameBloc gameBloc;
setUp(() { setUp(() {
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
flameBlocTester.testGameWidget( flameTester.testGameWidget(
'adds GameBonus.androidSpaceship to the game ' 'adds GameBonus.androidSpaceship to the game '
'when android spacehship has a bonus', 'when android spacehship has a bonus',
setUp: (game, tester) async { setUp: (game, tester) async {
@ -66,7 +74,10 @@ void main() {
final androidSpaceship = AndroidSpaceship(position: Vector2.zero()); final androidSpaceship = AndroidSpaceship(position: Vector2.zero());
await parent.add(androidSpaceship); await parent.add(androidSpaceship);
await game.ensureAdd(parent); await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
androidSpaceship.bloc.onBallEntered(); androidSpaceship.bloc.onBallEntered();

@ -1,3 +1,6 @@
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';
@ -7,7 +10,30 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme; import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../../../helpers/test_games.dart'; class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.load(theme.Assets.images.dash.ball.keyName);
}
Future<void> pump(
Iterable<Component> children, {
GameBloc? gameBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc ?? GameBloc(),
children: [
FlameProvider<theme.CharacterTheme>.value(
const theme.DashTheme(),
children: children,
),
],
),
);
}
}
class _MockGameState extends Mock implements GameState {} class _MockGameState extends Mock implements GameState {}
@ -17,7 +43,7 @@ void main() {
group( group(
'BallSpawningBehavior', 'BallSpawningBehavior',
() { () {
final flameTester = FlameTester(EmptyPinballTestGame.new); final flameTester = FlameTester(_TestGame.new);
test('can be instantiated', () { test('can be instantiated', () {
expect( expect(
@ -30,8 +56,8 @@ void main() {
'loads', 'loads',
(game) async { (game) async {
final behavior = BallSpawningBehavior(); final behavior = BallSpawningBehavior();
await game.ensureAdd(behavior); await game.pump([behavior]);
expect(game.contains(behavior), isTrue); expect(game.descendants(), contains(behavior));
}, },
); );
@ -97,9 +123,8 @@ void main() {
flameTester.test( flameTester.test(
'onNewState adds a ball', 'onNewState adds a ball',
(game) async { (game) async {
await game.images.load(theme.Assets.images.dash.ball.keyName);
final behavior = BallSpawningBehavior(); final behavior = BallSpawningBehavior();
await game.ensureAddAll([ await game.pump([
behavior, behavior,
ZCanvasComponent(), ZCanvasComponent(),
Plunger.test(compressionDistance: 10), Plunger.test(compressionDistance: 10),

@ -3,6 +3,8 @@
import 'dart:async'; import 'dart:async';
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_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';
@ -12,7 +14,41 @@ import 'package:pinball/game/game.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';
import '../../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.score.oneMillion.keyName,
]);
}
Future<void> pump(
SpaceshipRamp child, {
required GameBloc gameBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
children: [
ZCanvasComponent(children: [child]),
],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
@ -23,21 +59,6 @@ class _MockStreamSubscription extends Mock
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.score.oneMillion.keyName,
];
group('RampBonusBehavior', () { group('RampBonusBehavior', () {
const shotPoints = Points.oneMillion; const shotPoints = Points.oneMillion;
@ -46,22 +67,13 @@ void main() {
setUp(() { setUp(() {
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
flameBlocTester.testGameWidget( flameTester.test(
'when hits are multiples of 10 times adds a ScoringBehavior', 'when hits are multiples of 10 times adds a ScoringBehavior',
setUp: (game, tester) async { (game) async {
final bloc = _MockSpaceshipRampCubit(); final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>(); final streamController = StreamController<SpaceshipRampState>();
whenListen( whenListen(
@ -69,14 +81,13 @@ void main() {
streamController.stream, streamController.stream,
initialState: SpaceshipRampState(hits: 9), initialState: SpaceshipRampState(hits: 9),
); );
final behavior = RampBonusBehavior( final behavior = RampBonusBehavior(points: shotPoints);
points: shotPoints, final parent = SpaceshipRamp.test(bloc: bloc);
);
final parent = SpaceshipRamp.test(
bloc: bloc,
);
await game.ensureAdd(ZCanvasComponent(children: [parent])); await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
streamController.add(SpaceshipRampState(hits: 10)); streamController.add(SpaceshipRampState(hits: 10));
@ -88,9 +99,9 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameTester.test(
"when hits are not multiple of 10 times doesn't add any ScoringBehavior", "when hits are not multiple of 10 times doesn't add any ScoringBehavior",
setUp: (game, tester) async { (game) async {
final bloc = _MockSpaceshipRampCubit(); final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>(); final streamController = StreamController<SpaceshipRampState>();
whenListen( whenListen(
@ -98,14 +109,13 @@ void main() {
streamController.stream, streamController.stream,
initialState: SpaceshipRampState.initial(), initialState: SpaceshipRampState.initial(),
); );
final behavior = RampBonusBehavior( final behavior = RampBonusBehavior(points: shotPoints);
points: shotPoints, final parent = SpaceshipRamp.test(bloc: bloc);
);
final parent = SpaceshipRamp.test(
bloc: bloc,
);
await game.ensureAdd(ZCanvasComponent(children: [parent])); await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
streamController.add(SpaceshipRampState(hits: 1)); streamController.add(SpaceshipRampState(hits: 1));
@ -117,9 +127,9 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameTester.test(
'closes subscription when removed', 'closes subscription when removed',
setUp: (game, tester) async { (game) async {
final bloc = _MockSpaceshipRampCubit(); final bloc = _MockSpaceshipRampCubit();
whenListen( whenListen(
bloc, bloc,
@ -135,11 +145,12 @@ void main() {
points: shotPoints, points: shotPoints,
subscription: subscription, subscription: subscription,
); );
final parent = SpaceshipRamp.test( final parent = SpaceshipRamp.test(bloc: bloc);
bloc: bloc,
);
await game.ensureAdd(ZCanvasComponent(children: [parent])); await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
parent.remove(behavior); parent.remove(behavior);

@ -3,6 +3,8 @@
import 'dart:async'; import 'dart:async';
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_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';
@ -12,7 +14,41 @@ import 'package:pinball/game/game.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';
import '../../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.score.fiveThousand.keyName,
]);
}
Future<void> pump(
SpaceshipRamp child, {
required GameBloc gameBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
children: [
ZCanvasComponent(children: [child]),
],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
@ -23,21 +59,6 @@ class _MockStreamSubscription extends Mock
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.score.fiveThousand.keyName,
];
group('RampShotBehavior', () { group('RampShotBehavior', () {
const shotPoints = Points.fiveThousand; const shotPoints = Points.fiveThousand;
@ -46,23 +67,14 @@ void main() {
setUp(() { setUp(() {
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameBlocTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
flameBlocTester.testGameWidget( flameBlocTester.test(
'when hits are not multiple of 10 times ' 'when hits are not multiple of 10 times '
'increases multiplier and adds a ScoringBehavior', 'increases multiplier and adds a ScoringBehavior',
setUp: (game, tester) async { (game) async {
final bloc = _MockSpaceshipRampCubit(); final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>(); final streamController = StreamController<SpaceshipRampState>();
whenListen( whenListen(
@ -70,14 +82,13 @@ void main() {
streamController.stream, streamController.stream,
initialState: SpaceshipRampState.initial(), initialState: SpaceshipRampState.initial(),
); );
final behavior = RampShotBehavior( final behavior = RampShotBehavior(points: shotPoints);
points: shotPoints, final parent = SpaceshipRamp.test(bloc: bloc);
);
final parent = SpaceshipRamp.test(
bloc: bloc,
);
await game.ensureAdd(ZCanvasComponent(children: [parent])); await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
streamController.add(SpaceshipRampState(hits: 1)); streamController.add(SpaceshipRampState(hits: 1));
@ -90,10 +101,10 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameBlocTester.test(
'when hits multiple of 10 times ' 'when hits multiple of 10 times '
"doesn't increase multiplier, neither ScoringBehavior", "doesn't increase multiplier, neither ScoringBehavior",
setUp: (game, tester) async { (game) async {
final bloc = _MockSpaceshipRampCubit(); final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>(); final streamController = StreamController<SpaceshipRampState>();
whenListen( whenListen(
@ -108,7 +119,10 @@ void main() {
bloc: bloc, bloc: bloc,
); );
await game.ensureAdd(ZCanvasComponent(children: [parent])); await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
streamController.add(SpaceshipRampState(hits: 10)); streamController.add(SpaceshipRampState(hits: 10));
@ -121,9 +135,9 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameBlocTester.test(
'closes subscription when removed', 'closes subscription when removed',
setUp: (game, tester) async { (game) async {
final bloc = _MockSpaceshipRampCubit(); final bloc = _MockSpaceshipRampCubit();
whenListen( whenListen(
bloc, bloc,
@ -143,7 +157,10 @@ void main() {
bloc: bloc, bloc: bloc,
); );
await game.ensureAdd(ZCanvasComponent(children: [parent])); await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
parent.remove(behavior); parent.remove(behavior);

@ -3,7 +3,9 @@
import 'dart:async'; 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/input.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/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -15,9 +17,39 @@ import 'package:pinball/game/components/backbox/displays/displays.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.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_theme/pinball_theme.dart' as theme; import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents {
final character = theme.DashTheme();
@override
Color backgroundColor() => Colors.transparent;
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
character.leaderboardIcon.keyName,
Assets.images.backbox.marquee.keyName,
Assets.images.backbox.displayDivider.keyName,
]);
}
Future<void> pump(Backbox component) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [
FlameProvider.value(
_MockAppLocalizations(),
children: [component],
),
],
),
);
}
}
class _MockRawKeyUpEvent extends Mock implements RawKeyUpEvent { class _MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
@override @override
@ -65,18 +97,8 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
const character = theme.AndroidTheme();
final assets = [ final flameTester = FlameTester(_TestGame.new);
character.leaderboardIcon.keyName,
Assets.images.backbox.marquee.keyName,
Assets.images.backbox.displayDivider.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(
assets: assets,
l10n: _MockAppLocalizations(),
),
);
late BackboxBloc bloc; late BackboxBloc bloc;
@ -94,27 +116,26 @@ void main() {
'loads correctly', 'loads correctly',
(game) async { (game) async {
final backbox = Backbox.test(bloc: bloc); final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox); await game.pump(backbox);
expect(game.descendants(), contains(backbox));
expect(game.children, contains(backbox));
}, },
); );
flameTester.testGameWidget( flameTester.testGameWidget(
'renders correctly', 'renders correctly',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
game.camera game.camera
..followVector2(Vector2(0, -130)) ..followVector2(Vector2(0, -130))
..zoom = 6; ..zoom = 6;
await game.ensureAdd( await game.pump(
Backbox.test(bloc: bloc), Backbox.test(bloc: bloc),
); );
await tester.pump(); await tester.pump();
}, },
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(
find.byGame<EmptyPinballTestGame>(), find.byGame<_TestGame>(),
matchesGoldenFile('../golden/backbox.png'), matchesGoldenFile('../golden/backbox.png'),
); );
}, },
@ -128,10 +149,10 @@ void main() {
leaderboardRepository: _MockLeaderboardRepository(), leaderboardRepository: _MockLeaderboardRepository(),
), ),
); );
await game.ensureAdd(backbox); await game.pump(backbox);
backbox.requestInitials( backbox.requestInitials(
score: 0, score: 0,
character: character, character: game.character,
); );
await game.ready(); await game.ready();
@ -148,7 +169,7 @@ void main() {
final bloc = _MockBackboxBloc(); final bloc = _MockBackboxBloc();
final state = InitialsFormState( final state = InitialsFormState(
score: 10, score: 10,
character: theme.AndroidTheme(), character: game.character,
); );
whenListen( whenListen(
bloc, bloc,
@ -156,7 +177,7 @@ void main() {
initialState: state, initialState: state,
); );
final backbox = Backbox.test(bloc: bloc); final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox); await game.pump(backbox);
game.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.enter), {}); game.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.enter), {});
verify( verify(
@ -164,7 +185,7 @@ void main() {
PlayerInitialsSubmitted( PlayerInitialsSubmitted(
score: 10, score: 10,
initials: 'AAA', initials: 'AAA',
character: theme.AndroidTheme(), character: game.character,
), ),
), ),
).called(1); ).called(1);
@ -180,7 +201,7 @@ void main() {
initialState: InitialsSuccessState(), initialState: InitialsSuccessState(),
); );
final backbox = Backbox.test(bloc: bloc); final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox); await game.pump(backbox);
expect( expect(
game game
@ -201,7 +222,7 @@ void main() {
initialState: InitialsFailureState(), initialState: InitialsFailureState(),
); );
final backbox = Backbox.test(bloc: bloc); final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox); await game.pump(backbox);
expect( expect(
game game
@ -224,7 +245,7 @@ void main() {
); );
final backbox = Backbox.test(bloc: bloc); final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox); await game.pump(backbox);
backbox.removeFromParent(); backbox.removeFromParent();
await game.ready(); await game.ready();

@ -1,17 +1,50 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/input.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/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.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/game/bloc/game_bloc.dart';
import 'package:pinball/game/components/backbox/displays/initials_input_display.dart'; import 'package:pinball/game/components/backbox/displays/initials_input_display.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.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_theme/pinball_theme.dart' as theme; import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents {
final characterIconPath = theme.Assets.images.dash.leaderboardIcon.keyName;
@override
Future<void> onLoad() async {
await super.onLoad();
images.prefix = '';
await images.loadAll(
[
characterIconPath,
Assets.images.backbox.displayDivider.keyName,
],
);
}
Future<void> pump(InitialsInputDisplay component) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [
FlameProvider.value(
_MockAppLocalizations(),
children: [component],
),
],
),
);
}
}
class _MockAppLocalizations extends Mock implements AppLocalizations { class _MockAppLocalizations extends Mock implements AppLocalizations {
@override @override
@ -38,43 +71,33 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final characterIconPath = theme.Assets.images.dash.leaderboardIcon.keyName;
final assets = [ final flameTester = FlameTester(_TestGame.new);
characterIconPath,
Assets.images.backbox.displayDivider.keyName,
];
final flameTester = FlameTester(
() => EmptyKeyboardPinballTestGame(
assets: assets,
l10n: _MockAppLocalizations(),
),
);
group('InitialsInputDisplay', () { group('InitialsInputDisplay', () {
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
(game) async { (game) async {
final initialsInputDisplay = InitialsInputDisplay( final component = InitialsInputDisplay(
score: 0, score: 0,
characterIconPath: characterIconPath, characterIconPath: game.characterIconPath,
onSubmit: (_) {}, onSubmit: (_) {},
); );
await game.ensureAdd(initialsInputDisplay); await game.pump(component);
expect(game.descendants(), contains(component));
expect(game.children, contains(initialsInputDisplay));
}, },
); );
flameTester.testGameWidget( flameTester.testGameWidget(
'can change the initials', 'can change the initials',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
final initialsInputDisplay = InitialsInputDisplay( final component = InitialsInputDisplay(
score: 1000, score: 1000,
characterIconPath: characterIconPath, characterIconPath: game.characterIconPath,
onSubmit: (_) {}, onSubmit: (_) {},
); );
await game.ensureAdd(initialsInputDisplay); await game.pump(component);
// Focus is on the first letter // Focus is on the first letter
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
@ -99,10 +122,10 @@ void main() {
await tester.pump(); await tester.pump();
}, },
verify: (game, tester) async { verify: (game, tester) async {
final initialsInputDisplay = final component =
game.descendants().whereType<InitialsInputDisplay>().single; game.descendants().whereType<InitialsInputDisplay>().single;
expect(initialsInputDisplay.initials, equals('BCB')); expect(component.initials, equals('BCB'));
}, },
); );
@ -110,15 +133,15 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'submits the initials', 'submits the initials',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
final initialsInputDisplay = InitialsInputDisplay( final component = InitialsInputDisplay(
score: 1000, score: 1000,
characterIconPath: characterIconPath, characterIconPath: game.characterIconPath,
onSubmit: (value) { onSubmit: (value) {
submitedInitials = value; submitedInitials = value;
}, },
); );
await game.ensureAdd(initialsInputDisplay); await game.pump(component);
await tester.sendKeyEvent(LogicalKeyboardKey.enter); await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pump(); await tester.pump();
@ -132,7 +155,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'cycles the char up and down when it has focus', 'cycles the char up and down when it has focus',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
await game.ensureAdd( await game.ensureAdd(
InitialsLetterPrompt(hasFocus: true, position: Vector2.zero()), InitialsLetterPrompt(hasFocus: true, position: Vector2.zero()),
); );
@ -154,7 +177,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
"does nothing when it doesn't have focus", "does nothing when it doesn't have focus",
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
await game.ensureAdd( await game.ensureAdd(
InitialsLetterPrompt(position: Vector2.zero()), InitialsLetterPrompt(position: Vector2.zero()),
); );
@ -170,7 +193,7 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'blinks the prompt when it has the focus', 'blinks the prompt when it has the focus',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
await game.ensureAdd( await game.ensureAdd(
InitialsLetterPrompt(position: Vector2.zero(), hasFocus: true), InitialsLetterPrompt(position: Vector2.zero(), hasFocus: true),
); );

@ -1,15 +1,14 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:flame/components.dart'; import 'package:flame/components.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:pinball/game/components/backbox/displays/initials_submission_failure_display.dart'; import 'package:pinball/game/components/backbox/displays/initials_submission_failure_display.dart';
import '../../../../helpers/helpers.dart';
void main() { void main() {
group('InitialsSubmissionFailureDisplay', () { group('InitialsSubmissionFailureDisplay', () {
final flameTester = FlameTester(EmptyKeyboardPinballTestGame.new); final flameTester = FlameTester(Forge2DGame.new);
flameTester.test('renders correctly', (game) async { flameTester.test('renders correctly', (game) async {
await game.ensureAdd(InitialsSubmissionFailureDisplay()); await game.ensureAdd(InitialsSubmissionFailureDisplay());

@ -1,15 +1,14 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:flame/components.dart'; import 'package:flame/components.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:pinball/game/components/backbox/displays/initials_submission_success_display.dart'; import 'package:pinball/game/components/backbox/displays/initials_submission_success_display.dart';
import '../../../../helpers/helpers.dart';
void main() { void main() {
group('InitialsSubmissionSuccessDisplay', () { group('InitialsSubmissionSuccessDisplay', () {
final flameTester = FlameTester(EmptyKeyboardPinballTestGame.new); final flameTester = FlameTester(Forge2DGame.new);
flameTester.test('renders correctly', (game) async { flameTester.test('renders correctly', (game) async {
await game.ensureAdd(InitialsSubmissionSuccessDisplay()); await game.ensureAdd(InitialsSubmissionSuccessDisplay());

@ -1,13 +1,31 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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/game/bloc/game_bloc.dart';
import 'package:pinball/game/components/backbox/displays/loading_display.dart'; import 'package:pinball/game/components/backbox/displays/loading_display.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
Future<void> pump(LoadingDisplay component) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [
FlameProvider.value(
_MockAppLocalizations(),
children: [component],
),
],
),
);
}
}
class _MockAppLocalizations extends Mock implements AppLocalizations { class _MockAppLocalizations extends Mock implements AppLocalizations {
@override @override
@ -16,39 +34,35 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
void main() { void main() {
group('LoadingDisplay', () { group('LoadingDisplay', () {
final flameTester = FlameTester( final flameTester = FlameTester(_TestGame.new);
() => EmptyPinballTestGame(
l10n: _MockAppLocalizations(),
),
);
flameTester.test('renders correctly', (game) async { flameTester.test('renders correctly', (game) async {
await game.ensureAdd(LoadingDisplay()); await game.pump(LoadingDisplay());
final component = game.firstChild<TextComponent>(); final component = game.descendants().whereType<TextComponent>().first;
expect(component, isNotNull); expect(component, isNotNull);
expect(component?.text, equals('Loading')); expect(component.text, equals('Loading'));
}); });
flameTester.test('use ellipses as animation', (game) async { flameTester.test('use ellipses as animation', (game) async {
await game.ensureAdd(LoadingDisplay()); await game.pump(LoadingDisplay());
final component = game.firstChild<TextComponent>(); final component = game.descendants().whereType<TextComponent>().first;
expect(component?.text, equals('Loading')); expect(component.text, equals('Loading'));
final timer = component?.firstChild<TimerComponent>(); final timer = component.firstChild<TimerComponent>();
timer?.update(1.1); timer?.update(1.1);
expect(component?.text, equals('Loading.')); expect(component.text, equals('Loading.'));
timer?.update(1.1); timer?.update(1.1);
expect(component?.text, equals('Loading..')); expect(component.text, equals('Loading..'));
timer?.update(1.1); timer?.update(1.1);
expect(component?.text, equals('Loading...')); expect(component.text, equals('Loading...'));
timer?.update(1.1); timer?.update(1.1);
expect(component?.text, equals('Loading')); expect(component.text, equals('Loading'));
}); });
}); });
} }

@ -1,36 +1,47 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/forge2d_game.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:pinball/game/game.dart'; import 'package:pinball/game/game.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.kicker.left.lit.keyName,
Assets.images.kicker.left.dimmed.keyName,
Assets.images.kicker.right.lit.keyName,
Assets.images.kicker.right.dimmed.keyName,
Assets.images.baseboard.left.keyName,
Assets.images.baseboard.right.keyName,
Assets.images.flipper.left.keyName,
Assets.images.flipper.right.keyName,
]);
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.kicker.left.lit.keyName,
Assets.images.kicker.left.dimmed.keyName,
Assets.images.kicker.right.lit.keyName,
Assets.images.kicker.right.dimmed.keyName,
Assets.images.baseboard.left.keyName,
Assets.images.baseboard.right.keyName,
Assets.images.flipper.left.keyName,
Assets.images.flipper.right.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
);
group('BottomGroup', () { group('BottomGroup', () {
final flameTester = FlameTester(_TestGame.new);
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
(game) async { (game) async {
final bottomGroup = BottomGroup(); final bottomGroup = BottomGroup();
await game.ensureAdd(bottomGroup); await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [bottomGroup],
),
);
expect(game.contains(bottomGroup), isTrue); expect(game.descendants(), contains(bottomGroup));
}, },
); );
@ -39,7 +50,12 @@ void main() {
'one left flipper', 'one left flipper',
(game) async { (game) async {
final bottomGroup = BottomGroup(); final bottomGroup = BottomGroup();
await game.ensureAdd(bottomGroup); await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [bottomGroup],
),
);
final leftFlippers = final leftFlippers =
bottomGroup.descendants().whereType<Flipper>().where( bottomGroup.descendants().whereType<Flipper>().where(
@ -53,7 +69,12 @@ void main() {
'one right flipper', 'one right flipper',
(game) async { (game) async {
final bottomGroup = BottomGroup(); final bottomGroup = BottomGroup();
await game.ensureAdd(bottomGroup); await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [bottomGroup],
),
);
final rightFlippers = final rightFlippers =
bottomGroup.descendants().whereType<Flipper>().where( bottomGroup.descendants().whereType<Flipper>().where(
@ -67,7 +88,12 @@ void main() {
'two Baseboards', 'two Baseboards',
(game) async { (game) async {
final bottomGroup = BottomGroup(); final bottomGroup = BottomGroup();
await game.ensureAdd(bottomGroup); await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [bottomGroup],
),
);
final basebottomGroups = final basebottomGroups =
bottomGroup.descendants().whereType<Baseboard>(); bottomGroup.descendants().whereType<Baseboard>();
@ -79,7 +105,12 @@ void main() {
'two Kickers', 'two Kickers',
(game) async { (game) async {
final bottomGroup = BottomGroup(); final bottomGroup = BottomGroup();
await game.ensureAdd(bottomGroup); await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [bottomGroup],
),
);
final kickers = bottomGroup.descendants().whereType<Kicker>(); final kickers = bottomGroup.descendants().whereType<Kicker>();
expect(kickers.length, equals(2)); expect(kickers.length, equals(2));

@ -1,7 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame/components.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';
@ -9,32 +9,29 @@ import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme; import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
// TODO(allisonryan0002): remove once
// https://github.com/flame-engine/flame/pull/1520 is merged
class _WrappedBallController extends BallController {
_WrappedBallController(Ball ball, this._gameRef) : super(ball);
final PinballGame _gameRef;
@override @override
PinballGame get gameRef => _gameRef; Future<void> onLoad() async {
images.prefix = '';
await images.load(theme.Assets.images.dash.ball.keyName);
}
Future<void> pump(Ball child, {required GameBloc gameBloc}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
children: [child],
),
);
}
} }
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
class _MockPinballGame extends Mock implements PinballGame {}
class _MockControlledBall extends Mock implements ControlledBall {}
class _MockBall extends Mock implements Ball {} class _MockBall extends Mock implements Ball {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
theme.Assets.images.dash.ball.keyName,
];
group('BallController', () { group('BallController', () {
late Ball ball; late Ball ball;
@ -43,18 +40,9 @@ void main() {
setUp(() { setUp(() {
ball = Ball(); ball = Ball();
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameBlocTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
test('can be instantiated', () { test('can be instantiated', () {
expect( expect(
@ -63,59 +51,21 @@ void main() {
); );
}); });
group('turboCharge', () { flameBlocTester.testGameWidget(
setUpAll(() { 'turboCharge adds TurboChargeActivated',
registerFallbackValue(Vector2.zero()); setUp: (game, tester) async {
registerFallbackValue(Component()); await game.onLoad();
});
final controller = BallController(ball);
flameBlocTester.testGameWidget( await ball.add(controller);
'adds TurboChargeActivated', await game.pump(ball, gameBloc: gameBloc);
setUp: (game, tester) async {
await game.images.loadAll(assets); await controller.turboCharge();
final controller = BallController(ball); },
await ball.add(controller); verify: (game, tester) async {
await game.ensureAdd(ball); verify(() => gameBloc.add(const SparkyTurboChargeActivated()))
.called(1);
await controller.turboCharge(); },
}, );
verify: (game, tester) async {
verify(() => gameBloc.add(const SparkyTurboChargeActivated()))
.called(1);
},
);
flameBlocTester.test(
'initially stops the ball',
(game) async {
final gameRef = _MockPinballGame();
final ball = _MockControlledBall();
final controller = _WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller);
when(() => ball.add(any())).thenAnswer((_) async {});
await controller.turboCharge();
verify(ball.stop).called(1);
},
);
flameBlocTester.test(
'resumes the ball',
(game) async {
final gameRef = _MockPinballGame();
final ball = _MockControlledBall();
final controller = _WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller);
when(() => ball.add(any())).thenAnswer((_) async {});
await controller.turboCharge();
verify(ball.resume).called(1);
},
);
});
}); });
} }

@ -1,6 +1,9 @@
import 'dart:collection'; import 'dart:collection';
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/input.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/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -10,17 +13,31 @@ import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
Assets.images.flipper.left.keyName,
Assets.images.flipper.right.keyName,
]);
}
Future<void> pump(Flipper flipper, {required GameBloc gameBloc}) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
children: [flipper],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [ final flameTester = FlameTester(_TestGame.new);
Assets.images.flipper.left.keyName,
Assets.images.flipper.right.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
);
group('FlipperController', () { group('FlipperController', () {
late GameBloc gameBloc; late GameBloc gameBloc;
@ -29,12 +46,6 @@ void main() {
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
}); });
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
group('onKeyEvent', () { group('onKeyEvent', () {
final leftKeys = UnmodifiableListView([ final leftKeys = UnmodifiableListView([
LogicalKeyboardKey.arrowLeft, LogicalKeyboardKey.arrowLeft,
@ -63,11 +74,13 @@ void main() {
whenListen( whenListen(
gameBloc, gameBloc,
const Stream<GameState>.empty(), const Stream<GameState>.empty(),
initialState: const GameState.initial(), initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
); );
await game.ready(); await game.ready();
await game.add(flipper); await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative); expect(flipper.body.linearVelocity.y, isNegative);
@ -77,9 +90,9 @@ void main() {
}); });
testRawKeyDownEvents(leftKeys, (event) { testRawKeyDownEvents(leftKeys, (event) {
flameBlocTester.testGameWidget( flameTester.test(
'does nothing when is game over', 'does nothing when is game over',
setUp: (game, tester) async { (game) async {
whenListen( whenListen(
gameBloc, gameBloc,
const Stream<GameState>.empty(), const Stream<GameState>.empty(),
@ -88,10 +101,9 @@ void main() {
), ),
); );
await game.ensureAdd(flipper); await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
},
verify: (game, tester) async {
expect(flipper.body.linearVelocity.y, isZero); expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero); expect(flipper.body.linearVelocity.x, isZero);
}, },
@ -106,11 +118,13 @@ void main() {
whenListen( whenListen(
gameBloc, gameBloc,
const Stream<GameState>.empty(), const Stream<GameState>.empty(),
initialState: const GameState.initial(), initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
); );
await game.ready(); await game.ready();
await game.add(flipper); await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive); expect(flipper.body.linearVelocity.y, isPositive);
@ -131,7 +145,7 @@ void main() {
); );
await game.ready(); await game.ready();
await game.add(flipper); await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero); expect(flipper.body.linearVelocity.y, isZero);
@ -159,11 +173,13 @@ void main() {
whenListen( whenListen(
gameBloc, gameBloc,
const Stream<GameState>.empty(), const Stream<GameState>.empty(),
initialState: const GameState.initial(), initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
); );
await game.ready(); await game.ready();
await game.add(flipper); await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative); expect(flipper.body.linearVelocity.y, isNegative);
@ -180,11 +196,13 @@ void main() {
whenListen( whenListen(
gameBloc, gameBloc,
const Stream<GameState>.empty(), const Stream<GameState>.empty(),
initialState: const GameState.initial(), initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
); );
await game.ready(); await game.ready();
await game.add(flipper); await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive); expect(flipper.body.linearVelocity.y, isPositive);
@ -194,9 +212,9 @@ void main() {
}); });
testRawKeyDownEvents(rightKeys, (event) { testRawKeyDownEvents(rightKeys, (event) {
flameBlocTester.testGameWidget( flameTester.test(
'does nothing when is game over', 'does nothing when is game over',
setUp: (game, tester) async { (game) async {
whenListen( whenListen(
gameBloc, gameBloc,
const Stream<GameState>.empty(), const Stream<GameState>.empty(),
@ -205,10 +223,9 @@ void main() {
), ),
); );
await game.ensureAdd(flipper); await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
},
verify: (game, tester) async {
expect(flipper.body.linearVelocity.y, isZero); expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero); expect(flipper.body.linearVelocity.x, isZero);
}, },
@ -220,8 +237,16 @@ void main() {
'does nothing ' 'does nothing '
'when ${event.logicalKey.keyLabel} is released', 'when ${event.logicalKey.keyLabel} is released',
(game) async { (game) async {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
);
await game.ready(); await game.ready();
await game.add(flipper); await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero); expect(flipper.body.linearVelocity.y, isZero);

@ -4,6 +4,9 @@ import 'dart:collection';
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/input.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/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -11,26 +14,49 @@ import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.load(Assets.images.plunger.plunger.keyName);
}
Future<void> pump(
Plunger child, {
GameBloc? gameBloc,
PinballPlayer? pinballPlayer,
}) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc ?? GameBloc()
..add(const GameStarted()),
children: [
FlameProvider<PinballPlayer>.value(
pinballPlayer ?? _MockPinballPlayer(),
children: [child],
)
],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
class _MockPinballPlayer extends Mock implements PinballPlayer {} class _MockPinballPlayer extends Mock implements PinballPlayer {}
class _MockPinballGame extends Mock implements PinballGame {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(EmptyPinballTestGame.new); final flameTester = FlameTester(_TestGame.new);
group('PlungerController', () { group('PlungerController', () {
late GameBloc gameBloc; late GameBloc gameBloc;
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>( final flameBlocTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
);
late Plunger plunger; late Plunger plunger;
late PlungerController controller; late PlungerController controller;
@ -54,13 +80,7 @@ void main() {
'moves down ' 'moves down '
'when ${event.logicalKey.keyLabel} is pressed', 'when ${event.logicalKey.keyLabel} is pressed',
(game) async { (game) async {
whenListen( await game.pump(plunger);
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
await game.ensureAdd(plunger);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
expect(plunger.body.linearVelocity.y, isPositive); expect(plunger.body.linearVelocity.y, isPositive);
@ -75,13 +95,7 @@ void main() {
'when ${event.logicalKey.keyLabel} is released ' 'when ${event.logicalKey.keyLabel} is released '
'and plunger is below its starting position', 'and plunger is below its starting position',
(game) async { (game) async {
whenListen( await game.pump(plunger);
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
await game.ensureAdd(plunger);
plunger.body.setTransform(Vector2(0, 1), 0); plunger.body.setTransform(Vector2(0, 1), 0);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
@ -96,13 +110,7 @@ void main() {
'does not move when ${event.logicalKey.keyLabel} is released ' 'does not move when ${event.logicalKey.keyLabel} is released '
'and plunger is in its starting position', 'and plunger is in its starting position',
(game) async { (game) async {
whenListen( await game.pump(plunger);
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
await game.ensureAdd(plunger);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
expect(plunger.body.linearVelocity.y, isZero); expect(plunger.body.linearVelocity.y, isZero);
@ -123,7 +131,7 @@ void main() {
), ),
); );
await game.ensureAdd(plunger); await game.pump(plunger, gameBloc: gameBloc);
controller.onKeyEvent(event, {}); controller.onKeyEvent(event, {});
}, },
verify: (game, tester) async { verify: (game, tester) async {
@ -137,7 +145,7 @@ void main() {
flameTester.test( flameTester.test(
'adds the PlungerNoiseBehavior plunger is released', 'adds the PlungerNoiseBehavior plunger is released',
(game) async { (game) async {
await game.ensureAdd(plunger); await game.pump(plunger);
plunger.body.setTransform(Vector2(0, 1), 0); plunger.body.setTransform(Vector2(0, 1), 0);
plunger.release(); plunger.release();
@ -150,27 +158,22 @@ void main() {
}); });
group('PlungerNoiseBehavior', () { group('PlungerNoiseBehavior', () {
late PinballGame game;
late PinballPlayer player; late PinballPlayer player;
late PlungerNoiseBehavior behavior;
setUp(() { setUp(() {
game = _MockPinballGame();
player = _MockPinballPlayer(); player = _MockPinballPlayer();
when(() => game.player).thenReturn(player);
behavior = PlungerNoiseBehavior();
behavior.mockGameRef(game);
}); });
test('plays the correct sound on load', () async { flameTester.test('plays the correct sound on load', (game) async {
await behavior.onLoad(); final parent = ControlledPlunger(compressionDistance: 10);
await game.pump(parent, pinballPlayer: player);
await parent.ensureAdd(PlungerNoiseBehavior());
verify(() => player.play(PinballAudio.launcher)).called(1); verify(() => player.play(PinballAudio.launcher)).called(1);
}); });
test('is removed on the first update', () { test('is removed on the first update', () {
final parent = Component(); final parent = Component();
final behavior = PlungerNoiseBehavior();
parent.add(behavior); parent.add(behavior);
parent.update(0); // Run a tick to ensure it is added parent.update(0); // Run a tick to ensure it is added

@ -1,6 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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'; 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';
@ -8,7 +9,34 @@ import 'package:pinball/game/components/dino_desert/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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.dino.animatronic.head.keyName,
Assets.images.dino.animatronic.mouth.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.bottomWall.keyName,
Assets.images.slingshot.upper.keyName,
Assets.images.slingshot.lower.keyName,
],
);
}
Future<void> pump(
DinoDesert child, {
required GameBloc gameBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
children: [child],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
@ -16,43 +44,30 @@ class _MockBall extends Mock implements Ball {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.dino.animatronic.head.keyName,
Assets.images.dino.animatronic.mouth.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.bottomWall.keyName,
Assets.images.slingshot.upper.keyName,
Assets.images.slingshot.lower.keyName,
];
group('ChromeDinoBonusBehavior', () { group('ChromeDinoBonusBehavior', () {
late GameBloc gameBloc; late GameBloc gameBloc;
setUp(() { setUp(() {
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
flameBlocTester.testGameWidget( flameTester.testGameWidget(
'adds GameBonus.dinoChomp to the game ' 'adds GameBonus.dinoChomp to the game '
'when ChromeDinoStatus.chomping is emitted', 'when ChromeDinoStatus.chomping is emitted',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.onLoad();
final behavior = ChromeDinoBonusBehavior(); final behavior = ChromeDinoBonusBehavior();
final parent = DinoDesert.test(); final parent = DinoDesert.test();
final chromeDino = ChromeDino(); final chromeDino = ChromeDino();
await parent.add(chromeDino); await parent.add(chromeDino);
await game.ensureAdd(parent); await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
chromeDino.bloc.onChomp(_MockBall()); chromeDino.bloc.onChomp(_MockBall());

@ -1,42 +1,59 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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/game/behaviors/behaviors.dart'; import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/components/dino_desert/behaviors/behaviors.dart'; import 'package:pinball/game/components/dino_desert/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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.dino.animatronic.head.keyName,
Assets.images.dino.animatronic.mouth.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.topWallTunnel.keyName,
Assets.images.dino.bottomWall.keyName,
Assets.images.slingshot.upper.keyName,
Assets.images.slingshot.lower.keyName,
]);
}
Future<void> pump(DinoDesert child) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: _MockGameBloc(),
children: [child],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.dino.animatronic.head.keyName,
Assets.images.dino.animatronic.mouth.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.topWallTunnel.keyName,
Assets.images.dino.bottomWall.keyName,
Assets.images.slingshot.upper.keyName,
Assets.images.slingshot.lower.keyName,
];
final flameTester = FlameTester( final flameTester = FlameTester(_TestGame.new);
() => EmptyPinballTestGame(assets: assets),
);
group('DinoDesert', () { group('DinoDesert', () {
flameTester.test('loads correctly', (game) async { flameTester.test('loads correctly', (game) async {
final component = DinoDesert(); final component = DinoDesert();
await game.ensureAdd(component); await game.pump(component);
expect(game.contains(component), isTrue); expect(game.descendants(), contains(component));
}); });
group('loads', () { group('loads', () {
flameTester.test( flameTester.test(
'a ChromeDino', 'a ChromeDino',
(game) async { (game) async {
await game.ensureAdd(DinoDesert()); await game.pump(DinoDesert());
expect( expect(
game.descendants().whereType<ChromeDino>().length, game.descendants().whereType<ChromeDino>().length,
equals(1), equals(1),
@ -47,17 +64,18 @@ void main() {
flameTester.test( flameTester.test(
'DinoWalls', 'DinoWalls',
(game) async { (game) async {
await game.ensureAdd(DinoDesert()); await game.pump(DinoDesert());
expect( expect(
game.descendants().whereType<DinoWalls>().length, game.descendants().whereType<DinoWalls>().length,
equals(1), equals(1),
); );
}, },
); );
flameTester.test( flameTester.test(
'Slingshots', 'Slingshots',
(game) async { (game) async {
await game.ensureAdd(DinoDesert()); await game.pump(DinoDesert());
expect( expect(
game.descendants().whereType<Slingshots>().length, game.descendants().whereType<Slingshots>().length,
equals(1), equals(1),
@ -70,7 +88,7 @@ void main() {
flameTester.test( flameTester.test(
'ScoringContactBehavior to ChromeDino', 'ScoringContactBehavior to ChromeDino',
(game) async { (game) async {
await game.ensureAdd(DinoDesert()); await game.pump(DinoDesert());
final chromeDino = game.descendants().whereType<ChromeDino>().single; final chromeDino = game.descendants().whereType<ChromeDino>().single;
expect( expect(
@ -81,10 +99,10 @@ void main() {
); );
flameTester.test('a ChromeDinoBonusBehavior', (game) async { flameTester.test('a ChromeDinoBonusBehavior', (game) async {
final dinoDesert = DinoDesert(); final component = DinoDesert();
await game.ensureAdd(dinoDesert); await game.pump(component);
expect( expect(
dinoDesert.children.whereType<ChromeDinoBonusBehavior>().single, component.children.whereType<ChromeDinoBonusBehavior>().single,
isNotNull, isNotNull,
); );
}); });

@ -1,6 +1,6 @@
// ignore_for_file: cascade_invocations // 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_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 +10,25 @@ import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme; import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.load(theme.Assets.images.dash.ball.keyName);
}
Future<void> pump(
Drain child, {
required GameBloc gameBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
children: [child],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
@ -40,32 +58,26 @@ void main() {
); );
group('beginContact', () { group('beginContact', () {
final asset = theme.Assets.images.dash.ball.keyName;
late GameBloc gameBloc; late GameBloc gameBloc;
setUp(() { setUp(() {
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
);
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameBlocTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
);
flameBlocTester.testGameWidget( flameBlocTester.test(
'adds RoundLost when no balls left', 'adds RoundLost when no balls left',
setUp: (game, tester) async { (game) async {
await game.images.load(asset);
final drain = Drain.test(); final drain = Drain.test();
final behavior = DrainingBehavior(); final behavior = DrainingBehavior();
final ball = Ball.test(); final ball = Ball.test();
await drain.add(behavior); await drain.add(behavior);
await game.ensureAddAll([drain, ball]); await game.pump(
drain,
gameBloc: gameBloc,
);
await game.ensureAdd(ball);
behavior.beginContact(ball, _MockContact()); behavior.beginContact(ball, _MockContact());
await game.ready(); await game.ready();
@ -75,21 +87,19 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameBlocTester.test(
"doesn't add RoundLost when there are balls left", "doesn't add RoundLost when there are balls left",
setUp: (game, tester) async { (game) async {
await game.images.load(asset);
final drain = Drain.test(); final drain = Drain.test();
final behavior = DrainingBehavior(); final behavior = DrainingBehavior();
final ball1 = Ball.test(); final ball1 = Ball.test();
final ball2 = Ball.test(); final ball2 = Ball.test();
await drain.add(behavior); await drain.add(behavior);
await game.ensureAddAll([ await game.pump(
drain, drain,
ball1, gameBloc: gameBloc,
ball2, );
]); await game.ensureAddAll([ball1, ball2]);
behavior.beginContact(ball1, _MockContact()); behavior.beginContact(ball1, _MockContact());
await game.ready(); await game.ready();
@ -99,15 +109,18 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameBlocTester.test(
'removes the Ball', 'removes the Ball',
setUp: (game, tester) async { (game) async {
await game.images.load(asset);
final drain = Drain.test(); final drain = Drain.test();
final behavior = DrainingBehavior(); final behavior = DrainingBehavior();
final ball = Ball.test(); final ball = Ball.test();
await drain.add(behavior); await drain.add(behavior);
await game.ensureAddAll([drain, ball]); await game.pump(
drain,
gameBloc: gameBloc,
);
await game.ensureAdd(ball);
behavior.beginContact(ball, _MockContact()); behavior.beginContact(ball, _MockContact());
await game.ready(); await game.ready();

@ -6,11 +6,9 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import '../../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new); final flameTester = FlameTester(Forge2DGame.new);
group('Drain', () { group('Drain', () {
flameTester.test( flameTester.test(

@ -1,8 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'dart:async'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/forge2d_game.dart';
import 'package:bloc_test/bloc_test.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';
@ -12,7 +11,37 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme; import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
Assets.images.dash.animatronic.keyName,
theme.Assets.images.dash.ball.keyName,
]);
}
Future<void> pump(
FlutterForest child, {
required GameBloc gameBloc,
}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
children: [
FlameProvider<theme.CharacterTheme>.value(
const theme.DashTheme(),
children: [
ZCanvasComponent(
children: [child],
),
],
),
],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
@ -21,34 +50,21 @@ void main() {
group('FlutterForestBonusBehavior', () { group('FlutterForestBonusBehavior', () {
late GameBloc gameBloc; late GameBloc gameBloc;
final assets = [
Assets.images.dash.animatronic.keyName,
theme.Assets.images.dash.ball.keyName,
];
setUp(() { setUp(() {
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
void _contactedBumper(DashNestBumper bumper) => void _contactedBumper(DashNestBumper bumper) =>
bumper.bloc.onBallContacted(); bumper.bloc.onBallContacted();
flameBlocTester.testGameWidget( flameTester.testGameWidget(
'adds GameBonus.dashNest to the game ' 'adds GameBonus.dashNest to the game '
'when bumpers are activated three times', 'when bumpers are activated three times',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
final behavior = FlutterForestBonusBehavior(); final behavior = FlutterForestBonusBehavior();
final parent = FlutterForest.test(); final parent = FlutterForest.test();
final bumpers = [ final bumpers = [
@ -58,7 +74,7 @@ void main() {
]; ];
final animatronic = DashAnimatronic(); final animatronic = DashAnimatronic();
final signpost = Signpost.test(bloc: SignpostCubit()); final signpost = Signpost.test(bloc: SignpostCubit());
await game.ensureAdd(ZCanvasComponent(children: [parent])); await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAddAll([...bumpers, animatronic, signpost]); await parent.ensureAddAll([...bumpers, animatronic, signpost]);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
@ -76,11 +92,11 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameTester.testGameWidget(
'adds a new Ball to the game ' 'adds a new Ball to the game '
'when bumpers are activated three times', 'when bumpers are activated three times',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
final behavior = FlutterForestBonusBehavior(); final behavior = FlutterForestBonusBehavior();
final parent = FlutterForest.test(); final parent = FlutterForest.test();
final bumpers = [ final bumpers = [
@ -90,7 +106,7 @@ void main() {
]; ];
final animatronic = DashAnimatronic(); final animatronic = DashAnimatronic();
final signpost = Signpost.test(bloc: SignpostCubit()); final signpost = Signpost.test(bloc: SignpostCubit());
await game.ensureAdd(ZCanvasComponent(children: [parent])); await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAddAll([...bumpers, animatronic, signpost]); await parent.ensureAddAll([...bumpers, animatronic, signpost]);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
@ -110,11 +126,11 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget( flameTester.testGameWidget(
'progress the signpost ' 'progress the signpost '
'when bumpers are activated', 'when bumpers are activated',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); await game.onLoad();
final behavior = FlutterForestBonusBehavior(); final behavior = FlutterForestBonusBehavior();
final parent = FlutterForest.test(); final parent = FlutterForest.test();
final bumpers = [ final bumpers = [
@ -124,7 +140,7 @@ void main() {
]; ];
final animatronic = DashAnimatronic(); final animatronic = DashAnimatronic();
final signpost = Signpost.test(bloc: SignpostCubit()); final signpost = Signpost.test(bloc: SignpostCubit());
await game.ensureAdd(ZCanvasComponent(children: [parent])); await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAddAll([...bumpers, animatronic, signpost]); await parent.ensureAddAll([...bumpers, animatronic, signpost]);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);

@ -1,40 +1,67 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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/game/behaviors/behaviors.dart'; import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.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';
import '../../../helpers/helpers.dart'; 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,
Assets.images.dash.animatronic.keyName,
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
]);
}
Future<void> pump(FlutterForest child) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: _MockGameBloc(),
children: [
FlameProvider.value(
_MockPinballPlayer(),
children: [
ZCanvasComponent(children: [child]),
],
),
],
),
);
}
}
class _MockPinballPlayer extends Mock implements PinballPlayer {}
class _MockGameBloc extends Mock implements GameBloc {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [ final flameTester = FlameTester(_TestGame.new);
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,
Assets.images.dash.animatronic.keyName,
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
);
group('FlutterForest', () { group('FlutterForest', () {
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
(game) async { (game) async {
final flutterForest = FlutterForest(); final component = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest])); await game.pump(component);
expect(game.descendants(), contains(flutterForest)); expect(game.descendants(), contains(component));
}, },
); );
@ -42,8 +69,8 @@ void main() {
flameTester.test( flameTester.test(
'a Signpost', 'a Signpost',
(game) async { (game) async {
final flutterForest = FlutterForest(); final component = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest])); await game.pump(component);
expect( expect(
game.descendants().whereType<Signpost>().length, game.descendants().whereType<Signpost>().length,
equals(1), equals(1),
@ -54,8 +81,8 @@ void main() {
flameTester.test( flameTester.test(
'a DashAnimatronic', 'a DashAnimatronic',
(game) async { (game) async {
final flutterForest = FlutterForest(); final component = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest])); await game.pump(component);
expect( expect(
game.descendants().whereType<DashAnimatronic>().length, game.descendants().whereType<DashAnimatronic>().length,
equals(1), equals(1),
@ -66,8 +93,8 @@ void main() {
flameTester.test( flameTester.test(
'three DashNestBumper', 'three DashNestBumper',
(game) async { (game) async {
final flutterForest = FlutterForest(); final component = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest])); await game.pump(component);
expect( expect(
game.descendants().whereType<DashNestBumper>().length, game.descendants().whereType<DashNestBumper>().length,
equals(3), equals(3),
@ -78,8 +105,8 @@ void main() {
flameTester.test( flameTester.test(
'three DashNestBumpers with BumperNoiseBehavior', 'three DashNestBumpers with BumperNoiseBehavior',
(game) async { (game) async {
final flutterForest = FlutterForest(); final component = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest])); await game.pump(component);
final bumpers = game.descendants().whereType<DashNestBumper>(); final bumpers = game.descendants().whereType<DashNestBumper>();
for (final bumper in bumpers) { for (final bumper in bumpers) {
expect( expect(

@ -1,39 +1,89 @@
// ignore_for_file: type_annotate_public_apis, prefer_const_constructors // ignore_for_file: cascade_invocations
import 'package:flame/game.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:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_theme/pinball_theme.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class _MockPinballGame extends Mock implements PinballGame {} import 'package:pinball_theme/pinball_theme.dart' as theme;
class _MockBackbox extends Mock implements Backbox {} class _TestGame extends Forge2DGame {
@override
class _MockActiveOverlaysNotifier extends Mock Future<void> onLoad() async {
implements ActiveOverlaysNotifier {} images.prefix = '';
await images.load(Assets.images.backbox.marquee.keyName);
}
Future<void> pump(
Iterable<Component> children, {
PinballPlayer? pinballPlayer,
}) async {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [
MultiFlameProvider(
providers: [
FlameProvider<PinballPlayer>.value(
pinballPlayer ?? _MockPinballPlayer(),
),
FlameProvider<theme.CharacterTheme>.value(
const theme.DashTheme(),
),
],
children: children,
),
],
),
);
}
}
class _MockPinballPlayer extends Mock implements PinballPlayer {} class _MockPinballPlayer extends Mock implements PinballPlayer {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('GameBlocStatusListener', () { group('GameBlocStatusListener', () {
setUpAll(() { test('can be instantiated', () {
registerFallbackValue(AndroidTheme()); expect(
GameBlocStatusListener(),
isA<GameBlocStatusListener>(),
);
}); });
final flameTester = FlameTester(_TestGame.new);
flameTester.test(
'can be loaded',
(game) async {
final component = GameBlocStatusListener();
await game.pump([component]);
expect(game.descendants(), contains(component));
},
);
group('listenWhen', () { group('listenWhen', () {
test('is true when the game over state has changed', () { test('is true when the game over state has changed', () {
final state = GameState( const state = GameState(
totalScore: 0, totalScore: 0,
roundScore: 10, roundScore: 10,
multiplier: 1, multiplier: 1,
rounds: 0, rounds: 0,
bonusHistory: const [], bonusHistory: [],
status: GameStatus.playing, status: GameStatus.playing,
); );
final previous = GameState.initial(); const previous = GameState.initial();
expect( expect(
GameBlocStatusListener().listenWhen(previous, state), GameBlocStatusListener().listenWhen(previous, state),
isTrue, isTrue,
@ -42,92 +92,52 @@ void main() {
}); });
group('onNewState', () { group('onNewState', () {
late PinballGame game; flameTester.test(
late Backbox backbox;
late GameBlocStatusListener gameBlocStatusListener;
late PinballPlayer pinballPlayer;
late ActiveOverlaysNotifier overlays;
setUp(() {
game = _MockPinballGame();
backbox = _MockBackbox();
gameBlocStatusListener = GameBlocStatusListener();
overlays = _MockActiveOverlaysNotifier();
pinballPlayer = _MockPinballPlayer();
gameBlocStatusListener.mockGameRef(game);
when(
() => backbox.requestInitials(
score: any(named: 'score'),
character: any(named: 'character'),
),
).thenAnswer((_) async {});
when(() => overlays.remove(any())).thenAnswer((_) => true);
when(() => game.descendants().whereType<Backbox>())
.thenReturn([backbox]);
when(() => game.overlays).thenReturn(overlays);
when(() => game.characterTheme).thenReturn(DashTheme());
when(() => game.player).thenReturn(pinballPlayer);
});
test(
'changes the backbox display when the game is over', 'changes the backbox display when the game is over',
() { (game) async {
final state = GameState( final component = GameBlocStatusListener();
totalScore: 0, final repository = _MockLeaderboardRepository();
roundScore: 10, final backbox = Backbox(leaderboardRepository: repository);
multiplier: 1, final state = const GameState.initial()
rounds: 0, ..copyWith(
bonusHistory: const [], status: GameStatus.gameOver,
status: GameStatus.gameOver, );
);
gameBlocStatusListener.onNewState(state); await game.pump([component, backbox]);
verify( expect(() => component.onNewState(state), returnsNormally);
() => backbox.requestInitials(
score: any(named: 'score'),
character: any(named: 'character'),
),
).called(1);
}, },
); );
test( flameTester.test(
'changes the backbox when it is not a game over',
() {
gameBlocStatusListener.onNewState(
GameState.initial().copyWith(status: GameStatus.playing),
);
verify(() => overlays.remove(PinballGame.playButtonOverlay))
.called(1);
},
);
test(
'plays the background music on start', 'plays the background music on start',
() { (game) async {
gameBlocStatusListener.onNewState( final player = _MockPinballPlayer();
GameState.initial().copyWith(status: GameStatus.playing), final component = GameBlocStatusListener();
await game.pump([component], pinballPlayer: player);
component.onNewState(
const GameState.initial().copyWith(status: GameStatus.playing),
); );
verify(() => pinballPlayer.play(PinballAudio.backgroundMusic)) verify(() => player.play(PinballAudio.backgroundMusic)).called(1);
.called(1);
}, },
); );
test( flameTester.test(
'plays the game over voice over when it is game over', 'plays the game over voice over when it is game over',
() { (game) async {
gameBlocStatusListener.onNewState( final player = _MockPinballPlayer();
GameState.initial().copyWith(status: GameStatus.gameOver), final component = GameBlocStatusListener();
final repository = _MockLeaderboardRepository();
final backbox = Backbox(leaderboardRepository: repository);
await game.pump([component, backbox], pinballPlayer: player);
component.onNewState(
const GameState.initial().copyWith(status: GameStatus.gameOver),
); );
verify(() => pinballPlayer.play(PinballAudio.gameOverVoiceOver)) verify(() => player.play(PinballAudio.gameOverVoiceOver)).called(1);
.called(1);
}, },
); );
}); });

@ -1,55 +1,71 @@
// ignore_for_file: cascade_invocations // 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: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/game/components/google_word/behaviors/behaviors.dart'; import 'package:pinball/game/components/google_word/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../../helpers/helpers.dart'; class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
images.prefix = '';
await images.loadAll([
Assets.images.googleWord.letter1.lit.keyName,
Assets.images.googleWord.letter1.dimmed.keyName,
Assets.images.googleWord.letter2.lit.keyName,
Assets.images.googleWord.letter2.dimmed.keyName,
Assets.images.googleWord.letter3.lit.keyName,
Assets.images.googleWord.letter3.dimmed.keyName,
Assets.images.googleWord.letter4.lit.keyName,
Assets.images.googleWord.letter4.dimmed.keyName,
Assets.images.googleWord.letter5.lit.keyName,
Assets.images.googleWord.letter5.dimmed.keyName,
Assets.images.googleWord.letter6.lit.keyName,
Assets.images.googleWord.letter6.dimmed.keyName,
]);
}
Future<void> pump(GoogleWord child, {required GameBloc gameBloc}) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc,
children: [
FlameProvider<PinballPlayer>.value(
_MockPinballPlayer(),
children: [child],
)
],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
class _MockPinballPlayer extends Mock implements PinballPlayer {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.googleWord.letter1.lit.keyName,
Assets.images.googleWord.letter1.dimmed.keyName,
Assets.images.googleWord.letter2.lit.keyName,
Assets.images.googleWord.letter2.dimmed.keyName,
Assets.images.googleWord.letter3.lit.keyName,
Assets.images.googleWord.letter3.dimmed.keyName,
Assets.images.googleWord.letter4.lit.keyName,
Assets.images.googleWord.letter4.dimmed.keyName,
Assets.images.googleWord.letter5.lit.keyName,
Assets.images.googleWord.letter5.dimmed.keyName,
Assets.images.googleWord.letter6.lit.keyName,
Assets.images.googleWord.letter6.dimmed.keyName,
];
group('GoogleWordBonusBehaviors', () { group('GoogleWordBonusBehaviors', () {
late GameBloc gameBloc; late GameBloc gameBloc;
setUp(() { setUp(() {
gameBloc = _MockGameBloc(); gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
flameBlocTester.testGameWidget( flameTester.testGameWidget(
'adds GameBonus.googleWord to the game when all letters are activated', 'adds GameBonus.googleWord to the game when all letters are activated',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.onLoad();
final behavior = GoogleWordBonusBehavior(); final behavior = GoogleWordBonusBehavior();
final parent = GoogleWord.test(); final parent = GoogleWord.test();
final letters = [ final letters = [
@ -61,7 +77,7 @@ void main() {
GoogleLetter(5), GoogleLetter(5),
]; ];
await parent.addAll(letters); await parent.addAll(letters);
await game.ensureAdd(parent); await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
for (final letter in letters) { for (final letter in letters) {

@ -1,5 +1,6 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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';
@ -7,25 +8,40 @@ import 'package:pinball/game/components/google_word/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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.googleWord.letter1.lit.keyName,
Assets.images.googleWord.letter1.dimmed.keyName,
Assets.images.googleWord.letter2.lit.keyName,
Assets.images.googleWord.letter2.dimmed.keyName,
Assets.images.googleWord.letter3.lit.keyName,
Assets.images.googleWord.letter3.dimmed.keyName,
Assets.images.googleWord.letter4.lit.keyName,
Assets.images.googleWord.letter4.dimmed.keyName,
Assets.images.googleWord.letter5.lit.keyName,
Assets.images.googleWord.letter5.dimmed.keyName,
Assets.images.googleWord.letter6.lit.keyName,
Assets.images.googleWord.letter6.dimmed.keyName,
]);
}
Future<void> pump(GoogleWord child, {GameBloc? gameBloc}) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc ?? GameBloc(),
children: [child],
),
);
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.googleWord.letter1.lit.keyName, final flameTester = FlameTester(_TestGame.new);
Assets.images.googleWord.letter1.dimmed.keyName,
Assets.images.googleWord.letter2.lit.keyName,
Assets.images.googleWord.letter2.dimmed.keyName,
Assets.images.googleWord.letter3.lit.keyName,
Assets.images.googleWord.letter3.dimmed.keyName,
Assets.images.googleWord.letter4.lit.keyName,
Assets.images.googleWord.letter4.dimmed.keyName,
Assets.images.googleWord.letter5.lit.keyName,
Assets.images.googleWord.letter5.dimmed.keyName,
Assets.images.googleWord.letter6.lit.keyName,
Assets.images.googleWord.letter6.dimmed.keyName,
];
final flameTester = FlameTester(() => EmptyPinballTestGame(assets: assets));
group('GoogleWord', () { group('GoogleWord', () {
flameTester.test( flameTester.test(
@ -33,7 +49,7 @@ void main() {
(game) async { (game) async {
const word = 'Google'; const word = 'Google';
final googleWord = GoogleWord(position: Vector2.zero()); final googleWord = GoogleWord(position: Vector2.zero());
await game.ensureAdd(googleWord); await game.pump(googleWord);
final letters = googleWord.children.whereType<GoogleLetter>(); final letters = googleWord.children.whereType<GoogleLetter>();
expect(letters.length, equals(word.length)); expect(letters.length, equals(word.length));
@ -42,7 +58,7 @@ void main() {
flameTester.test('adds a GoogleWordBonusBehavior', (game) async { flameTester.test('adds a GoogleWordBonusBehavior', (game) async {
final googleWord = GoogleWord(position: Vector2.zero()); final googleWord = GoogleWord(position: Vector2.zero());
await game.ensureAdd(googleWord); await game.pump(googleWord);
expect( expect(
googleWord.children.whereType<GoogleWordBonusBehavior>().single, googleWord.children.whereType<GoogleWordBonusBehavior>().single,
isNotNull, isNotNull,

@ -1,36 +1,49 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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:pinball/game/game.dart'; import 'package:pinball/game/game.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.launchRamp.ramp.keyName,
Assets.images.launchRamp.backgroundRailing.keyName,
Assets.images.launchRamp.foregroundRailing.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
Assets.images.flapper.flap.keyName,
Assets.images.plunger.plunger.keyName,
Assets.images.plunger.rocket.keyName,
]);
}
Future<void> pump(Launcher launchRamp) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [launchRamp],
),
);
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [ final flameTester = FlameTester(_TestGame.new);
Assets.images.launchRamp.ramp.keyName,
Assets.images.launchRamp.backgroundRailing.keyName,
Assets.images.launchRamp.foregroundRailing.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
Assets.images.flapper.flap.keyName,
Assets.images.plunger.plunger.keyName,
Assets.images.plunger.rocket.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
);
group('Launcher', () { group('Launcher', () {
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
(game) async { (game) async {
final launcher = Launcher(); final component = Launcher();
await game.ensureAdd(launcher); await game.pump(component);
expect(game.descendants(), contains(component));
expect(game.contains(launcher), isTrue);
}, },
); );
@ -38,11 +51,11 @@ void main() {
flameTester.test( flameTester.test(
'a LaunchRamp', 'a LaunchRamp',
(game) async { (game) async {
final launcher = Launcher(); final component = Launcher();
await game.ensureAdd(launcher); await game.pump(component);
final descendantsQuery = final descendantsQuery =
launcher.descendants().whereType<LaunchRamp>(); component.descendants().whereType<LaunchRamp>();
expect(descendantsQuery.length, equals(1)); expect(descendantsQuery.length, equals(1));
}, },
); );
@ -50,10 +63,10 @@ void main() {
flameTester.test( flameTester.test(
'a Flapper', 'a Flapper',
(game) async { (game) async {
final launcher = Launcher(); final component = Launcher();
await game.ensureAdd(launcher); await game.pump(component);
final descendantsQuery = launcher.descendants().whereType<Flapper>(); final descendantsQuery = component.descendants().whereType<Flapper>();
expect(descendantsQuery.length, equals(1)); expect(descendantsQuery.length, equals(1));
}, },
); );
@ -61,10 +74,10 @@ void main() {
flameTester.test( flameTester.test(
'a Plunger', 'a Plunger',
(game) async { (game) async {
final launcher = Launcher(); final component = Launcher();
await game.ensureAdd(launcher); await game.pump(component);
final descendantsQuery = launcher.descendants().whereType<Plunger>(); final descendantsQuery = component.descendants().whereType<Plunger>();
expect(descendantsQuery.length, equals(1)); expect(descendantsQuery.length, equals(1));
}, },
); );
@ -72,11 +85,11 @@ void main() {
flameTester.test( flameTester.test(
'a RocketSpriteComponent', 'a RocketSpriteComponent',
(game) async { (game) async {
final launcher = Launcher(); final component = Launcher();
await game.ensureAdd(launcher); await game.pump(component);
final descendantsQuery = final descendantsQuery =
launcher.descendants().whereType<RocketSpriteComponent>(); component.descendants().whereType<RocketSpriteComponent>();
expect(descendantsQuery.length, equals(1)); expect(descendantsQuery.length, equals(1));
}, },
); );

@ -3,6 +3,8 @@
import 'dart:async'; import 'dart:async';
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_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';
@ -10,7 +12,25 @@ import 'package:pinball/game/components/multiballs/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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.multiball.lit.keyName,
Assets.images.multiball.dimmed.keyName,
]);
}
Future<void> pump(Multiballs child, {GameBloc? gameBloc}) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc ?? GameBloc(),
children: [child],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
@ -18,43 +38,44 @@ class _MockMultiballCubit extends Mock implements MultiballCubit {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.multiball.lit.keyName,
Assets.images.multiball.dimmed.keyName,
];
group('MultiballsBehavior', () { group('MultiballsBehavior', () {
late GameBloc gameBloc; final flameTester = FlameTester(_TestGame.new);
setUp(() { test('can be instantiated', () {
gameBloc = _MockGameBloc(); expect(
whenListen( MultiballsBehavior(),
gameBloc, isA<MultiballsBehavior>(),
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
); );
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( flameTester.test(
gameBuilder: EmptyPinballTestGame.new, 'can be loaded',
blocBuilder: () => gameBloc, (game) async {
assets: assets, final parent = Multiballs.test();
final behavior = MultiballsBehavior();
await game.pump(parent);
await parent.ensureAdd(behavior);
expect(parent.children, contains(behavior));
},
); );
group('listenWhen', () { group('listenWhen', () {
test( test(
'is true when the bonusHistory has changed ' 'is true when the bonusHistory has changed '
'with a new GameBonus.dashNest', () { 'with a new GameBonus.dashNest',
final previous = GameState.initial(); () {
final state = previous.copyWith( final previous = GameState.initial();
bonusHistory: [GameBonus.dashNest], final state = previous.copyWith(
); bonusHistory: [GameBonus.dashNest],
);
expect( expect(
MultiballsBehavior().listenWhen(previous, state), MultiballsBehavior().listenWhen(previous, state),
isTrue, isTrue,
); );
}); },
);
test( test(
'is false when the bonusHistory has changed ' 'is false when the bonusHistory has changed '
@ -90,7 +111,18 @@ void main() {
}); });
group('onNewState', () { group('onNewState', () {
flameBlocTester.testGameWidget( late GameBloc gameBloc;
setUp(() {
gameBloc = _MockGameBloc();
whenListen(
gameBloc,
Stream<GameState>.empty(),
initialState: GameState.initial(),
);
});
flameTester.testGameWidget(
"calls 'onAnimate' once for every multiball", "calls 'onAnimate' once for every multiball",
setUp: (game, tester) async { setUp: (game, tester) async {
final behavior = MultiballsBehavior(); final behavior = MultiballsBehavior();
@ -121,7 +153,7 @@ void main() {
when(otherMultiballCubit.onAnimate).thenAnswer((_) async {}); when(otherMultiballCubit.onAnimate).thenAnswer((_) async {});
await parent.addAll(multiballs); await parent.addAll(multiballs);
await game.ensureAdd(parent); await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
await tester.pump(); await tester.pump();

@ -1,54 +1,57 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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:pinball/game/game.dart'; import 'package:pinball/game/game.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.multiball.lit.keyName,
Assets.images.multiball.dimmed.keyName,
]);
}
Future<void> pump(Multiballs child, {GameBloc? gameBloc}) {
return ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: gameBloc ?? GameBloc(),
children: [child],
),
);
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.multiball.lit.keyName,
Assets.images.multiball.dimmed.keyName,
];
late GameBloc gameBloc;
setUp(() { final flameBlocTester = FlameTester(_TestGame.new);
gameBloc = GameBloc();
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
group('Multiballs', () { group('Multiballs', () {
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
'loads correctly', 'loads correctly',
setUp: (game, tester) async { setUp: (game, tester) async {
final multiballs = Multiballs(); final multiballs = Multiballs();
await game.ensureAdd(multiballs); await game.pump(multiballs);
expect(game.descendants(), contains(multiballs));
expect(game.contains(multiballs), isTrue);
}, },
); );
group('loads', () { flameBlocTester.test(
flameBlocTester.testGameWidget( 'loads four Multiball',
'four Multiball', (game) async {
setUp: (game, tester) async { final multiballs = Multiballs();
final multiballs = Multiballs(); await game.pump(multiballs);
await game.ensureAdd(multiballs); expect(
multiballs.descendants().whereType<Multiball>().length,
expect( equals(4),
multiballs.descendants().whereType<Multiball>().length, );
equals(4), },
); );
},
);
});
}); });
} }

@ -4,6 +4,8 @@ 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';
@ -11,7 +13,33 @@ import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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.multiplier.x2.lit.keyName,
Assets.images.multiplier.x2.dimmed.keyName,
Assets.images.multiplier.x3.lit.keyName,
Assets.images.multiplier.x3.dimmed.keyName,
Assets.images.multiplier.x4.lit.keyName,
Assets.images.multiplier.x4.dimmed.keyName,
Assets.images.multiplier.x5.lit.keyName,
Assets.images.multiplier.x5.dimmed.keyName,
Assets.images.multiplier.x6.lit.keyName,
Assets.images.multiplier.x6.dimmed.keyName,
]);
}
Future<void> pump(Multipliers child) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [child],
),
);
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
@ -21,18 +49,6 @@ class _MockMultiplierCubit extends Mock implements MultiplierCubit {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.multiplier.x2.lit.keyName,
Assets.images.multiplier.x2.dimmed.keyName,
Assets.images.multiplier.x3.lit.keyName,
Assets.images.multiplier.x3.dimmed.keyName,
Assets.images.multiplier.x4.lit.keyName,
Assets.images.multiplier.x4.dimmed.keyName,
Assets.images.multiplier.x5.lit.keyName,
Assets.images.multiplier.x5.dimmed.keyName,
Assets.images.multiplier.x6.lit.keyName,
Assets.images.multiplier.x6.dimmed.keyName,
];
group('MultipliersBehavior', () { group('MultipliersBehavior', () {
late GameBloc gameBloc; late GameBloc gameBloc;
@ -47,11 +63,7 @@ void main() {
); );
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameBlocTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
group('listenWhen', () { group('listenWhen', () {
test('is true when the multiplier has changed', () { test('is true when the multiplier has changed', () {
@ -63,8 +75,8 @@ void main() {
status: GameStatus.playing, status: GameStatus.playing,
bonusHistory: const [], bonusHistory: const [],
); );
final previous = GameState.initial(); final previous = GameState.initial();
expect( expect(
MultipliersBehavior().listenWhen(previous, state), MultipliersBehavior().listenWhen(previous, state),
isTrue, isTrue,
@ -80,8 +92,8 @@ void main() {
status: GameStatus.playing, status: GameStatus.playing,
bonusHistory: const [], bonusHistory: const [],
); );
final previous = GameState.initial(); final previous = GameState.initial();
expect( expect(
MultipliersBehavior().listenWhen(previous, state), MultipliersBehavior().listenWhen(previous, state),
isFalse, isFalse,
@ -93,6 +105,7 @@ void main() {
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
"calls 'next' once per each multiplier when GameBloc emit state", "calls 'next' once per each multiplier when GameBloc emit state",
setUp: (game, tester) async { setUp: (game, tester) async {
await game.onLoad();
final behavior = MultipliersBehavior(); final behavior = MultipliersBehavior();
final parent = Multipliers.test(); final parent = Multipliers.test();
final multiplierX2Cubit = _MockMultiplierCubit(); final multiplierX2Cubit = _MockMultiplierCubit();
@ -123,7 +136,7 @@ void main() {
when(() => multiplierX3Cubit.next(any())).thenAnswer((_) async {}); when(() => multiplierX3Cubit.next(any())).thenAnswer((_) async {});
await parent.addAll(multipliers); await parent.addAll(multipliers);
await game.ensureAdd(parent); await game.pump(parent);
await parent.ensureAdd(behavior); await parent.ensureAdd(behavior);
await tester.pump(); await tester.pump();

@ -1,63 +1,65 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
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:pinball/game/game.dart'; import 'package:pinball/game/game.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.multiplier.x2.lit.keyName,
Assets.images.multiplier.x2.dimmed.keyName,
Assets.images.multiplier.x3.lit.keyName,
Assets.images.multiplier.x3.dimmed.keyName,
Assets.images.multiplier.x4.lit.keyName,
Assets.images.multiplier.x4.dimmed.keyName,
Assets.images.multiplier.x5.lit.keyName,
Assets.images.multiplier.x5.dimmed.keyName,
Assets.images.multiplier.x6.lit.keyName,
Assets.images.multiplier.x6.dimmed.keyName,
]);
}
Future<void> pump(Multipliers child) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [child],
),
);
}
}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.multiplier.x2.lit.keyName,
Assets.images.multiplier.x2.dimmed.keyName,
Assets.images.multiplier.x3.lit.keyName,
Assets.images.multiplier.x3.dimmed.keyName,
Assets.images.multiplier.x4.lit.keyName,
Assets.images.multiplier.x4.dimmed.keyName,
Assets.images.multiplier.x5.lit.keyName,
Assets.images.multiplier.x5.dimmed.keyName,
Assets.images.multiplier.x6.lit.keyName,
Assets.images.multiplier.x6.dimmed.keyName,
];
late GameBloc gameBloc;
setUp(() {
gameBloc = GameBloc();
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameTester = FlameTester(_TestGame.new);
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
group('Multipliers', () { group('Multipliers', () {
flameBlocTester.testGameWidget( flameTester.test(
'loads correctly', 'loads correctly',
setUp: (game, tester) async { (game) async {
final multipliersGroup = Multipliers(); final component = Multipliers();
await game.ensureAdd(multipliersGroup); await game.pump(component);
expect(game.descendants(), contains(component));
expect(game.contains(multipliersGroup), isTrue);
}, },
); );
group('loads', () { flameTester.test(
flameBlocTester.testGameWidget( 'loads five Multiplier',
'five Multiplier', (game) async {
setUp: (game, tester) async { final multipliersGroup = Multipliers();
final multipliersGroup = Multipliers(); await game.pump(multipliersGroup);
await game.ensureAdd(multipliersGroup); expect(
multipliersGroup.descendants().whereType<Multiplier>().length,
expect( equals(5),
multipliersGroup.descendants().whereType<Multiplier>().length, );
equals(5), },
); );
},
);
});
}); });
} }

@ -8,7 +8,24 @@ import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.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.sparky.computer.top.keyName,
Assets.images.sparky.computer.base.keyName,
Assets.images.sparky.computer.glow.keyName,
Assets.images.sparky.animatronic.keyName,
Assets.images.sparky.bumper.a.lit.keyName,
Assets.images.sparky.bumper.a.dimmed.keyName,
Assets.images.sparky.bumper.b.lit.keyName,
Assets.images.sparky.bumper.b.dimmed.keyName,
Assets.images.sparky.bumper.c.lit.keyName,
Assets.images.sparky.bumper.c.dimmed.keyName,
]);
}
}
class _MockControlledBall extends Mock implements ControlledBall {} class _MockControlledBall extends Mock implements ControlledBall {}
@ -18,22 +35,8 @@ class _MockContact extends Mock implements Contact {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.sparky.computer.top.keyName, final flameTester = FlameTester(_TestGame.new);
Assets.images.sparky.computer.base.keyName,
Assets.images.sparky.computer.glow.keyName,
Assets.images.sparky.animatronic.keyName,
Assets.images.sparky.bumper.a.lit.keyName,
Assets.images.sparky.bumper.a.dimmed.keyName,
Assets.images.sparky.bumper.b.lit.keyName,
Assets.images.sparky.bumper.b.dimmed.keyName,
Assets.images.sparky.bumper.c.lit.keyName,
Assets.images.sparky.bumper.c.dimmed.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
);
group('SparkyScorch', () { group('SparkyScorch', () {
flameTester.test('loads correctly', (game) async { flameTester.test('loads correctly', (game) async {

@ -7,17 +7,58 @@ import 'package:flame/components.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/src/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_audio/src/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme; import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../helpers/helpers.dart'; class _TestPinballGame extends PinballGame {
_TestPinballGame()
: super(
characterTheme: const theme.DashTheme(),
leaderboardRepository: _MockLeaderboardRepository(),
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
player: _MockPinballPlayer(),
);
@override
Future<void> onLoad() async {
images.prefix = '';
final futures = preLoadAssets();
await Future.wait<void>(futures);
await super.onLoad();
}
}
class _TestDebugPinballGame extends DebugPinballGame {
_TestDebugPinballGame()
: super(
characterTheme: const theme.DashTheme(),
leaderboardRepository: _MockLeaderboardRepository(),
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
player: _MockPinballPlayer(),
);
@override
Future<void> onLoad() async {
images.prefix = '';
final futures = preLoadAssets();
await Future.wait<void>(futures);
await super.onLoad();
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
class _MockAppLocalizations extends Mock implements AppLocalizations {}
class _MockEventPosition extends Mock implements EventPosition {} class _MockEventPosition extends Mock implements EventPosition {}
class _MockTapDownDetails extends Mock implements TapDownDetails {} class _MockTapDownDetails extends Mock implements TapDownDetails {}
@ -34,115 +75,13 @@ class _MockDragUpdateInfo extends Mock implements DragUpdateInfo {}
class _MockDragEndInfo extends Mock implements DragEndInfo {} class _MockDragEndInfo extends Mock implements DragEndInfo {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockPinballPlayer extends Mock implements PinballPlayer {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.android.bumper.a.lit.keyName,
Assets.images.android.bumper.a.dimmed.keyName,
Assets.images.android.bumper.b.lit.keyName,
Assets.images.android.bumper.b.dimmed.keyName,
Assets.images.android.bumper.cow.lit.keyName,
Assets.images.android.bumper.cow.dimmed.keyName,
Assets.images.backbox.marquee.keyName,
Assets.images.backbox.displayDivider.keyName,
Assets.images.boardBackground.keyName,
theme.Assets.images.android.ball.keyName,
theme.Assets.images.dash.ball.keyName,
theme.Assets.images.dino.ball.keyName,
theme.Assets.images.sparky.ball.keyName,
Assets.images.ball.flameEffect.keyName,
Assets.images.baseboard.left.keyName,
Assets.images.baseboard.right.keyName,
Assets.images.boundary.bottom.keyName,
Assets.images.boundary.outer.keyName,
Assets.images.boundary.outerBottom.keyName,
Assets.images.dino.animatronic.mouth.keyName,
Assets.images.dino.animatronic.head.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.topWallTunnel.keyName,
Assets.images.dino.bottomWall.keyName,
Assets.images.dash.animatronic.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,
Assets.images.dash.bumper.main.active.keyName,
Assets.images.dash.bumper.main.inactive.keyName,
Assets.images.flipper.left.keyName,
Assets.images.flipper.right.keyName,
Assets.images.googleWord.letter1.lit.keyName,
Assets.images.googleWord.letter1.dimmed.keyName,
Assets.images.googleWord.letter2.lit.keyName,
Assets.images.googleWord.letter2.dimmed.keyName,
Assets.images.googleWord.letter3.lit.keyName,
Assets.images.googleWord.letter3.dimmed.keyName,
Assets.images.googleWord.letter4.lit.keyName,
Assets.images.googleWord.letter4.dimmed.keyName,
Assets.images.googleWord.letter5.lit.keyName,
Assets.images.googleWord.letter5.dimmed.keyName,
Assets.images.googleWord.letter6.lit.keyName,
Assets.images.googleWord.letter6.dimmed.keyName,
Assets.images.kicker.left.lit.keyName,
Assets.images.kicker.left.dimmed.keyName,
Assets.images.kicker.right.lit.keyName,
Assets.images.kicker.right.dimmed.keyName,
Assets.images.launchRamp.ramp.keyName,
Assets.images.launchRamp.foregroundRailing.keyName,
Assets.images.launchRamp.backgroundRailing.keyName,
Assets.images.multiball.lit.keyName,
Assets.images.multiball.dimmed.keyName,
Assets.images.multiplier.x2.lit.keyName,
Assets.images.multiplier.x2.dimmed.keyName,
Assets.images.multiplier.x3.lit.keyName,
Assets.images.multiplier.x3.dimmed.keyName,
Assets.images.multiplier.x4.lit.keyName,
Assets.images.multiplier.x4.dimmed.keyName,
Assets.images.multiplier.x5.lit.keyName,
Assets.images.multiplier.x5.dimmed.keyName,
Assets.images.multiplier.x6.lit.keyName,
Assets.images.multiplier.x6.dimmed.keyName,
Assets.images.plunger.plunger.keyName,
Assets.images.plunger.rocket.keyName,
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
Assets.images.slingshot.upper.keyName,
Assets.images.slingshot.lower.keyName,
Assets.images.android.spaceship.saucer.keyName,
Assets.images.android.spaceship.animatronic.keyName,
Assets.images.android.spaceship.lightBeam.keyName,
Assets.images.android.ramp.boardOpening.keyName,
Assets.images.android.ramp.railingForeground.keyName,
Assets.images.android.ramp.railingBackground.keyName,
Assets.images.android.ramp.main.keyName,
Assets.images.android.ramp.arrow.inactive.keyName,
Assets.images.android.ramp.arrow.active1.keyName,
Assets.images.android.ramp.arrow.active2.keyName,
Assets.images.android.ramp.arrow.active3.keyName,
Assets.images.android.ramp.arrow.active4.keyName,
Assets.images.android.ramp.arrow.active5.keyName,
Assets.images.android.rail.main.keyName,
Assets.images.android.rail.exit.keyName,
Assets.images.sparky.animatronic.keyName,
Assets.images.sparky.computer.top.keyName,
Assets.images.sparky.computer.base.keyName,
Assets.images.sparky.computer.glow.keyName,
Assets.images.sparky.animatronic.keyName,
Assets.images.sparky.bumper.a.lit.keyName,
Assets.images.sparky.bumper.a.dimmed.keyName,
Assets.images.sparky.bumper.b.lit.keyName,
Assets.images.sparky.bumper.b.dimmed.keyName,
Assets.images.sparky.bumper.c.lit.keyName,
Assets.images.sparky.bumper.c.dimmed.keyName,
Assets.images.flapper.flap.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
Assets.images.skillShot.decal.keyName,
Assets.images.skillShot.pin.keyName,
Assets.images.skillShot.lit.keyName,
Assets.images.skillShot.dimmed.keyName,
];
late GameBloc gameBloc; late GameBloc gameBloc;
@ -156,17 +95,10 @@ void main() {
}); });
group('PinballGame', () { group('PinballGame', () {
final flameTester = FlameTester( final flameTester = FlameTester(_TestPinballGame.new);
() => PinballTestGame(assets: assets),
);
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: () => PinballTestGame(assets: assets),
blocBuilder: () => gameBloc,
);
group('components', () { group('components', () {
flameBlocTester.test( flameTester.test(
'has only one BallSpawningBehavior', 'has only one BallSpawningBehavior',
(game) async { (game) async {
await game.ready(); await game.ready();
@ -177,7 +109,7 @@ void main() {
}, },
); );
flameBlocTester.test( flameTester.test(
'has only one Drain', 'has only one Drain',
(game) async { (game) async {
await game.ready(); await game.ready();
@ -188,7 +120,7 @@ void main() {
}, },
); );
flameBlocTester.test( flameTester.test(
'has only one BottomGroup', 'has only one BottomGroup',
(game) async { (game) async {
await game.ready(); await game.ready();
@ -199,7 +131,7 @@ void main() {
}, },
); );
flameBlocTester.test( flameTester.test(
'has only one Launcher', 'has only one Launcher',
(game) async { (game) async {
await game.ready(); await game.ready();
@ -210,7 +142,7 @@ void main() {
}, },
); );
flameBlocTester.test( flameTester.test(
'has one FlutterForest', 'has one FlutterForest',
(game) async { (game) async {
await game.ready(); await game.ready();
@ -221,11 +153,10 @@ void main() {
}, },
); );
flameBlocTester.test( flameTester.test(
'has only one Multiballs', 'has only one Multiballs',
(game) async { (game) async {
await game.ready(); await game.ready();
expect( expect(
game.descendants().whereType<Multiballs>().length, game.descendants().whereType<Multiballs>().length,
equals(1), equals(1),
@ -233,7 +164,7 @@ void main() {
}, },
); );
flameBlocTester.test( flameTester.test(
'one GoogleWord', 'one GoogleWord',
(game) async { (game) async {
await game.ready(); await game.ready();
@ -244,7 +175,7 @@ void main() {
}, },
); );
flameBlocTester.test('one SkillShot', (game) async { flameTester.test('one SkillShot', (game) async {
await game.ready(); await game.ready();
expect( expect(
game.descendants().whereType<SkillShot>().length, game.descendants().whereType<SkillShot>().length,
@ -252,10 +183,13 @@ void main() {
); );
}); });
flameBlocTester.testGameWidget( flameTester.testGameWidget(
'paints sprites with FilterQuality.medium', 'paints sprites with FilterQuality.medium',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets); game.images.prefix = '';
final futures = game.preLoadAssets();
await Future.wait<void>(futures);
await game.ready(); await game.ready();
final descendants = game.descendants(); final descendants = game.descendants();
@ -459,12 +393,9 @@ void main() {
}); });
group('DebugPinballGame', () { group('DebugPinballGame', () {
final debugAssets = [Assets.images.ball.flameEffect.keyName, ...assets]; final flameTester = FlameTester(_TestDebugPinballGame.new);
final debugModeFlameTester = FlameTester(
() => DebugPinballTestGame(assets: debugAssets),
);
debugModeFlameTester.test( flameTester.test(
'adds a ball on tap up', 'adds a ball on tap up',
(game) async { (game) async {
final eventPosition = _MockEventPosition(); final eventPosition = _MockEventPosition();
@ -494,7 +425,7 @@ void main() {
}, },
); );
debugModeFlameTester.test( flameTester.test(
'set lineStart on pan start', 'set lineStart on pan start',
(game) async { (game) async {
final startPosition = Vector2.all(10); final startPosition = Vector2.all(10);
@ -514,7 +445,7 @@ void main() {
}, },
); );
debugModeFlameTester.test( flameTester.test(
'set lineEnd on pan update', 'set lineEnd on pan update',
(game) async { (game) async {
final endPosition = Vector2.all(10); final endPosition = Vector2.all(10);
@ -534,7 +465,7 @@ void main() {
}, },
); );
debugModeFlameTester.test( flameTester.test(
'launch ball on pan end', 'launch ball on pan end',
(game) async { (game) async {
final startPosition = Vector2.zero(); final startPosition = Vector2.zero();

@ -4,14 +4,38 @@ import 'package:bloc_test/bloc_test.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/assets_manager/assets_manager.dart'; import 'package:pinball/assets_manager/assets_manager.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart'; import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
class _TestPinballGame extends PinballGame {
_TestPinballGame()
: super(
characterTheme: const theme.DashTheme(),
leaderboardRepository: _MockLeaderboardRepository(),
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
player: _MockPinballPlayer(),
);
@override
Future<void> onLoad() async {
images.prefix = '';
final futures = preLoadAssets();
await Future.wait<void>(futures);
return super.onLoad();
}
}
class _MockGameBloc extends Mock implements GameBloc {} class _MockGameBloc extends Mock implements GameBloc {}
class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {} class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {}
@ -20,8 +44,15 @@ class _MockAssetsManagerCubit extends Mock implements AssetsManagerCubit {}
class _MockStartGameBloc extends Mock implements StartGameBloc {} class _MockStartGameBloc extends Mock implements StartGameBloc {}
class _MockAppLocalizations extends Mock implements AppLocalizations {}
class _MockPinballPlayer extends Mock implements PinballPlayer {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
void main() { void main() {
final game = PinballTestGame(); final game = _TestPinballGame();
group('PinballGamePage', () { group('PinballGamePage', () {
late CharacterThemeCubit characterThemeCubit; late CharacterThemeCubit characterThemeCubit;

@ -1,34 +0,0 @@
import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
class FlameBlocTester<T extends FlameGame, B extends Bloc<dynamic, dynamic>>
extends FlameTester<T> {
FlameBlocTester({
required GameCreateFunction<T> gameBuilder,
required B Function() blocBuilder,
// TODO(allisonryan0002): find alternative for testGameWidget. Loading
// assets in onLoad fails because the game loads after
List<String>? assets,
List<RepositoryProvider> Function()? repositories,
}) : super(
gameBuilder,
pumpWidget: (gameWidget, tester) async {
if (assets != null) {
await Future.wait(assets.map(gameWidget.game.images.load));
}
await tester.pumpWidget(
BlocProvider.value(
value: blocBuilder(),
child: repositories == null
? gameWidget
: MultiRepositoryProvider(
providers: repositories.call(),
child: gameWidget,
),
),
);
},
);
}

@ -1,7 +0,0 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
class FakeContact extends Fake implements Contact {}
class FakeGameEvent extends Fake implements GameEvent {}

@ -1,13 +0,0 @@
import 'package:flame_forge2d/flame_forge2d.dart';
void beginContact(Forge2DGame game, BodyComponent bodyA, BodyComponent bodyB) {
assert(
bodyA.body.fixtures.isNotEmpty && bodyB.body.fixtures.isNotEmpty,
'Bodies require fixtures to contact each other.',
);
final fixtureA = bodyA.body.fixtures.first;
final fixtureB = bodyB.body.fixtures.first;
final contact = Contact.init(fixtureA, 0, fixtureB, 0);
game.world.contactManager.contactListener?.beginContact(contact);
}

@ -1,8 +1,3 @@
export 'builders.dart';
export 'fakes.dart';
export 'forge2d.dart';
export 'key_testers.dart'; export 'key_testers.dart';
export 'mock_flame_images.dart'; export 'mock_flame_images.dart';
export 'pump_app.dart'; export 'pump_app.dart';
export 'test_games.dart';
export 'text_span.dart';

@ -1,122 +0,0 @@
// ignore_for_file: must_call_super
import 'dart:async';
import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_theme/pinball_theme.dart';
class _MockPinballPlayer extends Mock implements PinballPlayer {}
class _MockAppLocalizations extends Mock implements AppLocalizations {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class TestGame extends Forge2DGame with FlameBloc {
TestGame() {
images.prefix = '';
}
}
class PinballTestGame extends PinballGame {
PinballTestGame({
List<String>? assets,
PinballPlayer? player,
LeaderboardRepository? leaderboardRepository,
CharacterTheme? theme,
AppLocalizations? l10n,
}) : _assets = assets,
super(
player: player ?? _MockPinballPlayer(),
leaderboardRepository:
leaderboardRepository ?? _MockLeaderboardRepository(),
characterTheme: theme ?? const DashTheme(),
l10n: l10n ?? _MockAppLocalizations(),
);
final List<String>? _assets;
@override
Future<void> onLoad() async {
if (_assets != null) {
await images.loadAll(_assets!);
}
await super.onLoad();
}
}
class DebugPinballTestGame extends DebugPinballGame {
DebugPinballTestGame({
List<String>? assets,
PinballPlayer? player,
LeaderboardRepository? leaderboardRepository,
CharacterTheme? theme,
AppLocalizations? l10n,
}) : _assets = assets,
super(
player: player ?? _MockPinballPlayer(),
leaderboardRepository:
leaderboardRepository ?? _MockLeaderboardRepository(),
characterTheme: theme ?? const DashTheme(),
l10n: l10n ?? _MockAppLocalizations(),
);
final List<String>? _assets;
@override
Future<void> onLoad() async {
if (_assets != null) {
await images.loadAll(_assets!);
}
await super.onLoad();
}
}
class EmptyPinballTestGame extends PinballTestGame {
EmptyPinballTestGame({
List<String>? assets,
PinballPlayer? player,
CharacterTheme? theme,
AppLocalizations? l10n,
}) : super(
assets: assets,
player: player,
theme: theme,
l10n: l10n ?? _MockAppLocalizations(),
);
@override
Future<void> onLoad() async {
if (_assets != null) {
await images.loadAll(_assets!);
}
}
}
class EmptyKeyboardPinballTestGame extends PinballTestGame
with HasKeyboardHandlerComponents {
EmptyKeyboardPinballTestGame({
List<String>? assets,
PinballPlayer? player,
CharacterTheme? theme,
AppLocalizations? l10n,
}) : super(
assets: assets,
player: player,
theme: theme,
l10n: l10n ?? _MockAppLocalizations(),
);
@override
Future<void> onLoad() async {
if (_assets != null) {
await images.loadAll(_assets!);
}
}
}

@ -1,17 +0,0 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
bool tapTextSpan(RichText richText, String text) {
final isTapped = !richText.text.visitChildren(
(visitor) => _findTextAndTap(visitor, text),
);
return isTapped;
}
bool _findTextAndTap(InlineSpan visitor, String text) {
if (visitor is TextSpan && visitor.text == text) {
(visitor.recognizer as TapGestureRecognizer?)?.onTap?.call();
return false;
}
return true;
}
Loading…
Cancel
Save