chore: restore GameHud visibility and control game flow (#247)

pull/336/head
arturplaczek 2 years ago committed by GitHub
parent d729a9ac69
commit 03f60fbffe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,6 +8,7 @@ import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart'; import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
@ -34,8 +35,11 @@ class App extends StatelessWidget {
RepositoryProvider.value(value: _leaderboardRepository), RepositoryProvider.value(value: _leaderboardRepository),
RepositoryProvider.value(value: _pinballAudio), RepositoryProvider.value(value: _pinballAudio),
], ],
child: BlocProvider( child: MultiBlocProvider(
create: (context) => CharacterThemeCubit(), providers: [
BlocProvider(create: (_) => CharacterThemeCubit()),
BlocProvider(create: (_) => StartGameBloc()),
],
child: MaterialApp( child: MaterialApp(
title: 'I/O Pinball', title: 'I/O Pinball',
theme: PinballTheme.standard, theme: PinballTheme.standard,

@ -60,7 +60,6 @@ class PinballGamePage extends StatelessWidget {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider(create: (_) => StartGameBloc(game: game)),
BlocProvider(create: (_) => GameBloc()), BlocProvider(create: (_) => GameBloc()),
BlocProvider(create: (_) => AssetsManagerCubit(loadables)..load()), BlocProvider(create: (_) => AssetsManagerCubit(loadables)..load()),
], ],
@ -105,11 +104,16 @@ class PinballGameLoadedView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isPlaying = context.select(
(StartGameBloc bloc) => bloc.state.status == StartGameStatus.play,
);
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16; final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8); final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8);
return Stack( return StartGameListener(
game: game,
child: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
child: GameWidget<PinballGame>( child: GameWidget<PinballGame>(
@ -117,24 +121,26 @@ class PinballGameLoadedView extends StatelessWidget {
initialActiveOverlays: const [PinballGame.playButtonOverlay], initialActiveOverlays: const [PinballGame.playButtonOverlay],
overlayBuilderMap: { overlayBuilderMap: {
PinballGame.playButtonOverlay: (context, game) { PinballGame.playButtonOverlay: (context, game) {
return Positioned( return const Positioned(
bottom: 20, bottom: 20,
right: 0, right: 0,
left: 0, left: 0,
child: PlayButtonOverlay(game: game), child: PlayButtonOverlay(),
); );
}, },
}, },
), ),
), ),
// TODO(arturplaczek): add Visibility to GameHud based on StartGameBloc
// status
Positioned( Positioned(
top: 16, top: 16,
left: leftMargin, left: leftMargin,
child: Visibility(
visible: isPlaying,
child: const GameHud(), child: const GameHud(),
), ),
),
], ],
),
); );
} }
} }

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/pinball_game.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
/// {@template play_button_overlay} /// {@template play_button_overlay}
@ -9,13 +9,7 @@ import 'package:pinball_ui/pinball_ui.dart';
/// {@endtemplate} /// {@endtemplate}
class PlayButtonOverlay extends StatelessWidget { class PlayButtonOverlay extends StatelessWidget {
/// {@macro play_button_overlay} /// {@macro play_button_overlay}
const PlayButtonOverlay({ const PlayButtonOverlay({Key? key}) : super(key: key);
Key? key,
required PinballGame game,
}) : _game = game,
super(key: key);
final PinballGame _game;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -23,9 +17,8 @@ class PlayButtonOverlay extends StatelessWidget {
return PinballButton( return PinballButton(
text: l10n.play, text: l10n.play,
onTap: () async { onTap: () {
_game.gameFlowController.start(); context.read<StartGameBloc>().add(const PlayTapped());
await showCharacterSelectionDialog(context);
}, },
); );
} }

@ -3,10 +3,8 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/gen/gen.dart'; import 'package:pinball/gen/gen.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart'; import 'package:platform_helper/platform_helper.dart';
@ -51,24 +49,16 @@ extension on Control {
} }
} }
Future<void> showHowToPlayDialog(BuildContext context) {
final audio = context.read<PinballAudio>();
return showDialog<void>(
context: context,
builder: (_) => HowToPlayDialog(),
).then((_) {
audio.ioPinballVoiceOver();
});
}
class HowToPlayDialog extends StatefulWidget { class HowToPlayDialog extends StatefulWidget {
HowToPlayDialog({ HowToPlayDialog({
Key? key, Key? key,
required this.onDismissCallback,
@visibleForTesting PlatformHelper? platformHelper, @visibleForTesting PlatformHelper? platformHelper,
}) : platformHelper = platformHelper ?? PlatformHelper(), }) : platformHelper = platformHelper ?? PlatformHelper(),
super(key: key); super(key: key);
final PlatformHelper platformHelper; final PlatformHelper platformHelper;
final VoidCallback onDismissCallback;
@override @override
State<HowToPlayDialog> createState() => _HowToPlayDialogState(); State<HowToPlayDialog> createState() => _HowToPlayDialogState();
@ -82,6 +72,7 @@ class _HowToPlayDialogState extends State<HowToPlayDialog> {
closeTimer = Timer(const Duration(seconds: 3), () { closeTimer = Timer(const Duration(seconds: 3), () {
if (mounted) { if (mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
widget.onDismissCallback.call();
} }
}); });
} }
@ -96,10 +87,17 @@ class _HowToPlayDialogState extends State<HowToPlayDialog> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isMobile = widget.platformHelper.isMobile; final isMobile = widget.platformHelper.isMobile;
final l10n = context.l10n; final l10n = context.l10n;
return PinballDialog(
return WillPopScope(
onWillPop: () {
widget.onDismissCallback.call();
return Future.value(true);
},
child: PinballDialog(
title: l10n.howToPlay, title: l10n.howToPlay,
subtitle: l10n.tipsForFlips, subtitle: l10n.tipsForFlips,
child: isMobile ? const _MobileBody() : const _DesktopBody(), child: isMobile ? const _MobileBody() : const _DesktopBody(),
),
); );
} }
} }

