mirror of https://github.com/flutter/pinball.git
feat: multiball asset (#235)
* feat: multiball assets * feat: added multiball to components * feat: added controller for multiball * feat: positioned multiball and changed animation * feat: added sandbox for multiball * chore: unused import * refactor: add rotation to multiball constructor * test: coverage for multiball * chore: todos for refactor multiball childrens * test: removed unused mock * chore: removed unused imports * test: removed golden tests * refactor: changed assets and refactored multiball * refactor: changed assets and refactored multiball * test: tests for multiball * refactor: multiballs group refactored * chore: names and doc * refactor: removed duplicated images for multiball * refactor: changed multiball cubit and state * refactor: changed multiball and group * chore: positions of lights * refactor: changing blink behavior * test: blink behavior * refactor: blinking multiball lights * test: tests for blink behavior * chore: analysis errors * test: coverage for blinking * test: coverage * test: trying to fix tests * fix: fixed bloc error on behavior with tests * refactor: multiball blink * refactor: blinking behavior to TimerComponent and test coverage * refactor: modified blinking behavior * chore: error on merge tests * test: coverage multiballs * refactor: cleaned blink behavior * chore: unused import * Update packages/pinball_components/test/src/components/multiball/behaviors/multiball_blinking_behavior_test.dart Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * Update packages/pinball_components/test/src/components/multiball/behaviors/multiball_blinking_behavior_test.dart Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * Update test/game/components/multiballs/behaviors/multiballs_behavior_test.dart Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * Update packages/pinball_components/test/src/components/multiball/multiball_test.dart Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * refactor: changed multiball states enum values * test: multiball descendant test at pinball Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>pull/293/head
parent
26acb63460
commit
3a593c2e6e
@ -0,0 +1 @@
|
||||
export 'multiballs_behavior.dart';
|
@ -0,0 +1,28 @@
|
||||
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<PinballGame>,
|
||||
ParentIsA<Multiballs>,
|
||||
BlocComponent<GameBloc, GameState> {
|
||||
@override
|
||||
bool listenWhen(GameState? previousState, GameState newState) {
|
||||
final hasChanged = previousState?.bonusHistory != newState.bonusHistory;
|
||||
final lastBonusIsMultiball = newState.bonusHistory.isNotEmpty &&
|
||||
newState.bonusHistory.last == GameBonus.dashNest;
|
||||
|
||||
return hasChanged && lastBonusIsMultiball;
|
||||
}
|
||||
|
||||
@override
|
||||
void onNewState(GameState state) {
|
||||
parent.children.whereType<Multiball>().forEach((multiball) {
|
||||
multiball.bloc.onAnimate();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/game/components/multiballs/behaviors/behaviors.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template multiballs_component}
|
||||
/// A [SpriteGroupComponent] for the multiball over the board.
|
||||
/// {@endtemplate}
|
||||
class Multiballs extends Component with ZIndex {
|
||||
/// {@macro multiballs_component}
|
||||
Multiballs()
|
||||
: super(
|
||||
children: [
|
||||
Multiball.a(),
|
||||
Multiball.b(),
|
||||
Multiball.c(),
|
||||
Multiball.d(),
|
||||
MultiballsBehavior(),
|
||||
],
|
||||
) {
|
||||
zIndex = ZIndexes.decal;
|
||||
}
|
||||
|
||||
/// Creates a [Multiballs] without any children.
|
||||
///
|
||||
/// This can be used for testing [Multiballs]'s behaviors in isolation.
|
||||
@visibleForTesting
|
||||
Multiballs.test();
|
||||
}
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1 @@
|
||||
export 'multiball_blinking_behavior.dart';
|
@ -0,0 +1,78 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
/// {@template multiball_blinking_behavior}
|
||||
/// Makes a [Multiball] blink back to [MultiballLightState.lit] when
|
||||
/// [MultiballLightState.dimmed].
|
||||
/// {@endtemplate}
|
||||
class MultiballBlinkingBehavior extends TimerComponent
|
||||
with ParentIsA<Multiball> {
|
||||
/// {@macro multiball_blinking_behavior}
|
||||
MultiballBlinkingBehavior() : super(period: 0.1);
|
||||
|
||||
final _maxBlinks = 10;
|
||||
|
||||
int _blinksCounter = 0;
|
||||
|
||||
bool _isAnimating = false;
|
||||
|
||||
void _onNewState(MultiballState state) {
|
||||
final animationEnabled =
|
||||
state.animationState == MultiballAnimationState.blinking;
|
||||
final canBlink = _blinksCounter < _maxBlinks;
|
||||
|
||||
if (animationEnabled && canBlink) {
|
||||
_start();
|
||||
} else {
|
||||
_stop();
|
||||
}
|
||||
}
|
||||
|
||||
void _start() {
|
||||
if (!_isAnimating) {
|
||||
_isAnimating = true;
|
||||
timer
|
||||
..reset()
|
||||
..start();
|
||||
_animate();
|
||||
}
|
||||
}
|
||||
|
||||
void _animate() {
|
||||
parent.bloc.onBlink();
|
||||
_blinksCounter++;
|
||||
}
|
||||
|
||||
void _stop() {
|
||||
if (_isAnimating) {
|
||||
_isAnimating = false;
|
||||
timer.stop();
|
||||
_blinksCounter = 0;
|
||||
parent.bloc.onStop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
parent.bloc.stream.listen(_onNewState);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTick() {
|
||||
super.onTick();
|
||||
if (!_isAnimating) {
|
||||
timer.stop();
|
||||
} else {
|
||||
if (_blinksCounter < _maxBlinks) {
|
||||
_animate();
|
||||
timer
|
||||
..reset()
|
||||
..start();
|
||||
} else {
|
||||
timer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'multiball_state.dart';
|
||||
|
||||
class MultiballCubit extends Cubit<MultiballState> {
|
||||
MultiballCubit() : super(const MultiballState.initial());
|
||||
|
||||
void onAnimate() {
|
||||
emit(
|
||||
state.copyWith(animationState: MultiballAnimationState.blinking),
|
||||
);
|
||||
}
|
||||
|
||||
void onStop() {
|
||||
emit(
|
||||
state.copyWith(animationState: MultiballAnimationState.idle),
|
||||
);
|
||||
}
|
||||
|
||||
void onBlink() {
|
||||
switch (state.lightState) {
|
||||
case MultiballLightState.lit:
|
||||
emit(
|
||||
state.copyWith(lightState: MultiballLightState.dimmed),
|
||||
);
|
||||
break;
|
||||
case MultiballLightState.dimmed:
|
||||
emit(
|
||||
state.copyWith(lightState: MultiballLightState.lit),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
// ignore_for_file: comment_references, public_member_api_docs
|
||||
|
||||
part of 'multiball_cubit.dart';
|
||||
|
||||
/// Indicates the different sprite states for [MultiballSpriteGroupComponent].
|
||||
enum MultiballLightState {
|
||||
lit,
|
||||
dimmed,
|
||||
}
|
||||
|
||||
// Indicates if the blinking animation is running.
|
||||
enum MultiballAnimationState {
|
||||
idle,
|
||||
blinking,
|
||||
}
|
||||
|
||||
class MultiballState extends Equatable {
|
||||
const MultiballState({
|
||||
required this.lightState,
|
||||
required this.animationState,
|
||||
});
|
||||
|
||||
const MultiballState.initial()
|
||||
: this(
|
||||
lightState: MultiballLightState.dimmed,
|
||||
animationState: MultiballAnimationState.idle,
|
||||
);
|
||||
|
||||
final MultiballLightState lightState;
|
||||
final MultiballAnimationState animationState;
|
||||
|
||||
MultiballState copyWith({
|
||||
MultiballLightState? lightState,
|
||||
MultiballAnimationState? animationState,
|
||||
}) {
|
||||
return MultiballState(
|
||||
lightState: lightState ?? this.lightState,
|
||||
animationState: animationState ?? this.animationState,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object> get props => [lightState, animationState];
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball_components/gen/assets.gen.dart';
|
||||
import 'package:pinball_components/src/components/multiball/behaviors/behaviors.dart';
|
||||
import 'package:pinball_components/src/pinball_components.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
export 'cubit/multiball_cubit.dart';
|
||||
|
||||
/// {@template multiball}
|
||||
/// A [Component] for the multiball lighting decals on the board.
|
||||
/// {@endtemplate}
|
||||
class Multiball extends Component {
|
||||
/// {@macro multiball}
|
||||
Multiball._({
|
||||
required Vector2 position,
|
||||
double rotation = 0,
|
||||
Iterable<Component>? children,
|
||||
required this.bloc,
|
||||
}) : super(
|
||||
children: [
|
||||
MultiballBlinkingBehavior(),
|
||||
MultiballSpriteGroupComponent(
|
||||
position: position,
|
||||
litAssetPath: Assets.images.multiball.lit.keyName,
|
||||
dimmedAssetPath: Assets.images.multiball.dimmed.keyName,
|
||||
rotation: rotation,
|
||||
state: bloc.state.lightState,
|
||||
),
|
||||
...?children,
|
||||
],
|
||||
);
|
||||
|
||||
/// {@macro multiball}
|
||||
Multiball.a({
|
||||
Iterable<Component>? children,
|
||||
}) : this._(
|
||||
position: Vector2(-23, 7.5),
|
||||
rotation: -24 * math.pi / 180,
|
||||
bloc: MultiballCubit(),
|
||||
children: children,
|
||||
);
|
||||
|
||||
/// {@macro multiball}
|
||||
Multiball.b({
|
||||
Iterable<Component>? children,
|
||||
}) : this._(
|
||||
position: Vector2(-7.2, -6.2),
|
||||
rotation: -5 * math.pi / 180,
|
||||
bloc: MultiballCubit(),
|
||||
children: children,
|
||||
);
|
||||
|
||||
/// {@macro multiball}
|
||||
Multiball.c({
|
||||
Iterable<Component>? children,
|
||||
}) : this._(
|
||||
position: Vector2(-0.7, -9.3),
|
||||
rotation: 2.7 * math.pi / 180,
|
||||
bloc: MultiballCubit(),
|
||||
children: children,
|
||||
);
|
||||
|
||||
/// {@macro multiball}
|
||||
Multiball.d({
|
||||
Iterable<Component>? children,
|
||||
}) : this._(
|
||||
position: Vector2(15, 7),
|
||||
rotation: 24 * math.pi / 180,
|
||||
bloc: MultiballCubit(),
|
||||
children: children,
|
||||
);
|
||||
|
||||
/// Creates an [Multiball] without any children.
|
||||
///
|
||||
/// This can be used for testing [Multiball]'s behaviors in isolation.
|
||||
// TODO(alestiago): Refactor injecting bloc once the following is merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
@visibleForTesting
|
||||
Multiball.test({
|
||||
required this.bloc,
|
||||
});
|
||||
|
||||
// TODO(alestiago): Consider refactoring once the following is merged:
|
||||
// https://github.com/flame-engine/flame/pull/1538
|
||||
// ignore: public_member_api_docs
|
||||
final MultiballCubit bloc;
|
||||
|
||||
@override
|
||||
void onRemove() {
|
||||
bloc.close();
|
||||
super.onRemove();
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template multiball_sprite_group_component}
|
||||
/// A [SpriteGroupComponent] for the multiball over the board.
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class MultiballSpriteGroupComponent
|
||||
extends SpriteGroupComponent<MultiballLightState>
|
||||
with HasGameRef, ParentIsA<Multiball> {
|
||||
/// {@macro multiball_sprite_group_component}
|
||||
MultiballSpriteGroupComponent({
|
||||
required Vector2 position,
|
||||
required String litAssetPath,
|
||||
required String dimmedAssetPath,
|
||||
required double rotation,
|
||||
required MultiballLightState state,
|
||||
}) : _litAssetPath = litAssetPath,
|
||||
_dimmedAssetPath = dimmedAssetPath,
|
||||
super(
|
||||
anchor: Anchor.center,
|
||||
position: position,
|
||||
angle: rotation,
|
||||
current: state,
|
||||
);
|
||||
|
||||
final String _litAssetPath;
|
||||
final String _dimmedAssetPath;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
parent.bloc.stream.listen((state) => current = state.lightState);
|
||||
|
||||
final sprites = {
|
||||
MultiballLightState.lit: Sprite(
|
||||
gameRef.images.fromCache(_litAssetPath),
|
||||
),
|
||||
MultiballLightState.dimmed:
|
||||
Sprite(gameRef.images.fromCache(_dimmedAssetPath)),
|
||||
};
|
||||
this.sprites = sprites;
|
||||
size = sprites[current]!.originalSize / 10;
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
import 'package:sandbox/stories/ball/basic_ball_game.dart';
|
||||
|
||||
class MultiballGame extends BallGame with KeyboardEvents {
|
||||
MultiballGame()
|
||||
: super(
|
||||
imagesFileNames: [
|
||||
Assets.images.multiball.lit.keyName,
|
||||
Assets.images.multiball.dimmed.keyName,
|
||||
],
|
||||
);
|
||||
|
||||
static const description = '''
|
||||
Shows how the Multiball are rendered.
|
||||
|
||||
- Tap anywhere on the screen to spawn a ball into the game.
|
||||
- Press space bar to animate multiballs.
|
||||
''';
|
||||
|
||||
final List<Multiball> multiballs = [
|
||||
Multiball.a(),
|
||||
Multiball.b(),
|
||||
Multiball.c(),
|
||||
Multiball.d(),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await super.onLoad();
|
||||
|
||||
camera.followVector2(Vector2.zero());
|
||||
|
||||
await addAll(multiballs);
|
||||
await traceAllBodies();
|
||||
}
|
||||
|
||||
@override
|
||||
KeyEventResult onKeyEvent(
|
||||
RawKeyEvent event,
|
||||
Set<LogicalKeyboardKey> keysPressed,
|
||||
) {
|
||||
if (event is RawKeyDownEvent &&
|
||||
event.logicalKey == LogicalKeyboardKey.space) {
|
||||
for (final multiball in multiballs) {
|
||||
multiball.bloc.onBlink();
|
||||
}
|
||||
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import 'package:dashbook/dashbook.dart';
|
||||
import 'package:sandbox/common/common.dart';
|
||||
import 'package:sandbox/stories/multiball/multiball_game.dart';
|
||||
|
||||
void addMultiballStories(Dashbook dashbook) {
|
||||
dashbook.storiesOf('Multiball').addGame(
|
||||
title: 'Assets',
|
||||
description: MultiballGame.description,
|
||||
gameBuilder: (_) => MultiballGame(),
|
||||
);
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
// ignore_for_file: prefer_const_constructors, cascade_invocations
|
||||
|
||||
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/multiball/behaviors/behaviors.dart';
|
||||
|
||||
import '../../../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final flameTester = FlameTester(TestGame.new);
|
||||
|
||||
group(
|
||||
'MultiballBlinkingBehavior',
|
||||
() {
|
||||
flameTester.testGameWidget(
|
||||
'calls onBlink every 0.1 seconds when animation state is animated',
|
||||
setUp: (game, tester) async {
|
||||
final behavior = MultiballBlinkingBehavior();
|
||||
final bloc = MockMultiballCubit();
|
||||
final streamController = StreamController<MultiballState>();
|
||||
whenListen(
|
||||
bloc,
|
||||
streamController.stream,
|
||||
initialState: MultiballState.initial(),
|
||||
);
|
||||
|
||||
final multiball = Multiball.test(bloc: bloc);
|
||||
await multiball.add(behavior);
|
||||
await game.ensureAdd(multiball);
|
||||
|
||||
streamController.add(
|
||||
MultiballState(
|
||||
animationState: MultiballAnimationState.blinking,
|
||||
lightState: MultiballLightState.lit,
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
game.update(0);
|
||||
|
||||
verify(bloc.onBlink).called(1);
|
||||
|
||||
await tester.pump();
|
||||
game.update(0.1);
|
||||
|
||||
await streamController.close();
|
||||
verify(bloc.onBlink).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'calls onStop when animation state is stopped',
|
||||
setUp: (game, tester) async {
|
||||
final behavior = MultiballBlinkingBehavior();
|
||||
final bloc = MockMultiballCubit();
|
||||
final streamController = StreamController<MultiballState>();
|
||||
whenListen(
|
||||
bloc,
|
||||
streamController.stream,
|
||||
initialState: MultiballState.initial(),
|
||||
);
|
||||
when(bloc.onBlink).thenAnswer((_) async {});
|
||||
|
||||
final multiball = Multiball.test(bloc: bloc);
|
||||
await multiball.add(behavior);
|
||||
await game.ensureAdd(multiball);
|
||||
|
||||
streamController.add(
|
||||
MultiballState(
|
||||
animationState: MultiballAnimationState.blinking,
|
||||
lightState: MultiballLightState.lit,
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
streamController.add(
|
||||
MultiballState(
|
||||
animationState: MultiballAnimationState.idle,
|
||||
lightState: MultiballLightState.lit,
|
||||
),
|
||||
);
|
||||
|
||||
await streamController.close();
|
||||
verify(bloc.onStop).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'onTick stops when there is no animation',
|
||||
setUp: (game, tester) async {
|
||||
final behavior = MultiballBlinkingBehavior();
|
||||
final bloc = MockMultiballCubit();
|
||||
final streamController = StreamController<MultiballState>();
|
||||
whenListen(
|
||||
bloc,
|
||||
streamController.stream,
|
||||
initialState: MultiballState.initial(),
|
||||
);
|
||||
when(bloc.onBlink).thenAnswer((_) async {});
|
||||
|
||||
final multiball = Multiball.test(bloc: bloc);
|
||||
await multiball.add(behavior);
|
||||
await game.ensureAdd(multiball);
|
||||
|
||||
streamController.add(
|
||||
MultiballState(
|
||||
animationState: MultiballAnimationState.idle,
|
||||
lightState: MultiballLightState.lit,
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
behavior.onTick();
|
||||
|
||||
expect(behavior.timer.isRunning(), false);
|
||||
},
|
||||
);
|
||||
|
||||
flameTester.testGameWidget(
|
||||
'onTick stops after 10 blinks repetitions',
|
||||
setUp: (game, tester) async {
|
||||
final behavior = MultiballBlinkingBehavior();
|
||||
final bloc = MockMultiballCubit();
|
||||
final streamController = StreamController<MultiballState>();
|
||||
whenListen(
|
||||
bloc,
|
||||
streamController.stream,
|
||||
initialState: MultiballState.initial(),
|
||||
);
|
||||
when(bloc.onBlink).thenAnswer((_) async {});
|
||||
|
||||
final multiball = Multiball.test(bloc: bloc);
|
||||
await multiball.add(behavior);
|
||||
await game.ensureAdd(multiball);
|
||||
|
||||
streamController.add(
|
||||
MultiballState(
|
||||
animationState: MultiballAnimationState.blinking,
|
||||
lightState: MultiballLightState.dimmed,
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
behavior.onTick();
|
||||
}
|
||||
|
||||
expect(behavior.timer.isRunning(), false);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
void main() {
|
||||
group(
|
||||
'MultiballCubit',
|
||||
() {
|
||||
blocTest<MultiballCubit, MultiballState>(
|
||||
'onAnimate emits animationState [animate]',
|
||||
build: MultiballCubit.new,
|
||||
act: (bloc) => bloc.onAnimate(),
|
||||
expect: () => [
|
||||
isA<MultiballState>()
|
||||
..having(
|
||||
(state) => state.animationState,
|
||||
'animationState',
|
||||
MultiballAnimationState.blinking,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<MultiballCubit, MultiballState>(
|
||||
'onStop emits animationState [stopped]',
|
||||
build: MultiballCubit.new,
|
||||
act: (bloc) => bloc.onStop(),
|
||||
expect: () => [
|
||||
isA<MultiballState>()
|
||||
..having(
|
||||
(state) => state.animationState,
|
||||
'animationState',
|
||||
MultiballAnimationState.idle,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<MultiballCubit, MultiballState>(
|
||||
'onBlink emits lightState [lit, dimmed, lit]',
|
||||
build: MultiballCubit.new,
|
||||
act: (bloc) => bloc
|
||||
..onBlink()
|
||||
..onBlink()
|
||||
..onBlink(),
|
||||
expect: () => [
|
||||
isA<MultiballState>()
|
||||
..having(
|
||||
(state) => state.lightState,
|
||||
'lightState',
|
||||
MultiballLightState.lit,
|
||||
),
|
||||
isA<MultiballState>()
|
||||
..having(
|
||||
(state) => state.lightState,
|
||||
'lightState',
|
||||
MultiballLightState.dimmed,
|
||||
),
|
||||
isA<MultiballState>()
|
||||
..having(
|
||||
(state) => state.lightState,
|
||||
'lightState',
|
||||
MultiballLightState.lit,
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/src/pinball_components.dart';
|
||||
|
||||
void main() {
|
||||
group('MultiballState', () {
|
||||
test('supports value equality', () {
|
||||
expect(
|
||||
MultiballState(
|
||||
animationState: MultiballAnimationState.idle,
|
||||
lightState: MultiballLightState.dimmed,
|
||||
),
|
||||
equals(
|
||||
MultiballState(
|
||||
animationState: MultiballAnimationState.idle,
|
||||
lightState: MultiballLightState.dimmed,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
group('constructor', () {
|
||||
test('can be instantiated', () {
|
||||
expect(
|
||||
MultiballState(
|
||||
animationState: MultiballAnimationState.idle,
|
||||
lightState: MultiballLightState.dimmed,
|
||||
),
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('copyWith', () {
|
||||
test(
|
||||
'copies correctly '
|
||||
'when no argument specified',
|
||||
() {
|
||||
final multiballState = MultiballState(
|
||||
animationState: MultiballAnimationState.idle,
|
||||
lightState: MultiballLightState.dimmed,
|
||||
);
|
||||
expect(
|
||||
multiballState.copyWith(),
|
||||
equals(multiballState),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'copies correctly '
|
||||
'when all arguments specified',
|
||||
() {
|
||||
final multiballState = MultiballState(
|
||||
animationState: MultiballAnimationState.idle,
|
||||
lightState: MultiballLightState.dimmed,
|
||||
);
|
||||
final otherMultiballState = MultiballState(
|
||||
animationState: MultiballAnimationState.blinking,
|
||||
lightState: MultiballLightState.lit,
|
||||
);
|
||||
expect(multiballState, isNot(equals(otherMultiballState)));
|
||||
|
||||
expect(
|
||||
multiballState.copyWith(
|
||||
animationState: MultiballAnimationState.blinking,
|
||||
lightState: MultiballLightState.lit,
|
||||
),
|
||||
equals(otherMultiballState),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
// 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/multiball/behaviors/behaviors.dart';
|
||||
|
||||
import '../../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final assets = [
|
||||
Assets.images.multiball.lit.keyName,
|
||||
Assets.images.multiball.dimmed.keyName,
|
||||
];
|
||||
final flameTester = FlameTester(() => TestGame(assets));
|
||||
|
||||
group('Multiball', () {
|
||||
group('loads correctly', () {
|
||||
flameTester.test('"a"', (game) async {
|
||||
final multiball = Multiball.a();
|
||||
await game.ensureAdd(multiball);
|
||||
|
||||
expect(game.contains(multiball), isTrue);
|
||||
});
|
||||
|
||||
flameTester.test('"b"', (game) async {
|
||||
final multiball = Multiball.b();
|
||||
await game.ensureAdd(multiball);
|
||||
expect(game.contains(multiball), isTrue);
|
||||
});
|
||||
|
||||
flameTester.test('"c"', (game) async {
|
||||
final multiball = Multiball.c();
|
||||
await game.ensureAdd(multiball);
|
||||
|
||||
expect(game.contains(multiball), isTrue);
|
||||
});
|
||||
|
||||
flameTester.test('"d"', (game) async {
|
||||
final multiball = Multiball.d();
|
||||
await game.ensureAdd(multiball);
|
||||
expect(game.contains(multiball), isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
flameTester.test(
|
||||
'closes bloc when removed',
|
||||
(game) async {
|
||||
final bloc = MockMultiballCubit();
|
||||
whenListen(
|
||||
bloc,
|
||||
const Stream<MultiballLightState>.empty(),
|
||||
initialState: MultiballLightState.dimmed,
|
||||
);
|
||||
when(bloc.close).thenAnswer((_) async {});
|
||||
final multiball = Multiball.test(bloc: bloc);
|
||||
|
||||
await game.ensureAdd(multiball);
|
||||
game.remove(multiball);
|
||||
await game.ready();
|
||||
|
||||
verify(bloc.close).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
group('adds', () {
|
||||
flameTester.test('new children', (game) async {
|
||||
final component = Component();
|
||||
final multiball = Multiball.a(
|
||||
children: [component],
|
||||
);
|
||||
await game.ensureAdd(multiball);
|
||||
expect(multiball.children, contains(component));
|
||||
});
|
||||
|
||||
flameTester.test('a MultiballBlinkingBehavior', (game) async {
|
||||
final multiball = Multiball.a();
|
||||
await game.ensureAdd(multiball);
|
||||
expect(
|
||||
multiball.children.whereType<MultiballBlinkingBehavior>().single,
|
||||
isNotNull,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
// ignore_for_file: cascade_invocations, prefer_const_constructors
|
||||
|
||||
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/game/components/multiballs/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.multiball.lit.keyName,
|
||||
Assets.images.multiball.dimmed.keyName,
|
||||
];
|
||||
|
||||
group('MultiballsBehavior', () {
|
||||
late GameBloc gameBloc;
|
||||
|
||||
setUp(() {
|
||||
gameBloc = MockGameBloc();
|
||||
whenListen(
|
||||
gameBloc,
|
||||
const Stream<GameState>.empty(),
|
||||
initialState: const GameState.initial(),
|
||||
);
|
||||
});
|
||||
|
||||
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
|
||||
gameBuilder: EmptyPinballTestGame.new,
|
||||
blocBuilder: () => gameBloc,
|
||||
assets: assets,
|
||||
);
|
||||
|
||||
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],
|
||||
);
|
||||
|
||||
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().copyWith(bonusHistory: [GameBonus.dashNest]);
|
||||
final state = previous.copyWith(
|
||||
bonusHistory: [...previous.bonusHistory, GameBonus.androidSpaceship],
|
||||
);
|
||||
|
||||
expect(
|
||||
MultiballsBehavior().listenWhen(previous, state),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
test('is false when the bonusHistory state is the same', () {
|
||||
final previous = GameState.initial();
|
||||
final state = GameState(
|
||||
score: 10,
|
||||
multiplier: 1,
|
||||
rounds: 0,
|
||||
bonusHistory: const [],
|
||||
);
|
||||
|
||||
expect(
|
||||
MultiballsBehavior().listenWhen(previous, state),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('onNewState', () {
|
||||
flameBlocTester.testGameWidget(
|
||||
"calls 'onAnimate' once for every multiball",
|
||||
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<MultiballState>.empty(),
|
||||
initialState: MultiballState.initial(),
|
||||
);
|
||||
when(multiballCubit.onAnimate).thenAnswer((_) async {});
|
||||
|
||||
whenListen(
|
||||
otherMultiballCubit,
|
||||
const Stream<MultiballState>.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);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.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.multiball.lit.keyName,
|
||||
Assets.images.multiball.dimmed.keyName,
|
||||
];
|
||||
late GameBloc gameBloc;
|
||||
|
||||
setUp(() {
|
||||
gameBloc = GameBloc();
|
||||
});
|
||||
|
||||
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
|
||||
gameBuilder: EmptyPinballTestGame.new,
|
||||
blocBuilder: () => gameBloc,
|
||||
assets: assets,
|
||||
);
|
||||
|
||||
group('Multiballs', () {
|
||||
flameBlocTester.testGameWidget(
|
||||
'loads correctly',
|
||||
setUp: (game, tester) async {
|
||||
final multiballs = Multiballs();
|
||||
await game.ensureAdd(multiballs);
|
||||
|
||||
expect(game.contains(multiballs), isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
group('loads', () {
|
||||
flameBlocTester.testGameWidget(
|
||||
'four Multiball',
|
||||
setUp: (game, tester) async {
|
||||
final multiballs = Multiballs();
|
||||
await game.ensureAdd(multiballs);
|
||||
|
||||
expect(
|
||||
multiballs.descendants().whereType<Multiball>().length,
|
||||
equals(4),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in new issue