// 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: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 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, Assets.images.alienBumper.a.active.keyName, Assets.images.alienBumper.a.inactive.keyName, Assets.images.alienBumper.b.active.keyName, Assets.images.alienBumper.b.inactive.keyName, Assets.images.sparky.bumper.a.active.keyName, Assets.images.sparky.bumper.a.inactive.keyName, Assets.images.sparky.bumper.b.active.keyName, Assets.images.sparky.bumper.b.inactive.keyName, Assets.images.sparky.bumper.c.active.keyName, Assets.images.sparky.bumper.c.inactive.keyName, Assets.images.sparky.animatronic.keyName, Assets.images.spaceship.ramp.boardOpening.keyName, Assets.images.spaceship.ramp.railingForeground.keyName, Assets.images.spaceship.ramp.railingBackground.keyName, Assets.images.spaceship.ramp.main.keyName, Assets.images.spaceship.ramp.arrow.inactive.keyName, Assets.images.spaceship.ramp.arrow.active1.keyName, Assets.images.spaceship.ramp.arrow.active2.keyName, Assets.images.spaceship.ramp.arrow.active3.keyName, Assets.images.spaceship.ramp.arrow.active4.keyName, Assets.images.spaceship.ramp.arrow.active5.keyName, Assets.images.baseboard.left.keyName, Assets.images.baseboard.right.keyName, Assets.images.flipper.left.keyName, Assets.images.flipper.right.keyName, Assets.images.boundary.outer.keyName, Assets.images.boundary.outerBottom.keyName, Assets.images.boundary.bottom.keyName, Assets.images.slingshot.upper.keyName, Assets.images.slingshot.lower.keyName, Assets.images.dino.dinoLandTop.keyName, Assets.images.dino.dinoLandBottom.keyName, ]; final flameTester = FlameTester( () => PinballTestGame(assets: assets), ); final debugModeFlameTester = FlameTester( () => DebugPinballTestGame(assets: assets), ); group('PinballGame', () { group('components', () { // TODO(alestiago): tests that Blueprints get added once the Blueprint // class is removed. flameTester.test( 'has only one BottomWall', (game) async { await game.ready(); expect( game.children.whereType().length, equals(1), ); }, ); flameTester.test( 'has only one Plunger', (game) async { await game.ready(); expect( game.children.whereType().length, equals(1), ); }, ); flameTester.test('has one Board', (game) async { await game.ready(); expect( game.children.whereType().length, equals(1), ); }); group('controller', () { // TODO(alestiago): Write test to be controller agnostic. group('listenWhen', () { late GameBloc gameBloc; setUp(() { gameBloc = GameBloc(); }); final flameBlocTester = FlameBlocTester( gameBuilder: EmptyPinballTestGame.new, blocBuilder: () => gameBloc, // assets: assets, ); flameBlocTester.testGameWidget( 'listens when all balls are lost and there are more than 0 balls', setUp: (game, tester) async { final newState = MockGameState(); when(() => newState.balls).thenReturn(2); 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.balls).thenReturn(1); expect( game.descendants().whereType().length, greaterThan(0), ); expect( game.controller.listenWhen(MockGameState(), newState), isFalse, ); }, ); flameBlocTester.test( "doesn't listen when no balls left", (game) async { final newState = MockGameState(); when(() => newState.balls).thenReturn(0); 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 { await game.ready(); 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('DebugPinballGame', () { debugModeFlameTester.test('adds a ball on tap up', (game) async { await game.ready(); final eventPosition = MockEventPosition(); when(() => eventPosition.game).thenReturn(Vector2.all(10)); final tapUpEvent = MockTapUpInfo(); when(() => tapUpEvent.eventPosition).thenReturn(eventPosition); final previousBalls = game.descendants().whereType().toList(); game.onTapUp(tapUpEvent); await game.ready(); expect( game.children.whereType().length, equals(previousBalls.length + 1), ); }); group('controller', () { late GameBloc gameBloc; setUp(() { gameBloc = GameBloc(); }); final debugModeFlameBlocTester = FlameBlocTester( gameBuilder: DebugPinballTestGame.new, blocBuilder: () => gameBloc, assets: assets, ); debugModeFlameBlocTester.testGameWidget( 'ignores debug balls', setUp: (game, tester) async { final newState = MockGameState(); when(() => newState.balls).thenReturn(1); await game.ready(); game.children.removeWhere((component) => component is Ball); await game.ready(); await game.ensureAdd(ControlledBall.debug()); expect( game.controller.listenWhen(MockGameState(), newState), isTrue, ); }, ); }); }); }