diff --git a/lib/game/components/wall.dart b/lib/game/components/wall.dart index 1acce4d3..96522cbd 100644 --- a/lib/game/components/wall.dart +++ b/lib/game/components/wall.dart @@ -78,13 +78,7 @@ class BottomWall extends Wall { class BottomWallBallContactCallback extends ContactCallback { @override void begin(Ball ball, BottomWall wall, Contact contact) { - ball.shouldRemove = true; - // TODO(alestiago): replace with .firstChild when available. - late final BallController? controller; - final children = ball.children.whereType(); - controller = children.isEmpty ? null : children.first; - - if (controller != null) controller.lost(); + ball.children.whereType().first.lost(); } } diff --git a/test/game/components/ball_controller_test.dart b/test/game/components/ball_controller_test.dart new file mode 100644 index 00000000..22473099 --- /dev/null +++ b/test/game/components/ball_controller_test.dart @@ -0,0 +1,144 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/game.dart'; + +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(PinballGameTest.create); + + group('BallController', () { + late Ball ball; + + setUp(() { + ball = Ball(baseColor: const Color(0xFF00FFFF)); + }); + + flameTester.test( + 'lost removes ball', + (game) async { + await game.add(ball); + final controller = BallController(ball); + await controller.attach(); + await game.ready(); + + controller.lost(); + await game.ready(); + + expect(game.contains(ball), isFalse); + }, + ); + }); + + group('PlungerBallController', () { + group('lost', () { + late GameBloc gameBloc; + late Ball ball; + + setUp(() { + gameBloc = MockGameBloc(); + ball = Ball(baseColor: const Color(0xFF00FFFF)); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); + }); + + final tester = flameBlocTester( + game: PinballGameTest.create, + gameBloc: () => gameBloc, + ); + + tester.widgetTest( + 'removes ball', + (game, tester) async { + await game.add(ball); + final controller = PlungerBallController(ball); + await controller.attach(); + await game.ready(); + + controller.lost(); + await game.ready(); + + expect(game.contains(ball), isFalse); + }, + ); + + tester.widgetTest( + 'adds BallLost to GameBloc', + (game, tester) async { + final controller = PlungerBallController(ball); + await controller.attach(); + await game.add(ball); + await game.ready(); + + controller.lost(); + + verify(() => gameBloc.add(const BallLost())).called(1); + }, + ); + + tester.widgetTest( + 'adds a new ball if the game is not over', + (game, tester) async { + final controller = PlungerBallController(ball); + await controller.attach(); + await game.add(ball); + await game.ready(); + + final previousBalls = game.descendants().whereType(); + controller.lost(); + await game.ready(); + final currentBalls = game.descendants().whereType(); + + expect( + previousBalls.length, + equals(currentBalls.length), + ); + }, + ); + + tester.widgetTest( + 'no ball is added on game over', + (game, tester) async { + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState( + score: 10, + balls: 1, + activatedBonusLetters: [], + activatedDashNests: {}, + bonusHistory: [], + ), + ); + final controller = BallController(ball); + await controller.attach(); + await game.add(ball); + await game.ready(); + + final previousBalls = game.descendants().whereType(); + controller.lost(); + await game.ready(); + final currentBalls = game.descendants().whereType(); + + expect( + currentBalls.length, + equals( + (previousBalls.toList()..remove(ball)).length, + ), + ); + }, + ); + }); + }); +} diff --git a/test/game/components/ball_test.dart b/test/game/components/ball_test.dart deleted file mode 100644 index f12b3569..00000000 --- a/test/game/components/ball_test.dart +++ /dev/null @@ -1,87 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:pinball/game/game.dart'; - -import 'package:pinball_components/pinball_components.dart'; - -import '../../helpers/helpers.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - group('Ball', () { - group('lost', () { - late GameBloc gameBloc; - - setUp(() { - gameBloc = MockGameBloc(); - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); - }); - - final tester = flameBlocTester(gameBloc: () => gameBloc); - - tester.testGameWidget( - 'adds BallLost to GameBloc', - setUp: (game, tester) async { - await game.ready(); - }, - verify: (game, tester) async { - game.children.whereType().first.controller.lost(); - await tester.pump(); - - verify(() => gameBloc.add(const BallLost())).called(1); - }, - ); - - tester.testGameWidget( - 'resets the ball if the game is not over', - setUp: (game, tester) async { - await game.ready(); - - game.children.whereType().first.controller.lost(); - await game.ready(); // Making sure that all additions are done - }, - verify: (game, tester) async { - expect( - game.children.whereType().length, - equals(1), - ); - }, - ); - - tester.testGameWidget( - 'no ball is added on game over', - setUp: (game, tester) async { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState( - score: 10, - balls: 1, - activatedBonusLetters: [], - activatedDashNests: {}, - bonusHistory: [], - ), - ); - await game.ready(); - - game.children.whereType().first.controller.lost(); - await tester.pump(); - }, - verify: (game, tester) async { - expect( - game.children.whereType().length, - equals(0), - ); - }, - ); - }); - }); -} diff --git a/test/game/components/bonus_word_test.dart b/test/game/components/bonus_word_test.dart index 42cf19c4..24063da8 100644 --- a/test/game/components/bonus_word_test.dart +++ b/test/game/components/bonus_word_test.dart @@ -194,10 +194,15 @@ void main() { }); group('bonus letter activation', () { - final gameBloc = MockGameBloc(); - final tester = flameBlocTester(gameBloc: () => gameBloc); + late GameBloc gameBloc; + final tester = flameBlocTester( + // TODO(alestiago): Use TestGame once BonusLetter has controller. + game: PinballGameTest.create, + gameBloc: () => gameBloc, + ); setUp(() { + gameBloc = MockGameBloc(); whenListen( gameBloc, const Stream.empty(), @@ -236,8 +241,10 @@ void main() { initialState: state, ); + final bonusLetter = BonusLetter(letter: '', index: 0); + await game.add(bonusLetter); await game.ready(); - final bonusLetter = game.descendants().whereType().first; + bonusLetter.activate(); await game.ready(); }, @@ -257,15 +264,19 @@ void main() { bonusHistory: [], ); + final bonusLetter = BonusLetter(letter: '', index: 0); + await game.add(bonusLetter); await game.ready(); - final bonusLetter = game.descendants().whereType().first; + bonusLetter.activate(); bonusLetter.onNewState(state); await tester.pump(); }, verify: (game, tester) async { - final bonusLetter = game.descendants().whereType().first; + // TODO(aleastiago): Look into making `testGameWidget` pass the + // subject. + final bonusLetter = game.descendants().whereType().last; expect( bonusLetter.children.whereType().length, equals(1), diff --git a/test/game/components/flutter_forest_test.dart b/test/game/components/flutter_forest_test.dart index 3f4db6ff..48586895 100644 --- a/test/game/components/flutter_forest_test.dart +++ b/test/game/components/flutter_forest_test.dart @@ -59,7 +59,10 @@ void main() { group('listenWhen', () { final gameBloc = MockGameBloc(); - final tester = flameBlocTester(gameBloc: () => gameBloc); + final tester = flameBlocTester( + game: TestGame.new, + gameBloc: () => gameBloc, + ); setUp(() { whenListen( @@ -71,12 +74,8 @@ void main() { tester.testGameWidget( 'listens when a Bonus.dashNest is added', - setUp: (game, tester) async { - await game.ready(); - }, verify: (game, tester) async { - final flutterForest = - game.descendants().whereType().first; + final flutterForest = FlutterForest(); const state = GameState( score: 0, @@ -96,7 +95,11 @@ void main() { group('DashNestBumperBallContactCallback', () { final gameBloc = MockGameBloc(); - final tester = flameBlocTester(gameBloc: () => gameBloc); + final tester = flameBlocTester( + // TODO(alestiago): Use TestGame.new once a controller is implemented. + game: PinballGameTest.create, + gameBloc: () => gameBloc, + ); setUp(() { whenListen( @@ -118,8 +121,9 @@ void main() { final contactCallback = DashNestBumperBallContactCallback(); contactCallback.begin(dashNestBumper, MockBall(), MockContact()); - verify(() => gameBloc.add(DashNestActivated(dashNestBumper.id))) - .called(1); + verify( + () => gameBloc.add(DashNestActivated(dashNestBumper.id)), + ).called(1); }, ); }); diff --git a/test/game/components/plunger_test.dart b/test/game/components/plunger_test.dart index c6787be6..2a49ae2d 100644 --- a/test/game/components/plunger_test.dart +++ b/test/game/components/plunger_test.dart @@ -2,7 +2,6 @@ import 'dart:collection'; -import 'package:bloc_test/bloc_test.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/services.dart'; @@ -189,22 +188,14 @@ void main() { group('PlungerAnchorPrismaticJointDef', () { const compressionDistance = 10.0; - final gameBloc = MockGameBloc(); late Plunger plunger; setUp(() { - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); plunger = Plunger( compressionDistance: compressionDistance, ); }); - final flameTester = flameBlocTester(gameBloc: () => gameBloc); - group('initializes with', () { flameTester.test( 'plunger body as bodyA', diff --git a/test/game/components/score_points_test.dart b/test/game/components/score_points_test.dart index 30ec70db..f97bdada 100644 --- a/test/game/components/score_points_test.dart +++ b/test/game/components/score_points_test.dart @@ -1,4 +1,3 @@ -import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -7,14 +6,6 @@ import 'package:pinball_components/pinball_components.dart'; import '../../helpers/helpers.dart'; -class MockGameBloc extends Mock implements GameBloc {} - -class MockPinballGame extends Mock implements PinballGame {} - -class FakeContact extends Fake implements Contact {} - -class FakeGameEvent extends Fake implements GameEvent {} - class FakeScorePoints extends BodyComponent with ScorePoints { @override Body createBody() { @@ -30,16 +21,12 @@ void main() { late PinballGame game; late GameBloc bloc; late Ball ball; - late ComponentSet componentSet; - late BallController ballController; late FakeScorePoints fakeScorePoints; setUp(() { game = MockPinballGame(); bloc = MockGameBloc(); ball = MockBall(); - componentSet = MockComponentSet(); - ballController = MockBallController(); fakeScorePoints = FakeScorePoints(); }); @@ -51,13 +38,9 @@ void main() { test( 'emits Scored event with points', () { - when(() => componentSet.whereType()) - .thenReturn([ballController]); - when(() => ball.children).thenReturn(componentSet); - when(() => ballController.gameRef).thenReturn(game); when(game.read).thenReturn(bloc); - BallScorePointsCallback().begin( + BallScorePointsCallback(game).begin( ball, fakeScorePoints, FakeContact(), @@ -71,19 +54,5 @@ void main() { }, ); }); - - group('end', () { - test("doesn't add events to GameBloc", () { - BallScorePointsCallback().end( - ball, - fakeScorePoints, - FakeContact(), - ); - - verifyNever( - () => bloc.add(any()), - ); - }); - }); }); } diff --git a/test/helpers/builders.dart b/test/helpers/builders.dart index d8ffd715..970dd12b 100644 --- a/test/helpers/builders.dart +++ b/test/helpers/builders.dart @@ -1,14 +1,14 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pinball/game/game.dart'; -import 'helpers.dart'; - -FlameTester flameBlocTester({ +FlameTester flameBlocTester({ + required T Function() game, required GameBloc Function() gameBloc, }) { - return FlameTester( - PinballGameTest.create, + return FlameTester( + game, pumpWidget: (gameWidget, tester) async { await tester.pumpWidget( BlocProvider.value( diff --git a/test/helpers/fakes.dart b/test/helpers/fakes.dart new file mode 100644 index 00000000..706733a1 --- /dev/null +++ b/test/helpers/fakes.dart @@ -0,0 +1,7 @@ +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/helpers.dart b/test/helpers/helpers.dart index 223ec627..d9dc2a17 100644 --- a/test/helpers/helpers.dart +++ b/test/helpers/helpers.dart @@ -6,7 +6,9 @@ // license that can be found in the LICENSE file or at export 'builders.dart'; export 'extensions.dart'; +export 'fakes.dart'; export 'key_testers.dart'; export 'mocks.dart'; export 'navigator.dart'; export 'pump_app.dart'; +export 'test_game.dart'; diff --git a/test/helpers/test_game.dart b/test/helpers/test_game.dart new file mode 100644 index 00000000..a1219868 --- /dev/null +++ b/test/helpers/test_game.dart @@ -0,0 +1,7 @@ +import 'package:flame_forge2d/flame_forge2d.dart'; + +class TestGame extends Forge2DGame { + TestGame() { + images.prefix = ''; + } +}