From ace61193fb913273120594cb58382a115a93daea Mon Sep 17 00:00:00 2001 From: Alejandro Santiago Date: Thu, 5 May 2022 21:05:22 +0100 Subject: [PATCH] refactor: removed `GameBallsController` (#349) --- .../behaviors/ball_spawning_behavior.dart | 33 +++++ lib/game/behaviors/behaviors.dart | 1 + lib/game/bloc/game_state.dart | 1 + lib/game/components/components.dart | 2 +- lib/game/components/controlled_ball.dart | 17 --- lib/game/components/drain.dart | 34 ----- .../components/drain/behaviors/behaviors.dart | 1 + .../drain/behaviors/draining_behavior.dart | 21 +++ lib/game/components/drain/drain.dart | 36 ++++++ lib/game/pinball_game.dart | 49 +------ .../lib/src/components/plunger.dart | 25 ++-- .../test/src/components/plunger_test.dart | 11 ++ .../ball_spawning_behavior_test.dart | 117 +++++++++++++++++ .../game/components/controlled_ball_test.dart | 37 ------ .../behaviors/draining_behavior_test.dart | 121 ++++++++++++++++++ .../components/{ => drain}/drain_test.dart | 24 +--- test/game/pinball_game_test.dart | 101 ++------------- 17 files changed, 377 insertions(+), 254 deletions(-) create mode 100644 lib/game/behaviors/ball_spawning_behavior.dart delete mode 100644 lib/game/components/drain.dart create mode 100644 lib/game/components/drain/behaviors/behaviors.dart create mode 100644 lib/game/components/drain/behaviors/draining_behavior.dart create mode 100644 lib/game/components/drain/drain.dart create mode 100644 test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart create mode 100644 test/game/components/drain/behaviors/draining_behavior_test.dart rename test/game/components/{ => drain}/drain_test.dart (60%) diff --git a/lib/game/behaviors/ball_spawning_behavior.dart b/lib/game/behaviors/ball_spawning_behavior.dart new file mode 100644 index 00000000..3602615b --- /dev/null +++ b/lib/game/behaviors/ball_spawning_behavior.dart @@ -0,0 +1,33 @@ +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'; + +/// Spawns a new [Ball] into the game when all balls are lost and still +/// [GameStatus.playing]. +class BallSpawningBehavior extends Component + with ParentIsA, BlocComponent { + @override + bool listenWhen(GameState? previousState, GameState newState) { + if (!newState.status.isPlaying) return false; + + final startedGame = previousState?.status.isWaiting ?? true; + final lostRound = + (previousState?.rounds ?? newState.rounds + 1) > newState.rounds; + return startedGame || lostRound; + } + + @override + void onNewState(GameState state) { + final plunger = parent.descendants().whereType().single; + final canvas = parent.descendants().whereType().single; + final ball = ControlledBall.launch(characterTheme: parent.characterTheme) + ..initialPosition = Vector2( + plunger.body.position.x, + plunger.body.position.y - Ball.size.y, + ); + + canvas.add(ball); + } +} diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart index f87b4f10..243fff82 100644 --- a/lib/game/behaviors/behaviors.dart +++ b/lib/game/behaviors/behaviors.dart @@ -1,3 +1,4 @@ +export 'ball_spawning_behavior.dart'; export 'bumper_noisy_behavior.dart'; export 'camera_focusing_behavior.dart'; export 'scoring_behavior.dart'; diff --git a/lib/game/bloc/game_state.dart b/lib/game/bloc/game_state.dart index a9e86720..d0311442 100644 --- a/lib/game/bloc/game_state.dart +++ b/lib/game/bloc/game_state.dart @@ -27,6 +27,7 @@ enum GameStatus { } extension GameStatusX on GameStatus { + bool get isWaiting => this == GameStatus.waiting; bool get isPlaying => this == GameStatus.playing; bool get isGameOver => this == GameStatus.gameOver; } diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 8f900475..b96b6a65 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -5,7 +5,7 @@ export 'controlled_ball.dart'; export 'controlled_flipper.dart'; export 'controlled_plunger.dart'; export 'dino_desert/dino_desert.dart'; -export 'drain.dart'; +export 'drain/drain.dart'; export 'flutter_forest/flutter_forest.dart'; export 'game_bloc_status_listener.dart'; export 'google_word/google_word.dart'; diff --git a/lib/game/components/controlled_ball.dart b/lib/game/components/controlled_ball.dart index 132639d4..2356e0d8 100644 --- a/lib/game/components/controlled_ball.dart +++ b/lib/game/components/controlled_ball.dart @@ -22,9 +22,7 @@ class ControlledBall extends Ball with Controls { zIndex = ZIndexes.ballOnLaunchRamp; } - /// {@template bonus_ball} /// {@macro controlled_ball} - /// {@endtemplate} ControlledBall.bonus({ required CharacterTheme characterTheme, }) : super(assetPath: characterTheme.ball.keyName) { @@ -47,12 +45,6 @@ class BallController extends ComponentController /// {@macro ball_controller} BallController(Ball ball) : super(ball); - /// Event triggered when the ball is lost. - // TODO(alestiago): Refactor using behaviors. - void lost() { - component.shouldRemove = true; - } - /// Stops the [Ball] inside of the [SparkyComputer] while the turbo charge /// sequence runs, then boosts the ball out of the computer. Future turboCharge() async { @@ -70,13 +62,4 @@ class BallController extends ComponentController BallTurboChargingBehavior(impulse: Vector2(40, 110)), ); } - - @override - void onRemove() { - super.onRemove(); - final noBallsLeft = gameRef.descendants().whereType().isEmpty; - if (noBallsLeft) { - gameRef.read().add(const RoundLost()); - } - } } diff --git a/lib/game/components/drain.dart b/lib/game/components/drain.dart deleted file mode 100644 index 1dc3e211..00000000 --- a/lib/game/components/drain.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flame/extensions.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; - -/// {@template drain} -/// Area located at the bottom of the board to detect when a [Ball] is lost. -/// {@endtemplate} -// TODO(allisonryan0002): move to components package when possible. -class Drain extends BodyComponent with ContactCallbacks { - /// {@macro drain} - Drain() : super(renderBody: false); - - @override - Body createBody() { - final shape = EdgeShape() - ..set( - BoardDimensions.bounds.bottomLeft.toVector2(), - BoardDimensions.bounds.bottomRight.toVector2(), - ); - final fixtureDef = FixtureDef(shape, isSensor: true); - final bodyDef = BodyDef(userData: this); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } - -// TODO(allisonryan0002): move this to ball.dart when BallLost is removed. - @override - void beginContact(Object other, Contact contact) { - super.beginContact(other, contact); - if (other is! ControlledBall) return; - other.controller.lost(); - } -} diff --git a/lib/game/components/drain/behaviors/behaviors.dart b/lib/game/components/drain/behaviors/behaviors.dart new file mode 100644 index 00000000..a7c2a401 --- /dev/null +++ b/lib/game/components/drain/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'draining_behavior.dart'; diff --git a/lib/game/components/drain/behaviors/draining_behavior.dart b/lib/game/components/drain/behaviors/draining_behavior.dart new file mode 100644 index 00000000..36512efa --- /dev/null +++ b/lib/game/components/drain/behaviors/draining_behavior.dart @@ -0,0 +1,21 @@ +import 'package:flame/components.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 { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + if (other is! Ball) return; + + other.removeFromParent(); + final ballsLeft = gameRef.descendants().whereType().length; + if (ballsLeft - 1 == 0) { + gameRef.read().add(const RoundLost()); + } + } +} diff --git a/lib/game/components/drain/drain.dart b/lib/game/components/drain/drain.dart new file mode 100644 index 00000000..aaf09023 --- /dev/null +++ b/lib/game/components/drain/drain.dart @@ -0,0 +1,36 @@ +import 'package:flame/extensions.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/foundation.dart'; +import 'package:pinball/game/components/drain/behaviors/behaviors.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// {@template drain} +/// Area located at the bottom of the board. +/// +/// Its [DrainingBehavior] handles removing a [Ball] from the game. +/// {@endtemplate} +class Drain extends BodyComponent with ContactCallbacks { + /// {@macro drain} + Drain() + : super( + renderBody: false, + children: [DrainingBehavior()], + ); + + /// Creates a [Drain] without any children. + /// + /// This can be used for testing a [Drain]'s behaviors in isolation. + @visibleForTesting + Drain.test(); + + @override + Body createBody() { + final shape = EdgeShape() + ..set( + BoardDimensions.bounds.bottomLeft.toVector2(), + BoardDimensions.bounds.bottomRight.toVector2(), + ); + final fixtureDef = FixtureDef(shape, isSensor: true); + return world.createBody(BodyDef())..createFixture(fixtureDef); + } +} diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 438dd7da..bbab932b 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -17,11 +17,7 @@ import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_theme/pinball_theme.dart'; class PinballGame extends PinballForge2DGame - with - FlameBloc, - HasKeyboardHandlerComponents, - Controls<_GameBallsController>, - MultiTouchTapDetector { + with FlameBloc, HasKeyboardHandlerComponents, MultiTouchTapDetector { PinballGame({ required this.characterTheme, required this.leaderboardRepository, @@ -29,7 +25,6 @@ class PinballGame extends PinballForge2DGame required this.player, }) : super(gravity: Vector2(0, 30)) { images.prefix = ''; - controller = _GameBallsController(this); } /// Identifier of the play button overlay @@ -73,6 +68,7 @@ class PinballGame extends PinballForge2DGame await addAll( [ GameBlocStatusListener(), + BallSpawningBehavior(), CameraFocusingBehavior(), CanvasComponent( onSpritePainted: (paint) { @@ -147,43 +143,6 @@ class PinballGame extends PinballForge2DGame } } -class _GameBallsController extends ComponentController - with BlocComponent { - _GameBallsController(PinballGame game) : super(game); - - @override - bool listenWhen(GameState? previousState, GameState newState) { - final noBallsLeft = component.descendants().whereType().isEmpty; - return noBallsLeft && newState.status.isPlaying; - } - - @override - void onNewState(GameState state) { - super.onNewState(state); - spawnBall(); - } - - @override - Future onLoad() async { - await super.onLoad(); - spawnBall(); - } - - void spawnBall() { - // TODO(alestiago): Refactor with behavioural pattern. - component.ready().whenComplete(() { - final plunger = parent!.descendants().whereType().single; - final ball = ControlledBall.launch( - characterTheme: component.characterTheme, - )..initialPosition = Vector2( - plunger.body.position.x, - plunger.body.position.y - Ball.size.y, - ); - component.descendants().whereType().single.add(ball); - }); - } -} - class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { DebugPinballGame({ required CharacterTheme characterTheme, @@ -195,9 +154,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector { player: player, leaderboardRepository: leaderboardRepository, l10n: l10n, - ) { - controller = _GameBallsController(this); - } + ); Vector2? lineStart; Vector2? lineEnd; diff --git a/packages/pinball_components/lib/src/components/plunger.dart b/packages/pinball_components/lib/src/components/plunger.dart index 040c3287..5b9b77b2 100644 --- a/packages/pinball_components/lib/src/components/plunger.dart +++ b/packages/pinball_components/lib/src/components/plunger.dart @@ -1,5 +1,6 @@ import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -13,16 +14,23 @@ class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex { /// {@macro plunger} Plunger({ required this.compressionDistance, - }) : super(renderBody: false) { + }) : super( + renderBody: false, + children: [_PlungerSpriteAnimationGroupComponent()], + ) { zIndex = ZIndexes.plunger; layer = Layer.launcher; } + /// Creates a [Plunger] without any children. + /// + /// This can be used for testing [Plunger]'s behaviors in isolation. + @visibleForTesting + Plunger.test({required this.compressionDistance}); + /// Distance the plunger can lower. final double compressionDistance; - late final _PlungerSpriteAnimationGroupComponent _spriteComponent; - List _createFixtureDefs() { final fixturesDef = []; @@ -78,8 +86,10 @@ class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex { /// Set a constant downward velocity on the [Plunger]. void pull() { + final sprite = firstChild<_PlungerSpriteAnimationGroupComponent>()!; + body.linearVelocity = Vector2(0, 7); - _spriteComponent.pull(); + sprite.pull(); } /// Set an upward velocity on the [Plunger]. @@ -87,10 +97,12 @@ class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex { /// The velocity's magnitude depends on how far the [Plunger] has been pulled /// from its original [initialPosition]. void release() { + final sprite = firstChild<_PlungerSpriteAnimationGroupComponent>()!; + _pullingDownTime = 0; final velocity = (initialPosition.y - body.position.y) * 11; body.linearVelocity = Vector2(0, velocity); - _spriteComponent.release(); + sprite.release(); } @override @@ -127,9 +139,6 @@ class Plunger extends BodyComponent with InitialPosition, Layered, ZIndex { Future onLoad() async { await super.onLoad(); await _anchorToJoint(); - - _spriteComponent = _PlungerSpriteAnimationGroupComponent(); - await add(_spriteComponent); } } diff --git a/packages/pinball_components/test/src/components/plunger_test.dart b/packages/pinball_components/test/src/components/plunger_test.dart index fd759f8d..ea1ba826 100644 --- a/packages/pinball_components/test/src/components/plunger_test.dart +++ b/packages/pinball_components/test/src/components/plunger_test.dart @@ -14,6 +14,17 @@ void main() { group('Plunger', () { const compressionDistance = 0.0; + test('can be instantiated', () { + expect( + Plunger(compressionDistance: compressionDistance), + isA(), + ); + expect( + Plunger.test(compressionDistance: compressionDistance), + isA(), + ); + }); + flameTester.testGameWidget( 'renders correctly', setUp: (game, tester) async { 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 new file mode 100644 index 00000000..41c3e301 --- /dev/null +++ b/test/game/components/android_acres/behaviors/ball_spawning_behavior_test.dart @@ -0,0 +1,117 @@ +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/behaviors/ball_spawning_behavior.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' as theme; + +import '../../../../helpers/test_games.dart'; + +class _MockGameState extends Mock implements GameState {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'BallSpawningBehavior', + () { + final flameTester = FlameTester(EmptyPinballTestGame.new); + + test('can be instantiated', () { + expect( + BallSpawningBehavior(), + isA(), + ); + }); + + flameTester.test( + 'loads', + (game) async { + final behavior = BallSpawningBehavior(); + await game.ensureAdd(behavior); + expect(game.contains(behavior), isTrue); + }, + ); + + group('listenWhen', () { + test( + 'never listens when new state not playing', + () { + final waiting = const GameState.initial() + ..copyWith(status: GameStatus.waiting); + final gameOver = const GameState.initial() + ..copyWith(status: GameStatus.gameOver); + + final behavior = BallSpawningBehavior(); + expect(behavior.listenWhen(_MockGameState(), waiting), isFalse); + expect(behavior.listenWhen(_MockGameState(), gameOver), isFalse); + }, + ); + + test( + 'listens when started playing', + () { + final waiting = + const GameState.initial().copyWith(status: GameStatus.waiting); + final playing = + const GameState.initial().copyWith(status: GameStatus.playing); + + final behavior = BallSpawningBehavior(); + expect(behavior.listenWhen(waiting, playing), isTrue); + }, + ); + + test( + 'listens when lost rounds', + () { + final playing1 = const GameState.initial().copyWith( + status: GameStatus.playing, + rounds: 2, + ); + final playing2 = const GameState.initial().copyWith( + status: GameStatus.playing, + rounds: 1, + ); + + final behavior = BallSpawningBehavior(); + expect(behavior.listenWhen(playing1, playing2), isTrue); + }, + ); + + test( + "doesn't listen when didn't lose any rounds", + () { + final playing = const GameState.initial().copyWith( + status: GameStatus.playing, + rounds: 2, + ); + + final behavior = BallSpawningBehavior(); + expect(behavior.listenWhen(playing, playing), isFalse); + }, + ); + }); + + flameTester.test( + 'onNewState adds a ball', + (game) async { + await game.images.load(theme.Assets.images.dash.ball.keyName); + final behavior = BallSpawningBehavior(); + await game.ensureAddAll([ + behavior, + ZCanvasComponent(), + Plunger.test(compressionDistance: 10), + ]); + expect(game.descendants().whereType(), isEmpty); + + behavior.onNewState(_MockGameState()); + await game.ready(); + + expect(game.descendants().whereType(), isNotEmpty); + }, + ); + }, + ); +} diff --git a/test/game/components/controlled_ball_test.dart b/test/game/components/controlled_ball_test.dart index dc142ffd..04ac0e0f 100644 --- a/test/game/components/controlled_ball_test.dart +++ b/test/game/components/controlled_ball_test.dart @@ -63,43 +63,6 @@ void main() { ); }); - flameBlocTester.testGameWidget( - "lost doesn't adds RoundLost to GameBloc " - 'when there are balls left', - setUp: (game, tester) async { - final controller = BallController(ball); - await ball.add(controller); - await game.ensureAdd(ball); - - final otherBall = Ball(); - final otherController = BallController(otherBall); - await otherBall.add(otherController); - await game.ensureAdd(otherBall); - - controller.lost(); - await game.ready(); - }, - verify: (game, tester) async { - verifyNever(() => gameBloc.add(const RoundLost())); - }, - ); - - flameBlocTester.testGameWidget( - 'lost adds RoundLost to GameBloc ' - 'when there are no balls left', - setUp: (game, tester) async { - final controller = BallController(ball); - await ball.add(controller); - await game.ensureAdd(ball); - - controller.lost(); - await game.ready(); - }, - verify: (game, tester) async { - verify(() => gameBloc.add(const RoundLost())).called(1); - }, - ); - group('turboCharge', () { setUpAll(() { registerFallbackValue(Vector2.zero()); diff --git a/test/game/components/drain/behaviors/draining_behavior_test.dart b/test/game/components/drain/behaviors/draining_behavior_test.dart new file mode 100644 index 00000000..dbc62006 --- /dev/null +++ b/test/game/components/drain/behaviors/draining_behavior_test.dart @@ -0,0 +1,121 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.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/drain/behaviors/behaviors.dart'; +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 _MockGameBloc extends Mock implements GameBloc {} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'DrainingBehavior', + () { + final flameTester = FlameTester(Forge2DGame.new); + + test('can be instantiated', () { + expect(DrainingBehavior(), isA()); + }); + + flameTester.test( + 'loads', + (game) async { + final parent = Drain.test(); + final behavior = DrainingBehavior(); + await parent.add(behavior); + await game.ensureAdd(parent); + expect(parent.contains(behavior), isTrue); + }, + ); + + 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, + ); + + flameBlocTester.testGameWidget( + 'adds RoundLost when no balls left', + setUp: (game, tester) async { + await game.images.load(asset); + + final drain = Drain.test(); + final behavior = DrainingBehavior(); + final ball = Ball.test(); + await drain.add(behavior); + await game.ensureAddAll([drain, ball]); + + behavior.beginContact(ball, _MockContact()); + await game.ready(); + + expect(game.descendants().whereType(), isEmpty); + verify(() => gameBloc.add(const RoundLost())).called(1); + }, + ); + + flameBlocTester.testGameWidget( + "doesn't add RoundLost when there are balls left", + setUp: (game, tester) async { + await game.images.load(asset); + + final drain = Drain.test(); + final behavior = DrainingBehavior(); + final ball1 = Ball.test(); + final ball2 = Ball.test(); + await drain.add(behavior); + await game.ensureAddAll([ + drain, + ball1, + ball2, + ]); + + behavior.beginContact(ball1, _MockContact()); + await game.ready(); + + expect(game.descendants().whereType(), isNotEmpty); + verifyNever(() => gameBloc.add(const RoundLost())); + }, + ); + + flameBlocTester.testGameWidget( + 'removes the Ball', + setUp: (game, tester) async { + await game.images.load(asset); + final drain = Drain.test(); + final behavior = DrainingBehavior(); + final ball = Ball.test(); + await drain.add(behavior); + await game.ensureAddAll([drain, ball]); + + behavior.beginContact(ball, _MockContact()); + await game.ready(); + + expect(game.descendants().whereType(), isEmpty); + }, + ); + }); + }, + ); +} diff --git a/test/game/components/drain_test.dart b/test/game/components/drain/drain_test.dart similarity index 60% rename from test/game/components/drain_test.dart rename to test/game/components/drain/drain_test.dart index 984abce3..98c55ca1 100644 --- a/test/game/components/drain_test.dart +++ b/test/game/components/drain/drain_test.dart @@ -3,16 +3,10 @@ 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/game.dart'; - -import '../../helpers/helpers.dart'; - -class _MockControlledBall extends Mock implements ControlledBall {} -class _MockBallController extends Mock implements BallController {} +import 'package:pinball/game/game.dart'; -class _MockContact extends Mock implements Contact {} +import '../../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -45,19 +39,5 @@ void main() { expect(drain.body.fixtures.first.isSensor, isTrue); }, ); - - test( - 'calls lost on contact with ball', - () async { - final drain = Drain(); - final ball = _MockControlledBall(); - final controller = _MockBallController(); - when(() => ball.controller).thenReturn(controller); - - drain.beginContact(ball, _MockContact()); - - verify(controller.lost).called(1); - }, - ); }); } diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index cf70ad43..e2998f5d 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -9,6 +9,7 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter/gestures.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_components/pinball_components.dart'; import 'package:pinball_theme/pinball_theme.dart' as theme; @@ -17,8 +18,6 @@ import '../helpers/helpers.dart'; class _MockGameBloc extends Mock implements GameBloc {} -class _MockGameState extends Mock implements GameState {} - class _MockEventPosition extends Mock implements EventPosition {} class _MockTapDownDetails extends Mock implements TapDownDetails {} @@ -167,8 +166,17 @@ void main() { ); group('components', () { - // TODO(alestiago): tests that Blueprints get added once the Blueprint - // class is removed. + flameBlocTester.test( + 'has only one BallSpawningBehavior', + (game) async { + await game.ready(); + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + flameBlocTester.test( 'has only one Drain', (game) async { @@ -272,91 +280,6 @@ void main() { } }, ); - - group('controller', () { - group('listenWhen', () { - flameTester.testGameWidget( - 'listens when all balls are lost and there are more than 0 rounds', - setUp: (game, tester) async { - // TODO(ruimiguel): check why testGameWidget doesn't add any ball - // to the game. Test needs to have no balls, so fortunately works. - final newState = _MockGameState(); - when(() => newState.status).thenReturn(GameStatus.playing); - game.descendants().whereType().forEach( - (ball) => ball.controller.lost(), - ); - await game.ready(); - - expect( - game.controller.listenWhen(_MockGameState(), newState), - isTrue, - ); - }, - ); - - flameTester.test( - "doesn't listen when some balls are left", - (game) async { - final newState = _MockGameState(); - when(() => newState.status).thenReturn(GameStatus.playing); - - await game.ready(); - - expect( - game.descendants().whereType().length, - greaterThan(0), - ); - expect( - game.controller.listenWhen(_MockGameState(), newState), - isFalse, - ); - }, - ); - - flameTester.testGameWidget( - "doesn't listen when game is over", - setUp: (game, tester) async { - // TODO(ruimiguel): check why testGameWidget doesn't add any ball - // to the game. Test needs to have no balls, so fortunately works. - final newState = _MockGameState(); - when(() => newState.status).thenReturn(GameStatus.gameOver); - game.descendants().whereType().forEach( - (ball) => ball.controller.lost(), - ); - await game.ready(); - - expect( - game.descendants().whereType().isEmpty, - isTrue, - ); - expect( - game.controller.listenWhen(_MockGameState(), newState), - isFalse, - ); - }, - ); - }); - - group('onNewState', () { - flameTester.test( - 'spawns a ball', - (game) async { - final previousBalls = - game.descendants().whereType().toList(); - - game.controller.onNewState(_MockGameState()); - await game.ready(); - final currentBalls = - game.descendants().whereType().toList(); - - expect( - currentBalls.length, - equals(previousBalls.length + 1), - ); - }, - ); - }); - }); }); group('flipper control', () {