From b3136e5857f0f63ad01822fb45e5f1a74015c90d Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Fri, 6 May 2022 05:31:29 +0100 Subject: [PATCH] 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 --- .../behaviors/ball_spawning_behavior.dart | 10 +- lib/game/behaviors/bumper_noise_behavior.dart | 6 +- .../behaviors/camera_focusing_behavior.dart | 13 +- lib/game/behaviors/scoring_behavior.dart | 10 +- .../android_spaceship_bonus_behavior.dart | 7 +- .../behaviors/ramp_bonus_behavior.dart | 4 +- .../behaviors/ramp_shot_behavior.dart | 5 +- lib/game/components/backbox/backbox.dart | 3 +- .../displays/initials_input_display.dart | 36 ++-- .../initials_submission_failure_display.dart | 3 +- .../initials_submission_success_display.dart | 3 +- .../backbox/displays/loading_display.dart | 7 +- lib/game/components/controlled_ball.dart | 5 +- lib/game/components/controlled_flipper.dart | 4 +- lib/game/components/controlled_plunger.dart | 12 +- .../behaviors/chrome_dino_bonus_behavior.dart | 5 +- .../drain/behaviors/draining_behavior.dart | 10 +- .../flutter_forest_bonus_behavior.dart | 16 +- .../components/game_bloc_status_listener.dart | 10 +- .../behaviors/google_word_bonus_behavior.dart | 9 +- .../behaviors/multiballs_behavior.dart | 5 +- .../behaviors/multipliers_behavior.dart | 5 +- lib/game/pinball_game.dart | 118 +++++----- lib/game/view/pinball_game_page.dart | 57 ++--- .../multiplier/multiplier_test.dart | 67 +++--- packages/pinball_flame/lib/pinball_flame.dart | 1 + .../lib/src/contact_behavior.dart | 1 + .../pinball_flame/lib/src/flame_provider.dart | 65 ++++++ .../test/src/flame_provider_test.dart | 103 +++++++++ pubspec.lock | 2 +- pubspec.yaml | 2 +- test/footer/footer_test.dart | 20 +- ...t.dart => bumper_noise_behavior_test.dart} | 26 ++- .../camera_focusing_behavior_test.dart | 41 ++-- .../game/behaviors/scoring_behavior_test.dart | 89 ++++---- .../android_acres/android_acres_test.dart | 86 ++++---- ...android_spaceship_bonus_behavior_test.dart | 87 ++++---- .../ball_spawning_behavior_test.dart | 37 +++- .../behaviors/ramp_bonus_behavior_test.dart | 111 +++++----- .../behaviors/ramp_shot_behavior_test.dart | 99 +++++---- .../game/components/backbox/backbox_test.dart | 77 ++++--- .../displays/initials_input_display_test.dart | 83 +++++--- ...tials_submission_failure_display_test.dart | 5 +- ...tials_submission_success_display_test.dart | 5 +- .../displays/loading_display_test.dart | 48 +++-- test/game/components/bottom_group_test.dart | 71 +++++-- .../game/components/controlled_ball_test.dart | 116 +++------- .../components/controlled_flipper_test.dart | 91 +++++--- .../components/controlled_plunger_test.dart | 83 ++++---- .../chrome_dino_bonus_behavior_test.dart | 59 +++-- .../dino_desert/dino_desert_test.dart | 62 ++++-- .../behaviors/draining_behavior_test.dart | 69 +++--- test/game/components/drain/drain_test.dart | 4 +- .../flutter_forest_bonus_behavior_test.dart | 70 +++--- .../flutter_forest/flutter_forest_test.dart | 83 +++++--- .../game_bloc_status_listener_test.dart | 186 ++++++++-------- .../google_word_bonus_behavior_test.dart | 72 ++++--- .../google_word/google_word_test.dart | 52 +++-- test/game/components/launcher_test.dart | 73 ++++--- .../behaviors/multiballs_behavior_test.dart | 92 +++++--- .../multiballs/multiballs_test.dart | 67 +++--- .../behaviors/multipliers_behavior_test.dart | 55 +++-- .../multipliers/multipliers_test.dart | 90 ++++---- test/game/components/sparky_scorch_test.dart | 37 ++-- test/game/pinball_game_test.dart | 201 ++++++------------ test/game/view/pinball_game_page_test.dart | 33 ++- test/helpers/builders.dart | 34 --- test/helpers/fakes.dart | 7 - test/helpers/forge2d.dart | 13 -- test/helpers/helpers.dart | 5 - test/helpers/test_games.dart | 122 ----------- test/helpers/text_span.dart | 17 -- 72 files changed, 1808 insertions(+), 1474 deletions(-) create mode 100644 packages/pinball_flame/lib/src/flame_provider.dart create mode 100644 packages/pinball_flame/test/src/flame_provider_test.dart rename test/game/behaviors/{bumper_noisy_behavior_test.dart => bumper_noise_behavior_test.dart} (70%) delete mode 100644 test/helpers/builders.dart delete mode 100644 test/helpers/fakes.dart delete mode 100644 test/helpers/forge2d.dart delete mode 100644 test/helpers/test_games.dart delete mode 100644 test/helpers/text_span.dart diff --git a/lib/game/behaviors/ball_spawning_behavior.dart b/lib/game/behaviors/ball_spawning_behavior.dart index 3602615b..c074fe52 100644 --- a/lib/game/behaviors/ball_spawning_behavior.dart +++ b/lib/game/behaviors/ball_spawning_behavior.dart @@ -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, BlocComponent { + with FlameBlocListenable, 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().single; - final canvas = parent.descendants().whereType().single; - final ball = ControlledBall.launch(characterTheme: parent.characterTheme) + final plunger = gameRef.descendants().whereType().single; + final canvas = gameRef.descendants().whereType().single; + final characterTheme = readProvider(); + final ball = ControlledBall.launch(characterTheme: characterTheme) ..initialPosition = Vector2( plunger.body.position.x, plunger.body.position.y - Ball.size.y, diff --git a/lib/game/behaviors/bumper_noise_behavior.dart b/lib/game/behaviors/bumper_noise_behavior.dart index e89ec23a..9c5da701 100644 --- a/lib/game/behaviors/bumper_noise_behavior.dart +++ b/lib/game/behaviors/bumper_noise_behavior.dart @@ -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 { +class BumperNoiseBehavior extends ContactBehavior { @override void beginContact(Object other, Contact contact) { super.beginContact(other, contact); - gameRef.player.play(PinballAudio.bumper); + readProvider().play(PinballAudio.bumper); } } diff --git a/lib/game/behaviors/camera_focusing_behavior.dart b/lib/game/behaviors/camera_focusing_behavior.dart index 9b753469..8a13821d 100644 --- a/lib/game/behaviors/camera_focusing_behavior.dart +++ b/lib/game/behaviors/camera_focusing_behavior.dart @@ -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, BlocComponent { + with FlameBlocListenable, HasGameRef { late final Map _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); } diff --git a/lib/game/behaviors/scoring_behavior.dart b/lib/game/behaviors/scoring_behavior.dart index eddcb580..8b403d1e 100644 --- a/lib/game/behaviors/scoring_behavior.dart +++ b/lib/game/behaviors/scoring_behavior.dart @@ -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 { +class ScoringBehavior extends Component + with HasGameRef, FlameBlocReader { /// {@macto scoring_behavior} ScoringBehavior({ required Points points, @@ -39,7 +41,8 @@ class ScoringBehavior extends Component with HasGameRef { @override Future onLoad() async { - gameRef.read().add(Scored(points: _points.value)); + await super.onLoad(); + bloc.add(Scored(points: _points.value)); final canvas = gameRef.descendants().whereType().single; await canvas.add( ScoreComponent( @@ -54,8 +57,7 @@ class ScoringBehavior extends Component with HasGameRef { /// {@template scoring_contact_behavior} /// Adds points to the score when the [Ball] contacts the [parent]. /// {@endtemplate} -class ScoringContactBehavior extends ContactBehavior - with HasGameRef { +class ScoringContactBehavior extends ContactBehavior { /// {@macro scoring_contact_behavior} ScoringContactBehavior({ required Points points, diff --git a/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart b/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart index 833ac8e4..da181f9e 100644 --- a/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart +++ b/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart @@ -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, ParentIsA { + with ParentIsA, FlameBlocReader { @override void onMount() { super.onMount(); @@ -18,9 +19,7 @@ class AndroidSpaceshipBonusBehavior extends Component final listenWhen = state == AndroidSpaceshipState.withBonus; if (!listenWhen) return; - gameRef - .read() - .add(const BonusActivated(GameBonus.androidSpaceship)); + bloc.add(const BonusActivated(GameBonus.androidSpaceship)); androidSpaceship.bloc.onBonusAwarded(); }); } diff --git a/lib/game/components/android_acres/behaviors/ramp_bonus_behavior.dart b/lib/game/components/android_acres/behaviors/ramp_bonus_behavior.dart index 218ad8b4..bc28650f 100644 --- a/lib/game/components/android_acres/behaviors/ramp_bonus_behavior.dart +++ b/lib/game/components/android_acres/behaviors/ramp_bonus_behavior.dart @@ -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, HasGameRef { +class RampBonusBehavior extends Component with ParentIsA { /// {@macro ramp_bonus_behavior} RampBonusBehavior({ required Points points, diff --git a/lib/game/components/android_acres/behaviors/ramp_shot_behavior.dart b/lib/game/components/android_acres/behaviors/ramp_shot_behavior.dart index 8a9c1a9c..b15f5e30 100644 --- a/lib/game/components/android_acres/behaviors/ramp_shot_behavior.dart +++ b/lib/game/components/android_acres/behaviors/ramp_shot_behavior.dart @@ -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, HasGameRef { + with ParentIsA, FlameBlocReader { /// {@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().add(const MultiplierIncreased()); + bloc.add(const MultiplierIncreased()); parent.add( ScoringBehavior( diff --git a/lib/game/components/backbox/backbox.dart b/lib/game/components/backbox/backbox.dart index 30b2a1aa..9414ae96 100644 --- a/lib/game/components/backbox/backbox.dart +++ b/lib/game/components/backbox/backbox.dart @@ -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, ZIndex { +class Backbox extends PositionComponent with ZIndex { /// {@macro backbox} Backbox({ required LeaderboardRepository leaderboardRepository, diff --git a/lib/game/components/backbox/displays/initials_input_display.dart b/lib/game/components/backbox/displays/initials_input_display.dart index f4900891..244a3e5b 100644 --- a/lib/game/components/backbox/displays/initials_input_display.dart +++ b/lib/game/components/backbox/displays/initials_input_display.dart @@ -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 { +class _ScoreLabelTextComponent extends TextComponent { _ScoreLabelTextComponent() : super( anchor: Anchor.centerLeft, @@ -119,7 +118,7 @@ class _ScoreLabelTextComponent extends TextComponent @override Future onLoad() async { await super.onLoad(); - text = gameRef.l10n.score; + text = readProvider().score; } } @@ -133,8 +132,7 @@ class _ScoreTextComponent extends TextComponent { ); } -class _NameLabelTextComponent extends TextComponent - with HasGameRef { +class _NameLabelTextComponent extends TextComponent { _NameLabelTextComponent() : super( anchor: Anchor.center, @@ -149,7 +147,7 @@ class _NameLabelTextComponent extends TextComponent @override Future onLoad() async { await super.onLoad(); - text = gameRef.l10n.name; + text = readProvider().name; } } @@ -300,8 +298,7 @@ class _InstructionsComponent extends PositionComponent with HasGameRef { ); } -class _EnterInitialsTextComponent extends TextComponent - with HasGameRef { +class _EnterInitialsTextComponent extends TextComponent { _EnterInitialsTextComponent() : super( anchor: Anchor.center, @@ -312,11 +309,11 @@ class _EnterInitialsTextComponent extends TextComponent @override Future onLoad() async { await super.onLoad(); - text = gameRef.l10n.enterInitials; + text = readProvider().enterInitials; } } -class _ArrowsTextComponent extends TextComponent with HasGameRef { +class _ArrowsTextComponent extends TextComponent { _ArrowsTextComponent() : super( anchor: Anchor.center, @@ -331,12 +328,11 @@ class _ArrowsTextComponent extends TextComponent with HasGameRef { @override Future onLoad() async { await super.onLoad(); - text = gameRef.l10n.arrows; + text = readProvider().arrows; } } -class _AndPressTextComponent extends TextComponent - with HasGameRef { +class _AndPressTextComponent extends TextComponent { _AndPressTextComponent() : super( anchor: Anchor.center, @@ -347,12 +343,11 @@ class _AndPressTextComponent extends TextComponent @override Future onLoad() async { await super.onLoad(); - text = gameRef.l10n.andPress; + text = readProvider().andPress; } } -class _EnterReturnTextComponent extends TextComponent - with HasGameRef { +class _EnterReturnTextComponent extends TextComponent { _EnterReturnTextComponent() : super( anchor: Anchor.center, @@ -367,12 +362,11 @@ class _EnterReturnTextComponent extends TextComponent @override Future onLoad() async { await super.onLoad(); - text = gameRef.l10n.enterReturn; + text = readProvider().enterReturn; } } -class _ToSubmitTextComponent extends TextComponent - with HasGameRef { +class _ToSubmitTextComponent extends TextComponent { _ToSubmitTextComponent() : super( anchor: Anchor.center, @@ -383,6 +377,6 @@ class _ToSubmitTextComponent extends TextComponent @override Future onLoad() async { await super.onLoad(); - text = gameRef.l10n.toSubmit; + text = readProvider().toSubmit; } } diff --git a/lib/game/components/backbox/displays/initials_submission_failure_display.dart b/lib/game/components/backbox/displays/initials_submission_failure_display.dart index 178354c2..4cc5a9f5 100644 --- a/lib/game/components/backbox/displays/initials_submission_failure_display.dart +++ b/lib/game/components/backbox/displays/initials_submission_failure_display.dart @@ -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 { +class InitialsSubmissionFailureDisplay extends TextComponent { @override Future onLoad() async { await super.onLoad(); diff --git a/lib/game/components/backbox/displays/initials_submission_success_display.dart b/lib/game/components/backbox/displays/initials_submission_success_display.dart index 46c35b0e..c963a660 100644 --- a/lib/game/components/backbox/displays/initials_submission_success_display.dart +++ b/lib/game/components/backbox/displays/initials_submission_success_display.dart @@ -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 { +class InitialsSubmissionSuccessDisplay extends TextComponent { @override Future onLoad() async { await super.onLoad(); diff --git a/lib/game/components/backbox/displays/loading_display.dart b/lib/game/components/backbox/displays/loading_display.dart index 7b1d4280..6178b940 100644 --- a/lib/game/components/backbox/displays/loading_display.dart +++ b/lib/game/components/backbox/displays/loading_display.dart @@ -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 { +class LoadingDisplay extends TextComponent { /// {@template loading_display} LoadingDisplay(); @@ -27,7 +28,7 @@ class LoadingDisplay extends TextComponent with HasGameRef { position = Vector2(0, -10); anchor = Anchor.center; - text = _label = gameRef.l10n.loading; + text = _label = readProvider().loading; textRenderer = _bodyTextPaint; await add( diff --git a/lib/game/components/controlled_ball.dart b/lib/game/components/controlled_ball.dart index 2356e0d8..241465dd 100644 --- a/lib/game/components/controlled_ball.dart +++ b/lib/game/components/controlled_ball.dart @@ -1,6 +1,7 @@ // ignore_for_file: avoid_renaming_method_parameters import 'package:flame/components.dart'; +import 'package:flame_bloc/flame_bloc.dart'; import 'package: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 { /// Controller attached to a [Ball] that handles its game related logic. /// {@endtemplate} class BallController extends ComponentController - with HasGameRef { + with FlameBlocReader { /// {@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 turboCharge() async { - gameRef.read().add(const SparkyTurboChargeActivated()); + bloc.add(const SparkyTurboChargeActivated()); component.stop(); // TODO(alestiago): Refactor this hard coded duration once the following is diff --git a/lib/game/components/controlled_flipper.dart b/lib/game/components/controlled_flipper.dart index 9d5a8164..1d5502c6 100644 --- a/lib/game/components/controlled_flipper.dart +++ b/lib/game/components/controlled_flipper.dart @@ -21,7 +21,7 @@ class ControlledFlipper extends Flipper with Controls { /// A [ComponentController] that controls a [Flipper]s movement. /// {@endtemplate} class FlipperController extends ComponentController - with KeyboardHandler, BlocComponent { + with KeyboardHandler, FlameBlocReader { /// {@macro flipper_controller} FlipperController(Flipper flipper) : _keys = flipper.side.flipperKeys, @@ -37,7 +37,7 @@ class FlipperController extends ComponentController RawKeyEvent event, Set 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) { diff --git a/lib/game/components/controlled_plunger.dart b/lib/game/components/controlled_plunger.dart index bcebbacc..c8cb90fb 100644 --- a/lib/game/components/controlled_plunger.dart +++ b/lib/game/components/controlled_plunger.dart @@ -24,13 +24,13 @@ class ControlledPlunger extends Plunger with Controls { } } -/// A behavior attached to the plunger when it launches the ball -/// which plays the related sound effects. -class PlungerNoiseBehavior extends Component with HasGameRef { +/// A behavior attached to the plunger when it launches the ball which plays the +/// related sound effects. +class PlungerNoiseBehavior extends Component { @override Future onLoad() async { await super.onLoad(); - gameRef.player.play(PinballAudio.launcher); + readProvider().play(PinballAudio.launcher); } @override @@ -44,7 +44,7 @@ class PlungerNoiseBehavior extends Component with HasGameRef { /// A [ComponentController] that controls a [Plunger]s movement. /// {@endtemplate} class PlungerController extends ComponentController - with KeyboardHandler, BlocComponent { + with KeyboardHandler, FlameBlocReader { /// {@macro plunger_controller} PlungerController(Plunger plunger) : super(plunger); @@ -62,7 +62,7 @@ class PlungerController extends ComponentController RawKeyEvent event, Set 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) { diff --git a/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart b/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart index e4d69f9c..f1e4f53d 100644 --- a/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart +++ b/lib/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior.dart @@ -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, ParentIsA { + with ParentIsA, FlameBlocReader { @override void onMount() { super.onMount(); @@ -18,7 +19,7 @@ class ChromeDinoBonusBehavior extends Component final listenWhen = state.status == ChromeDinoStatus.chomping; if (!listenWhen) return; - gameRef.read().add(const BonusActivated(GameBonus.dinoChomp)); + bloc.add(const BonusActivated(GameBonus.dinoChomp)); }); } } diff --git a/lib/game/components/drain/behaviors/draining_behavior.dart b/lib/game/components/drain/behaviors/draining_behavior.dart index 36512efa..630d04af 100644 --- a/lib/game/components/drain/behaviors/draining_behavior.dart +++ b/lib/game/components/drain/behaviors/draining_behavior.dart @@ -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 - with HasGameRef { +class DrainingBehavior extends ContactBehavior with HasGameRef { @override void beginContact(Object other, Contact contact) { super.beginContact(other, contact); @@ -15,7 +15,11 @@ class DrainingBehavior extends ContactBehavior other.removeFromParent(); final ballsLeft = gameRef.descendants().whereType().length; if (ballsLeft - 1 == 0) { - gameRef.read().add(const RoundLost()); + ancestors() + .whereType>() + .first + .bloc + .add(const RoundLost()); } } } diff --git a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart index c06e6f87..532e8eff 100644 --- a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart +++ b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart @@ -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, HasGameRef { + with + ParentIsA, + HasGameRef, + FlameBlocReader { @override void onMount() { super.onMount(); @@ -35,12 +40,11 @@ class FlutterForestBonusBehavior extends Component } if (signpost.bloc.isFullyProgressed()) { - gameRef - .read() - .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(), + )..initialPosition = Vector2(29.5, -24.5), ); animatronic.playing = true; signpost.bloc.onProgressed(); diff --git a/lib/game/components/game_bloc_status_listener.dart b/lib/game/components/game_bloc_status_listener.dart index 167447e6..6e11f3d6 100644 --- a/lib/game/components/game_bloc_status_listener.dart +++ b/lib/game/components/game_bloc_status_listener.dart @@ -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, HasGameRef { + with FlameBlocListenable, 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().play(PinballAudio.backgroundMusic); gameRef.overlays.remove(PinballGame.playButtonOverlay); break; case GameStatus.gameOver: - gameRef.player.play(PinballAudio.gameOverVoiceOver); + readProvider().play(PinballAudio.gameOverVoiceOver); gameRef.descendants().whereType().first.requestInitials( score: state.displayScore, - character: gameRef.characterTheme, + character: readProvider(), ); break; } diff --git a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart index a9522e76..e49d4537 100644 --- a/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart +++ b/lib/game/components/google_word/behaviors/google_word_bonus_behavior.dart @@ -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, ParentIsA { + with ParentIsA, FlameBlocReader { @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() - .add(const BonusActivated(GameBonus.googleWord)); + readProvider().play(PinballAudio.google); + bloc.add(const BonusActivated(GameBonus.googleWord)); for (final letter in googleLetters) { letter.bloc.onReset(); } diff --git a/lib/game/components/multiballs/behaviors/multiballs_behavior.dart b/lib/game/components/multiballs/behaviors/multiballs_behavior.dart index 8b323ff4..b01c32e1 100644 --- a/lib/game/components/multiballs/behaviors/multiballs_behavior.dart +++ b/lib/game/components/multiballs/behaviors/multiballs_behavior.dart @@ -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, - ParentIsA, - BlocComponent { + with ParentIsA, FlameBlocListenable { @override bool listenWhen(GameState? previousState, GameState newState) { final hasChanged = previousState?.bonusHistory != newState.bonusHistory; diff --git a/lib/game/components/multipliers/behaviors/multipliers_behavior.dart b/lib/game/components/multipliers/behaviors/multipliers_behavior.dart index 33a59a08..ce58a8eb 100644 --- a/lib/game/components/multipliers/behaviors/multipliers_behavior.dart +++ b/lib/game/components/multipliers/behaviors/multipliers_behavior.dart @@ -6,10 +6,7 @@ import 'package:pinball_flame/pinball_flame.dart'; /// Toggle each [Multiplier] when GameState.multiplier changes. class MultipliersBehavior extends Component - with - HasGameRef, - ParentIsA, - BlocComponent { + with ParentIsA, FlameBlocListenable { @override bool listenWhen(GameState? previousState, GameState newState) { return previousState?.multiplier != newState.multiplier; diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index bbab932b..899ec45d 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -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,63 +40,68 @@ 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 onLoad() async { - final machine = [ - BoardBackgroundSpriteComponent(), - Boundaries(), - Backbox(leaderboardRepository: leaderboardRepository), - ]; - final decals = [ - GoogleWord(position: Vector2(-4.25, 1.8)), - Multipliers(), - Multiballs(), - SkillShot( + await add( + FlameBlocProvider.value( + value: _gameBloc, children: [ - ScoringContactBehavior(points: Points.oneMillion), + MultiFlameProvider( + providers: [ + FlameProvider.value(_player), + FlameProvider.value(_characterTheme), + FlameProvider.value(leaderboardRepository), + FlameProvider.value(_l10n), + ], + children: [ + GameBlocStatusListener(), + BallSpawningBehavior(), + CameraFocusingBehavior(), + CanvasComponent( + onSpritePainted: (paint) { + if (paint.filterQuality != FilterQuality.medium) { + paint.filterQuality = FilterQuality.medium; + } + }, + children: [ + ZCanvasComponent( + children: [ + BoardBackgroundSpriteComponent(), + Boundaries(), + Backbox(leaderboardRepository: leaderboardRepository), + GoogleWord(position: Vector2(-4.25, 1.8)), + Multipliers(), + Multiballs(), + SkillShot( + children: [ + ScoringContactBehavior(points: Points.oneMillion), + ], + ), + AndroidAcres(), + DinoDesert(), + FlutterForest(), + SparkyScorch(), + Drain(), + BottomGroup(), + Launcher(), + ], + ), + ], + ), + ], + ), ], ), - ]; - final characterAreas = [ - AndroidAcres(), - DinoDesert(), - FlutterForest(), - SparkyScorch(), - ]; - - await addAll( - [ - GameBlocStatusListener(), - BallSpawningBehavior(), - CameraFocusingBehavior(), - CanvasComponent( - onSpritePainted: (paint) { - if (paint.filterQuality != FilterQuality.medium) { - paint.filterQuality = FilterQuality.medium; - } - }, - children: [ - ZCanvasComponent( - children: [ - ...machine, - ...decals, - ...characterAreas, - Drain(), - BottomGroup(), - Launcher(), - ], - ), - ], - ), - ], ); await super.onLoad(); @@ -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; diff --git a/lib/game/view/pinball_game_page.dart b/lib/game/view/pinball_game_page.dart index 31ba304b..be6615f1 100644 --- a/lib/game/view/pinball_game_page.dart +++ b/lib/game/view/pinball_game_page.dart @@ -40,33 +40,40 @@ class PinballGamePage extends StatelessWidget { final player = context.read(); final leaderboardRepository = context.read(); - final game = isDebugMode - ? DebugPinballGame( - characterTheme: characterTheme, - player: player, - leaderboardRepository: leaderboardRepository, - l10n: context.l10n, - ) - : PinballGame( - characterTheme: characterTheme, - player: player, - leaderboardRepository: leaderboardRepository, - l10n: context.l10n, - ); + return BlocProvider( + create: (_) => GameBloc(), + child: Builder( + builder: (context) { + final gameBloc = context.read(); + 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 = [ - ...game.preLoadAssets(), - ...player.load(), - ...BonusAnimation.loadAssets(), - ...SelectedCharacter.loadAssets(), - ]; + final loadables = [ + ...game.preLoadAssets(), + ...player.load(), + ...BonusAnimation.loadAssets(), + ...SelectedCharacter.loadAssets(), + ]; - return MultiBlocProvider( - providers: [ - BlocProvider(create: (_) => GameBloc()), - BlocProvider(create: (_) => AssetsManagerCubit(loadables)..load()), - ], - child: PinballGameView(game: game), + return BlocProvider( + create: (_) => AssetsManagerCubit(loadables)..load(), + child: PinballGameView(game: game), + ); + }, + ), ); } } diff --git a/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart b/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart index deb69a44..c612ecb9 100644 --- a/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart +++ b/packages/pinball_components/test/src/components/multiplier/multiplier_test.dart @@ -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 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + 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(), + find.byGame<_TestGame>(), matchesGoldenFile('../golden/multipliers/x6-dimmed.png'), ); }, diff --git a/packages/pinball_flame/lib/pinball_flame.dart b/packages/pinball_flame/lib/pinball_flame.dart index 6f8a40f7..38f09b59 100644 --- a/packages/pinball_flame/lib/pinball_flame.dart +++ b/packages/pinball_flame/lib/pinball_flame.dart @@ -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'; diff --git a/packages/pinball_flame/lib/src/contact_behavior.dart b/packages/pinball_flame/lib/src/contact_behavior.dart index ff715b12..92f108d8 100644 --- a/packages/pinball_flame/lib/src/contact_behavior.dart +++ b/packages/pinball_flame/lib/src/contact_behavior.dart @@ -26,6 +26,7 @@ class ContactBehavior extends Component @override Future onLoad() async { + await super.onLoad(); if (_fixturesUserData.isNotEmpty) { for (final fixture in _targetedFixtures) { fixture.userData = _UserData.fromFixture(fixture)..add(this); diff --git a/packages/pinball_flame/lib/src/flame_provider.dart b/packages/pinball_flame/lib/src/flame_provider.dart new file mode 100644 index 00000000..35afb0a5 --- /dev/null +++ b/packages/pinball_flame/lib/src/flame_provider.dart @@ -0,0 +1,65 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame/components.dart'; + +class FlameProvider extends Component { + FlameProvider.value( + this.provider, { + Iterable? children, + }) : super( + children: children, + ); + + final T provider; +} + +class MultiFlameProvider extends Component { + MultiFlameProvider({ + required List> providers, + Iterable? children, + }) : _providers = providers, + _initialChildren = children, + assert(providers.isNotEmpty, 'At least one provider must be given') { + _addProviders(); + } + + final List> _providers; + final Iterable? _initialChildren; + FlameProvider? _lastProvider; + + Future _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 add(Component component) async { + if (_lastProvider == null) { + await super.add(component); + } + await _lastProvider?.add(component); + } +} + +extension ReadFlameProvider on Component { + T readProvider() { + final providers = ancestors().whereType>(); + assert( + providers.isNotEmpty, + 'No FlameProvider<$T> available on the component tree', + ); + + return providers.first.provider; + } +} diff --git a/packages/pinball_flame/test/src/flame_provider_test.dart b/packages/pinball_flame/test/src/flame_provider_test.dart new file mode 100644 index 00000000..cfc10613 --- /dev/null +++ b/packages/pinball_flame/test/src/flame_provider_test.dart @@ -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.value(true), + isA>(), + ); + }); + + flameTester.test('can be loaded', (game) async { + final component = FlameProvider.value(true); + await game.ensureAdd(component); + expect(game.children, contains(component)); + }); + + flameTester.test('adds children', (game) async { + final component = Component(); + final provider = FlameProvider.value( + true, + children: [component], + ); + await game.ensureAdd(provider); + expect(provider.children, contains(component)); + }); + }, + ); + + group('MultiFlameProvider', () { + test('can be instantiated', () { + expect( + MultiFlameProvider( + providers: [ + FlameProvider.value(true), + ], + ), + isA(), + ); + }); + + flameTester.test('adds multiple providers', (game) async { + final provider1 = FlameProvider.value(true); + final provider2 = FlameProvider.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.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.value( + true, + children: [component], + ); + await game.ensureAdd(provider); + expect(component.readProvider(), isTrue); + }); + + flameTester.test( + 'throws assertionError when no provider is found', + (game) async { + final component = Component(); + await game.ensureAdd(component); + + expect( + () => component.readProvider(), + throwsAssertionError, + ); + }, + ); + }, + ); +} diff --git a/pubspec.lock b/pubspec.lock index ffbd3899..96f9f2a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index b98c84a6..fcee1e6e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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/ diff --git a/test/footer/footer_test.dart b/test/footer/footer_test.dart index f8f69259..8f683cbf 100644 --- a/test/footer/footer_test.dart +++ b/test/footer/footer_test.dart @@ -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(); diff --git a/test/game/behaviors/bumper_noisy_behavior_test.dart b/test/game/behaviors/bumper_noise_behavior_test.dart similarity index 70% rename from test/game/behaviors/bumper_noisy_behavior_test.dart rename to test/game/behaviors/bumper_noise_behavior_test.dart index e860a094..d8075726 100644 --- a/test/game/behaviors/bumper_noisy_behavior_test.dart +++ b/test/game/behaviors/bumper_noise_behavior_test.dart @@ -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 pump(_TestBodyComponent child, {required PinballPlayer player}) { + return ensureAdd( + FlameProvider.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()); }, diff --git a/test/game/behaviors/camera_focusing_behavior_test.dart b/test/game/behaviors/camera_focusing_behavior_test.dart index ba6ea3a1..a856b392 100644 --- a/test/game/behaviors/camera_focusing_behavior_test.dart +++ b/test/game/behaviors/camera_focusing_behavior_test.dart @@ -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.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.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.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.value( + value: GameBloc(), + children: [behavior], + ), + ); behavior.onNewState(playing); final previousPosition = game.camera.position.clone(); diff --git a/test/game/behaviors/scoring_behavior_test.dart b/test/game/behaviors/scoring_behavior_test.dart index 5673e165..ef3f10ca 100644 --- a/test/game/behaviors/scoring_behavior_test.dart +++ b/test/game/behaviors/scoring_behavior_test.dart @@ -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 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 pump(BodyComponent child, {GameBloc? gameBloc}) { + return ensureAdd( + FlameBlocProvider.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( - 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(), @@ -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); diff --git a/test/game/components/android_acres/android_acres_test.dart b/test/game/components/android_acres/android_acres_test.dart index 5de7576b..e88d1608 100644 --- a/test/game/components/android_acres/android_acres_test.dart +++ b/test/game/components/android_acres/android_acres_test.dart @@ -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,50 +9,62 @@ 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 _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.android.spaceship.saucer.keyName, + Assets.images.android.spaceship.animatronic.keyName, + Assets.images.android.spaceship.lightBeam.keyName, + Assets.images.android.ramp.boardOpening.keyName, + Assets.images.android.ramp.railingForeground.keyName, + Assets.images.android.ramp.railingBackground.keyName, + Assets.images.android.ramp.main.keyName, + Assets.images.android.ramp.arrow.inactive.keyName, + Assets.images.android.ramp.arrow.active1.keyName, + Assets.images.android.ramp.arrow.active2.keyName, + Assets.images.android.ramp.arrow.active3.keyName, + Assets.images.android.ramp.arrow.active4.keyName, + Assets.images.android.ramp.arrow.active5.keyName, + Assets.images.android.rail.main.keyName, + Assets.images.android.rail.exit.keyName, + Assets.images.android.bumper.a.lit.keyName, + Assets.images.android.bumper.a.dimmed.keyName, + Assets.images.android.bumper.b.lit.keyName, + Assets.images.android.bumper.b.dimmed.keyName, + Assets.images.android.bumper.cow.lit.keyName, + Assets.images.android.bumper.cow.dimmed.keyName, + ]); + } + + Future pump(AndroidAcres child) async { + await ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [child], + ), + ); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.android.spaceship.saucer.keyName, - Assets.images.android.spaceship.animatronic.keyName, - Assets.images.android.spaceship.lightBeam.keyName, - Assets.images.android.ramp.boardOpening.keyName, - Assets.images.android.ramp.railingForeground.keyName, - Assets.images.android.ramp.railingBackground.keyName, - Assets.images.android.ramp.main.keyName, - Assets.images.android.ramp.arrow.inactive.keyName, - Assets.images.android.ramp.arrow.active1.keyName, - Assets.images.android.ramp.arrow.active2.keyName, - Assets.images.android.ramp.arrow.active3.keyName, - Assets.images.android.ramp.arrow.active4.keyName, - Assets.images.android.ramp.arrow.active5.keyName, - Assets.images.android.rail.main.keyName, - Assets.images.android.rail.exit.keyName, - Assets.images.android.bumper.a.lit.keyName, - Assets.images.android.bumper.a.dimmed.keyName, - Assets.images.android.bumper.b.lit.keyName, - Assets.images.android.bumper.b.dimmed.keyName, - Assets.images.android.bumper.cow.lit.keyName, - Assets.images.android.bumper.cow.dimmed.keyName, - ]; group('AndroidAcres', () { - final flameTester = FlameTester( - () => EmptyPinballTestGame(assets: assets), - ); + 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().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().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().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().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().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(); 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().single, isNotNull, diff --git a/test/game/components/android_acres/behaviors/android_spaceship_bonus_behavior_test.dart b/test/game/components/android_acres/behaviors/android_spaceship_bonus_behavior_test.dart index 6be120d5..4ecdb05b 100644 --- a/test/game/components/android_acres/behaviors/android_spaceship_bonus_behavior_test.dart +++ b/test/game/components/android_acres/behaviors/android_spaceship_bonus_behavior_test.dart @@ -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,55 +9,63 @@ 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 _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.android.spaceship.saucer.keyName, + Assets.images.android.spaceship.animatronic.keyName, + Assets.images.android.spaceship.lightBeam.keyName, + Assets.images.android.ramp.boardOpening.keyName, + Assets.images.android.ramp.railingForeground.keyName, + Assets.images.android.ramp.railingBackground.keyName, + Assets.images.android.ramp.main.keyName, + Assets.images.android.ramp.arrow.inactive.keyName, + Assets.images.android.ramp.arrow.active1.keyName, + Assets.images.android.ramp.arrow.active2.keyName, + Assets.images.android.ramp.arrow.active3.keyName, + Assets.images.android.ramp.arrow.active4.keyName, + Assets.images.android.ramp.arrow.active5.keyName, + Assets.images.android.rail.main.keyName, + Assets.images.android.rail.exit.keyName, + Assets.images.android.bumper.a.lit.keyName, + Assets.images.android.bumper.a.dimmed.keyName, + Assets.images.android.bumper.b.lit.keyName, + Assets.images.android.bumper.b.dimmed.keyName, + Assets.images.android.bumper.cow.lit.keyName, + Assets.images.android.bumper.cow.dimmed.keyName, + ]); + } + + Future pump( + AndroidAcres child, { + required GameBloc gameBloc, + }) async { + await ensureAdd( + FlameBlocProvider.value( + value: gameBloc, + children: [child], + ), + ); + } +} class _MockGameBloc extends Mock implements GameBloc {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.android.spaceship.saucer.keyName, - Assets.images.android.spaceship.animatronic.keyName, - Assets.images.android.spaceship.lightBeam.keyName, - Assets.images.android.ramp.boardOpening.keyName, - Assets.images.android.ramp.railingForeground.keyName, - Assets.images.android.ramp.railingBackground.keyName, - Assets.images.android.ramp.main.keyName, - Assets.images.android.ramp.arrow.inactive.keyName, - Assets.images.android.ramp.arrow.active1.keyName, - Assets.images.android.ramp.arrow.active2.keyName, - Assets.images.android.ramp.arrow.active3.keyName, - Assets.images.android.ramp.arrow.active4.keyName, - Assets.images.android.ramp.arrow.active5.keyName, - Assets.images.android.rail.main.keyName, - Assets.images.android.rail.exit.keyName, - Assets.images.android.bumper.a.lit.keyName, - Assets.images.android.bumper.a.dimmed.keyName, - Assets.images.android.bumper.b.lit.keyName, - Assets.images.android.bumper.b.dimmed.keyName, - Assets.images.android.bumper.cow.lit.keyName, - Assets.images.android.bumper.cow.dimmed.keyName, - ]; group('AndroidSpaceshipBonusBehavior', () { late GameBloc gameBloc; setUp(() { gameBloc = _MockGameBloc(); - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); }); - final flameBlocTester = FlameBlocTester( - 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(); diff --git a/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart b/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart index 41c3e301..f41487cd 100644 --- a/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart +++ b/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart @@ -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 onLoad() async { + images.prefix = ''; + await images.load(theme.Assets.images.dash.ball.keyName); + } + + Future pump( + Iterable children, { + GameBloc? gameBloc, + }) async { + await ensureAdd( + FlameBlocProvider.value( + value: gameBloc ?? GameBloc(), + children: [ + FlameProvider.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), diff --git a/test/game/components/android_acres/behaviors/ramp_bonus_behavior_test.dart b/test/game/components/android_acres/behaviors/ramp_bonus_behavior_test.dart index acd17717..cb6c2784 100644 --- a/test/game/components/android_acres/behaviors/ramp_bonus_behavior_test.dart +++ b/test/game/components/android_acres/behaviors/ramp_bonus_behavior_test.dart @@ -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,7 +14,41 @@ 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 onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.android.ramp.boardOpening.keyName, + Assets.images.android.ramp.railingForeground.keyName, + Assets.images.android.ramp.railingBackground.keyName, + Assets.images.android.ramp.main.keyName, + Assets.images.android.ramp.arrow.inactive.keyName, + Assets.images.android.ramp.arrow.active1.keyName, + Assets.images.android.ramp.arrow.active2.keyName, + Assets.images.android.ramp.arrow.active3.keyName, + Assets.images.android.ramp.arrow.active4.keyName, + Assets.images.android.ramp.arrow.active5.keyName, + Assets.images.android.rail.main.keyName, + Assets.images.android.rail.exit.keyName, + Assets.images.score.oneMillion.keyName, + ]); + } + + Future pump( + SpaceshipRamp child, { + required GameBloc gameBloc, + }) async { + await ensureAdd( + FlameBlocProvider.value( + value: gameBloc, + children: [ + ZCanvasComponent(children: [child]), + ], + ), + ); + } +} class _MockGameBloc extends Mock implements GameBloc {} @@ -23,21 +59,6 @@ class _MockStreamSubscription extends Mock void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.android.ramp.boardOpening.keyName, - Assets.images.android.ramp.railingForeground.keyName, - Assets.images.android.ramp.railingBackground.keyName, - Assets.images.android.ramp.main.keyName, - Assets.images.android.ramp.arrow.inactive.keyName, - Assets.images.android.ramp.arrow.active1.keyName, - Assets.images.android.ramp.arrow.active2.keyName, - Assets.images.android.ramp.arrow.active3.keyName, - Assets.images.android.ramp.arrow.active4.keyName, - Assets.images.android.ramp.arrow.active5.keyName, - Assets.images.android.rail.main.keyName, - Assets.images.android.rail.exit.keyName, - Assets.images.score.oneMillion.keyName, - ]; group('RampBonusBehavior', () { const shotPoints = Points.oneMillion; @@ -46,22 +67,13 @@ void main() { setUp(() { gameBloc = _MockGameBloc(); - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); }); - final flameBlocTester = FlameBlocTester( - 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(); 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(); 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); diff --git a/test/game/components/android_acres/behaviors/ramp_shot_behavior_test.dart b/test/game/components/android_acres/behaviors/ramp_shot_behavior_test.dart index 23f02220..ae072ea4 100644 --- a/test/game/components/android_acres/behaviors/ramp_shot_behavior_test.dart +++ b/test/game/components/android_acres/behaviors/ramp_shot_behavior_test.dart @@ -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,7 +14,41 @@ 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 onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.android.ramp.boardOpening.keyName, + Assets.images.android.ramp.railingForeground.keyName, + Assets.images.android.ramp.railingBackground.keyName, + Assets.images.android.ramp.main.keyName, + Assets.images.android.ramp.arrow.inactive.keyName, + Assets.images.android.ramp.arrow.active1.keyName, + Assets.images.android.ramp.arrow.active2.keyName, + Assets.images.android.ramp.arrow.active3.keyName, + Assets.images.android.ramp.arrow.active4.keyName, + Assets.images.android.ramp.arrow.active5.keyName, + Assets.images.android.rail.main.keyName, + Assets.images.android.rail.exit.keyName, + Assets.images.score.fiveThousand.keyName, + ]); + } + + Future pump( + SpaceshipRamp child, { + required GameBloc gameBloc, + }) async { + await ensureAdd( + FlameBlocProvider.value( + value: gameBloc, + children: [ + ZCanvasComponent(children: [child]), + ], + ), + ); + } +} class _MockGameBloc extends Mock implements GameBloc {} @@ -23,21 +59,6 @@ class _MockStreamSubscription extends Mock void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.android.ramp.boardOpening.keyName, - Assets.images.android.ramp.railingForeground.keyName, - Assets.images.android.ramp.railingBackground.keyName, - Assets.images.android.ramp.main.keyName, - Assets.images.android.ramp.arrow.inactive.keyName, - Assets.images.android.ramp.arrow.active1.keyName, - Assets.images.android.ramp.arrow.active2.keyName, - Assets.images.android.ramp.arrow.active3.keyName, - Assets.images.android.ramp.arrow.active4.keyName, - Assets.images.android.ramp.arrow.active5.keyName, - Assets.images.android.rail.main.keyName, - Assets.images.android.rail.exit.keyName, - Assets.images.score.fiveThousand.keyName, - ]; group('RampShotBehavior', () { const shotPoints = Points.fiveThousand; @@ -46,23 +67,14 @@ void main() { setUp(() { gameBloc = _MockGameBloc(); - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); }); - final flameBlocTester = FlameBlocTester( - 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(); 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(); 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); diff --git a/test/game/components/backbox/backbox_test.dart b/test/game/components/backbox/backbox_test.dart index 33d43aa8..52e2746e 100644 --- a/test/game/components/backbox/backbox_test.dart +++ b/test/game/components/backbox/backbox_test.dart @@ -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 onLoad() async { + images.prefix = ''; + await images.loadAll([ + character.leaderboardIcon.keyName, + Assets.images.backbox.marquee.keyName, + Assets.images.backbox.displayDivider.keyName, + ]); + } + + Future pump(Backbox component) { + return ensureAdd( + FlameBlocProvider.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(), + 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(); diff --git a/test/game/components/backbox/displays/initials_input_display_test.dart b/test/game/components/backbox/displays/initials_input_display_test.dart index e2a3c58c..1b92aedd 100644 --- a/test/game/components/backbox/displays/initials_input_display_test.dart +++ b/test/game/components/backbox/displays/initials_input_display_test.dart @@ -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 onLoad() async { + await super.onLoad(); + images.prefix = ''; + await images.loadAll( + [ + characterIconPath, + Assets.images.backbox.displayDivider.keyName, + ], + ); + } + + Future pump(InitialsInputDisplay component) { + return ensureAdd( + FlameBlocProvider.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().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), ); diff --git a/test/game/components/backbox/displays/initials_submission_failure_display_test.dart b/test/game/components/backbox/displays/initials_submission_failure_display_test.dart index 5989445f..b37b41e7 100644 --- a/test/game/components/backbox/displays/initials_submission_failure_display_test.dart +++ b/test/game/components/backbox/displays/initials_submission_failure_display_test.dart @@ -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()); diff --git a/test/game/components/backbox/displays/initials_submission_success_display_test.dart b/test/game/components/backbox/displays/initials_submission_success_display_test.dart index 1bd1fcd9..7ad3d182 100644 --- a/test/game/components/backbox/displays/initials_submission_success_display_test.dart +++ b/test/game/components/backbox/displays/initials_submission_success_display_test.dart @@ -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()); diff --git a/test/game/components/backbox/displays/loading_display_test.dart b/test/game/components/backbox/displays/loading_display_test.dart index a09d0d68..efd84097 100644 --- a/test/game/components/backbox/displays/loading_display_test.dart +++ b/test/game/components/backbox/displays/loading_display_test.dart @@ -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 pump(LoadingDisplay component) { + return ensureAdd( + FlameBlocProvider.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(); + final component = game.descendants().whereType().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(); - expect(component?.text, equals('Loading')); + final component = game.descendants().whereType().first; + expect(component.text, equals('Loading')); - final timer = component?.firstChild(); + final timer = component.firstChild(); 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')); }); }); } diff --git a/test/game/components/bottom_group_test.dart b/test/game/components/bottom_group_test.dart index 1d9e58ab..fab8dfaf 100644 --- a/test/game/components/bottom_group_test.dart +++ b/test/game/components/bottom_group_test.dart @@ -1,36 +1,47 @@ // 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'; +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.kicker.left.lit.keyName, + Assets.images.kicker.left.dimmed.keyName, + Assets.images.kicker.right.lit.keyName, + Assets.images.kicker.right.dimmed.keyName, + Assets.images.baseboard.left.keyName, + Assets.images.baseboard.right.keyName, + Assets.images.flipper.left.keyName, + Assets.images.flipper.right.keyName, + ]); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.kicker.left.lit.keyName, - Assets.images.kicker.left.dimmed.keyName, - Assets.images.kicker.right.lit.keyName, - Assets.images.kicker.right.dimmed.keyName, - Assets.images.baseboard.left.keyName, - Assets.images.baseboard.right.keyName, - Assets.images.flipper.left.keyName, - Assets.images.flipper.right.keyName, - ]; - final flameTester = FlameTester( - () => EmptyPinballTestGame(assets: assets), - ); group('BottomGroup', () { + final flameTester = FlameTester(_TestGame.new); + flameTester.test( 'loads correctly', (game) async { final bottomGroup = BottomGroup(); - await game.ensureAdd(bottomGroup); + await game.ensureAdd( + FlameBlocProvider.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.value( + value: GameBloc(), + children: [bottomGroup], + ), + ); final leftFlippers = bottomGroup.descendants().whereType().where( @@ -53,7 +69,12 @@ void main() { 'one right flipper', (game) async { final bottomGroup = BottomGroup(); - await game.ensureAdd(bottomGroup); + await game.ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [bottomGroup], + ), + ); final rightFlippers = bottomGroup.descendants().whereType().where( @@ -67,7 +88,12 @@ void main() { 'two Baseboards', (game) async { final bottomGroup = BottomGroup(); - await game.ensureAdd(bottomGroup); + await game.ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [bottomGroup], + ), + ); final basebottomGroups = bottomGroup.descendants().whereType(); @@ -79,7 +105,12 @@ void main() { 'two Kickers', (game) async { final bottomGroup = BottomGroup(); - await game.ensureAdd(bottomGroup); + await game.ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [bottomGroup], + ), + ); final kickers = bottomGroup.descendants().whereType(); expect(kickers.length, equals(2)); diff --git a/test/game/components/controlled_ball_test.dart b/test/game/components/controlled_ball_test.dart index 04ac0e0f..95451515 100644 --- a/test/game/components/controlled_ball_test.dart +++ b/test/game/components/controlled_ball_test.dart @@ -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 onLoad() async { + images.prefix = ''; + await images.load(theme.Assets.images.dash.ball.keyName); + } + + Future pump(Ball child, {required GameBloc gameBloc}) async { + await ensureAdd( + FlameBlocProvider.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.empty(), - initialState: const GameState.initial(), - ); }); - final flameBlocTester = FlameBlocTester( - gameBuilder: EmptyPinballTestGame.new, - blocBuilder: () => gameBloc, - assets: assets, - ); + final flameBlocTester = FlameTester(_TestGame.new); test('can be instantiated', () { expect( @@ -63,59 +51,21 @@ void main() { ); }); - group('turboCharge', () { - setUpAll(() { - registerFallbackValue(Vector2.zero()); - registerFallbackValue(Component()); - }); - - flameBlocTester.testGameWidget( - 'adds TurboChargeActivated', - setUp: (game, tester) async { - await game.images.loadAll(assets); - final controller = BallController(ball); - await ball.add(controller); - await game.ensureAdd(ball); - - await controller.turboCharge(); - }, - verify: (game, tester) async { - verify(() => gameBloc.add(const SparkyTurboChargeActivated())) - .called(1); - }, - ); - - flameBlocTester.test( - 'initially stops the ball', - (game) async { - final gameRef = _MockPinballGame(); - final ball = _MockControlledBall(); - final controller = _WrappedBallController(ball, gameRef); - when(() => gameRef.read()).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()).thenReturn(gameBloc); - when(() => ball.controller).thenReturn(controller); - when(() => ball.add(any())).thenAnswer((_) async {}); - - await controller.turboCharge(); - - verify(ball.resume).called(1); - }, - ); - }); + flameBlocTester.testGameWidget( + 'turboCharge adds TurboChargeActivated', + setUp: (game, tester) async { + await game.onLoad(); + + final controller = BallController(ball); + await ball.add(controller); + await game.pump(ball, gameBloc: gameBloc); + + await controller.turboCharge(); + }, + verify: (game, tester) async { + verify(() => gameBloc.add(const SparkyTurboChargeActivated())) + .called(1); + }, + ); }); } diff --git a/test/game/components/controlled_flipper_test.dart b/test/game/components/controlled_flipper_test.dart index af262dbf..00a69f9e 100644 --- a/test/game/components/controlled_flipper_test.dart +++ b/test/game/components/controlled_flipper_test.dart @@ -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 onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.flipper.left.keyName, + Assets.images.flipper.right.keyName, + ]); + } + + Future pump(Flipper flipper, {required GameBloc gameBloc}) { + return ensureAdd( + FlameBlocProvider.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( - gameBuilder: EmptyPinballTestGame.new, - blocBuilder: () => gameBloc, - assets: assets, - ); - group('onKeyEvent', () { final leftKeys = UnmodifiableListView([ LogicalKeyboardKey.arrowLeft, @@ -63,11 +74,13 @@ void main() { whenListen( gameBloc, const Stream.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.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.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.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.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.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.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); diff --git a/test/game/components/controlled_plunger_test.dart b/test/game/components/controlled_plunger_test.dart index f772b39a..25b1f739 100644 --- a/test/game/components/controlled_plunger_test.dart +++ b/test/game/components/controlled_plunger_test.dart @@ -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 onLoad() async { + images.prefix = ''; + await images.load(Assets.images.plunger.plunger.keyName); + } + + Future pump( + Plunger child, { + GameBloc? gameBloc, + PinballPlayer? pinballPlayer, + }) { + return ensureAdd( + FlameBlocProvider.value( + value: gameBloc ?? GameBloc() + ..add(const GameStarted()), + children: [ + FlameProvider.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( - 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.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.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.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 diff --git a/test/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior_test.dart b/test/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior_test.dart index 22b6313b..54b3b42b 100644 --- a/test/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior_test.dart +++ b/test/game/components/dino_desert/behaviors/chrome_dino_bonus_behavior_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: cascade_invocations -import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_bloc/flame_bloc.dart'; +import 'package:flame_forge2d/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 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 pump( + DinoDesert child, { + required GameBloc gameBloc, + }) async { + await ensureAdd( + FlameBlocProvider.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.empty(), - initialState: const GameState.initial(), - ); }); - final flameBlocTester = FlameBlocTester( - 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()); diff --git a/test/game/components/dino_desert/dino_desert_test.dart b/test/game/components/dino_desert/dino_desert_test.dart index 63e45e5b..7dea25a3 100644 --- a/test/game/components/dino_desert/dino_desert_test.dart +++ b/test/game/components/dino_desert/dino_desert_test.dart @@ -1,42 +1,59 @@ // 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'; +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.dino.animatronic.head.keyName, + Assets.images.dino.animatronic.mouth.keyName, + Assets.images.dino.topWall.keyName, + Assets.images.dino.topWallTunnel.keyName, + Assets.images.dino.bottomWall.keyName, + Assets.images.slingshot.upper.keyName, + Assets.images.slingshot.lower.keyName, + ]); + } + + Future pump(DinoDesert child) async { + await ensureAdd( + FlameBlocProvider.value( + value: _MockGameBloc(), + children: [child], + ), + ); + } +} + +class _MockGameBloc extends Mock implements GameBloc {} 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.topWallTunnel.keyName, - Assets.images.dino.bottomWall.keyName, - Assets.images.slingshot.upper.keyName, - Assets.images.slingshot.lower.keyName, - ]; - final flameTester = FlameTester( - () => EmptyPinballTestGame(assets: assets), - ); + 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().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().length, equals(1), ); }, ); + flameTester.test( 'Slingshots', (game) async { - await game.ensureAdd(DinoDesert()); + await game.pump(DinoDesert()); expect( game.descendants().whereType().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().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().single, + component.children.whereType().single, isNotNull, ); }); diff --git a/test/game/components/drain/behaviors/draining_behavior_test.dart b/test/game/components/drain/behaviors/draining_behavior_test.dart index dbc62006..d25a7da6 100644 --- a/test/game/components/drain/behaviors/draining_behavior_test.dart +++ b/test/game/components/drain/behaviors/draining_behavior_test.dart @@ -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 onLoad() async { + images.prefix = ''; + await images.load(theme.Assets.images.dash.ball.keyName); + } + + Future pump( + Drain child, { + required GameBloc gameBloc, + }) async { + await ensureAdd( + FlameBlocProvider.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.empty(), - ); }); - final flameBlocTester = FlameBlocTester( - 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(); diff --git a/test/game/components/drain/drain_test.dart b/test/game/components/drain/drain_test.dart index 98c55ca1..b10c55e3 100644 --- a/test/game/components/drain/drain_test.dart +++ b/test/game/components/drain/drain_test.dart @@ -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( diff --git a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart index 71b41029..3dcd870b 100644 --- a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart +++ b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart @@ -1,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 onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.dash.animatronic.keyName, + theme.Assets.images.dash.ball.keyName, + ]); + } + + Future pump( + FlutterForest child, { + required GameBloc gameBloc, + }) async { + await ensureAdd( + FlameBlocProvider.value( + value: gameBloc, + children: [ + FlameProvider.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.empty(), - initialState: const GameState.initial(), - ); }); - final flameBlocTester = FlameBlocTester( - 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); diff --git a/test/game/components/flutter_forest/flutter_forest_test.dart b/test/game/components/flutter_forest/flutter_forest_test.dart index bc0e5ff4..470719d8 100644 --- a/test/game/components/flutter_forest/flutter_forest_test.dart +++ b/test/game/components/flutter_forest/flutter_forest_test.dart @@ -1,40 +1,67 @@ // 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'; +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + Assets.images.dash.animatronic.keyName, + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]); + } + + Future pump(FlutterForest child) async { + await ensureAdd( + FlameBlocProvider.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 assets = [ - Assets.images.dash.bumper.main.active.keyName, - Assets.images.dash.bumper.main.inactive.keyName, - Assets.images.dash.bumper.a.active.keyName, - Assets.images.dash.bumper.a.inactive.keyName, - Assets.images.dash.bumper.b.active.keyName, - Assets.images.dash.bumper.b.inactive.keyName, - Assets.images.dash.animatronic.keyName, - Assets.images.signpost.inactive.keyName, - Assets.images.signpost.active1.keyName, - Assets.images.signpost.active2.keyName, - Assets.images.signpost.active3.keyName, - ]; - final flameTester = FlameTester( - () => EmptyPinballTestGame(assets: assets), - ); + 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().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().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().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(); for (final bumper in bumpers) { expect( diff --git a/test/game/components/game_bloc_status_listener_test.dart b/test/game/components/game_bloc_status_listener_test.dart index 73f47161..7118aa8d 100644 --- a/test/game/components/game_bloc_status_listener_test.dart +++ b/test/game/components/game_bloc_status_listener_test.dart @@ -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 onLoad() async { + images.prefix = ''; + await images.load(Assets.images.backbox.marquee.keyName); + } + + Future pump( + Iterable children, { + PinballPlayer? pinballPlayer, + }) async { + return ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [ + MultiFlameProvider( + providers: [ + FlameProvider.value( + pinballPlayer ?? _MockPinballPlayer(), + ), + FlameProvider.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(), + ); }); + 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()) - .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 [], - status: GameStatus.gameOver, - ); - gameBlocStatusListener.onNewState(state); - - verify( - () => backbox.requestInitials( - score: any(named: 'score'), - character: any(named: 'character'), - ), - ).called(1); + (game) async { + final component = GameBlocStatusListener(); + final repository = _MockLeaderboardRepository(); + final backbox = Backbox(leaderboardRepository: repository); + final state = const GameState.initial() + ..copyWith( + status: GameStatus.gameOver, + ); + + await game.pump([component, backbox]); + + expect(() => component.onNewState(state), returnsNormally); }, ); - test( - 'changes the backbox when it is not a game over', - () { - gameBlocStatusListener.onNewState( - GameState.initial().copyWith(status: GameStatus.playing), - ); - - verify(() => overlays.remove(PinballGame.playButtonOverlay)) - .called(1); - }, - ); - - test( + 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); }, ); }); diff --git a/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart b/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart index c9910fd7..40afeb09 100644 --- a/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart +++ b/test/game/components/google_word/behaviors/google_word_bonus_behavior_test.dart @@ -1,55 +1,71 @@ // 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 _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleWord.letter1.lit.keyName, + Assets.images.googleWord.letter1.dimmed.keyName, + Assets.images.googleWord.letter2.lit.keyName, + Assets.images.googleWord.letter2.dimmed.keyName, + Assets.images.googleWord.letter3.lit.keyName, + Assets.images.googleWord.letter3.dimmed.keyName, + Assets.images.googleWord.letter4.lit.keyName, + Assets.images.googleWord.letter4.dimmed.keyName, + Assets.images.googleWord.letter5.lit.keyName, + Assets.images.googleWord.letter5.dimmed.keyName, + Assets.images.googleWord.letter6.lit.keyName, + Assets.images.googleWord.letter6.dimmed.keyName, + ]); + } + + Future pump(GoogleWord child, {required GameBloc gameBloc}) async { + await ensureAdd( + FlameBlocProvider.value( + value: gameBloc, + children: [ + FlameProvider.value( + _MockPinballPlayer(), + children: [child], + ) + ], + ), + ); + } +} class _MockGameBloc extends Mock implements GameBloc {} +class _MockPinballPlayer extends Mock implements PinballPlayer {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.googleWord.letter1.lit.keyName, - Assets.images.googleWord.letter1.dimmed.keyName, - Assets.images.googleWord.letter2.lit.keyName, - Assets.images.googleWord.letter2.dimmed.keyName, - Assets.images.googleWord.letter3.lit.keyName, - Assets.images.googleWord.letter3.dimmed.keyName, - Assets.images.googleWord.letter4.lit.keyName, - Assets.images.googleWord.letter4.dimmed.keyName, - Assets.images.googleWord.letter5.lit.keyName, - Assets.images.googleWord.letter5.dimmed.keyName, - Assets.images.googleWord.letter6.lit.keyName, - Assets.images.googleWord.letter6.dimmed.keyName, - ]; group('GoogleWordBonusBehaviors', () { late GameBloc gameBloc; setUp(() { gameBloc = _MockGameBloc(); - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); }); - final flameBlocTester = FlameBlocTester( - 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) { diff --git a/test/game/components/google_word/google_word_test.dart b/test/game/components/google_word/google_word_test.dart index 11751238..c0258281 100644 --- a/test/game/components/google_word/google_word_test.dart +++ b/test/game/components/google_word/google_word_test.dart @@ -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,25 +8,40 @@ 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'; +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.googleWord.letter1.lit.keyName, + Assets.images.googleWord.letter1.dimmed.keyName, + Assets.images.googleWord.letter2.lit.keyName, + Assets.images.googleWord.letter2.dimmed.keyName, + Assets.images.googleWord.letter3.lit.keyName, + Assets.images.googleWord.letter3.dimmed.keyName, + Assets.images.googleWord.letter4.lit.keyName, + Assets.images.googleWord.letter4.dimmed.keyName, + Assets.images.googleWord.letter5.lit.keyName, + Assets.images.googleWord.letter5.dimmed.keyName, + Assets.images.googleWord.letter6.lit.keyName, + Assets.images.googleWord.letter6.dimmed.keyName, + ]); + } + + Future pump(GoogleWord child, {GameBloc? gameBloc}) { + return ensureAdd( + FlameBlocProvider.value( + value: gameBloc ?? GameBloc(), + children: [child], + ), + ); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.googleWord.letter1.lit.keyName, - Assets.images.googleWord.letter1.dimmed.keyName, - Assets.images.googleWord.letter2.lit.keyName, - Assets.images.googleWord.letter2.dimmed.keyName, - Assets.images.googleWord.letter3.lit.keyName, - Assets.images.googleWord.letter3.dimmed.keyName, - Assets.images.googleWord.letter4.lit.keyName, - Assets.images.googleWord.letter4.dimmed.keyName, - Assets.images.googleWord.letter5.lit.keyName, - Assets.images.googleWord.letter5.dimmed.keyName, - Assets.images.googleWord.letter6.lit.keyName, - Assets.images.googleWord.letter6.dimmed.keyName, - ]; - final flameTester = FlameTester(() => EmptyPinballTestGame(assets: assets)); + + 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(); 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().single, isNotNull, diff --git a/test/game/components/launcher_test.dart b/test/game/components/launcher_test.dart index c76e6b7e..35272569 100644 --- a/test/game/components/launcher_test.dart +++ b/test/game/components/launcher_test.dart @@ -1,36 +1,49 @@ // 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'; +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.launchRamp.ramp.keyName, + Assets.images.launchRamp.backgroundRailing.keyName, + Assets.images.launchRamp.foregroundRailing.keyName, + Assets.images.flapper.backSupport.keyName, + Assets.images.flapper.frontSupport.keyName, + Assets.images.flapper.flap.keyName, + Assets.images.plunger.plunger.keyName, + Assets.images.plunger.rocket.keyName, + ]); + } + + Future pump(Launcher launchRamp) { + return ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [launchRamp], + ), + ); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.launchRamp.ramp.keyName, - Assets.images.launchRamp.backgroundRailing.keyName, - Assets.images.launchRamp.foregroundRailing.keyName, - Assets.images.flapper.backSupport.keyName, - Assets.images.flapper.frontSupport.keyName, - Assets.images.flapper.flap.keyName, - Assets.images.plunger.plunger.keyName, - Assets.images.plunger.rocket.keyName, - ]; - final flameTester = FlameTester( - () => EmptyPinballTestGame(assets: assets), - ); + 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(); + component.descendants().whereType(); 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(); + final descendantsQuery = component.descendants().whereType(); 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(); + final descendantsQuery = component.descendants().whereType(); 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(); + component.descendants().whereType(); expect(descendantsQuery.length, equals(1)); }, ); diff --git a/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart b/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart index 03c50041..139c7e47 100644 --- a/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart +++ b/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart @@ -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 onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.multiball.lit.keyName, + Assets.images.multiball.dimmed.keyName, + ]); + } + + Future pump(Multiballs child, {GameBloc? gameBloc}) { + return ensureAdd( + FlameBlocProvider.value( + value: gameBloc ?? GameBloc(), + children: [child], + ), + ); + } +} class _MockGameBloc extends Mock implements GameBloc {} @@ -18,43 +38,44 @@ 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; - - setUp(() { - gameBloc = _MockGameBloc(); - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), + final flameTester = FlameTester(_TestGame.new); + + test('can be instantiated', () { + expect( + MultiballsBehavior(), + isA(), ); }); - final flameBlocTester = FlameBlocTester( - 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', () { - final previous = GameState.initial(); - final state = previous.copyWith( - bonusHistory: [GameBonus.dashNest], - ); + 'is true when the bonusHistory has changed ' + 'with a new GameBonus.dashNest', + () { + final previous = GameState.initial(); + final state = previous.copyWith( + bonusHistory: [GameBonus.dashNest], + ); - expect( - MultiballsBehavior().listenWhen(previous, state), - isTrue, - ); - }); + expect( + 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.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(); diff --git a/test/game/components/multiballs/multiballs_test.dart b/test/game/components/multiballs/multiballs_test.dart index c1a328b1..1841d0a3 100644 --- a/test/game/components/multiballs/multiballs_test.dart +++ b/test/game/components/multiballs/multiballs_test.dart @@ -1,54 +1,57 @@ // 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'; +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.multiball.lit.keyName, + Assets.images.multiball.dimmed.keyName, + ]); + } + + Future pump(Multiballs child, {GameBloc? gameBloc}) { + return ensureAdd( + FlameBlocProvider.value( + value: gameBloc ?? GameBloc(), + children: [child], + ), + ); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.multiball.lit.keyName, - Assets.images.multiball.dimmed.keyName, - ]; - late GameBloc gameBloc; - setUp(() { - gameBloc = GameBloc(); - }); - - final flameBlocTester = FlameBlocTester( - 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 { - final multiballs = Multiballs(); - await game.ensureAdd(multiballs); - - expect( - multiballs.descendants().whereType().length, - equals(4), - ); - }, - ); - }); + flameBlocTester.test( + 'loads four Multiball', + (game) async { + final multiballs = Multiballs(); + await game.pump(multiballs); + expect( + multiballs.descendants().whereType().length, + equals(4), + ); + }, + ); }); } diff --git a/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart b/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart index ef39aad2..f1e42a51 100644 --- a/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart +++ b/test/game/components/multipliers/behaviors/multipliers_behavior_test.dart @@ -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,7 +13,33 @@ 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 _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.multiplier.x2.lit.keyName, + Assets.images.multiplier.x2.dimmed.keyName, + Assets.images.multiplier.x3.lit.keyName, + Assets.images.multiplier.x3.dimmed.keyName, + Assets.images.multiplier.x4.lit.keyName, + Assets.images.multiplier.x4.dimmed.keyName, + Assets.images.multiplier.x5.lit.keyName, + Assets.images.multiplier.x5.dimmed.keyName, + Assets.images.multiplier.x6.lit.keyName, + Assets.images.multiplier.x6.dimmed.keyName, + ]); + } + + Future pump(Multipliers child) async { + await ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [child], + ), + ); + } +} class _MockGameBloc extends Mock implements GameBloc {} @@ -21,18 +49,6 @@ class _MockMultiplierCubit extends Mock implements MultiplierCubit {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.multiplier.x2.lit.keyName, - Assets.images.multiplier.x2.dimmed.keyName, - Assets.images.multiplier.x3.lit.keyName, - Assets.images.multiplier.x3.dimmed.keyName, - Assets.images.multiplier.x4.lit.keyName, - Assets.images.multiplier.x4.dimmed.keyName, - Assets.images.multiplier.x5.lit.keyName, - Assets.images.multiplier.x5.dimmed.keyName, - Assets.images.multiplier.x6.lit.keyName, - Assets.images.multiplier.x6.dimmed.keyName, - ]; group('MultipliersBehavior', () { late GameBloc gameBloc; @@ -47,11 +63,7 @@ void main() { ); }); - final flameBlocTester = FlameBlocTester( - 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(); diff --git a/test/game/components/multipliers/multipliers_test.dart b/test/game/components/multipliers/multipliers_test.dart index 6b2d95a6..7f98058e 100644 --- a/test/game/components/multipliers/multipliers_test.dart +++ b/test/game/components/multipliers/multipliers_test.dart @@ -1,63 +1,65 @@ // 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'; +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.multiplier.x2.lit.keyName, + Assets.images.multiplier.x2.dimmed.keyName, + Assets.images.multiplier.x3.lit.keyName, + Assets.images.multiplier.x3.dimmed.keyName, + Assets.images.multiplier.x4.lit.keyName, + Assets.images.multiplier.x4.dimmed.keyName, + Assets.images.multiplier.x5.lit.keyName, + Assets.images.multiplier.x5.dimmed.keyName, + Assets.images.multiplier.x6.lit.keyName, + Assets.images.multiplier.x6.dimmed.keyName, + ]); + } + + Future pump(Multipliers child) async { + await ensureAdd( + FlameBlocProvider.value( + value: GameBloc(), + children: [child], + ), + ); + } +} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.multiplier.x2.lit.keyName, - Assets.images.multiplier.x2.dimmed.keyName, - Assets.images.multiplier.x3.lit.keyName, - Assets.images.multiplier.x3.dimmed.keyName, - Assets.images.multiplier.x4.lit.keyName, - Assets.images.multiplier.x4.dimmed.keyName, - Assets.images.multiplier.x5.lit.keyName, - Assets.images.multiplier.x5.dimmed.keyName, - Assets.images.multiplier.x6.lit.keyName, - Assets.images.multiplier.x6.dimmed.keyName, - ]; - - late GameBloc gameBloc; - - setUp(() { - gameBloc = GameBloc(); - }); - final flameBlocTester = FlameBlocTester( - 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 { - final multipliersGroup = Multipliers(); - await game.ensureAdd(multipliersGroup); - - expect( - multipliersGroup.descendants().whereType().length, - equals(5), - ); - }, - ); - }); + flameTester.test( + 'loads five Multiplier', + (game) async { + final multipliersGroup = Multipliers(); + await game.pump(multipliersGroup); + expect( + multipliersGroup.descendants().whereType().length, + equals(5), + ); + }, + ); }); } diff --git a/test/game/components/sparky_scorch_test.dart b/test/game/components/sparky_scorch_test.dart index 0eeb9b45..92a3ab01 100644 --- a/test/game/components/sparky_scorch_test.dart +++ b/test/game/components/sparky_scorch_test.dart @@ -8,7 +8,24 @@ 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 _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.loadAll([ + Assets.images.sparky.computer.top.keyName, + Assets.images.sparky.computer.base.keyName, + Assets.images.sparky.computer.glow.keyName, + Assets.images.sparky.animatronic.keyName, + Assets.images.sparky.bumper.a.lit.keyName, + Assets.images.sparky.bumper.a.dimmed.keyName, + Assets.images.sparky.bumper.b.lit.keyName, + Assets.images.sparky.bumper.b.dimmed.keyName, + Assets.images.sparky.bumper.c.lit.keyName, + Assets.images.sparky.bumper.c.dimmed.keyName, + ]); + } +} class _MockControlledBall extends Mock implements ControlledBall {} @@ -18,22 +35,8 @@ class _MockContact extends Mock implements Contact {} void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - 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, - ]; - - final flameTester = FlameTester( - () => EmptyPinballTestGame(assets: assets), - ); + + final flameTester = FlameTester(_TestGame.new); group('SparkyScorch', () { flameTester.test('loads correctly', (game) async { diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index e2998f5d..b983b0b8 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -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 onLoad() async { + images.prefix = ''; + final futures = preLoadAssets(); + await Future.wait(futures); + await super.onLoad(); + } +} + +class _TestDebugPinballGame extends DebugPinballGame { + _TestDebugPinballGame() + : super( + characterTheme: const theme.DashTheme(), + leaderboardRepository: _MockLeaderboardRepository(), + gameBloc: GameBloc(), + l10n: _MockAppLocalizations(), + player: _MockPinballPlayer(), + ); + + @override + Future onLoad() async { + images.prefix = ''; + final futures = preLoadAssets(); + await Future.wait(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( - 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().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().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(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(); diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index 90d1b194..f78f6278 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -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 onLoad() async { + images.prefix = ''; + final futures = preLoadAssets(); + await Future.wait(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; diff --git a/test/helpers/builders.dart b/test/helpers/builders.dart deleted file mode 100644 index 2c23e3fe..00000000 --- a/test/helpers/builders.dart +++ /dev/null @@ -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> - extends FlameTester { - FlameBlocTester({ - required GameCreateFunction gameBuilder, - required B Function() blocBuilder, - // TODO(allisonryan0002): find alternative for testGameWidget. Loading - // assets in onLoad fails because the game loads after - List? assets, - List 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, - ), - ), - ); - }, - ); -} diff --git a/test/helpers/fakes.dart b/test/helpers/fakes.dart deleted file mode 100644 index 706733a1..00000000 --- a/test/helpers/fakes.dart +++ /dev/null @@ -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 {} diff --git a/test/helpers/forge2d.dart b/test/helpers/forge2d.dart deleted file mode 100644 index f000d404..00000000 --- a/test/helpers/forge2d.dart +++ /dev/null @@ -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); -} diff --git a/test/helpers/helpers.dart b/test/helpers/helpers.dart index 6621abcc..613fd5b8 100644 --- a/test/helpers/helpers.dart +++ b/test/helpers/helpers.dart @@ -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'; diff --git a/test/helpers/test_games.dart b/test/helpers/test_games.dart deleted file mode 100644 index 220693c3..00000000 --- a/test/helpers/test_games.dart +++ /dev/null @@ -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? 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? _assets; - - @override - Future onLoad() async { - if (_assets != null) { - await images.loadAll(_assets!); - } - await super.onLoad(); - } -} - -class DebugPinballTestGame extends DebugPinballGame { - DebugPinballTestGame({ - List? 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? _assets; - - @override - Future onLoad() async { - if (_assets != null) { - await images.loadAll(_assets!); - } - await super.onLoad(); - } -} - -class EmptyPinballTestGame extends PinballTestGame { - EmptyPinballTestGame({ - List? assets, - PinballPlayer? player, - CharacterTheme? theme, - AppLocalizations? l10n, - }) : super( - assets: assets, - player: player, - theme: theme, - l10n: l10n ?? _MockAppLocalizations(), - ); - - @override - Future onLoad() async { - if (_assets != null) { - await images.loadAll(_assets!); - } - } -} - -class EmptyKeyboardPinballTestGame extends PinballTestGame - with HasKeyboardHandlerComponents { - EmptyKeyboardPinballTestGame({ - List? assets, - PinballPlayer? player, - CharacterTheme? theme, - AppLocalizations? l10n, - }) : super( - assets: assets, - player: player, - theme: theme, - l10n: l10n ?? _MockAppLocalizations(), - ); - - @override - Future onLoad() async { - if (_assets != null) { - await images.loadAll(_assets!); - } - } -} diff --git a/test/helpers/text_span.dart b/test/helpers/text_span.dart deleted file mode 100644 index c98d33d9..00000000 --- a/test/helpers/text_span.dart +++ /dev/null @@ -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; -}