@ -1,20 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_theme/pinball_theme.dart'; import 'package:pinball_theme/pinball_theme.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
/// Inflates [CharacterSelectionDialog] using [showDialog].
Future<void> showCharacterSelectionDialog(BuildContext context) {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => const CharacterSelectionDialog(),
);
}
/// {@template character_selection_dialog} /// {@template character_selection_dialog}
/// Dialog used to select the playing character of the game. /// Dialog used to select the playing character of the game.
/// {@endtemplate character_selection_dialog} /// {@endtemplate character_selection_dialog}
@ -59,7 +50,7 @@ class _SelectCharacterButton extends StatelessWidget {
return PinballButton( return PinballButton(
onTap: () async { onTap: () async {
Navigator.of(context).pop(); Navigator.of(context).pop();
await showHowToPlayDialog(context); context.read<StartGameBloc>().add(const CharacterSelected());
}, },
text: l10n.select, text: l10n.select,
); );

@ -1,6 +1,5 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:pinball/game/game.dart';
part 'start_game_event.dart'; part 'start_game_event.dart';
part 'start_game_state.dart'; part 'start_game_state.dart';
@ -10,23 +9,16 @@ part 'start_game_state.dart';
/// {@endtemplate} /// {@endtemplate}
class StartGameBloc extends Bloc<StartGameEvent, StartGameState> { class StartGameBloc extends Bloc<StartGameEvent, StartGameState> {
/// {@macro start_game_bloc} /// {@macro start_game_bloc}
StartGameBloc({ StartGameBloc() : super(const StartGameState.initial()) {
required PinballGame game,
}) : _game = game,
super(const StartGameState.initial()) {
on<PlayTapped>(_onPlayTapped); on<PlayTapped>(_onPlayTapped);
on<CharacterSelected>(_onCharacterSelected); on<CharacterSelected>(_onCharacterSelected);
on<HowToPlayFinished>(_onHowToPlayFinished); on<HowToPlayFinished>(_onHowToPlayFinished);
} }
final PinballGame _game;
void _onPlayTapped( void _onPlayTapped(
PlayTapped event, PlayTapped event,
Emitter<StartGameState> emit, Emitter<StartGameState> emit,
) { ) {
_game.gameFlowController.start();
emit( emit(
state.copyWith( state.copyWith(
status: StartGameStatus.selectCharacter, status: StartGameStatus.selectCharacter,

@ -1 +1,2 @@
export 'bloc/start_game_bloc.dart'; export 'bloc/start_game_bloc.dart';
export 'widgets/start_game_listener.dart';

@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// {@template start_game_listener}
/// Listener that manages the display of dialogs for [StartGameStatus].
///
/// It's responsible for starting the game after pressing play button
/// and playing a sound after the 'how to play' dialog.
/// {@endtemplate}
class StartGameListener extends StatelessWidget {
/// {@macro start_game_listener}
const StartGameListener({
Key? key,
required Widget child,
required PinballGame game,
}) : _child = child,
_game = game,
super(key: key);
final Widget _child;
final PinballGame _game;
@override
Widget build(BuildContext context) {
return BlocListener<StartGameBloc, StartGameState>(
listener: (context, state) {
switch (state.status) {
case StartGameStatus.initial:
break;
case StartGameStatus.selectCharacter:
_onSelectCharacter(context);
_game.gameFlowController.start();
break;
case StartGameStatus.howToPlay:
_onHowToPlay(context);
break;
case StartGameStatus.play:
break;
}
},
child: _child,
);
}
void _onSelectCharacter(BuildContext context) {
_showPinballDialog(
context: context,
child: const CharacterSelectionDialog(),
barrierDismissible: false,
);
}
void _onHowToPlay(BuildContext context) {
final audio = context.read<PinballAudio>();
_showPinballDialog(
context: context,
child: HowToPlayDialog(
onDismissCallback: () {
context.read<StartGameBloc>().add(const HowToPlayFinished());
audio.ioPinballVoiceOver();
},
),
);
}
void _showPinballDialog({
required BuildContext context,
required Widget child,
bool barrierDismissible = true,
}) {
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
showDialog<void>(
context: context,
barrierColor: PinballColors.transparent,
barrierDismissible: barrierDismissible,
builder: (_) {
return Center(
child: SizedBox(
height: gameWidgetWidth * 0.87,
width: gameWidgetWidth,
child: child,
),
);
},
);
}
}

@ -200,12 +200,11 @@ void main() {
find.byWidgetPredicate((w) => w is GameWidget<PinballGame>), find.byWidgetPredicate((w) => w is GameWidget<PinballGame>),
findsOneWidget, findsOneWidget,
); );
// TODO(arturplaczek): add Visibility to GameHud based on StartGameBloc
// status expect(
// expect( find.byType(GameHud),
// find.byType(GameHud), findsNothing,
// findsNothing, );
// );
}); });
testWidgets('renders a hud on play state', (tester) async { testWidgets('renders a hud on play state', (tester) async {

@ -2,64 +2,44 @@ import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/start_game/bloc/start_game_bloc.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
class _MockPinballGame extends Mock implements PinballGame {} class _MockStartGameBloc extends Mock implements StartGameBloc {}
class _MockGameFlowController extends Mock implements GameFlowController {}
class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {}
void main() { void main() {
group('PlayButtonOverlay', () { group('PlayButtonOverlay', () {
late PinballGame game; late StartGameBloc startGameBloc;
late GameFlowController gameFlowController;
late CharacterThemeCubit characterThemeCubit;
setUp(() async { setUp(() async {
await mockFlameImages(); await mockFlameImages();
startGameBloc = _MockStartGameBloc();
game = _MockPinballGame();
gameFlowController = _MockGameFlowController();
characterThemeCubit = _MockCharacterThemeCubit();
whenListen( whenListen(
characterThemeCubit, startGameBloc,
const Stream<CharacterThemeState>.empty(), Stream.value(const StartGameState.initial()),
initialState: const CharacterThemeState.initial(), initialState: const StartGameState.initial(),
); );
when(() => characterThemeCubit.state)
.thenReturn(const CharacterThemeState.initial());
when(() => game.gameFlowController).thenReturn(gameFlowController);
when(gameFlowController.start).thenAnswer((_) {});
}); });
testWidgets('renders correctly', (tester) async { testWidgets('renders correctly', (tester) async {
await tester.pumpApp(PlayButtonOverlay(game: game)); await tester.pumpApp(const PlayButtonOverlay());
expect(find.text('Play'), findsOneWidget);
});
testWidgets('calls gameFlowController.start when tapped', (tester) async { expect(find.text('Play'), findsOneWidget);
await tester.pumpApp(
PlayButtonOverlay(game: game),
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.text('Play'));
await tester.pump();
verify(gameFlowController.start).called(1);
}); });
testWidgets('displays CharacterSelectionDialog when tapped', testWidgets('adds PlayTapped event to StartGameBloc when taped',
(tester) async { (tester) async {
await tester.pumpApp( await tester.pumpApp(
PlayButtonOverlay(game: game), const PlayButtonOverlay(),
characterThemeCubit: characterThemeCubit, startGameBloc: startGameBloc,
); );
await tester.tap(find.text('Play')); await tester.tap(find.text('Play'));
await tester.pump(); await tester.pump();
expect(find.byType(CharacterSelectionDialog), findsOneWidget);
verify(() => startGameBloc.add(const PlayTapped())).called(1);
}); });
}); });
} }

@ -3,13 +3,10 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/how_to_play/how_to_play.dart'; import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:platform_helper/platform_helper.dart'; import 'package:platform_helper/platform_helper.dart';
import '../helpers/helpers.dart'; import '../helpers/helpers.dart';
class _MockPinballAudio extends Mock implements PinballAudio {}
class _MockPlatformHelper extends Mock implements PlatformHelper {} class _MockPlatformHelper extends Mock implements PlatformHelper {}
void main() { void main() {
@ -25,7 +22,11 @@ void main() {
testWidgets( testWidgets(
'can be instantiated without passing in a platform helper', 'can be instantiated without passing in a platform helper',
(tester) async { (tester) async {
await tester.pumpApp(HowToPlayDialog()); await tester.pumpApp(
HowToPlayDialog(
onDismissCallback: () {},
),
);
expect(find.byType(HowToPlayDialog), findsOneWidget); expect(find.byType(HowToPlayDialog), findsOneWidget);
}, },
); );
@ -35,6 +36,7 @@ void main() {
await tester.pumpApp( await tester.pumpApp(
HowToPlayDialog( HowToPlayDialog(
platformHelper: platformHelper, platformHelper: platformHelper,
onDismissCallback: () {},
), ),
); );
expect(find.text(l10n.howToPlay), findsOneWidget); expect(find.text(l10n.howToPlay), findsOneWidget);
@ -49,6 +51,7 @@ void main() {
await tester.pumpApp( await tester.pumpApp(
HowToPlayDialog( HowToPlayDialog(
platformHelper: platformHelper, platformHelper: platformHelper,
onDismissCallback: () {},
), ),
); );
expect(find.text(l10n.howToPlay), findsOneWidget); expect(find.text(l10n.howToPlay), findsOneWidget);
@ -62,7 +65,12 @@ void main() {
Builder( Builder(
builder: (context) { builder: (context) {
return TextButton( return TextButton(
onPressed: () => showHowToPlayDialog(context), onPressed: () => showDialog<void>(
context: context,
builder: (_) => HowToPlayDialog(
onDismissCallback: () {},
),
),
child: const Text('test'), child: const Text('test'),
); );
}, },
@ -82,7 +90,12 @@ void main() {
Builder( Builder(
builder: (context) { builder: (context) {
return TextButton( return TextButton(
onPressed: () => showHowToPlayDialog(context), onPressed: () => showDialog<void>(
context: context,
builder: (_) => HowToPlayDialog(
onDismissCallback: () {},
),
),
child: const Text('test'), child: const Text('test'),
); );
}, },
@ -96,30 +109,5 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byType(HowToPlayDialog), findsNothing); expect(find.byType(HowToPlayDialog), findsNothing);
}); });
testWidgets(
'plays the I/O Pinball voice over audio on dismiss',
(tester) async {
final audio = _MockPinballAudio();
await tester.pumpApp(
Builder(
builder: (context) {
return TextButton(
onPressed: () => showHowToPlayDialog(context),
child: const Text('test'),
);
},
),
pinballAudio: audio,
);
expect(find.byType(HowToPlayDialog), findsNothing);
await tester.tap(find.text('test'));
await tester.pumpAndSettle();
await tester.tapAt(Offset.zero);
await tester.pumpAndSettle();
verify(audio.ioPinballVoiceOver).called(1);
},
);
}); });
} }

@ -2,8 +2,8 @@ import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/select_character/select_character.dart'; import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_theme/pinball_theme.dart'; import 'package:pinball_theme/pinball_theme.dart';
import 'package:pinball_ui/pinball_ui.dart'; import 'package:pinball_ui/pinball_ui.dart';
@ -11,14 +11,19 @@ import '../../helpers/helpers.dart';
class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {} class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {}
class _MockStartGameBloc extends Mock implements StartGameBloc {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
late CharacterThemeCubit characterThemeCubit; late CharacterThemeCubit characterThemeCubit;
late StartGameBloc startGameBloc;
setUp(() async { setUp(() async {
await mockFlameImages(); await mockFlameImages();
characterThemeCubit = _MockCharacterThemeCubit(); characterThemeCubit = _MockCharacterThemeCubit();
startGameBloc = _MockStartGameBloc();
whenListen( whenListen(
characterThemeCubit, characterThemeCubit,
const Stream<CharacterThemeState>.empty(), const Stream<CharacterThemeState>.empty(),
@ -29,25 +34,6 @@ void main() {
}); });
group('CharacterSelectionDialog', () { group('CharacterSelectionDialog', () {
group('showCharacterSelectionDialog', () {
testWidgets('inflates the dialog', (tester) async {
await tester.pumpApp(
Builder(
builder: (context) {
return TextButton(
onPressed: () => showCharacterSelectionDialog(context),
child: const Text('test'),
);
},
),
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.text('test'));
await tester.pump();
expect(find.byType(CharacterSelectionDialog), findsOneWidget);
});
});
testWidgets('selecting a new character calls characterSelected on cubit', testWidgets('selecting a new character calls characterSelected on cubit',
(tester) async { (tester) async {
await tester.pumpApp( await tester.pumpApp(
@ -63,15 +49,22 @@ void main() {
testWidgets( testWidgets(
'tapping the select button dismisses the character ' 'tapping the select button dismisses the character '
'dialog and shows the how to play dialog', (tester) async { 'dialog and calls CharacterSelected event to the bloc', (tester) async {
whenListen(
startGameBloc,
const Stream<StartGameState>.empty(),
initialState: const StartGameState.initial(),
);
await tester.pumpApp( await tester.pumpApp(
const CharacterSelectionDialog(), const CharacterSelectionDialog(),
characterThemeCubit: characterThemeCubit, characterThemeCubit: characterThemeCubit,
startGameBloc: startGameBloc,
); );
await tester.tap(find.byType(PinballButton)); await tester.tap(find.byType(PinballButton));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byType(CharacterSelectionDialog), findsNothing); expect(find.byType(CharacterSelectionDialog), findsNothing);
expect(find.byType(HowToPlayDialog), findsOneWidget); verify(() => startGameBloc.add(const CharacterSelected())).called(1);
}); });
testWidgets('updating the selected character updates the preview', testWidgets('updating the selected character updates the preview',

@ -24,9 +24,7 @@ void main() {
group('StartGameBloc', () { group('StartGameBloc', () {
blocTest<StartGameBloc, StartGameState>( blocTest<StartGameBloc, StartGameState>(
'on PlayTapped changes status to selectCharacter', 'on PlayTapped changes status to selectCharacter',
build: () => StartGameBloc( build: StartGameBloc.new,
game: pinballGame,
),
act: (bloc) => bloc.add(const PlayTapped()), act: (bloc) => bloc.add(const PlayTapped()),
expect: () => [ expect: () => [
const StartGameState( const StartGameState(
@ -37,9 +35,7 @@ void main() {
blocTest<StartGameBloc, StartGameState>( blocTest<StartGameBloc, StartGameState>(
'on CharacterSelected changes status to howToPlay', 'on CharacterSelected changes status to howToPlay',
build: () => StartGameBloc( build: StartGameBloc.new,
game: pinballGame,
),
act: (bloc) => bloc.add(const CharacterSelected()), act: (bloc) => bloc.add(const CharacterSelected()),
expect: () => [ expect: () => [
const StartGameState( const StartGameState(
@ -50,9 +46,7 @@ void main() {
blocTest<StartGameBloc, StartGameState>( blocTest<StartGameBloc, StartGameState>(
'on HowToPlayFinished changes status to play', 'on HowToPlayFinished changes status to play',
build: () => StartGameBloc( build: StartGameBloc.new,
game: pinballGame,
),
act: (bloc) => bloc.add(const HowToPlayFinished()), act: (bloc) => bloc.add(const HowToPlayFinished()),
expect: () => [ expect: () => [
const StartGameState( const StartGameState(

@ -0,0 +1,269 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import '../../helpers/helpers.dart';
class _MockStartGameBloc extends Mock implements StartGameBloc {}
class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {}
class _MockPinballGame extends Mock implements PinballGame {}
class _MockGameFlowController extends Mock implements GameFlowController {}
class _MockPinballAudio extends Mock implements PinballAudio {}
void main() {
late StartGameBloc startGameBloc;
late PinballGame pinballGame;
late PinballAudio pinballAudio;
late CharacterThemeCubit characterThemeCubit;
group('StartGameListener', () {
setUp(() async {
await mockFlameImages();
startGameBloc = _MockStartGameBloc();
pinballGame = _MockPinballGame();
pinballAudio = _MockPinballAudio();
characterThemeCubit = _MockCharacterThemeCubit();
});
group('on selectCharacter status', () {
testWidgets(
'calls start on the game controller',
(tester) async {
whenListen(
startGameBloc,
Stream.value(
const StartGameState(status: StartGameStatus.selectCharacter),
),
initialState: const StartGameState.initial(),
);
final gameController = _MockGameFlowController();
when(() => pinballGame.gameFlowController)
.thenAnswer((_) => gameController);
await tester.pumpApp(
StartGameListener(
game: pinballGame,
child: const SizedBox.shrink(),
),
startGameBloc: startGameBloc,
);
verify(gameController.start).called(1);
},
);
testWidgets(
'shows SelectCharacter dialog',
(tester) async {
whenListen(
startGameBloc,
Stream.value(
const StartGameState(status: StartGameStatus.selectCharacter),
),
initialState: const StartGameState.initial(),
);
whenListen(
characterThemeCubit,
Stream.value(const CharacterThemeState.initial()),
initialState: const CharacterThemeState.initial(),
);
final gameController = _MockGameFlowController();
when(() => pinballGame.gameFlowController)
.thenAnswer((_) => gameController);
await tester.pumpApp(
StartGameListener(
game: pinballGame,
child: const SizedBox.shrink(),
),
startGameBloc: startGameBloc,
characterThemeCubit: characterThemeCubit,
);
await tester.pump(kThemeAnimationDuration);
expect(
find.byType(CharacterSelectionDialog),
findsOneWidget,
);
},
);
});
testWidgets(
'on howToPlay status shows HowToPlay dialog',
(tester) async {
whenListen(
startGameBloc,
Stream.value(
const StartGameState(status: StartGameStatus.howToPlay),
),
initialState: const StartGameState.initial(),
);
await tester.pumpApp(
StartGameListener(
game: pinballGame,
child: const SizedBox.shrink(),
),
startGameBloc: startGameBloc,
);
await tester.pumpAndSettle();
expect(
find.byType(HowToPlayDialog),
findsOneWidget,
);
},
);
testWidgets(
'do nothing on play status',
(tester) async {
whenListen(
startGameBloc,
Stream.value(
const StartGameState(status: StartGameStatus.play),
),
initialState: const StartGameState.initial(),
);
await tester.pumpApp(
StartGameListener(
game: pinballGame,
child: const SizedBox.shrink(),
),
startGameBloc: startGameBloc,
);
await tester.pumpAndSettle();
expect(
find.byType(HowToPlayDialog),
findsNothing,
);
expect(
find.byType(CharacterSelectionDialog),
findsNothing,
);
},
);
testWidgets(
'do nothing on initial status',
(tester) async {
whenListen(
startGameBloc,
Stream.value(
const StartGameState(status: StartGameStatus.initial),
),
initialState: const StartGameState.initial(),
);
await tester.pumpApp(
StartGameListener(
game: pinballGame,
child: const SizedBox.shrink(),
),
startGameBloc: startGameBloc,
);
await tester.pumpAndSettle();
expect(
find.byType(HowToPlayDialog),
findsNothing,
);
expect(
find.byType(CharacterSelectionDialog),
findsNothing,
);
},
);
group('on dismiss HowToPlayDialog', () {
setUp(() {
whenListen(
startGameBloc,
Stream.value(
const StartGameState(status: StartGameStatus.howToPlay),
),
initialState: const StartGameState.initial(),
);
});
testWidgets(
'adds HowToPlayFinished event',
(tester) async {
await tester.pumpApp(
StartGameListener(
game: pinballGame,
child: const SizedBox.shrink(),
),
startGameBloc: startGameBloc,
);
await tester.pumpAndSettle();
expect(
find.byType(HowToPlayDialog),
findsOneWidget,
);
await tester.tapAt(const Offset(1, 1));
await tester.pumpAndSettle();
expect(
find.byType(HowToPlayDialog),
findsNothing,
);
await tester.pumpAndSettle();
verify(
() => startGameBloc.add(const HowToPlayFinished()),
).called(1);
},
);
testWidgets(
'plays the I/O Pinball voice over audio',
(tester) async {
await tester.pumpApp(
StartGameListener(
game: pinballGame,
child: const SizedBox.shrink(),
),
startGameBloc: startGameBloc,
pinballAudio: pinballAudio,
);
await tester.pumpAndSettle();
expect(
find.byType(HowToPlayDialog),
findsOneWidget,
);
await tester.tapAt(const Offset(1, 1));
await tester.pumpAndSettle();
expect(
find.byType(HowToPlayDialog),
findsNothing,
);
await tester.pumpAndSettle();
verify(pinballAudio.ioPinballVoiceOver).called(1);
},
);
});
});
}
Loading…
Cancel
Save