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_components/pinball_components.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
/// [GameStatus.playing].
class BallSpawningBehavior extends Component
with ParentIsA<PinballGame>, BlocComponent<GameBloc, GameState> {
with FlameBlocListenable<GameBloc, GameState>, HasGameRef {
@override
bool listenWhen(GameState? previousState, GameState newState) {
if (!newState.status.isPlaying) return false;
@ -20,9 +21,10 @@ class BallSpawningBehavior extends Component
@override
void onNewState(GameState state) {
final plunger = parent.descendants().whereType<Plunger>().single;
final canvas = parent.descendants().whereType<ZCanvasComponent>().single;
final ball = ControlledBall.launch(characterTheme: parent.characterTheme)
final plunger = gameRef.descendants().whereType<Plunger>().single;
final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
final characterTheme = readProvider<CharacterTheme>();
final ball = ControlledBall.launch(characterTheme: characterTheme)
..initialPosition = Vector2(
plunger.body.position.x,
plunger.body.position.y - Ball.size.y,

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

@ -2,6 +2,7 @@
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.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.
/// {@endtemplate}
class ScoringBehavior extends Component with HasGameRef<PinballGame> {
class ScoringBehavior extends Component
with HasGameRef, FlameBlocReader<GameBloc, GameState> {
/// {@macto scoring_behavior}
ScoringBehavior({
required Points points,
@ -39,7 +41,8 @@ class ScoringBehavior extends Component with HasGameRef<PinballGame> {
@override
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;
await canvas.add(
ScoreComponent(
@ -54,8 +57,7 @@ class ScoringBehavior extends Component with HasGameRef<PinballGame> {
/// {@template scoring_contact_behavior}
/// Adds points to the score when the [Ball] contacts the [parent].
/// {@endtemplate}
class ScoringContactBehavior extends ContactBehavior
with HasGameRef<PinballGame> {
class ScoringContactBehavior extends ContactBehavior {
/// {@macro scoring_contact_behavior}
ScoringContactBehavior({
required Points points,

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

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

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

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

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

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

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

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

@ -1,6 +1,7 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.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.
/// {@endtemplate}
class BallController extends ComponentController<Ball>
with HasGameRef<PinballGame> {
with FlameBlocReader<GameBloc, GameState> {
/// {@macro ball_controller}
BallController(Ball ball) : super(ball);
/// Stops the [Ball] inside of the [SparkyComputer] while the turbo charge
/// sequence runs, then boosts the ball out of the computer.
Future<void> turboCharge() async {
gameRef.read<GameBloc>().add(const SparkyTurboChargeActivated());
bloc.add(const SparkyTurboChargeActivated());
component.stop();
// 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.
/// {@endtemplate}
class FlipperController extends ComponentController<Flipper>
with KeyboardHandler, BlocComponent<GameBloc, GameState> {
with KeyboardHandler, FlameBlocReader<GameBloc, GameState> {
/// {@macro flipper_controller}
FlipperController(Flipper flipper)
: _keys = flipper.side.flipperKeys,
@ -37,7 +37,7 @@ class FlipperController extends ComponentController<Flipper>
RawKeyEvent event,
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 (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
/// which plays the related sound effects.
class PlungerNoiseBehavior extends Component with HasGameRef<PinballGame> {
/// A behavior attached to the plunger when it launches the ball which plays the
/// related sound effects.
class PlungerNoiseBehavior extends Component {
@override
Future<void> onLoad() async {
await super.onLoad();
gameRef.player.play(PinballAudio.launcher);
readProvider<PinballPlayer>().play(PinballAudio.launcher);
}
@override
@ -44,7 +44,7 @@ class PlungerNoiseBehavior extends Component with HasGameRef<PinballGame> {
/// A [ComponentController] that controls a [Plunger]s movement.
/// {@endtemplate}
class PlungerController extends ComponentController<Plunger>
with KeyboardHandler, BlocComponent<GameBloc, GameState> {
with KeyboardHandler, FlameBlocReader<GameBloc, GameState> {
/// {@macro plunger_controller}
PlungerController(Plunger plunger) : super(plunger);
@ -62,7 +62,7 @@ class PlungerController extends ComponentController<Plunger>
RawKeyEvent event,
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 (event is RawKeyDownEvent) {

@ -1,11 +1,12 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Adds a [GameBonus.dinoChomp] when a [Ball] is chomped by the [ChromeDino].
class ChromeDinoBonusBehavior extends Component
with HasGameRef<PinballGame>, ParentIsA<DinoDesert> {
with ParentIsA<DinoDesert>, FlameBlocReader<GameBloc, GameState> {
@override
void onMount() {
super.onMount();
@ -18,7 +19,7 @@ class ChromeDinoBonusBehavior extends Component
final listenWhen = state.status == ChromeDinoStatus.chomping;
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_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Handles removing a [Ball] from the game.
class DrainingBehavior extends ContactBehavior<Drain>
with HasGameRef<PinballGame> {
class DrainingBehavior extends ContactBehavior<Drain> with HasGameRef {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
@ -15,7 +15,11 @@ class DrainingBehavior extends ContactBehavior<Drain>
other.removeFromParent();
final ballsLeft = gameRef.descendants().whereType<Ball>().length;
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_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
/// 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]
/// is awarded, and the [DashNestBumper.main] releases a new [Ball].
class FlutterForestBonusBehavior extends Component
with ParentIsA<FlutterForest>, HasGameRef<PinballGame> {
with
ParentIsA<FlutterForest>,
HasGameRef,
FlameBlocReader<GameBloc, GameState> {
@override
void onMount() {
super.onMount();
@ -35,12 +40,11 @@ class FlutterForestBonusBehavior extends Component
}
if (signpost.bloc.isFullyProgressed()) {
gameRef
.read<GameBloc>()
.add(const BonusActivated(GameBonus.dashNest));
bloc.add(const BonusActivated(GameBonus.dashNest));
canvas.add(
ControlledBall.bonus(characterTheme: gameRef.characterTheme)
..initialPosition = Vector2(29.5, -24.5),
ControlledBall.bonus(
characterTheme: readProvider<CharacterTheme>(),
)..initialPosition = Vector2(29.5, -24.5),
);
animatronic.playing = true;
signpost.bloc.onProgressed();

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

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

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

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

@ -17,13 +17,20 @@ import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart';
class PinballGame extends PinballForge2DGame
with FlameBloc, HasKeyboardHandlerComponents, MultiTouchTapDetector {
with HasKeyboardHandlerComponents, MultiTouchTapDetector {
PinballGame({
required this.characterTheme,
required CharacterTheme characterTheme,
required this.leaderboardRepository,
required this.l10n,
required this.player,
}) : super(gravity: Vector2(0, 30)) {
required GameBloc gameBloc,
required AppLocalizations l10n,
required PinballPlayer player,
}) : _gameBloc = gameBloc,
_player = player,
_characterTheme = characterTheme,
_l10n = l10n,
super(
gravity: Vector2(0, 30),
) {
images.prefix = '';
}
@ -33,22 +40,45 @@ class PinballGame extends PinballForge2DGame
@override
Color backgroundColor() => Colors.transparent;
final CharacterTheme characterTheme;
final CharacterTheme _characterTheme;
final PinballPlayer player;
final PinballPlayer _player;
final LeaderboardRepository leaderboardRepository;
final AppLocalizations l10n;
final AppLocalizations _l10n;
final GameBloc _gameBloc;
@override
Future<void> onLoad() async {
final machine = [
await add(
FlameBlocProvider<GameBloc, GameState>.value(
value: _gameBloc,
children: [
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),
];
final decals = [
GoogleWord(position: Vector2(-4.25, 1.8)),
Multipliers(),
Multiballs(),
@ -57,31 +87,10 @@ class PinballGame extends PinballForge2DGame
ScoringContactBehavior(points: Points.oneMillion),
],
),
];
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(),
@ -90,6 +99,9 @@ class PinballGame extends PinballForge2DGame
],
),
],
),
],
),
);
await super.onLoad();
@ -149,11 +161,13 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
required LeaderboardRepository leaderboardRepository,
required AppLocalizations l10n,
required PinballPlayer player,
required GameBloc gameBloc,
}) : super(
characterTheme: characterTheme,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: l10n,
gameBloc: gameBloc,
);
Vector2? lineStart;

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

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

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

@ -26,6 +26,7 @@ class ContactBehavior<T extends BodyComponent> extends Component
@override
Future<void> onLoad() async {
await super.onLoad();
if (_fixturesUserData.isNotEmpty) {
for (final fixture in _targetedFixtures) {
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
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.4.0"
flame_forge2d:
dependency: "direct main"
description:

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

@ -1,3 +1,4 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -7,6 +8,21 @@ import 'package:plugin_platform_interface/plugin_platform_interface.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
with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {}
@ -49,7 +65,7 @@ void main() {
).thenAnswer((_) async => true);
await tester.pumpApp(const Footer());
final flutterTextFinder = find.byWidgetPredicate(
(widget) => widget is RichText && tapTextSpan(widget, 'Flutter'),
(widget) => widget is RichText && _tapTextSpan(widget, 'Flutter'),
);
await tester.tap(flutterTextFinder);
await tester.pumpAndSettle();
@ -84,7 +100,7 @@ void main() {
).thenAnswer((_) async => true);
await tester.pumpApp(const Footer());
final firebaseTextFinder = find.byWidgetPredicate(
(widget) => widget is RichText && tapTextSpan(widget, 'Firebase'),
(widget) => widget is RichText && _tapTextSpan(widget, 'Firebase'),
);
await tester.tap(firebaseTextFinder);
await tester.pumpAndSettle();

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

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

@ -1,6 +1,6 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
@ -10,7 +10,29 @@ import 'package:pinball/game/game.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.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 {
@override
@ -27,18 +49,13 @@ class _MockContact extends Mock implements Contact {}
void main() {
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 Ball ball;
late BodyComponent parent;
setUp(() {
bloc = _MockGameBloc();
ball = _MockBall();
final ballBody = _MockBody();
when(() => ball.body).thenReturn(ballBody);
@ -47,23 +64,7 @@ void main() {
parent = _TestBodyComponent();
});
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
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,
);
final flameBlocTester = FlameTester(_TestGame.new);
group('ScoringBehavior', () {
test('can be instantiated', () {
@ -76,16 +77,16 @@ void main() {
);
});
flameBlocTester.testGameWidget(
flameBlocTester.test(
'can be loaded',
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
(game) async {
await game.pump(parent);
final behavior = ScoringBehavior(
points: Points.fiveThousand,
position: Vector2.zero(),
);
await parent.add(behavior);
await game.ensureAdd(canvas);
await parent.ensureAdd(behavior);
expect(
parent.firstChild<ScoringBehavior>(),
@ -94,13 +95,12 @@ void main() {
},
);
flameBlocTester.testGameWidget(
flameBlocTester.test(
'emits Scored event with points when added',
setUp: (game, tester) async {
const points = Points.oneMillion;
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
(game) async {
await game.pump(parent, gameBloc: bloc);
const points = Points.oneMillion;
final behavior = ScoringBehavior(
points: points,
position: Vector2(0, 0),
@ -115,11 +115,10 @@ void main() {
},
);
flameBlocTester.testGameWidget(
flameBlocTester.test(
'correctly renders text',
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
(game) async {
await game.pump(parent);
const points = Points.oneMillion;
final position = Vector2.all(1);
@ -145,8 +144,8 @@ void main() {
flameBlocTester.testGameWidget(
'is removed after duration',
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
await game.onLoad();
await game.pump(parent);
const duration = 2.0;
final behavior = ScoringBehavior(
@ -173,8 +172,8 @@ void main() {
flameBlocTester.testGameWidget(
'beginContact adds a ScoringBehavior',
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
await game.onLoad();
await game.pump(parent);
final behavior = ScoringContactBehavior(points: Points.oneMillion);
await parent.ensureAdd(behavior);
@ -192,8 +191,8 @@ void main() {
flameBlocTester.testGameWidget(
"beginContact positions text at contact's position",
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
await game.onLoad();
await game.pump(parent);
final behavior = ScoringContactBehavior(points: Points.oneMillion);
await parent.ensureAdd(behavior);

@ -1,5 +1,7 @@
// 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:flutter_test/flutter_test.dart';
import 'package:pinball/game/behaviors/bumper_noise_behavior.dart';
@ -7,11 +9,11 @@ import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
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,
@ -33,24 +35,36 @@ void main() {
Assets.images.android.bumper.b.dimmed.keyName,
Assets.images.android.bumper.cow.lit.keyName,
Assets.images.android.bumper.cow.dimmed.keyName,
];
]);
}
group('AndroidAcres', () {
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
Future<void> pump(AndroidAcres child) async {
await ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [child],
),
);
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('AndroidAcres', () {
final flameTester = FlameTester(_TestGame.new);
flameTester.test('loads correctly', (game) async {
final component = AndroidAcres();
await game.ensureAdd(component);
expect(game.contains(component), isTrue);
await game.pump(component);
expect(game.descendants(), contains(component));
});
group('loads', () {
flameTester.test(
'an AndroidSpaceship',
(game) async {
await game.ensureAdd(AndroidAcres());
await game.pump(AndroidAcres());
expect(
game.descendants().whereType<AndroidSpaceship>().length,
equals(1),
@ -61,7 +75,7 @@ void main() {
flameTester.test(
'an AndroidAnimatronic',
(game) async {
await game.ensureAdd(AndroidAcres());
await game.pump(AndroidAcres());
expect(
game.descendants().whereType<AndroidAnimatronic>().length,
equals(1),
@ -72,7 +86,7 @@ void main() {
flameTester.test(
'a SpaceshipRamp',
(game) async {
await game.ensureAdd(AndroidAcres());
await game.pump(AndroidAcres());
expect(
game.descendants().whereType<SpaceshipRamp>().length,
equals(1),
@ -83,7 +97,7 @@ void main() {
flameTester.test(
'a SpaceshipRail',
(game) async {
await game.ensureAdd(AndroidAcres());
await game.pump(AndroidAcres());
expect(
game.descendants().whereType<SpaceshipRail>().length,
equals(1),
@ -94,7 +108,7 @@ void main() {
flameTester.test(
'three AndroidBumper',
(game) async {
await game.ensureAdd(AndroidAcres());
await game.pump(AndroidAcres());
expect(
game.descendants().whereType<AndroidBumper>().length,
equals(3),
@ -105,7 +119,7 @@ void main() {
flameTester.test(
'three AndroidBumpers with BumperNoiseBehavior',
(game) async {
await game.ensureAdd(AndroidAcres());
await game.pump(AndroidAcres());
final bumpers = game.descendants().whereType<AndroidBumper>();
for (final bumper in bumpers) {
expect(
@ -119,7 +133,7 @@ void main() {
flameTester.test('adds an AndroidSpaceshipBonusBehavior', (game) async {
final androidAcres = AndroidAcres();
await game.ensureAdd(androidAcres);
await game.pump(androidAcres);
expect(
androidAcres.children.whereType<AndroidSpaceshipBonusBehavior>().single,
isNotNull,

@ -1,7 +1,7 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/extensions.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -9,13 +9,11 @@ import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../../../helpers/helpers.dart';
class _MockGameBloc extends Mock implements GameBloc {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
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,
@ -37,27 +35,37 @@ void main() {
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 {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('AndroidSpaceshipBonusBehavior', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
final flameTester = FlameTester(_TestGame.new);
flameBlocTester.testGameWidget(
flameTester.testGameWidget(
'adds GameBonus.androidSpaceship to the game '
'when android spacehship has a bonus',
setUp: (game, tester) async {
@ -66,7 +74,10 @@ void main() {
final androidSpaceship = AndroidSpaceship(position: Vector2.zero());
await parent.add(androidSpaceship);
await game.ensureAdd(parent);
await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior);
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:flutter_test/flutter_test.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_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 {}
@ -17,7 +43,7 @@ void main() {
group(
'BallSpawningBehavior',
() {
final flameTester = FlameTester(EmptyPinballTestGame.new);
final flameTester = FlameTester(_TestGame.new);
test('can be instantiated', () {
expect(
@ -30,8 +56,8 @@ void main() {
'loads',
(game) async {
final behavior = BallSpawningBehavior();
await game.ensureAdd(behavior);
expect(game.contains(behavior), isTrue);
await game.pump([behavior]);
expect(game.descendants(), contains(behavior));
},
);
@ -97,9 +123,8 @@ void main() {
flameTester.test(
'onNewState adds a ball',
(game) async {
await game.images.load(theme.Assets.images.dash.ball.keyName);
final behavior = BallSpawningBehavior();
await game.ensureAddAll([
await game.pump([
behavior,
ZCanvasComponent(),
Plunger.test(compressionDistance: 10),

@ -3,6 +3,8 @@
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -12,18 +14,11 @@ import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../../helpers/helpers.dart';
class _MockGameBloc extends Mock implements GameBloc {}
class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
class _MockStreamSubscription extends Mock
implements StreamSubscription<SpaceshipRampState> {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
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,
@ -37,7 +32,33 @@ void main() {
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 _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
class _MockStreamSubscription extends Mock
implements StreamSubscription<SpaceshipRampState> {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('RampBonusBehavior', () {
const shotPoints = Points.oneMillion;
@ -46,22 +67,13 @@ void main() {
setUp(() {
gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
final flameTester = FlameTester(_TestGame.new);
flameBlocTester.testGameWidget(
flameTester.test(
'when hits are multiples of 10 times adds a ScoringBehavior',
setUp: (game, tester) async {
(game) async {
final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>();
whenListen(
@ -69,14 +81,13 @@ void main() {
streamController.stream,
initialState: SpaceshipRampState(hits: 9),
);
final behavior = RampBonusBehavior(
points: shotPoints,
);
final parent = SpaceshipRamp.test(
bloc: bloc,
);
final behavior = RampBonusBehavior(points: shotPoints);
final parent = SpaceshipRamp.test(bloc: bloc);
await game.ensureAdd(ZCanvasComponent(children: [parent]));
await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior);
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",
setUp: (game, tester) async {
(game) async {
final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>();
whenListen(
@ -98,14 +109,13 @@ void main() {
streamController.stream,
initialState: SpaceshipRampState.initial(),
);
final behavior = RampBonusBehavior(
points: shotPoints,
);
final parent = SpaceshipRamp.test(
bloc: bloc,
);
final behavior = RampBonusBehavior(points: shotPoints);
final parent = SpaceshipRamp.test(bloc: bloc);
await game.ensureAdd(ZCanvasComponent(children: [parent]));
await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior);
streamController.add(SpaceshipRampState(hits: 1));
@ -117,9 +127,9 @@ void main() {
},
);
flameBlocTester.testGameWidget(
flameTester.test(
'closes subscription when removed',
setUp: (game, tester) async {
(game) async {
final bloc = _MockSpaceshipRampCubit();
whenListen(
bloc,
@ -135,11 +145,12 @@ void main() {
points: shotPoints,
subscription: subscription,
);
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);
parent.remove(behavior);

@ -3,6 +3,8 @@
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -12,18 +14,11 @@ import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../../helpers/helpers.dart';
class _MockGameBloc extends Mock implements GameBloc {}
class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
class _MockStreamSubscription extends Mock
implements StreamSubscription<SpaceshipRampState> {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
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,
@ -37,7 +32,33 @@ void main() {
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 _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
class _MockStreamSubscription extends Mock
implements StreamSubscription<SpaceshipRampState> {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('RampShotBehavior', () {
const shotPoints = Points.fiveThousand;
@ -46,23 +67,14 @@ void main() {
setUp(() {
gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
final flameBlocTester = FlameTester(_TestGame.new);
flameBlocTester.testGameWidget(
flameBlocTester.test(
'when hits are not multiple of 10 times '
'increases multiplier and adds a ScoringBehavior',
setUp: (game, tester) async {
(game) async {
final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>();
whenListen(
@ -70,14 +82,13 @@ void main() {
streamController.stream,
initialState: SpaceshipRampState.initial(),
);
final behavior = RampShotBehavior(
points: shotPoints,
);
final parent = SpaceshipRamp.test(
bloc: bloc,
);
final behavior = RampShotBehavior(points: shotPoints);
final parent = SpaceshipRamp.test(bloc: bloc);
await game.ensureAdd(ZCanvasComponent(children: [parent]));
await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior);
streamController.add(SpaceshipRampState(hits: 1));
@ -90,10 +101,10 @@ void main() {
},
);
flameBlocTester.testGameWidget(
flameBlocTester.test(
'when hits multiple of 10 times '
"doesn't increase multiplier, neither ScoringBehavior",
setUp: (game, tester) async {
(game) async {
final bloc = _MockSpaceshipRampCubit();
final streamController = StreamController<SpaceshipRampState>();
whenListen(
@ -108,7 +119,10 @@ void main() {
bloc: bloc,
);
await game.ensureAdd(ZCanvasComponent(children: [parent]));
await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior);
streamController.add(SpaceshipRampState(hits: 10));
@ -121,9 +135,9 @@ void main() {
},
);
flameBlocTester.testGameWidget(
flameBlocTester.test(
'closes subscription when removed',
setUp: (game, tester) async {
(game) async {
final bloc = _MockSpaceshipRampCubit();
whenListen(
bloc,
@ -143,7 +157,10 @@ void main() {
bloc: bloc,
);
await game.ensureAdd(ZCanvasComponent(children: [parent]));
await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior);
parent.remove(behavior);

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

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

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

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

@ -1,13 +1,31 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/bloc/game_bloc.dart';
import 'package:pinball/game/components/backbox/displays/loading_display.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 {
@override
@ -16,39 +34,35 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
void main() {
group('LoadingDisplay', () {
final flameTester = FlameTester(
() => EmptyPinballTestGame(
l10n: _MockAppLocalizations(),
),
);
final flameTester = FlameTester(_TestGame.new);
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?.text, equals('Loading'));
expect(component.text, equals('Loading'));
});
flameTester.test('use ellipses as animation', (game) async {
await game.ensureAdd(LoadingDisplay());
await game.pump(LoadingDisplay());
final component = game.firstChild<TextComponent>();
expect(component?.text, equals('Loading'));
final component = game.descendants().whereType<TextComponent>().first;
expect(component.text, equals('Loading'));
final timer = component?.firstChild<TimerComponent>();
final timer = component.firstChild<TimerComponent>();
timer?.update(1.1);
expect(component?.text, equals('Loading.'));
expect(component.text, equals('Loading.'));
timer?.update(1.1);
expect(component?.text, equals('Loading..'));
expect(component.text, equals('Loading..'));
timer?.update(1.1);
expect(component?.text, equals('Loading...'));
expect(component.text, equals('Loading...'));
timer?.update(1.1);
expect(component?.text, equals('Loading'));
expect(component.text, equals('Loading'));
});
});
}

@ -1,15 +1,17 @@
// 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:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
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,
@ -18,19 +20,28 @@ void main() {
Assets.images.baseboard.right.keyName,
Assets.images.flipper.left.keyName,
Assets.images.flipper.right.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
);
]);
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('BottomGroup', () {
final flameTester = FlameTester(_TestGame.new);
flameTester.test(
'loads correctly',
(game) async {
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',
(game) async {
final bottomGroup = BottomGroup();
await game.ensureAdd(bottomGroup);
await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [bottomGroup],
),
);
final leftFlippers =
bottomGroup.descendants().whereType<Flipper>().where(
@ -53,7 +69,12 @@ void main() {
'one right flipper',
(game) async {
final bottomGroup = BottomGroup();
await game.ensureAdd(bottomGroup);
await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [bottomGroup],
),
);
final rightFlippers =
bottomGroup.descendants().whereType<Flipper>().where(
@ -67,7 +88,12 @@ void main() {
'two Baseboards',
(game) async {
final bottomGroup = BottomGroup();
await game.ensureAdd(bottomGroup);
await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [bottomGroup],
),
);
final basebottomGroups =
bottomGroup.descendants().whereType<Baseboard>();
@ -79,7 +105,12 @@ void main() {
'two Kickers',
(game) async {
final bottomGroup = BottomGroup();
await game.ensureAdd(bottomGroup);
await game.ensureAdd(
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [bottomGroup],
),
);
final kickers = bottomGroup.descendants().whereType<Kicker>();
expect(kickers.length, equals(2));

@ -1,7 +1,7 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -9,32 +9,29 @@ import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../helpers/helpers.dart';
// 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;
class _TestGame extends Forge2DGame {
@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 _MockPinballGame extends Mock implements PinballGame {}
class _MockControlledBall extends Mock implements ControlledBall {}
class _MockBall extends Mock implements Ball {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
theme.Assets.images.dash.ball.keyName,
];
group('BallController', () {
late Ball ball;
@ -43,18 +40,9 @@ void main() {
setUp(() {
ball = Ball();
gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
final flameBlocTester = FlameTester(_TestGame.new);
test('can be instantiated', () {
expect(
@ -63,19 +51,14 @@ void main() {
);
});
group('turboCharge', () {
setUpAll(() {
registerFallbackValue(Vector2.zero());
registerFallbackValue(Component());
});
flameBlocTester.testGameWidget(
'adds TurboChargeActivated',
'turboCharge adds TurboChargeActivated',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.onLoad();
final controller = BallController(ball);
await ball.add(controller);
await game.ensureAdd(ball);
await game.pump(ball, gameBloc: gameBloc);
await controller.turboCharge();
},
@ -84,38 +67,5 @@ void main() {
.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 '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:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@ -10,17 +13,31 @@ import 'package:pinball_components/pinball_components.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 {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.flipper.left.keyName,
Assets.images.flipper.right.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
);
final flameTester = FlameTester(_TestGame.new);
group('FlipperController', () {
late GameBloc gameBloc;
@ -29,12 +46,6 @@ void main() {
gameBloc = _MockGameBloc();
});
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
group('onKeyEvent', () {
final leftKeys = UnmodifiableListView([
LogicalKeyboardKey.arrowLeft,
@ -63,11 +74,13 @@ void main() {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
);
await game.ready();
await game.add(flipper);
await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative);
@ -77,9 +90,9 @@ void main() {
});
testRawKeyDownEvents(leftKeys, (event) {
flameBlocTester.testGameWidget(
flameTester.test(
'does nothing when is game over',
setUp: (game, tester) async {
(game) async {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
@ -88,10 +101,9 @@ void main() {
),
);
await game.ensureAdd(flipper);
await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {});
},
verify: (game, tester) async {
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
@ -106,11 +118,13 @@ void main() {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
);
await game.ready();
await game.add(flipper);
await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive);
@ -131,7 +145,7 @@ void main() {
);
await game.ready();
await game.add(flipper);
await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);
@ -159,11 +173,13 @@ void main() {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
);
await game.ready();
await game.add(flipper);
await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isNegative);
@ -180,11 +196,13 @@ void main() {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
);
await game.ready();
await game.add(flipper);
await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isPositive);
@ -194,9 +212,9 @@ void main() {
});
testRawKeyDownEvents(rightKeys, (event) {
flameBlocTester.testGameWidget(
flameTester.test(
'does nothing when is game over',
setUp: (game, tester) async {
(game) async {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
@ -205,10 +223,9 @@ void main() {
),
);
await game.ensureAdd(flipper);
await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {});
},
verify: (game, tester) async {
expect(flipper.body.linearVelocity.y, isZero);
expect(flipper.body.linearVelocity.x, isZero);
},
@ -220,8 +237,16 @@ void main() {
'does nothing '
'when ${event.logicalKey.keyLabel} is released',
(game) async {
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial().copyWith(
status: GameStatus.playing,
),
);
await game.ready();
await game.add(flipper);
await game.pump(flipper, gameBloc: gameBloc);
controller.onKeyEvent(event, {});
expect(flipper.body.linearVelocity.y, isZero);

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

@ -1,6 +1,7 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/forge2d_game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.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_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 {}
@ -16,43 +44,30 @@ class _MockBall extends Mock implements Ball {}
void main() {
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', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
final flameTester = FlameTester(_TestGame.new);
flameBlocTester.testGameWidget(
flameTester.testGameWidget(
'adds GameBonus.dinoChomp to the game '
'when ChromeDinoStatus.chomping is emitted',
setUp: (game, tester) async {
await game.onLoad();
final behavior = ChromeDinoBonusBehavior();
final parent = DinoDesert.test();
final chromeDino = ChromeDino();
await parent.add(chromeDino);
await game.ensureAdd(parent);
await game.pump(
parent,
gameBloc: gameBloc,
);
await parent.ensureAdd(behavior);
chromeDino.bloc.onChomp(_MockBall());

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

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

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

@ -1,8 +1,7 @@
// ignore_for_file: cascade_invocations
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/forge2d_game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.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_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 {}
@ -21,34 +50,21 @@ void main() {
group('FlutterForestBonusBehavior', () {
late GameBloc gameBloc;
final assets = [
Assets.images.dash.animatronic.keyName,
theme.Assets.images.dash.ball.keyName,
];
setUp(() {
gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
final flameTester = FlameTester(_TestGame.new);
void _contactedBumper(DashNestBumper bumper) =>
bumper.bloc.onBallContacted();
flameBlocTester.testGameWidget(
flameTester.testGameWidget(
'adds GameBonus.dashNest to the game '
'when bumpers are activated three times',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.onLoad();
final behavior = FlutterForestBonusBehavior();
final parent = FlutterForest.test();
final bumpers = [
@ -58,7 +74,7 @@ void main() {
];
final animatronic = DashAnimatronic();
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.ensureAdd(behavior);
@ -76,11 +92,11 @@ void main() {
},
);
flameBlocTester.testGameWidget(
flameTester.testGameWidget(
'adds a new Ball to the game '
'when bumpers are activated three times',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.onLoad();
final behavior = FlutterForestBonusBehavior();
final parent = FlutterForest.test();
final bumpers = [
@ -90,7 +106,7 @@ void main() {
];
final animatronic = DashAnimatronic();
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.ensureAdd(behavior);
@ -110,11 +126,11 @@ void main() {
},
);
flameBlocTester.testGameWidget(
flameTester.testGameWidget(
'progress the signpost '
'when bumpers are activated',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.onLoad();
final behavior = FlutterForestBonusBehavior();
final parent = FlutterForest.test();
final bumpers = [
@ -124,7 +140,7 @@ void main() {
];
final animatronic = DashAnimatronic();
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.ensureAdd(behavior);

@ -1,17 +1,21 @@
// 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:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
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,
@ -23,18 +27,41 @@ void main() {
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
]);
}
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() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(_TestGame.new);
group('FlutterForest', () {
flameTester.test(
'loads correctly',
(game) async {
final flutterForest = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest]));
expect(game.descendants(), contains(flutterForest));
final component = FlutterForest();
await game.pump(component);
expect(game.descendants(), contains(component));
},
);
@ -42,8 +69,8 @@ void main() {
flameTester.test(
'a Signpost',
(game) async {
final flutterForest = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest]));
final component = FlutterForest();
await game.pump(component);
expect(
game.descendants().whereType<Signpost>().length,
equals(1),
@ -54,8 +81,8 @@ void main() {
flameTester.test(
'a DashAnimatronic',
(game) async {
final flutterForest = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest]));
final component = FlutterForest();
await game.pump(component);
expect(
game.descendants().whereType<DashAnimatronic>().length,
equals(1),
@ -66,8 +93,8 @@ void main() {
flameTester.test(
'three DashNestBumper',
(game) async {
final flutterForest = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest]));
final component = FlutterForest();
await game.pump(component);
expect(
game.descendants().whereType<DashNestBumper>().length,
equals(3),
@ -78,8 +105,8 @@ void main() {
flameTester.test(
'three DashNestBumpers with BumperNoiseBehavior',
(game) async {
final flutterForest = FlutterForest();
await game.ensureAdd(ZCanvasComponent(children: [flutterForest]));
final component = FlutterForest();
await game.pump(component);
final bumpers = game.descendants().whereType<DashNestBumper>();
for (final bumper in bumpers) {
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:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_theme/pinball_theme.dart';
class _MockPinballGame extends Mock implements PinballGame {}
class _MockBackbox extends Mock implements Backbox {}
class _MockActiveOverlaysNotifier extends Mock
implements ActiveOverlaysNotifier {}
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
class _TestGame extends Forge2DGame {
@override
Future<void> onLoad() async {
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 _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('GameBlocStatusListener', () {
setUpAll(() {
registerFallbackValue(AndroidTheme());
test('can be instantiated', () {
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', () {
test('is true when the game over state has changed', () {
final state = GameState(
const state = GameState(
totalScore: 0,
roundScore: 10,
multiplier: 1,
rounds: 0,
bonusHistory: const [],
bonusHistory: [],
status: GameStatus.playing,
);
final previous = GameState.initial();
const previous = GameState.initial();
expect(
GameBlocStatusListener().listenWhen(previous, state),
isTrue,
@ -42,92 +92,52 @@ void main() {
});
group('onNewState', () {
late PinballGame game;
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(
flameTester.test(
'changes the backbox display when the game is over',
() {
final state = GameState(
totalScore: 0,
roundScore: 10,
multiplier: 1,
rounds: 0,
bonusHistory: const [],
(game) async {
final component = GameBlocStatusListener();
final repository = _MockLeaderboardRepository();
final backbox = Backbox(leaderboardRepository: repository);
final state = const GameState.initial()
..copyWith(
status: GameStatus.gameOver,
);
gameBlocStatusListener.onNewState(state);
verify(
() => backbox.requestInitials(
score: any(named: 'score'),
character: any(named: 'character'),
),
).called(1);
},
);
test(
'changes the backbox when it is not a game over',
() {
gameBlocStatusListener.onNewState(
GameState.initial().copyWith(status: GameStatus.playing),
);
await game.pump([component, backbox]);
verify(() => overlays.remove(PinballGame.playButtonOverlay))
.called(1);
expect(() => component.onNewState(state), returnsNormally);
},
);
test(
flameTester.test(
'plays the background music on start',
() {
gameBlocStatusListener.onNewState(
GameState.initial().copyWith(status: GameStatus.playing),
(game) async {
final player = _MockPinballPlayer();
final component = GameBlocStatusListener();
await game.pump([component], pinballPlayer: player);
component.onNewState(
const GameState.initial().copyWith(status: GameStatus.playing),
);
verify(() => pinballPlayer.play(PinballAudio.backgroundMusic))
.called(1);
verify(() => player.play(PinballAudio.backgroundMusic)).called(1);
},
);
test(
flameTester.test(
'plays the game over voice over when it is game over',
() {
gameBlocStatusListener.onNewState(
GameState.initial().copyWith(status: GameStatus.gameOver),
(game) async {
final player = _MockPinballPlayer();
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))
.called(1);
verify(() => player.play(PinballAudio.gameOverVoiceOver)).called(1);
},
);
});

@ -1,20 +1,21 @@
// ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/google_word/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../../helpers/helpers.dart';
class _MockGameBloc extends Mock implements GameBloc {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
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,
@ -27,29 +28,44 @@ void main() {
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 _MockPinballPlayer extends Mock implements PinballPlayer {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('GoogleWordBonusBehaviors', () {
late GameBloc gameBloc;
setUp(() {
gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
final flameTester = FlameTester(_TestGame.new);
flameBlocTester.testGameWidget(
flameTester.testGameWidget(
'adds GameBonus.googleWord to the game when all letters are activated',
setUp: (game, tester) async {
await game.onLoad();
final behavior = GoogleWordBonusBehavior();
final parent = GoogleWord.test();
final letters = [
@ -61,7 +77,7 @@ void main() {
GoogleLetter(5),
];
await parent.addAll(letters);
await game.ensureAdd(parent);
await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAdd(behavior);
for (final letter in letters) {

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

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

@ -3,6 +3,8 @@
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
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_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 {}
@ -18,33 +38,33 @@ class _MockMultiballCubit extends Mock implements MultiballCubit {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.multiball.lit.keyName,
Assets.images.multiball.dimmed.keyName,
];
group('MultiballsBehavior', () {
late GameBloc gameBloc;
final flameTester = FlameTester(_TestGame.new);
setUp(() {
gameBloc = _MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
test('can be instantiated', () {
expect(
MultiballsBehavior(),
isA<MultiballsBehavior>(),
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
flameTester.test(
'can be loaded',
(game) async {
final parent = Multiballs.test();
final behavior = MultiballsBehavior();
await game.pump(parent);
await parent.ensureAdd(behavior);
expect(parent.children, contains(behavior));
},
);
group('listenWhen', () {
test(
'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(
bonusHistory: [GameBonus.dashNest],
@ -54,7 +74,8 @@ void main() {
MultiballsBehavior().listenWhen(previous, state),
isTrue,
);
});
},
);
test(
'is false when the bonusHistory has changed '
@ -90,7 +111,18 @@ void main() {
});
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",
setUp: (game, tester) async {
final behavior = MultiballsBehavior();
@ -121,7 +153,7 @@ void main() {
when(otherMultiballCubit.onAnimate).thenAnswer((_) async {});
await parent.addAll(multiballs);
await game.ensureAdd(parent);
await game.pump(parent, gameBloc: gameBloc);
await parent.ensureAdd(behavior);
await tester.pump();

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

@ -4,6 +4,8 @@ import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
@ -11,17 +13,11 @@ import 'package:pinball/game/components/multipliers/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../../../helpers/helpers.dart';
class _MockGameBloc extends Mock implements GameBloc {}
class _MockComponent extends Mock implements Component {}
class _MockMultiplierCubit extends Mock implements MultiplierCubit {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
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,
@ -32,7 +28,27 @@ void main() {
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 _MockComponent extends Mock implements Component {}
class _MockMultiplierCubit extends Mock implements MultiplierCubit {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('MultipliersBehavior', () {
late GameBloc gameBloc;
@ -47,11 +63,7 @@ void main() {
);
});
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc,
assets: assets,
);
final flameBlocTester = FlameTester(_TestGame.new);
group('listenWhen', () {
test('is true when the multiplier has changed', () {
@ -63,8 +75,8 @@ void main() {
status: GameStatus.playing,
bonusHistory: const [],
);
final previous = GameState.initial();
expect(
MultipliersBehavior().listenWhen(previous, state),
isTrue,
@ -80,8 +92,8 @@ void main() {
status: GameStatus.playing,
bonusHistory: const [],
);
final previous = GameState.initial();
expect(
MultipliersBehavior().listenWhen(previous, state),
isFalse,
@ -93,6 +105,7 @@ void main() {
flameBlocTester.testGameWidget(
"calls 'next' once per each multiplier when GameBloc emit state",
setUp: (game, tester) async {
await game.onLoad();
final behavior = MultipliersBehavior();
final parent = Multipliers.test();
final multiplierX2Cubit = _MockMultiplierCubit();
@ -123,7 +136,7 @@ void main() {
when(() => multiplierX3Cubit.next(any())).thenAnswer((_) async {});
await parent.addAll(multipliers);
await game.ensureAdd(parent);
await game.pump(parent);
await parent.ensureAdd(behavior);
await tester.pump();

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

@ -8,17 +8,11 @@ import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
class _MockControlledBall extends Mock implements ControlledBall {}
class _MockBallController extends Mock implements BallController {}
class _MockContact extends Mock implements Contact {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
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,
@ -29,11 +23,20 @@ void main() {
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),
);
class _MockControlledBall extends Mock implements ControlledBall {}
class _MockBallController extends Mock implements BallController {}
class _MockContact extends Mock implements Contact {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(_TestGame.new);
group('SparkyScorch', () {
flameTester.test('loads correctly', (game) async {

@ -7,17 +7,58 @@ import 'package:flame/components.dart';
import 'package:flame/input.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/src/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/behaviors/behaviors.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_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 _MockAppLocalizations extends Mock implements AppLocalizations {}
class _MockEventPosition extends Mock implements EventPosition {}
class _MockTapDownDetails extends Mock implements TapDownDetails {}
@ -34,115 +75,13 @@ class _MockDragUpdateInfo extends Mock implements DragUpdateInfo {}
class _MockDragEndInfo extends Mock implements DragEndInfo {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockPinballPlayer extends Mock implements PinballPlayer {}
void main() {
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;
@ -156,17 +95,10 @@ void main() {
});
group('PinballGame', () {
final flameTester = FlameTester(
() => PinballTestGame(assets: assets),
);
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: () => PinballTestGame(assets: assets),
blocBuilder: () => gameBloc,
);
final flameTester = FlameTester(_TestPinballGame.new);
group('components', () {
flameBlocTester.test(
flameTester.test(
'has only one BallSpawningBehavior',
(game) async {
await game.ready();
@ -177,7 +109,7 @@ void main() {
},
);
flameBlocTester.test(
flameTester.test(
'has only one Drain',
(game) async {
await game.ready();
@ -188,7 +120,7 @@ void main() {
},
);
flameBlocTester.test(
flameTester.test(
'has only one BottomGroup',
(game) async {
await game.ready();
@ -199,7 +131,7 @@ void main() {
},
);
flameBlocTester.test(
flameTester.test(
'has only one Launcher',
(game) async {
await game.ready();
@ -210,7 +142,7 @@ void main() {
},
);
flameBlocTester.test(
flameTester.test(
'has one FlutterForest',
(game) async {
await game.ready();
@ -221,11 +153,10 @@ void main() {
},
);
flameBlocTester.test(
flameTester.test(
'has only one Multiballs',
(game) async {
await game.ready();
expect(
game.descendants().whereType<Multiballs>().length,
equals(1),
@ -233,7 +164,7 @@ void main() {
},
);
flameBlocTester.test(
flameTester.test(
'one GoogleWord',
(game) async {
await game.ready();
@ -244,7 +175,7 @@ void main() {
},
);
flameBlocTester.test('one SkillShot', (game) async {
flameTester.test('one SkillShot', (game) async {
await game.ready();
expect(
game.descendants().whereType<SkillShot>().length,
@ -252,10 +183,13 @@ void main() {
);
});
flameBlocTester.testGameWidget(
flameTester.testGameWidget(
'paints sprites with FilterQuality.medium',
setUp: (game, tester) async {
await game.images.loadAll(assets);
game.images.prefix = '';
final futures = game.preLoadAssets();
await Future.wait<void>(futures);
await game.ready();
final descendants = game.descendants();
@ -459,12 +393,9 @@ void main() {
});
group('DebugPinballGame', () {
final debugAssets = [Assets.images.ball.flameEffect.keyName, ...assets];
final debugModeFlameTester = FlameTester(
() => DebugPinballTestGame(assets: debugAssets),
);
final flameTester = FlameTester(_TestDebugPinballGame.new);
debugModeFlameTester.test(
flameTester.test(
'adds a ball on tap up',
(game) async {
final eventPosition = _MockEventPosition();
@ -494,7 +425,7 @@ void main() {
},
);
debugModeFlameTester.test(
flameTester.test(
'set lineStart on pan start',
(game) async {
final startPosition = Vector2.all(10);
@ -514,7 +445,7 @@ void main() {
},
);
debugModeFlameTester.test(
flameTester.test(
'set lineEnd on pan update',
(game) async {
final endPosition = Vector2.all(10);
@ -534,7 +465,7 @@ void main() {
},
);
debugModeFlameTester.test(
flameTester.test(
'launch ball on pan end',
(game) async {
final startPosition = Vector2.zero();

@ -4,14 +4,38 @@ import 'package:bloc_test/bloc_test.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.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';
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 _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {}
@ -20,8 +44,15 @@ class _MockAssetsManagerCubit extends Mock implements AssetsManagerCubit {}
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() {
final game = PinballTestGame();
final game = _TestPinballGame();
group('PinballGamePage', () {
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 'mock_flame_images.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