diff --git a/lib/game/components/android_acres.dart b/lib/game/components/android_acres/android_acres.dart similarity index 69% rename from lib/game/components/android_acres.dart rename to lib/game/components/android_acres/android_acres.dart index e7330c1f..3d1a8154 100644 --- a/lib/game/components/android_acres.dart +++ b/lib/game/components/android_acres/android_acres.dart @@ -1,6 +1,8 @@ // ignore_for_file: avoid_renaming_method_parameters import 'package:flame/components.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -16,6 +18,11 @@ class AndroidAcres extends Component { SpaceshipRamp(), SpaceshipRail(), AndroidSpaceship(position: Vector2(-26.5, -28.5)), + AndroidAnimatronic( + children: [ + ScoringBehavior(points: Points.twoHundredThousand), + ], + )..initialPosition = Vector2(-26, -28.25), AndroidBumper.a( children: [ ScoringBehavior(points: Points.twentyThousand), @@ -31,6 +38,13 @@ class AndroidAcres extends Component { ScoringBehavior(points: Points.twentyThousand), ], )..initialPosition = Vector2(-20.5, -13.8), + AndroidSpaceshipBonusBehavior(), ], ); + + /// Creates [AndroidAcres] without any children. + /// + /// This can be used for testing [AndroidAcres]'s behaviors in isolation. + @visibleForTesting + AndroidAcres.test(); } 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 new file mode 100644 index 00000000..87f19b2d --- /dev/null +++ b/lib/game/components/android_acres/behaviors/android_spaceship_bonus_behavior.dart @@ -0,0 +1,28 @@ +import 'package:flame/components.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] is activated. +class AndroidSpaceshipBonusBehavior extends Component + with HasGameRef, ParentIsA { + @override + void onMount() { + super.onMount(); + + final androidSpaceship = + parent.children.whereType().single; + + // TODO(alestiago): Refactor subscription management once the following is + // merged: + // https://github.com/flame-engine/flame/pull/1538 + androidSpaceship.bloc.stream.listen((_) { + if (androidSpaceship.bloc.state == AndroidSpaceshipState.activated) { + gameRef + .read() + .add(const BonusActivated(GameBonus.androidSpaceship)); + androidSpaceship.bloc.onBonusAwarded(); + } + }); + } +} diff --git a/lib/game/components/android_acres/behaviors/behaviors.dart b/lib/game/components/android_acres/behaviors/behaviors.dart new file mode 100644 index 00000000..e4ac5981 --- /dev/null +++ b/lib/game/components/android_acres/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'android_spaceship_bonus_behavior.dart'; diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index aeb5742e..8a3a6dbd 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -1,4 +1,4 @@ -export 'android_acres.dart'; +export 'android_acres/android_acres.dart'; export 'bottom_group.dart'; export 'camera_controller.dart'; export 'controlled_ball.dart'; diff --git a/packages/pinball_components/lib/src/components/android_animatronic.dart b/packages/pinball_components/lib/src/components/android_animatronic.dart new file mode 100644 index 00000000..772d88c4 --- /dev/null +++ b/packages/pinball_components/lib/src/components/android_animatronic.dart @@ -0,0 +1,71 @@ +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template android_animatronic} +/// Animated Android that sits on top of the [AndroidSpaceship]. +/// {@endtemplate} +class AndroidAnimatronic extends BodyComponent + with InitialPosition, Layered, ZIndex { + /// {@macro android_animatronic} + AndroidAnimatronic({Iterable? children}) + : super( + children: [ + _AndroidAnimatronicSpriteAnimationComponent(), + ...?children, + ], + renderBody: false, + ) { + layer = Layer.spaceship; + zIndex = ZIndexes.androidHead; + } + + @override + Body createBody() { + final shape = EllipseShape( + center: Vector2.zero(), + majorRadius: 3.1, + minorRadius: 2, + )..rotate(1.4); + final bodyDef = BodyDef(position: initialPosition); + + return world.createBody(bodyDef)..createFixtureFromShape(shape); + } +} + +class _AndroidAnimatronicSpriteAnimationComponent + extends SpriteAnimationComponent with HasGameRef { + _AndroidAnimatronicSpriteAnimationComponent() + : super( + anchor: Anchor.center, + position: Vector2(-0.24, -2.6), + ); + + @override + Future onLoad() async { + await super.onLoad(); + + final spriteSheet = gameRef.images.fromCache( + Assets.images.android.spaceship.animatronic.keyName, + ); + + const amountPerRow = 18; + const amountPerColumn = 4; + final textureSize = Vector2( + spriteSheet.width / amountPerRow, + spriteSheet.height / amountPerColumn, + ); + size = textureSize / 10; + + animation = SpriteAnimation.fromFrameData( + spriteSheet, + SpriteAnimationData.sequenced( + amount: amountPerRow * amountPerColumn, + amountPerRow: amountPerRow, + stepTime: 1 / 24, + textureSize: textureSize, + ), + ); + } +} diff --git a/packages/pinball_components/lib/src/components/android_spaceship.dart b/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart similarity index 65% rename from packages/pinball_components/lib/src/components/android_spaceship.dart rename to packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart index 77ede1e3..e7658856 100644 --- a/packages/pinball_components/lib/src/components/android_spaceship.dart +++ b/packages/pinball_components/lib/src/components/android_spaceship/android_spaceship.dart @@ -5,28 +5,58 @@ import 'dart:math' as math; 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_components/src/components/android_spaceship/behaviors/behaviors.dart'; import 'package:pinball_flame/pinball_flame.dart'; +export 'cubit/android_spaceship_cubit.dart'; + class AndroidSpaceship extends Component { - AndroidSpaceship({required Vector2 position}) - : super( + AndroidSpaceship({ + required Vector2 position, + }) : bloc = AndroidSpaceshipCubit(), + super( children: [ _SpaceshipSaucer()..initialPosition = position, _SpaceshipSaucerSpriteAnimationComponent()..position = position, _LightBeamSpriteComponent()..position = position + Vector2(2.5, 5), - _AndroidHead()..initialPosition = position + Vector2(0.5, 0.25), - _SpaceshipEntrance(), + AndroidSpaceshipEntrance( + children: [AndroidSpaceshipEntranceBallContactBehavior()], + ), _SpaceshipHole( outsideLayer: Layer.spaceshipExitRail, outsidePriority: ZIndexes.ballOnSpaceshipRail, - )..initialPosition = position - Vector2(5.3, -5.4), + ) + ..initialPosition = position - Vector2(5.3, -5.4) + ..renderBody, _SpaceshipHole( outsideLayer: Layer.board, outsidePriority: ZIndexes.ballOnBoard, )..initialPosition = position - Vector2(-7.5, -1.1), ], ); + + /// Creates an [AndroidSpaceship] without any children. + /// + /// This can be used for testing [AndroidSpaceship]'s behaviors in isolation. + // TODO(alestiago): Refactor injecting bloc once the following is merged: + // https://github.com/flame-engine/flame/pull/1538 + @visibleForTesting + AndroidSpaceship.test({ + required this.bloc, + Iterable? children, + }) : super(children: children); + + // TODO(alestiago): Consider refactoring once the following is merged: + // https://github.com/flame-engine/flame/pull/1538 + final AndroidSpaceshipCubit bloc; + + @override + void onRemove() { + bloc.close(); + super.onRemove(); + } } class _SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { @@ -124,86 +154,29 @@ class _LightBeamSpriteComponent extends SpriteComponent } } -class _AndroidHead extends BodyComponent with InitialPosition, Layered, ZIndex { - _AndroidHead() - : super( - children: [_AndroidHeadSpriteAnimationComponent()], - renderBody: false, - ) { +class AndroidSpaceshipEntrance extends BodyComponent + with ParentIsA, Layered { + AndroidSpaceshipEntrance({Iterable? children}) + : super(children: children) { layer = Layer.spaceship; - zIndex = ZIndexes.androidHead; } @override Body createBody() { - final shape = EllipseShape( - center: Vector2.zero(), - majorRadius: 3.1, - minorRadius: 2, - )..rotate(1.4); - final bodyDef = BodyDef(position: initialPosition); - - return world.createBody(bodyDef)..createFixtureFromShape(shape); - } -} - -class _AndroidHeadSpriteAnimationComponent extends SpriteAnimationComponent - with HasGameRef { - _AndroidHeadSpriteAnimationComponent() - : super( - anchor: Anchor.center, - position: Vector2(-0.24, -2.6), - ); - - @override - Future onLoad() async { - await super.onLoad(); - - final spriteSheet = gameRef.images.fromCache( - Assets.images.android.spaceship.animatronic.keyName, - ); - - const amountPerRow = 18; - const amountPerColumn = 4; - final textureSize = Vector2( - spriteSheet.width / amountPerRow, - spriteSheet.height / amountPerColumn, - ); - size = textureSize / 10; - - animation = SpriteAnimation.fromFrameData( - spriteSheet, - SpriteAnimationData.sequenced( - amount: amountPerRow * amountPerColumn, - amountPerRow: amountPerRow, - stepTime: 1 / 24, - textureSize: textureSize, - ), - ); - } -} - -class _SpaceshipEntrance extends LayerSensor { - _SpaceshipEntrance() - : super( - insideLayer: Layer.spaceship, - outsideLayer: Layer.spaceshipEntranceRamp, - orientation: LayerEntranceOrientation.up, - insidePriority: RenderPriority.ballOnSpaceship, - outsidePriority: RenderPriority.ballOnSpaceshipRamp, - ) { - layer = Layer.spaceship; - } - - @override - Shape get shape { - return PolygonShape() + final shape = PolygonShape() ..setAsBox( 2, 0.1, Vector2(-27.4, -37.2), -0.12, ); + final fixtureDef = FixtureDef( + shape, + isSensor: true, + ); + final bodyDef = BodyDef(); + + return world.createBody(bodyDef)..createFixture(fixtureDef); } } diff --git a/packages/pinball_components/lib/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior.dart.dart b/packages/pinball_components/lib/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior.dart.dart new file mode 100644 index 00000000..41d2935e --- /dev/null +++ b/packages/pinball_components/lib/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior.dart.dart @@ -0,0 +1,16 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class AndroidSpaceshipEntranceBallContactBehavior + extends ContactBehavior { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + if (other is! Ball) return; + + parent.parent.bloc.onEntered(); + } +} diff --git a/packages/pinball_components/lib/src/components/android_spaceship/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/android_spaceship/behaviors/behaviors.dart new file mode 100644 index 00000000..cbf54e5d --- /dev/null +++ b/packages/pinball_components/lib/src/components/android_spaceship/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'android_spaceship_entrance_ball_contact_behavior.dart.dart'; diff --git a/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_cubit.dart b/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_cubit.dart new file mode 100644 index 00000000..a95ff5fe --- /dev/null +++ b/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_cubit.dart @@ -0,0 +1,13 @@ +// ignore_for_file: public_member_api_docs + +import 'package:bloc/bloc.dart'; + +part 'android_spaceship_state.dart'; + +class AndroidSpaceshipCubit extends Cubit { + AndroidSpaceshipCubit() : super(AndroidSpaceshipState.idle); + + void onEntered() => emit(AndroidSpaceshipState.activated); + + void onBonusAwarded() => emit(AndroidSpaceshipState.idle); +} diff --git a/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_state.dart b/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_state.dart new file mode 100644 index 00000000..75082ab0 --- /dev/null +++ b/packages/pinball_components/lib/src/components/android_spaceship/cubit/android_spaceship_state.dart @@ -0,0 +1,8 @@ +// ignore_for_file: public_member_api_docs + +part of 'android_spaceship_cubit.dart'; + +enum AndroidSpaceshipState { + idle, + activated, +} diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 5b661691..81896c74 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -1,5 +1,6 @@ +export 'android_animatronic.dart'; export 'android_bumper/android_bumper.dart'; -export 'android_spaceship.dart'; +export 'android_spaceship/android_spaceship.dart'; export 'backboard/backboard.dart'; export 'ball.dart'; export 'baseboard.dart'; diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp.dart b/packages/pinball_components/lib/src/components/spaceship_ramp.dart index 025984b1..c1be0943 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp.dart @@ -15,7 +15,19 @@ class SpaceshipRamp extends Component { SpaceshipRamp() : super( children: [ - _SpaceshipRampOpening()..initialPosition = Vector2(1.7, -19.8), + _SpaceshipRampOpening( + outsidePriority: ZIndexes.ballOnBoard, + rotation: math.pi, + ) + ..initialPosition = Vector2(1.7, -19.8) + ..layer = Layer.opening, + _SpaceshipRampOpening( + outsideLayer: Layer.spaceship, + outsidePriority: ZIndexes.ballOnSpaceship, + rotation: math.pi, + ) + ..initialPosition = Vector2(-13.7, -18.6) + ..layer = Layer.spaceshipEntranceRamp, _SpaceshipRampBackground(), _SpaceshipRampBoardOpeningSpriteComponent() ..position = Vector2(3.4, -39.5), @@ -333,15 +345,21 @@ class _SpaceshipRampBase extends BodyComponent with InitialPosition, Layered { /// {@endtemplate} class _SpaceshipRampOpening extends LayerSensor { /// {@macro spaceship_ramp_opening} - _SpaceshipRampOpening() - : super( + _SpaceshipRampOpening({ + Layer? outsideLayer, + int? outsidePriority, + required double rotation, + }) : _rotation = rotation, + super( insideLayer: Layer.spaceshipEntranceRamp, - outsideLayer: Layer.opening, + outsideLayer: outsideLayer, orientation: LayerEntranceOrientation.down, insideZIndex: ZIndexes.ballOnSpaceshipRamp, - outsideZIndex: ZIndexes.ballOnBoard, + outsideZIndex: outsidePriority, ); + final double _rotation; + static final Vector2 _size = Vector2(_SpaceshipRampBackground.width / 3, .1); @override @@ -351,7 +369,7 @@ class _SpaceshipRampOpening extends LayerSensor { _size.x, _size.y, initialPosition, - math.pi, + _rotation, ); } } diff --git a/packages/pinball_components/test/src/components/android_animatronic_test.dart b/packages/pinball_components/test/src/components/android_animatronic_test.dart new file mode 100644 index 00000000..e27e9d86 --- /dev/null +++ b/packages/pinball_components/test/src/components/android_animatronic_test.dart @@ -0,0 +1,71 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final asset = Assets.images.android.spaceship.animatronic.keyName; + final flameTester = FlameTester(() => TestGame([asset])); + + group('AndroidAnimatronic', () { + flameTester.testGameWidget( + 'renders correctly', + setUp: (game, tester) async { + await game.images.load(asset); + await game.ensureAdd(AndroidAnimatronic()); + game.camera.followVector2(Vector2.zero()); + await tester.pump(); + }, + verify: (game, tester) async { + final animationDuration = game + .firstChild()! + .firstChild()! + .animation! + .totalDuration(); + + await expectLater( + find.byGame(), + matchesGoldenFile('golden/android_animatronic/start.png'), + ); + + game.update(animationDuration * 0.5); + await tester.pump(); + await expectLater( + find.byGame(), + matchesGoldenFile('golden/android_animatronic/middle.png'), + ); + + game.update(animationDuration * 0.5); + await tester.pump(); + await expectLater( + find.byGame(), + matchesGoldenFile('golden/android_animatronic/end.png'), + ); + }, + ); + + flameTester.test( + 'loads correctly', + (game) async { + final androidAnimatronic = AndroidAnimatronic(); + await game.ensureAdd(androidAnimatronic); + + expect(game.contains(androidAnimatronic), isTrue); + }, + ); + + flameTester.test('adds new children', (game) async { + final component = Component(); + final androidAnimatronic = AndroidAnimatronic( + children: [component], + ); + await game.ensureAdd(androidAnimatronic); + expect(androidAnimatronic.children, contains(component)); + }); + }); +} diff --git a/packages/pinball_components/test/src/components/android_spaceship_test.dart b/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart similarity index 50% rename from packages/pinball_components/test/src/components/android_spaceship_test.dart rename to packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart index 7e7eda96..30d592d2 100644 --- a/packages/pinball_components/test/src/components/android_spaceship_test.dart +++ b/packages/pinball_components/test/src/components/android_spaceship/android_spaceship_test.dart @@ -1,18 +1,23 @@ // ignore_for_file: cascade_invocations +import 'package:bloc_test/bloc_test.dart'; import 'package:flame/components.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 'package:pinball_components/src/components/android_spaceship/behaviors/behaviors.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import '../../helpers/helpers.dart'; +import '../../../helpers/helpers.dart'; + +class _MockAndroidSpaceshipCubit extends Mock implements AndroidSpaceshipCubit { +} void main() { group('AndroidSpaceship', () { final assets = [ Assets.images.android.spaceship.saucer.keyName, - Assets.images.android.spaceship.animatronic.keyName, Assets.images.android.spaceship.lightBeam.keyName, ]; final flameTester = FlameTester(() => TestGame(assets)); @@ -39,29 +44,65 @@ void main() { final animationDuration = game .descendants() .whereType() - .last + .single .animation! .totalDuration(); await expectLater( find.byGame(), - matchesGoldenFile('golden/android_spaceship/start.png'), + matchesGoldenFile('../golden/android_spaceship/start.png'), ); game.update(animationDuration * 0.5); await tester.pump(); await expectLater( find.byGame(), - matchesGoldenFile('golden/android_spaceship/middle.png'), + matchesGoldenFile('../golden/android_spaceship/middle.png'), ); game.update(animationDuration * 0.5); await tester.pump(); await expectLater( find.byGame(), - matchesGoldenFile('golden/android_spaceship/end.png'), + matchesGoldenFile('../golden/android_spaceship/end.png'), ); }, ); + + // TODO(alestiago): Consider refactoring once the following is merged: + // https://github.com/flame-engine/flame/pull/1538 + // ignore: public_member_api_docs + flameTester.test('closes bloc when removed', (game) async { + final bloc = _MockAndroidSpaceshipCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: AndroidSpaceshipState.idle, + ); + when(bloc.close).thenAnswer((_) async {}); + final androidSpaceship = AndroidSpaceship.test(bloc: bloc); + + await game.ensureAdd(androidSpaceship); + game.remove(androidSpaceship); + await game.ready(); + + verify(bloc.close).called(1); + }); + + flameTester.test( + 'AndroidSpaceshipEntrance has a ' + 'AndroidSpaceshipEntranceBallContactBehavior', (game) async { + final androidSpaceship = AndroidSpaceship(position: Vector2.zero()); + await game.ensureAdd(androidSpaceship); + + final androidSpaceshipEntrance = + androidSpaceship.firstChild(); + expect( + androidSpaceshipEntrance!.children + .whereType() + .single, + isNotNull, + ); + }); }); } diff --git a/packages/pinball_components/test/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior_test.dart b/packages/pinball_components/test/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior_test.dart new file mode 100644 index 00000000..a0be58e6 --- /dev/null +++ b/packages/pinball_components/test/src/components/android_spaceship/behaviors/android_spaceship_entrance_ball_contact_behavior_test.dart @@ -0,0 +1,55 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.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 'package:pinball_components/src/components/android_spaceship/behaviors/behaviors.dart'; + +import '../../../../helpers/helpers.dart'; + +class _MockAndroidSpaceshipCubit extends Mock implements AndroidSpaceshipCubit { +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(TestGame.new); + + group( + 'AndroidSpaceshipEntranceBallContactBehavior', + () { + test('can be instantiated', () { + expect( + AndroidSpaceshipEntranceBallContactBehavior(), + isA(), + ); + }); + + flameTester.test( + 'beginContact emits onEntered when entrance contacts with a ball', + (game) async { + final behavior = AndroidSpaceshipEntranceBallContactBehavior(); + final bloc = _MockAndroidSpaceshipCubit(); + whenListen( + bloc, + const Stream.empty(), + initialState: AndroidSpaceshipState.idle, + ); + + final entrance = AndroidSpaceshipEntrance(); + final androidSpaceship = AndroidSpaceship.test( + bloc: bloc, + children: [entrance], + ); + await entrance.add(behavior); + await game.ensureAdd(androidSpaceship); + + behavior.beginContact(MockBall(), MockContact()); + + verify(androidSpaceship.bloc.onEntered).called(1); + }, + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/android_spaceship/cubit/android_spaceship_cubit_test.dart b/packages/pinball_components/test/src/components/android_spaceship/cubit/android_spaceship_cubit_test.dart new file mode 100644 index 00000000..364f5bf4 --- /dev/null +++ b/packages/pinball_components/test/src/components/android_spaceship/cubit/android_spaceship_cubit_test.dart @@ -0,0 +1,24 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +void main() { + group( + 'AndroidSpaceshipCubit', + () { + blocTest( + 'onEntered emits activated', + build: AndroidSpaceshipCubit.new, + act: (bloc) => bloc.onEntered(), + expect: () => [AndroidSpaceshipState.activated], + ); + + blocTest( + 'onBonusAwarded emits idle', + build: AndroidSpaceshipCubit.new, + act: (bloc) => bloc.onBonusAwarded(), + expect: () => [AndroidSpaceshipState.idle], + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/golden/android_animatronic/end.png b/packages/pinball_components/test/src/components/golden/android_animatronic/end.png new file mode 100644 index 00000000..3d54999f Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/android_animatronic/end.png differ diff --git a/packages/pinball_components/test/src/components/golden/android_animatronic/middle.png b/packages/pinball_components/test/src/components/golden/android_animatronic/middle.png new file mode 100644 index 00000000..44916338 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/android_animatronic/middle.png differ diff --git a/packages/pinball_components/test/src/components/golden/android_animatronic/start.png b/packages/pinball_components/test/src/components/golden/android_animatronic/start.png new file mode 100644 index 00000000..95580e91 Binary files /dev/null and b/packages/pinball_components/test/src/components/golden/android_animatronic/start.png differ diff --git a/packages/pinball_components/test/src/components/golden/android_spaceship/end.png b/packages/pinball_components/test/src/components/golden/android_spaceship/end.png index c2a0631a..a64b4724 100644 Binary files a/packages/pinball_components/test/src/components/golden/android_spaceship/end.png and b/packages/pinball_components/test/src/components/golden/android_spaceship/end.png differ diff --git a/packages/pinball_components/test/src/components/golden/android_spaceship/middle.png b/packages/pinball_components/test/src/components/golden/android_spaceship/middle.png index c6651abd..90361e22 100644 Binary files a/packages/pinball_components/test/src/components/golden/android_spaceship/middle.png and b/packages/pinball_components/test/src/components/golden/android_spaceship/middle.png differ diff --git a/packages/pinball_components/test/src/components/golden/android_spaceship/start.png b/packages/pinball_components/test/src/components/golden/android_spaceship/start.png index 25e8863a..649a8654 100644 Binary files a/packages/pinball_components/test/src/components/golden/android_spaceship/start.png and b/packages/pinball_components/test/src/components/golden/android_spaceship/start.png differ diff --git a/test/game/components/android_acres_test.dart b/test/game/components/android_acres/android_acres_test.dart similarity index 79% rename from test/game/components/android_acres_test.dart rename to test/game/components/android_acres/android_acres_test.dart index 4c5e4cb7..73025551 100644 --- a/test/game/components/android_acres_test.dart +++ b/test/game/components/android_acres/android_acres_test.dart @@ -2,10 +2,11 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; +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'; +import '../../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -45,7 +46,7 @@ void main() { group('loads', () { flameTester.test( - 'a Spaceship', + 'an AndroidSpaceship', (game) async { await game.ensureAdd(AndroidAcres()); expect( @@ -55,6 +56,17 @@ void main() { }, ); + flameTester.test( + 'an AndroidAnimatronic', + (game) async { + await game.ensureAdd(AndroidAcres()); + expect( + game.descendants().whereType().length, + equals(1), + ); + }, + ); + flameTester.test( 'a SpaceshipRamp', (game) async { @@ -88,5 +100,14 @@ void main() { }, ); }); + + flameTester.test('adds an AndroidSpaceshipBonusBehavior', (game) async { + final androidAcres = AndroidAcres(); + await game.ensureAdd(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 new file mode 100644 index 00000000..ea2d37f9 --- /dev/null +++ b/test/game/components/android_acres/behaviors/android_spaceship_bonus_behavior_test.dart @@ -0,0 +1,79 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/extensions.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/android_acres/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + 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, + ); + + flameBlocTester.testGameWidget( + 'adds GameBonus.androidSpaceship to the game ' + 'when android spacehship is activated', + setUp: (game, tester) async { + final behavior = AndroidSpaceshipBonusBehavior(); + final parent = AndroidAcres.test(); + final androidSpaceship = AndroidSpaceship(position: Vector2.zero()); + + await parent.add(androidSpaceship); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + androidSpaceship.bloc.onEntered(); + await tester.pump(); + + verify( + () => gameBloc.add(const BonusActivated(GameBonus.androidSpaceship)), + ).called(1); + }, + ); + }); +}