diff --git a/lib/game/components/multiballs/behaviors/multiballs_behavior.dart b/lib/game/components/multiballs/behaviors/multiballs_behavior.dart index c9d7609f..80bfc355 100644 --- a/lib/game/components/multiballs/behaviors/multiballs_behavior.dart +++ b/lib/game/components/multiballs/behaviors/multiballs_behavior.dart @@ -1,40 +1,26 @@ 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'; /// Toggle each [Multiball] when there is a bonus ball. class MultiballsBehavior extends Component - with HasGameRef, ParentIsA { + with + HasGameRef, + ParentIsA, + BlocComponent { @override - void onMount() { - super.onMount(); - - var _previousMultiballBonus = 0; - - gameRef.read().stream.listen((state) { - // TODO(ruimiguel): only when state.bonusHistory dashNest has changed - final multiballBonus = state.bonusHistory.fold( - 0, - (previousValue, bonus) { - if (bonus == GameBonus.dashNest) { - previousValue++; - } - return previousValue; - }, - ); - - if (_previousMultiballBonus != multiballBonus) { - _previousMultiballBonus = multiballBonus; - - final hasMultiball = state.bonusHistory.contains(GameBonus.dashNest); + bool listenWhen(GameState? previousState, GameState newState) { + final hasMultiball = newState.bonusHistory.contains(GameBonus.dashNest); + final hasChanged = previousState?.bonusHistory != newState.bonusHistory; + return hasChanged && hasMultiball; + } - if (hasMultiball) { - parent.children.whereType().forEach((multiball) { - multiball.bloc.onAnimate(); - }); - } - } + @override + void onNewState(GameState state) { + parent.children.whereType().forEach((multiball) { + multiball.bloc.onAnimate(); }); } } diff --git a/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart b/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart index 64341b86..3aa7443e 100644 --- a/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart +++ b/test/game/components/multiballs/behaviors/multiballs_behavior_test.dart @@ -24,6 +24,11 @@ void main() { setUp(() { gameBloc = MockGameBloc(); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); }); final flameBlocTester = FlameBlocTester( @@ -32,74 +37,99 @@ void main() { assets: assets, ); - flameBlocTester.testGameWidget( - 'animate multiballs when new GameBonus.dashNest received', - setUp: (game, tester) async { - final streamController = StreamController(); - whenListen( - gameBloc, - streamController.stream, - initialState: const GameState.initial(), + group('listenWhen', () { + test( + 'is true when the bonusHistory has changed ' + 'with a new GameBonus.dashNest', () { + final previous = GameState.initial(); + final state = previous.copyWith( + bonusHistory: [GameBonus.dashNest], ); - final behavior = MultiballsBehavior(); - final parent = Multiballs.test(); - final multiballs = [ - Multiball.test(bloc: MockMultiballCubit()), - Multiball.test(bloc: MockMultiballCubit()), - Multiball.test(bloc: MockMultiballCubit()), - Multiball.test(bloc: MockMultiballCubit()), - ]; - - await parent.addAll(multiballs); - await game.ensureAdd(parent); - await parent.ensureAdd(behavior); - - streamController.add( - GameState.initial().copyWith(bonusHistory: [GameBonus.dashNest]), + expect( + MultiballsBehavior().listenWhen(previous, state), + isTrue, + ); + }); + + test( + 'is false when the bonusHistory has changed ' + 'with a bonus different than GameBonus.dashNest', () { + final previous = GameState.initial(); + final state = previous.copyWith( + bonusHistory: [GameBonus.androidSpaceship], ); - await tester.pump(); - - for (final multiball in multiballs) { - verify(multiball.bloc.onAnimate).called(1); - } - }, - ); - flameBlocTester.testGameWidget( - "don't animate multiballs when now new GameBonus.dashNest received", - setUp: (game, tester) async { - final streamController = StreamController(); - whenListen( - gameBloc, - streamController.stream, - initialState: const GameState.initial(), + expect( + MultiballsBehavior().listenWhen(previous, state), + isFalse, + ); + }); + + test('is false when the bonusHistory state is the same', () { + final state = GameState( + score: 10, + multiplier: 1, + rounds: 0, + bonusHistory: const [], ); - final behavior = MultiballsBehavior(); - final parent = Multiballs.test(); - final multiballs = [ - Multiball.test(bloc: MockMultiballCubit()), - Multiball.test(bloc: MockMultiballCubit()), - Multiball.test(bloc: MockMultiballCubit()), - Multiball.test(bloc: MockMultiballCubit()), - ]; - - await parent.addAll(multiballs); - await game.ensureAdd(parent); - await parent.ensureAdd(behavior); - - streamController.add( - GameState.initial().copyWith( - bonusHistory: [GameBonus.sparkyTurboCharge], - ), + final previous = GameState.initial(); + expect( + MultiballsBehavior().listenWhen(previous, state), + isFalse, ); - await tester.pump(); + }); + }); - for (final multiball in multiballs) { - verifyNever(multiball.bloc.onAnimate); - } - }, - ); + group('onNewState', () { + flameBlocTester.testGameWidget( + "calls 'onAnimate' once per each multiball when GameBloc emit state", + setUp: (game, tester) async { + final behavior = MultiballsBehavior(); + final parent = Multiballs.test(); + final multiballCubit = MockMultiballCubit(); + final otherMultiballCubit = MockMultiballCubit(); + final multiballs = [ + Multiball.test( + bloc: multiballCubit, + ), + Multiball.test( + bloc: otherMultiballCubit, + ), + ]; + + whenListen( + multiballCubit, + const Stream.empty(), + initialState: MultiballState.initial(), + ); + when(multiballCubit.onAnimate).thenAnswer((_) async {}); + + whenListen( + otherMultiballCubit, + const Stream.empty(), + initialState: MultiballState.initial(), + ); + when(otherMultiballCubit.onAnimate).thenAnswer((_) async {}); + + await parent.addAll(multiballs); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + + await tester.pump(); + + behavior.onNewState( + GameState.initial().copyWith(bonusHistory: [GameBonus.dashNest]), + ); + + for (final multiball in multiballs) { + verify( + multiball.bloc.onAnimate, + ).called(1); + } + }, + ); + }); }); }