test: tested pinball_components

pull/234/head
alestiago 3 years ago
parent 99689ea4da
commit 25128b614b

@ -16,12 +16,12 @@ class AlienZone extends Blueprint {
components: [ components: [
AlienBumper.a( AlienBumper.a(
children: [ children: [
ScoringBehaviour(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(-32.52, -9.1), )..initialPosition = Vector2(-32.52, -9.1),
AlienBumper.b( AlienBumper.b(
children: [ children: [
ScoringBehaviour(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(-22.89, -17.35), )..initialPosition = Vector2(-22.89, -17.35),
], ],

@ -8,6 +8,6 @@ export 'flutter_forest.dart';
export 'game_flow_controller.dart'; export 'game_flow_controller.dart';
export 'google_word/google_word.dart'; export 'google_word/google_word.dart';
export 'launcher.dart'; export 'launcher.dart';
export 'scoring_behaviour.dart'; export 'scoring_behavior.dart';
export 'sparky_fire_zone.dart'; export 'sparky_fire_zone.dart';
export 'wall.dart'; export 'wall.dart';

@ -20,22 +20,22 @@ class FlutterForest extends Component
children: [ children: [
Signpost( Signpost(
children: [ children: [
ScoringBehaviour(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(8.35, -58.3), )..initialPosition = Vector2(8.35, -58.3),
DashNestBumper.main( DashNestBumper.main(
children: [ children: [
ScoringBehaviour(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(18.55, -59.35), )..initialPosition = Vector2(18.55, -59.35),
DashNestBumper.a( DashNestBumper.a(
children: [ children: [
ScoringBehaviour(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(8.95, -51.95), )..initialPosition = Vector2(8.95, -51.95),
DashNestBumper.b( DashNestBumper.b(
children: [ children: [
ScoringBehaviour(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(23.3, -46.75), )..initialPosition = Vector2(23.3, -46.75),
DashAnimatronic()..position = Vector2(20, -66), DashAnimatronic()..position = Vector2(20, -66),

@ -1,5 +1,3 @@
import 'dart:async';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -8,10 +6,10 @@ import 'package:pinball_flame/pinball_flame.dart';
class GoogleWordBonusBehaviour extends Component class GoogleWordBonusBehaviour extends Component
with HasGameRef<PinballGame>, ParentIsA<GoogleWord> { with HasGameRef<PinballGame>, ParentIsA<GoogleWord> {
@override @override
Future<void> onLoad() async { void onMount() {
await super.onLoad(); super.onMount();
final googleLetters = parent.children.whereType<GoogleLetter>();
final googleLetters = parent.children.whereType<GoogleLetter>();
for (final letter in googleLetters) { for (final letter in googleLetters) {
letter.bloc.stream.listen((_) { letter.bloc.stream.listen((_) {
final achievedBonus = googleLetters final achievedBonus = googleLetters

@ -1,7 +1,3 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'dart:async';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; import 'package:pinball/game/components/google_word/behaviors/behaviors.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -21,12 +17,7 @@ class GoogleWord extends Component {
GoogleLetter(3)..initialPosition = position + Vector2(2.88, -1.75), GoogleLetter(3)..initialPosition = position + Vector2(2.88, -1.75),
GoogleLetter(4)..initialPosition = position + Vector2(8.33, -0.65), GoogleLetter(4)..initialPosition = position + Vector2(8.33, -0.65),
GoogleLetter(5)..initialPosition = position + Vector2(12.92, 1.82), GoogleLetter(5)..initialPosition = position + Vector2(12.92, 1.82),
GoogleWordBonusBehaviour(),
], ],
); );
@override
Future<void> onLoad() async {
await super.onLoad();
await add(GoogleWordBonusBehaviour());
}
} }

@ -9,32 +9,14 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@template scoring_behaviour} /// {@template scoring_behaviour}
/// ///
/// {@endtemplate} /// {@endtemplate}
class ScoringBehaviour extends Component class ScoringBehavior extends ContactBehavior with HasGameRef<PinballGame> {
with ContactCallbacks, HasGameRef<PinballGame>, ParentIsA<BodyComponent> {
/// {@macro scoring_behaviour} /// {@macro scoring_behaviour}
ScoringBehaviour({ ScoringBehavior({
required int points, required int points,
}) : _points = points; }) : _points = points;
final int _points; final int _points;
@override
Future<void> onLoad() async {
await super.onLoad();
final userData = parent.body.userData;
if (userData is ContactCallbacksGroup) {
userData.addContactCallbacks(this);
} else if (userData is ContactCallbacks) {
final notifier = ContactCallbacksGroup()
..addContactCallbacks(userData)
..addContactCallbacks(this);
parent.body.userData = notifier;
} else {
parent.body.userData = this;
}
}
@override @override
void beginContact(Object other, Contact contact) { void beginContact(Object other, Contact contact) {
super.beginContact(other, contact); super.beginContact(other, contact);

@ -18,17 +18,17 @@ class SparkyFireZone extends Blueprint {
components: [ components: [
SparkyBumper.a( SparkyBumper.a(
children: [ children: [
ScoringBehaviour(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(-22.9, -41.65), )..initialPosition = Vector2(-22.9, -41.65),
SparkyBumper.b( SparkyBumper.b(
children: [ children: [
ScoringBehaviour(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(-21.25, -57.9), )..initialPosition = Vector2(-21.25, -57.9),
SparkyBumper.c( SparkyBumper.c(
children: [ children: [
ScoringBehaviour(points: 20), ScoringBehavior(points: 20),
], ],
)..initialPosition = Vector2(-3.3, -52.55), )..initialPosition = Vector2(-3.3, -52.55),
SparkyComputerSensor()..initialPosition = Vector2(-13, -49.8), SparkyComputerSensor()..initialPosition = Vector2(-13, -49.8),

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/alien_bumper/behaviors/behaviors.dart'; import 'package:pinball_components/src/components/alien_bumper/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
@ -25,14 +26,14 @@ class AlienBumper extends BodyComponent with InitialPosition {
super( super(
priority: RenderPriority.alienBumper, priority: RenderPriority.alienBumper,
children: [ children: [
ContactBehavior(), AlienBumperBallContactBehavior(),
SpriteBehavior(), AlienBumperBlinkingBehavior(),
_AlienBumperSpriteGroupComponent( _AlienBumperSpriteGroupComponent(
offAssetPath: offAssetPath, offAssetPath: offAssetPath,
onAssetPath: onAssetPath, onAssetPath: onAssetPath,
state: bloc.state, state: bloc.state,
), ),
if (children != null) ...children, ...?children,
], ],
renderBody: false, renderBody: false,
); );
@ -61,11 +62,17 @@ class AlienBumper extends BodyComponent with InitialPosition {
children: children, children: children,
); );
/// {@macro alien_bumper}
@visibleForTesting
AlienBumper.test({
required this.bloc,
}) : _majorRadius = 3.52,
_minorRadius = 2.97;
final double _majorRadius; final double _majorRadius;
final double _minorRadius; final double _minorRadius;
// TODO(alestiago): Evaluate testing this.
final AlienBumperCubit bloc; final AlienBumperCubit bloc;
@override @override

@ -1,4 +1,3 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';

@ -2,16 +2,14 @@ import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template alien_bumper_sprite_behavior} /// {@template alien_bumper_blinking_behavior}
/// /// Makes a [SparkyBumper] blink back to [SparkyBumperState.active] when
/// [SparkyBumperState.inactive].
/// {@endtemplate} /// {@endtemplate}
class SpriteBehavior extends TimerComponent with ParentIsA<AlienBumper> { class AlienBumperBlinkingBehavior extends TimerComponent
/// {@macro alien_bumper_sprite_behavior} with ParentIsA<AlienBumper> {
SpriteBehavior() /// {@macro alien_bumper_blinking_behavior}
: super( AlienBumperBlinkingBehavior() : super(period: 0.05);
period: 0.05,
removeOnFinish: false,
);
void _onNewState(AlienBumperState state) { void _onNewState(AlienBumperState state) {
switch (state) { switch (state) {
@ -36,6 +34,6 @@ class SpriteBehavior extends TimerComponent with ParentIsA<AlienBumper> {
@override @override
void onTick() { void onTick() {
super.onTick(); super.onTick();
parent.bloc.onAnimated(); parent.bloc.onBlinked();
} }
} }

@ -1,2 +1,2 @@
export 'alien_bumper_ball_contact_behaviour.dart'; export 'alien_bumper_ball_contact_behavior.dart';
export 'sprite_behavior.dart'; export 'alien_bumper_blinking_behavior.dart';

@ -9,7 +9,7 @@ class AlienBumperCubit extends Cubit<AlienBumperState> {
emit(AlienBumperState.inactive); emit(AlienBumperState.inactive);
} }
void onAnimated() { void onBlinked() {
emit(AlienBumperState.active); emit(AlienBumperState.active);
} }
} }

@ -27,7 +27,7 @@ class DashNestBumper extends BodyComponent with InitialPosition {
inactiveAssetPath: inactiveAssetPath, inactiveAssetPath: inactiveAssetPath,
position: spritePosition, position: spritePosition,
), ),
if (children != null) ...children, ...?children,
], ],
renderBody: false, renderBody: false,
); );

@ -13,16 +13,24 @@ export 'cubit/google_letter_cubit.dart';
/// {@endtemplate} /// {@endtemplate}
class GoogleLetter extends BodyComponent with InitialPosition { class GoogleLetter extends BodyComponent with InitialPosition {
/// {@macro google_letter} /// {@macro google_letter}
GoogleLetter(int index) GoogleLetter(
: super( int index, {
GoogleLetterCubit? bloc,
}) : bloc = bloc ?? GoogleLetterCubit(),
super(
children: [ children: [
ContactBehavior(), GoogleLetterBallContactBehavior(),
_GoogleLetterSprite(_GoogleLetterSprite.spritePaths[index]) _GoogleLetterSprite(_GoogleLetterSprite.spritePaths[index])
], ],
); );
// TODO(alestiago): Evaluate testing this. final GoogleLetterCubit bloc;
final GoogleLetterCubit bloc = GoogleLetterCubit();
@override
void onRemove() {
bloc.close();
super.onRemove();
}
@override @override
Body createBody() { Body createBody() {
@ -57,23 +65,11 @@ class _GoogleLetterSprite extends SpriteComponent
final String _path; final String _path;
void _onNewState(GoogleLetterState state) {
switch (state) {
case GoogleLetterState.active:
add(_GoogleLetterColorEffect(color: Colors.green));
break;
case GoogleLetterState.inactive:
add(
_GoogleLetterColorEffect(color: Colors.red),
);
break;
}
}
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
parent.bloc.stream.listen(_onNewState); // TODO(alisonryan2002): Make SpriteGroupComponent.
// parent.bloc.stream.listen();
// TODO(alestiago): Used cached assets. // TODO(alestiago): Used cached assets.
final sprite = await gameRef.loadSprite(_path); final sprite = await gameRef.loadSprite(_path);
@ -82,13 +78,3 @@ class _GoogleLetterSprite extends SpriteComponent
size = sprite.originalSize / 5; size = sprite.originalSize / 5;
} }
} }
class _GoogleLetterColorEffect extends ColorEffect {
_GoogleLetterColorEffect({
required Color color,
}) : super(
color,
const Offset(0, 1),
EffectController(duration: 0.25),
);
}

@ -52,7 +52,7 @@ class Signpost extends BodyComponent with InitialPosition {
priority: RenderPriority.signpost, priority: RenderPriority.signpost,
children: [ children: [
_SignpostSpriteComponent(), _SignpostSpriteComponent(),
if (children != null) ...children, ...?children,
], ],
renderBody: false, renderBody: false,
); );

@ -1,2 +1,2 @@
export 'sparky_bumper_ball_contact_behaviour.dart'; export 'sparky_bumper_ball_contact_behavior.dart';
export 'sprite_behavior.dart'; export 'sparky_bumper_blinking_behavior.dart';

@ -2,16 +2,14 @@ import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template alien_bumper_sprite_behavior} /// {@template sparky_bumper_blinking_behavior}
/// /// Makes a [SparkyBumper] blink back to [SparkyBumperState.active] when
/// [SparkyBumperState.inactive].
/// {@endtemplate} /// {@endtemplate}
class SpriteBehavior extends TimerComponent with ParentIsA<SparkyBumper> { class SparkyBumperBlinkingBehavior extends TimerComponent
/// {@macro alien_bumper_sprite_behavior} with ParentIsA<SparkyBumper> {
SpriteBehavior() /// {@macro sparky_bumper_sprite_behavior}
: super( SparkyBumperBlinkingBehavior() : super(period: 0.05);
period: 0.05,
removeOnFinish: false,
);
void _onNewState(SparkyBumperState state) { void _onNewState(SparkyBumperState state) {
switch (state) { switch (state) {
@ -36,6 +34,6 @@ class SpriteBehavior extends TimerComponent with ParentIsA<SparkyBumper> {
@override @override
void onTick() { void onTick() {
super.onTick(); super.onTick();
parent.bloc.onAnimated(); parent.bloc.onBlinked();
} }
} }

@ -7,12 +7,9 @@ class SparkyBumperCubit extends Cubit<SparkyBumperState> {
void onBallContacted() { void onBallContacted() {
emit(SparkyBumperState.inactive); emit(SparkyBumperState.inactive);
// Future<void>.delayed(const Duration(milliseconds: 500)).whenComplete(
// () => emit(AlienBumperState.active),
// );
} }
void onAnimated() { void onBlinked() {
emit(SparkyBumperState.active); emit(SparkyBumperState.active);
} }
} }

@ -2,6 +2,7 @@ import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/sparky_bumper/behaviors/behaviors.dart'; import 'package:pinball_components/src/components/sparky_bumper/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
@ -26,15 +27,15 @@ class SparkyBumper extends BodyComponent with InitialPosition {
super( super(
priority: RenderPriority.sparkyBumper, priority: RenderPriority.sparkyBumper,
children: [ children: [
ContactBehavior(), SparkyBumperBallContactBehavior(),
SpriteBehavior(), SparkyBumperBlinkingBehaviour(),
_SparkyBumperSpriteGroupComponent( _SparkyBumperSpriteGroupComponent(
onAssetPath: onAssetPath, onAssetPath: onAssetPath,
offAssetPath: offAssetPath, offAssetPath: offAssetPath,
position: spritePosition, position: spritePosition,
state: bloc.state, state: bloc.state,
), ),
if (children != null) ...children, ...?children,
], ],
renderBody: false, renderBody: false,
); );
@ -78,12 +79,24 @@ class SparkyBumper extends BodyComponent with InitialPosition {
children: children, children: children,
); );
/// {@macro sparky_bumper}
@visibleForTesting
SparkyBumper.test({
required this.bloc,
}) : _majorRadius = 3,
_minorRadius = 2.2;
final double _majorRadius; final double _majorRadius;
final double _minorRadius; final double _minorRadius;
// TODO(alestiago): Evaluate testing this.
final SparkyBumperCubit bloc; final SparkyBumperCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
@override @override
Body createBody() { Body createBody() {
final shape = EllipseShape( final shape = EllipseShape(

@ -25,6 +25,7 @@ dependencies:
path: ../pinball_theme path: ../pinball_theme
dev_dependencies: dev_dependencies:
bloc_test: ^9.0.3
flame_test: ^1.3.0 flame_test: ^1.3.0
flutter_test: flutter_test:
sdk: flutter sdk: flutter

@ -16,3 +16,9 @@ class MockGame extends Mock implements Forge2DGame {}
class MockContact extends Mock implements Contact {} class MockContact extends Mock implements Contact {}
class MockComponent extends Mock implements Component {} class MockComponent extends Mock implements Component {}
class MockAlienBumperCubit extends Mock implements AlienBumperCubit {}
class MockGoogleLetterCubit extends Mock implements GoogleLetterCubit {}
class MockSparkyBumperCubit extends Mock implements SparkyBumperCubit {}

@ -1,9 +1,12 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_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/pinball_components.dart';
import 'package:pinball_components/src/components/alien_bumper/behaviors/behaviors.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
@ -19,39 +22,54 @@ void main() {
group('AlienBumper', () { group('AlienBumper', () {
flameTester.test('"a" loads correctly', (game) async { flameTester.test('"a" loads correctly', (game) async {
final bumper = AlienBumper.a(); final alienBumper = AlienBumper.a();
await game.ensureAdd(bumper); await game.ensureAdd(alienBumper);
expect(game.contains(alienBumper), isTrue);
expect(game.contains(bumper), isTrue);
}); });
flameTester.test('"b" loads correctly', (game) async { flameTester.test('"b" loads correctly', (game) async {
final bumper = AlienBumper.b(); final alienBumper = AlienBumper.b();
await game.ensureAdd(bumper); await game.ensureAdd(alienBumper);
expect(game.contains(bumper), isTrue); expect(game.contains(alienBumper), isTrue);
}); });
flameTester.test('animate switches between on and off sprites', flameTester.test('closes bloc when removed', (game) async {
(game) async { final bloc = MockAlienBumperCubit();
final bumper = AlienBumper.a(); whenListen(
await game.ensureAdd(bumper); bloc,
const Stream<AlienBumperState>.empty(),
initialState: AlienBumperState.active,
);
when(bloc.close).thenAnswer((_) async {});
final alienBumper = AlienBumper.test(bloc: bloc);
final spriteGroupComponent = bumper.firstChild<SpriteGroupComponent>()!; await game.ensureAdd(alienBumper);
game.remove(alienBumper);
await game.ready();
expect( verify(bloc.close).called(1);
spriteGroupComponent.current, });
equals(AlienBumperState.active),
);
expect( group('adds', () {
spriteGroupComponent.current, flameTester.test('new children', (game) async {
equals(AlienBumperState.inactive), final component = Component();
final alienBumper = AlienBumper.a(
children: [component],
); );
await game.ensureAdd(alienBumper);
expect(alienBumper.children, contains(component));
});
flameTester.test('an AlienBumperBallContactBehaviour', (game) async {
final alienBumper = AlienBumper.a();
await game.ensureAdd(alienBumper);
expect( expect(
spriteGroupComponent.current, alienBumper.children
equals(AlienBumperState.active), .whereType<AlienBumperBallContactBehavior>()
.single,
isNotNull,
); );
}); });
}); });
});
} }

@ -0,0 +1,51 @@
// 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/alien_bumper/behaviors/alien_bumper_ball_contact_behavior.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group(
'AlienBumperBallContactBehavior',
() {
test('can be instantiated', () {
expect(
AlienBumperBallContactBehavior(),
isA<AlienBumperBallContactBehavior>(),
);
});
group(
'beginContact',
() {
flameTester.test('emits onBallContacted when contacts with a ball',
(game) async {
final behavior = AlienBumperBallContactBehavior();
final bloc = MockAlienBumperCubit();
whenListen(
bloc,
const Stream<AlienBumperState>.empty(),
initialState: AlienBumperState.active,
);
final alienBumper = AlienBumper.test(bloc: bloc);
await alienBumper.add(behavior);
await game.ensureAdd(alienBumper);
behavior.beginContact(MockBall(), MockContact());
verify(alienBumper.bloc.onBallContacted).called(1);
});
},
);
},
);
}

@ -0,0 +1,45 @@
import 'dart:async';
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/alien_bumper/behaviors/alien_bumper_blinking_behavior.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group(
'AlienBumperBlinkingBehaviour',
() {
flameTester.test(
'calls onBlinked after 0.5 seconds when inactive',
(game) async {
// TODO(alestiago): Make this pass.
final behavior = AlienBumperBlinkingBehavior();
final bloc = MockAlienBumperCubit();
final streamController =
StreamController<AlienBumperState>.broadcast();
whenListen(
bloc,
streamController.stream,
initialState: AlienBumperState.active,
);
final alienBumper = AlienBumper.test(bloc: bloc);
await alienBumper.add(behavior);
await game.ensureAdd(alienBumper);
streamController.sink.add(AlienBumperState.inactive);
game.update(0.05);
verify(bloc.onBlinked).called(1);
},
);
},
);
}

@ -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(
'AlienBumperCubit',
() {
blocTest<AlienBumperCubit, AlienBumperState>(
'onBallContacted emits inactive',
build: AlienBumperCubit.new,
act: (bloc) => bloc.onBallContacted(),
expect: () => [AlienBumperState.inactive],
);
blocTest<AlienBumperCubit, AlienBumperState>(
'onBlinked emits active',
build: AlienBumperCubit.new,
act: (bloc) => bloc.onBlinked(),
expect: () => [AlienBumperState.active],
);
},
);
}

@ -73,5 +73,14 @@ void main() {
equals(DashNestBumperSpriteState.inactive), equals(DashNestBumperSpriteState.inactive),
); );
}); });
flameTester.test('adds new children', (game) async {
final component = Component();
final dashNestBumper = DashNestBumper.a(
children: [component],
);
await game.ensureAdd(dashNestBumper);
expect(dashNestBumper.children, contains(component));
});
}); });
} }

@ -0,0 +1,51 @@
// 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/google_letter/behaviors/google_letter_ball_contact_behavior.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group(
'GoogleLetterBallContactBehavior',
() {
test('can be instantiated', () {
expect(
GoogleLetterBallContactBehavior(),
isA<GoogleLetterBallContactBehavior>(),
);
});
group(
'beginContact',
() {
flameTester.test('emits onBallContacted when contacts with a ball',
(game) async {
final behavior = GoogleLetterBallContactBehavior();
final bloc = MockGoogleLetterCubit();
whenListen(
bloc,
const Stream<GoogleLetterState>.empty(),
initialState: GoogleLetterState.active,
);
final googleLetter = GoogleLetter(0, bloc: bloc);
await googleLetter.add(behavior);
await game.ensureAdd(googleLetter);
behavior.beginContact(MockBall(), MockContact());
verify(googleLetter.bloc.onBallContacted).called(1);
});
},
);
},
);
}

@ -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(
'GoogleLetterBumperCubit',
() {
blocTest<GoogleLetterCubit, GoogleLetterState>(
'onBallContacted emits active',
build: GoogleLetterCubit.new,
act: (bloc) => bloc.onBallContacted(),
expect: () => [GoogleLetterState.active],
);
blocTest<GoogleLetterCubit, GoogleLetterState>(
'onReset emits inactive',
build: GoogleLetterCubit.new,
act: (bloc) => bloc.onReset(),
expect: () => [GoogleLetterState.inactive],
);
},
);
}

@ -1,8 +1,11 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_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/pinball_components.dart';
import 'package:pinball_components/src/components/google_letter/behaviors/behaviors.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
@ -81,5 +84,33 @@ void main() {
expect(() => GoogleLetter(-1), throwsA(isA<RangeError>())); expect(() => GoogleLetter(-1), throwsA(isA<RangeError>()));
expect(() => GoogleLetter(6), throwsA(isA<RangeError>())); expect(() => GoogleLetter(6), throwsA(isA<RangeError>()));
}); });
flameTester.test('closes bloc when removed', (game) async {
final bloc = MockGoogleLetterCubit();
whenListen(
bloc,
const Stream<GoogleLetterState>.empty(),
initialState: GoogleLetterState.active,
);
when(bloc.close).thenAnswer((_) async {});
final googleLetter = GoogleLetter(0, bloc: bloc);
await game.ensureAdd(googleLetter);
game.remove(googleLetter);
await game.ready();
verify(bloc.close).called(1);
});
flameTester.test('adds a GoogleLetterBallContactBehaviour', (game) async {
final googleLetter = GoogleLetter(0);
await game.ensureAdd(googleLetter);
expect(
googleLetter.children
.whereType<GoogleLetterBallContactBehavior>()
.single,
isNotNull,
);
});
}); });
} }

@ -108,7 +108,7 @@ void main() {
}); });
}); });
group('LayerSensorBallContactCallback', () { group('beginContact', () {
late Ball ball; late Ball ball;
late Body body; late Body body;
@ -133,12 +133,14 @@ void main() {
when(() => body.linearVelocity).thenReturn(Vector2(0, -1)); when(() => body.linearVelocity).thenReturn(Vector2(0, -1));
sensor.beginContact(ball, MockContact());
verify(() => ball.layer = sensor.insideLayer).called(1); verify(() => ball.layer = sensor.insideLayer).called(1);
verify(() => ball.priority = sensor.insidePriority).called(1); verify(() => ball.priority = sensor.insidePriority).called(1);
verify(ball.reorderChildren).called(1); verify(ball.reorderChildren).called(1);
when(() => ball.layer).thenReturn(sensor.insideLayer); when(() => ball.layer).thenReturn(sensor.insideLayer);
sensor.beginContact(ball, MockContact());
verify(() => ball.layer = Layer.board); verify(() => ball.layer = Layer.board);
verify(() => ball.priority = RenderPriority.ballOnBoard).called(1); verify(() => ball.priority = RenderPriority.ballOnBoard).called(1);
verify(ball.reorderChildren).called(1); verify(ball.reorderChildren).called(1);
@ -156,12 +158,14 @@ void main() {
when(() => body.linearVelocity).thenReturn(Vector2(0, 1)); when(() => body.linearVelocity).thenReturn(Vector2(0, 1));
sensor.beginContact(ball, MockContact());
verify(() => ball.layer = sensor.insideLayer).called(1); verify(() => ball.layer = sensor.insideLayer).called(1);
verify(() => ball.priority = sensor.insidePriority).called(1); verify(() => ball.priority = sensor.insidePriority).called(1);
verify(ball.reorderChildren).called(1); verify(ball.reorderChildren).called(1);
when(() => ball.layer).thenReturn(sensor.insideLayer); when(() => ball.layer).thenReturn(sensor.insideLayer);
sensor.beginContact(ball, MockContact());
verify(() => ball.layer = Layer.board); verify(() => ball.layer = Layer.board);
verify(() => ball.priority = RenderPriority.ballOnBoard).called(1); verify(() => ball.priority = RenderPriority.ballOnBoard).called(1);
verify(ball.reorderChildren).called(1); verify(ball.reorderChildren).called(1);

@ -151,5 +151,14 @@ void main() {
expect(spriteComponent.current, SignpostSpriteState.inactive); expect(spriteComponent.current, SignpostSpriteState.inactive);
}, },
); );
flameTester.test('adds new children', (game) async {
final component = Component();
final signpost = Signpost(
children: [component],
);
await game.ensureAdd(signpost);
expect(signpost.children, contains(component));
});
}); });
} }

@ -0,0 +1,51 @@
// 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/sparky_bumper/behaviors/sparky_bumper_ball_contact_behavior.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group(
'AlienBumperBallContactBehavior',
() {
test('can be instantiated', () {
expect(
SparkyBumperBallContactBehavior(),
isA<SparkyBumperBallContactBehavior>(),
);
});
group(
'beginContact',
() {
flameTester.test('emits onBallContacted when contacts with a ball',
(game) async {
final behavior = SparkyBumperBallContactBehavior();
final bloc = MockSparkyBumperCubit();
whenListen(
bloc,
const Stream<SparkyBumperState>.empty(),
initialState: SparkyBumperState.active,
);
final sparkyBumper = SparkyBumper.test(bloc: bloc);
await sparkyBumper.add(behavior);
await game.ensureAdd(sparkyBumper);
behavior.beginContact(MockBall(), MockContact());
verify(sparkyBumper.bloc.onBallContacted).called(1);
});
},
);
},
);
}

@ -0,0 +1,45 @@
import 'dart:async';
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/sparky_bumper/behaviors/sparky_bumper_blinking_behavior.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group(
'SparkyBumperBlinkingBehaviour',
() {
flameTester.test(
'calls onBlinked after 0.5 seconds when inactive',
(game) async {
// TODO(alestiago): Make this pass.
final behavior = SparkyBumperBlinkingBehavior();
final bloc = MockSparkyBumperCubit();
final streamController =
StreamController<SparkyBumperState>.broadcast();
whenListen(
bloc,
streamController.stream,
initialState: SparkyBumperState.active,
);
final sparkyBumper = SparkyBumper.test(bloc: bloc);
await sparkyBumper.add(behavior);
await game.ensureAdd(sparkyBumper);
streamController.sink.add(SparkyBumperState.inactive);
game.update(0.05);
verify(bloc.onBlinked).called(1);
},
);
},
);
}

@ -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(
'SparkyBumperCubit',
() {
blocTest<SparkyBumperCubit, SparkyBumperState>(
'onBallContacted emits inactive',
build: SparkyBumperCubit.new,
act: (bloc) => bloc.onBallContacted(),
expect: () => [SparkyBumperState.inactive],
);
blocTest<SparkyBumperCubit, SparkyBumperState>(
'onBlinked emits active',
build: SparkyBumperCubit.new,
act: (bloc) => bloc.onBlinked(),
expect: () => [SparkyBumperState.active],
);
},
);
}

@ -1,8 +1,12 @@
// ignore_for_file: cascade_invocations // 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:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_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/pinball_components.dart';
import 'package:pinball_components/src/components/sparky_bumper/behaviors/behaviors.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
@ -20,22 +24,60 @@ void main() {
group('SparkyBumper', () { group('SparkyBumper', () {
flameTester.test('"a" loads correctly', (game) async { flameTester.test('"a" loads correctly', (game) async {
final bumper = SparkyBumper.a(); final sparkyBumper = SparkyBumper.a();
await game.ensureAdd(bumper); await game.ensureAdd(sparkyBumper);
expect(game.contains(sparkyBumper), isTrue);
expect(game.contains(bumper), isTrue);
}); });
flameTester.test('"b" loads correctly', (game) async { flameTester.test('"b" loads correctly', (game) async {
final bumper = SparkyBumper.b(); final sparkyBumper = SparkyBumper.b();
await game.ensureAdd(bumper); await game.ensureAdd(sparkyBumper);
expect(game.contains(bumper), isTrue); expect(game.contains(sparkyBumper), isTrue);
}); });
flameTester.test('"c" loads correctly', (game) async { flameTester.test('"c" loads correctly', (game) async {
final bumper = SparkyBumper.c(); final sparkyBumper = SparkyBumper.c();
await game.ensureAdd(bumper); await game.ensureAdd(sparkyBumper);
expect(game.contains(bumper), isTrue); expect(game.contains(sparkyBumper), isTrue);
});
flameTester.test('closes bloc when removed', (game) async {
final bloc = MockSparkyBumperCubit();
whenListen(
bloc,
const Stream<SparkyBumperState>.empty(),
initialState: SparkyBumperState.active,
);
when(bloc.close).thenAnswer((_) async {});
final sparkyBumper = SparkyBumper.test(bloc: bloc);
await game.ensureAdd(sparkyBumper);
game.remove(sparkyBumper);
await game.ready();
verify(bloc.close).called(1);
});
group('adds', () {
flameTester.test('new children', (game) async {
final component = Component();
final sparkyBumper = SparkyBumper.a(
children: [component],
);
await game.ensureAdd(sparkyBumper);
expect(sparkyBumper.children, contains(component));
});
flameTester.test('an SparkyBumperBallContactBehaviour', (game) async {
final sparkyBumper = SparkyBumper.a();
await game.ensureAdd(sparkyBumper);
expect(
sparkyBumper.children
.whereType<SparkyBumperBallContactBehavior>()
.single,
isNotNull,
);
});
}); });
}); });
} }

@ -3,16 +3,16 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
class ContactBehavior<T extends BodyComponent> extends Component abstract class ContactBehavior<T extends BodyComponent> extends Component
with ContactCallbacks, ParentIsA<T> { with ContactCallbacks, ParentIsA<T> {
@override @override
@mustCallSuper @mustCallSuper
Future<void> onLoad() async { Future<void> onLoad() async {
final userData = parent.body.userData; final userData = parent.body.userData;
if (userData is ContactCallbacksGroup) { if (userData is _ContactCallbacksGroup) {
userData.addContactCallbacks(this); userData.addContactCallbacks(this);
} else if (userData is ContactCallbacks) { } else if (userData is ContactCallbacks) {
final notifier = ContactCallbacksGroup() final notifier = _ContactCallbacksGroup()
..addContactCallbacks(userData) ..addContactCallbacks(userData)
..addContactCallbacks(this); ..addContactCallbacks(this);
parent.body.userData = notifier; parent.body.userData = notifier;
@ -22,7 +22,7 @@ class ContactBehavior<T extends BodyComponent> extends Component
} }
} }
class ContactCallbacksGroup implements ContactCallbacks { class _ContactCallbacksGroup implements ContactCallbacks {
final List<ContactCallbacks> _contactCallbacks = []; final List<ContactCallbacks> _contactCallbacks = [];
@override @override

@ -9,8 +9,7 @@ mixin ParentIsA<T extends Component> on Component {
T get parent => super.parent! as T; T get parent => super.parent! as T;
@override @override
Future<void>? addToParent(Component parent) { Future<void>? addToParent(covariant T parent) {
assert(parent is T, 'Parent must be of type $T');
return super.addToParent(parent); return super.addToParent(parent);
} }
} }

@ -47,7 +47,7 @@ void main() {
'emits Scored event with points', 'emits Scored event with points',
setUp: (game, tester) async { setUp: (game, tester) async {
const points = 20; const points = 20;
final scoringBehaviour = ScoringBehaviour(points: points); final scoringBehaviour = ScoringBehavior(points: points);
await game.ensureAdd(scoringBehaviour); await game.ensureAdd(scoringBehaviour);
scoringBehaviour.beginContact(ball, MockContact()); scoringBehaviour.beginContact(ball, MockContact());
@ -64,7 +64,7 @@ void main() {
'plays score sound', 'plays score sound',
setUp: (game, tester) async { setUp: (game, tester) async {
const points = 20; const points = 20;
final scoringBehaviour = ScoringBehaviour(points: points); final scoringBehaviour = ScoringBehavior(points: points);
await game.ensureAdd(scoringBehaviour); await game.ensureAdd(scoringBehaviour);
scoringBehaviour.beginContact(ball, MockContact()); scoringBehaviour.beginContact(ball, MockContact());
@ -77,7 +77,7 @@ void main() {
"adds a ScoreText component at Ball's position with points", "adds a ScoreText component at Ball's position with points",
setUp: (game, tester) async { setUp: (game, tester) async {
const points = 20; const points = 20;
final scoringBehaviour = ScoringBehaviour(points: points); final scoringBehaviour = ScoringBehavior(points: points);
await game.ensureAdd(scoringBehaviour); await game.ensureAdd(scoringBehaviour);
scoringBehaviour.beginContact(ball, MockContact()); scoringBehaviour.beginContact(ball, MockContact());

Loading…
Cancel
Save