diff --git a/lib/game/view/widgets/play_button_overlay.dart b/lib/game/view/widgets/play_button_overlay.dart index 3db62a50..c855f776 100644 --- a/lib/game/view/widgets/play_button_overlay.dart +++ b/lib/game/view/widgets/play_button_overlay.dart @@ -22,24 +22,9 @@ class PlayButtonOverlay extends StatelessWidget { return Center( child: ElevatedButton( - onPressed: () { + onPressed: () async { _game.gameFlowController.start(); - showDialog( - context: context, - barrierDismissible: false, - builder: (_) { - // TODO(arturplaczek): remove after merge StarBlocListener - final height = MediaQuery.of(context).size.height * 0.5; - - return Center( - child: SizedBox( - height: height, - width: height * 1.4, - child: const CharacterSelectionDialog(), - ), - ); - }, - ); + await showCharacterSelectionDialog(context); }, child: Text(l10n.play), ), diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 7691e2dd..562d9b1f 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -46,19 +46,19 @@ }, "select": "Select", "@select": { - "description": "Text displayed on the character selection page select button" + "description": "Text displayed on the character selection dialog - select button" }, "space": "Space", "@space": { "description": "Text displayed on space control button" }, - "characterSelectionTitle": "Choose your character!", + "characterSelectionTitle": "Select a Character", "@characterSelectionTitle": { - "description": "Title text displayed on the character selection page" + "description": "Title text displayed on the character selection dialog" }, - "characterSelectionSubtitle": "There’s no wrong answer", + "characterSelectionSubtitle": "There’s no wrong choice!", "@characterSelectionSubtitle": { - "description": "Text displayed on the selecting character dialog at game beginning" + "description": "Subtitle text displayed on the character selection dialog" }, "gameOver": "Game Over", "@gameOver": { @@ -124,4 +124,4 @@ "@footerGoogleIOText": { "description": "Text shown on the footer which mentions Google I/O" } -} \ No newline at end of file +} diff --git a/lib/select_character/cubit/character_theme_state.dart b/lib/select_character/cubit/character_theme_state.dart index ffe5667c..a1669f69 100644 --- a/lib/select_character/cubit/character_theme_state.dart +++ b/lib/select_character/cubit/character_theme_state.dart @@ -10,6 +10,14 @@ class CharacterThemeState extends Equatable { final CharacterTheme characterTheme; + bool get isSparkySelected => characterTheme == const SparkyTheme(); + + bool get isDashSelected => characterTheme == const DashTheme(); + + bool get isAndroidSelected => characterTheme == const AndroidTheme(); + + bool get isDinoSelected => characterTheme == const DinoTheme(); + @override List get props => [characterTheme]; } diff --git a/lib/select_character/view/character_selection_page.dart b/lib/select_character/view/character_selection_page.dart index 863722e6..864d1b41 100644 --- a/lib/select_character/view/character_selection_page.dart +++ b/lib/select_character/view/character_selection_page.dart @@ -1,139 +1,160 @@ -// ignore_for_file: public_member_api_docs - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/select_character/cubit/character_theme_cubit.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_ui/pinball_ui.dart'; +/// Inflates [CharacterSelectionDialog] using [showDialog]. +Future showCharacterSelectionDialog(BuildContext context) { + return showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const CharacterSelectionDialog(), + ); +} + +/// {@template character_selection_dialog} +/// Dialog used to select the playing character of the game. +/// {@endtemplate character_selection_dialog} class CharacterSelectionDialog extends StatelessWidget { + /// {@macro character_selection_dialog} const CharacterSelectionDialog({Key? key}) : super(key: key); - static Route route() { - return MaterialPageRoute( - builder: (_) => const CharacterSelectionDialog(), - ); - } - @override Widget build(BuildContext context) { - return BlocProvider( - create: (_) => CharacterThemeCubit(), - child: const CharacterSelectionView(), + final l10n = context.l10n; + return PinballDialog( + title: l10n.characterSelectionTitle, + subtitle: l10n.characterSelectionSubtitle, + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Row( + children: [ + Expanded(child: _CharacterPreview()), + Expanded(child: _CharacterGrid()), + ], + ), + ), + const SizedBox(height: 8), + const _SelectCharacterButton(), + ], + ), + ), ); } } -class CharacterSelectionView extends StatelessWidget { - const CharacterSelectionView({Key? key}) : super(key: key); +class _SelectCharacterButton extends StatelessWidget { + const _SelectCharacterButton({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final l10n = context.l10n; + return PinballButton( + onTap: () async { + Navigator.of(context).pop(); + await showHowToPlayDialog(context); + }, + text: l10n.select, + ); + } +} - return PixelatedDecoration( - header: Text( - l10n.characterSelectionTitle, - style: Theme.of(context).textTheme.headline3, - ), - body: SingleChildScrollView( - child: Column( +class _CharacterGrid extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const _CharacterSelectionGridView(), - const SizedBox(height: 20), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - // TODO(arturplaczek): remove after merge StarBlocListener - final height = MediaQuery.of(context).size.height * 0.5; - showDialog( - context: context, - builder: (_) => Center( - child: SizedBox( - height: height, - width: height * 1.4, - child: HowToPlayDialog(), - ), - ), - ); - }, - child: Text(l10n.start), + Column( + children: [ + _Character( + key: const Key('sparky_character_selection'), + character: const SparkyTheme(), + isSelected: state.isSparkySelected, + ), + const SizedBox(height: 6), + _Character( + key: const Key('android_character_selection'), + character: const AndroidTheme(), + isSelected: state.isAndroidSelected, + ), + ], + ), + const SizedBox(width: 6), + Column( + children: [ + _Character( + key: const Key('dash_character_selection'), + character: const DashTheme(), + isSelected: state.isDashSelected, + ), + const SizedBox(height: 6), + _Character( + key: const Key('dino_character_selection'), + character: const DinoTheme(), + isSelected: state.isDinoSelected, + ), + ], ), ], - ), - ), + ); + }, ); } } -class _CharacterSelectionGridView extends StatelessWidget { - const _CharacterSelectionGridView({Key? key}) : super(key: key); - +class _CharacterPreview extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(20), - child: GridView.count( - shrinkWrap: true, - crossAxisCount: 2, - mainAxisSpacing: 20, - crossAxisSpacing: 20, - children: const [ - CharacterImageButton( - DashTheme(), - key: Key('characterSelectionPage_dashButton'), - ), - CharacterImageButton( - SparkyTheme(), - key: Key('characterSelectionPage_sparkyButton'), - ), - CharacterImageButton( - AndroidTheme(), - key: Key('characterSelectionPage_androidButton'), - ), - CharacterImageButton( - DinoTheme(), - key: Key('characterSelectionPage_dinoButton'), - ), - ], - ), + return BlocBuilder( + builder: (context, state) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + state.characterTheme.name, + style: Theme.of(context).textTheme.headline2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + const SizedBox(height: 10), + Expanded(child: state.characterTheme.icon.image()), + ], + ); + }, ); } } -// TODO(allisonryan0002): remove visibility when adding final UI. -@visibleForTesting -class CharacterImageButton extends StatelessWidget { - const CharacterImageButton( - this.characterTheme, { +class _Character extends StatelessWidget { + const _Character({ Key? key, + required this.character, + required this.isSelected, }) : super(key: key); - final CharacterTheme characterTheme; + final CharacterTheme character; + final bool isSelected; @override Widget build(BuildContext context) { - final currentCharacterTheme = - context.select( - (cubit) => cubit.state.characterTheme, - ); - - return GestureDetector( - onTap: () => - context.read().characterSelected(characterTheme), - child: DecoratedBox( - decoration: BoxDecoration( - color: (currentCharacterTheme == characterTheme) - ? Colors.blue.withOpacity(0.5) - : null, - borderRadius: BorderRadius.circular(6), - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: characterTheme.icon.image(), + return Expanded( + child: Opacity( + opacity: isSelected ? 1 : 0.3, + child: InkWell( + onTap: () => + context.read().characterSelected(character), + child: character.icon.image(fit: BoxFit.contain), ), ), ); diff --git a/lib/start_game/widgets/how_to_play_dialog.dart b/lib/start_game/widgets/how_to_play_dialog.dart index 500a4288..5d8c68c3 100644 --- a/lib/start_game/widgets/how_to_play_dialog.dart +++ b/lib/start_game/widgets/how_to_play_dialog.dart @@ -50,6 +50,22 @@ extension on Control { } } +Future showHowToPlayDialog(BuildContext context) { + final height = MediaQuery.of(context).size.height * 0.5; + return showDialog( + context: context, + builder: (context) { + return Center( + child: SizedBox( + height: height, + width: height * 1.4, + child: HowToPlayDialog(), + ), + ); + }, + ); +} + class HowToPlayDialog extends StatefulWidget { HowToPlayDialog({ Key? key, @@ -70,7 +86,7 @@ class _HowToPlayDialogState extends State { super.initState(); closeTimer = Timer(const Duration(seconds: 3), () { if (mounted) { - Navigator.of(context).maybePop(); + Navigator.of(context).pop(); } }); } @@ -121,7 +137,10 @@ class _MobileLaunchControls extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; - final textStyle = Theme.of(context).textTheme.headline3; + final textStyle = Theme.of(context) + .textTheme + .headline3! + .copyWith(color: PinballColors.white); return Column( children: [ Text( @@ -137,7 +156,7 @@ class _MobileLaunchControls extends StatelessWidget { ), TextSpan( text: l10n.launch, - style: textStyle?.copyWith(color: PinballColors.blue), + style: textStyle.copyWith(color: PinballColors.blue), ), ], ), @@ -153,7 +172,10 @@ class _MobileFlipperControls extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; - final textStyle = Theme.of(context).textTheme.headline3; + final textStyle = Theme.of(context) + .textTheme + .headline3! + .copyWith(color: PinballColors.white); return Column( children: [ Text( @@ -169,7 +191,7 @@ class _MobileFlipperControls extends StatelessWidget { ), TextSpan( text: l10n.flip, - style: textStyle?.copyWith(color: PinballColors.orange), + style: textStyle.copyWith(color: PinballColors.orange), ), ], ), diff --git a/packages/pinball_ui/assets/images/button/pinball_button.png b/packages/pinball_ui/assets/images/button/pinball_button.png new file mode 100644 index 00000000..62373b85 Binary files /dev/null and b/packages/pinball_ui/assets/images/button/pinball_button.png differ diff --git a/packages/pinball_ui/lib/gen/assets.gen.dart b/packages/pinball_ui/lib/gen/assets.gen.dart index 41c45ece..8972e8e0 100644 --- a/packages/pinball_ui/lib/gen/assets.gen.dart +++ b/packages/pinball_ui/lib/gen/assets.gen.dart @@ -10,9 +10,18 @@ import 'package:flutter/widgets.dart'; class $AssetsImagesGen { const $AssetsImagesGen(); + $AssetsImagesButtonGen get button => const $AssetsImagesButtonGen(); $AssetsImagesDialogGen get dialog => const $AssetsImagesDialogGen(); } +class $AssetsImagesButtonGen { + const $AssetsImagesButtonGen(); + + /// File path: assets/images/button/pinball_button.png + AssetGenImage get pinballButton => + const AssetGenImage('assets/images/button/pinball_button.png'); +} + class $AssetsImagesDialogGen { const $AssetsImagesDialogGen(); diff --git a/packages/pinball_ui/lib/pinball_ui.dart b/packages/pinball_ui/lib/pinball_ui.dart index 332286ed..eacb5681 100644 --- a/packages/pinball_ui/lib/pinball_ui.dart +++ b/packages/pinball_ui/lib/pinball_ui.dart @@ -6,3 +6,4 @@ export 'package:url_launcher_platform_interface/url_launcher_platform_interface. export 'src/dialog/dialog.dart'; export 'src/external_links/external_links.dart'; export 'src/theme/theme.dart'; +export 'src/widgets/widgets.dart'; diff --git a/packages/pinball_ui/lib/src/dialog/dialog.dart b/packages/pinball_ui/lib/src/dialog/dialog.dart index 7a224272..4731eb5f 100644 --- a/packages/pinball_ui/lib/src/dialog/dialog.dart +++ b/packages/pinball_ui/lib/src/dialog/dialog.dart @@ -1 +1,2 @@ +export 'pinball_dialog.dart'; export 'pixelated_decoration.dart'; diff --git a/packages/pinball_ui/lib/src/dialog/pinball_dialog.dart b/packages/pinball_ui/lib/src/dialog/pinball_dialog.dart new file mode 100644 index 00000000..8ff04754 --- /dev/null +++ b/packages/pinball_ui/lib/src/dialog/pinball_dialog.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +/// {@template pinball_dialog} +/// Pinball-themed dialog. +/// {@endtemplate} +class PinballDialog extends StatelessWidget { + /// {@macro pinball_dialog} + const PinballDialog({ + Key? key, + required this.title, + required this.child, + this.subtitle, + }) : super(key: key); + + /// Title shown in the dialog. + final String title; + + /// Optional subtitle shown below the [title]. + final String? subtitle; + + /// Body of the dialog. + final Widget child; + + @override + Widget build(BuildContext context) { + final height = MediaQuery.of(context).size.height * 0.5; + return Center( + child: SizedBox( + height: height, + width: height * 1.4, + child: PixelatedDecoration( + header: subtitle != null + ? _TitleAndSubtitle(title: title, subtitle: subtitle!) + : _Title(title: title), + body: child, + ), + ), + ); + } +} + +class _Title extends StatelessWidget { + const _Title({Key? key, required this.title}) : super(key: key); + + final String title; + + @override + Widget build(BuildContext context) => Text( + title, + style: Theme.of(context).textTheme.headline3!.copyWith( + fontWeight: FontWeight.bold, + color: PinballColors.darkBlue, + ), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ); +} + +class _TitleAndSubtitle extends StatelessWidget { + const _TitleAndSubtitle({ + Key? key, + required this.title, + required this.subtitle, + }) : super(key: key); + + final String title; + final String subtitle; + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _Title(title: title), + Text( + subtitle, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: textTheme.headline3!.copyWith(fontWeight: FontWeight.normal), + ), + ], + ); + } +} diff --git a/packages/pinball_ui/lib/src/theme/pinball_text_style.dart b/packages/pinball_ui/lib/src/theme/pinball_text_style.dart index 378078fa..5e0a7fa2 100644 --- a/packages/pinball_ui/lib/src/theme/pinball_text_style.dart +++ b/packages/pinball_ui/lib/src/theme/pinball_text_style.dart @@ -19,10 +19,11 @@ abstract class PinballTextStyle { fontSize: 24, package: _fontPackage, fontFamily: _primaryFontFamily, + color: PinballColors.white, ); static const headline3 = TextStyle( - color: PinballColors.white, + color: PinballColors.darkBlue, fontSize: 20, package: _fontPackage, fontFamily: _primaryFontFamily, diff --git a/packages/pinball_ui/lib/src/widgets/pinball_button.dart b/packages/pinball_ui/lib/src/widgets/pinball_button.dart new file mode 100644 index 00000000..585a8d54 --- /dev/null +++ b/packages/pinball_ui/lib/src/widgets/pinball_button.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:pinball_ui/gen/gen.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +/// {@template pinball_button} +/// Pinball-themed button with pixel art. +/// {@endtemplate} +class PinballButton extends StatelessWidget { + /// {@macro pinball_button} + const PinballButton({ + Key? key, + required this.text, + required this.onTap, + }) : super(key: key); + + /// Text of the button. + final String text; + + /// Tap callback. + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.images.button.pinballButton.keyName), + ), + ), + child: Center( + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: Text( + text, + style: Theme.of(context) + .textTheme + .headline3! + .copyWith(color: PinballColors.white), + ), + ), + ), + ), + ); + } +} diff --git a/packages/pinball_ui/lib/src/widgets/widgets.dart b/packages/pinball_ui/lib/src/widgets/widgets.dart new file mode 100644 index 00000000..34d952b6 --- /dev/null +++ b/packages/pinball_ui/lib/src/widgets/widgets.dart @@ -0,0 +1 @@ +export 'pinball_button.dart'; diff --git a/packages/pinball_ui/pubspec.yaml b/packages/pinball_ui/pubspec.yaml index a89f7a67..747b1b8f 100644 --- a/packages/pinball_ui/pubspec.yaml +++ b/packages/pinball_ui/pubspec.yaml @@ -23,6 +23,7 @@ flutter: generate: true assets: - assets/images/dialog/ + - assets/images/button/ fonts: - family: PixeloidSans fonts: diff --git a/packages/pinball_ui/test/src/dialog/pinball_dialog_test.dart b/packages/pinball_ui/test/src/dialog/pinball_dialog_test.dart new file mode 100644 index 00000000..85e2c4da --- /dev/null +++ b/packages/pinball_ui/test/src/dialog/pinball_dialog_test.dart @@ -0,0 +1,44 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +void main() { + group('PinballDialog', () { + group('with title only', () { + testWidgets('renders the title and the body', (tester) async { + tester.binding.window.physicalSizeTestValue = const Size(2000, 4000); + addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + await tester.pumpWidget( + const MaterialApp( + home: PinballDialog(title: 'title', child: Placeholder()), + ), + ); + expect(find.byType(PixelatedDecoration), findsOneWidget); + expect(find.text('title'), findsOneWidget); + expect(find.byType(Placeholder), findsOneWidget); + }); + }); + + group('with title and subtitle', () { + testWidgets('renders the title, subtitle and the body', (tester) async { + tester.binding.window.physicalSizeTestValue = const Size(2000, 4000); + addTearDown(tester.binding.window.clearPhysicalSizeTestValue); + await tester.pumpWidget( + MaterialApp( + home: PinballDialog( + title: 'title', + subtitle: 'sub', + child: Icon(Icons.home), + ), + ), + ); + expect(find.byType(PixelatedDecoration), findsOneWidget); + expect(find.text('title'), findsOneWidget); + expect(find.text('sub'), findsOneWidget); + expect(find.byType(Icon), findsOneWidget); + }); + }); + }); +} diff --git a/packages/pinball_ui/test/src/theme/pinball_text_style_test.dart b/packages/pinball_ui/test/src/theme/pinball_text_style_test.dart index 60b382f3..2af092b2 100644 --- a/packages/pinball_ui/test/src/theme/pinball_text_style_test.dart +++ b/packages/pinball_ui/test/src/theme/pinball_text_style_test.dart @@ -14,10 +14,10 @@ void main() { expect(style.fontSize, 24); }); - test('headline3 has fontSize 20 and white color', () { + test('headline3 has fontSize 20 and dark blue color', () { const style = PinballTextStyle.headline3; expect(style.fontSize, 20); - expect(style.color, PinballColors.white); + expect(style.color, PinballColors.darkBlue); }); test('headline4 has fontSize 16 and white color', () { diff --git a/packages/pinball_ui/test/src/widgets/pinball_button_test.dart b/packages/pinball_ui/test/src/widgets/pinball_button_test.dart new file mode 100644 index 00000000..064fbb6a --- /dev/null +++ b/packages/pinball_ui/test/src/widgets/pinball_button_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_ui/pinball_ui.dart'; + +void main() { + group('PinballButton', () { + testWidgets('renders the given text and responds to taps', (tester) async { + var wasTapped = false; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: PinballButton( + text: 'test', + onTap: () { + wasTapped = true; + }, + ), + ), + ), + ), + ); + await tester.tap(find.text('test')); + expect(wasTapped, isTrue); + }); + }); +} diff --git a/test/game/view/widgets/play_button_overlay_test.dart b/test/game/view/widgets/play_button_overlay_test.dart index 0345978d..2229f4b5 100644 --- a/test/game/view/widgets/play_button_overlay_test.dart +++ b/test/game/view/widgets/play_button_overlay_test.dart @@ -1,3 +1,4 @@ +import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; @@ -9,37 +10,46 @@ void main() { group('PlayButtonOverlay', () { late PinballGame game; late GameFlowController gameFlowController; + late CharacterThemeCubit characterThemeCubit; setUp(() { game = MockPinballGame(); gameFlowController = MockGameFlowController(); - + characterThemeCubit = MockCharacterThemeCubit(); + whenListen( + characterThemeCubit, + const Stream.empty(), + initialState: const CharacterThemeState.initial(), + ); + when(() => characterThemeCubit.state) + .thenReturn(const CharacterThemeState.initial()); when(() => game.gameFlowController).thenReturn(gameFlowController); when(gameFlowController.start).thenAnswer((_) {}); }); testWidgets('renders correctly', (tester) async { await tester.pumpApp(PlayButtonOverlay(game: game)); - expect(find.text('Play'), findsOneWidget); }); - testWidgets('calls gameFlowController.start when taped', (tester) async { - await tester.pumpApp(PlayButtonOverlay(game: game)); - + testWidgets('calls gameFlowController.start when tapped', (tester) async { + 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', (tester) async { - await tester.pumpApp(PlayButtonOverlay(game: game)); - + await tester.pumpApp( + PlayButtonOverlay(game: game), + characterThemeCubit: characterThemeCubit, + ); await tester.tap(find.text('Play')); - await tester.pump(); - + await tester.pumpAndSettle(); expect(find.byType(CharacterSelectionDialog), findsOneWidget); }); }); diff --git a/test/select_character/view/character_selection_page_test.dart b/test/select_character/view/character_selection_page_test.dart index dc5d70ea..9a94fdbd 100644 --- a/test/select_character/view/character_selection_page_test.dart +++ b/test/select_character/view/character_selection_page_test.dart @@ -1,14 +1,11 @@ -// ignore_for_file: prefer_const_constructors - -import 'dart:async'; - import 'package:bloc_test/bloc_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockingjay/mockingjay.dart'; +import 'package:mocktail/mocktail.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_ui/pinball_ui.dart'; import '../../helpers/helpers.dart'; @@ -22,141 +19,54 @@ void main() { const Stream.empty(), initialState: const CharacterThemeState.initial(), ); + when(() => characterThemeCubit.state) + .thenReturn(const CharacterThemeState.initial()); }); - group('CharacterSelectionPage', () { - testWidgets('renders CharacterSelectionView', (tester) async { - await tester.pumpApp( - CharacterSelectionDialog(), - characterThemeCubit: characterThemeCubit, - ); - expect(find.byType(CharacterSelectionView), findsOneWidget); - }); - - testWidgets('route returns a valid navigation route', (tester) async { - await tester.pumpApp( - Scaffold( - body: Builder( + group('CharacterSelectionDialog', () { + group('showCharacterSelectionDialog', () { + testWidgets('inflates the dialog', (tester) async { + await tester.pumpApp( + Builder( builder: (context) { - return ElevatedButton( - onPressed: () { - Navigator.of(context) - .push(CharacterSelectionDialog.route()); - }, - child: Text('Tap me'), + return TextButton( + onPressed: () => showCharacterSelectionDialog(context), + child: const Text('test'), ); }, ), - ), - characterThemeCubit: characterThemeCubit, - ); - - await tester.tap(find.text('Tap me')); - await tester.pumpAndSettle(); - - expect(find.byType(CharacterSelectionDialog), findsOneWidget); + characterThemeCubit: characterThemeCubit, + ); + await tester.tap(find.text('test')); + await tester.pumpAndSettle(); + expect(find.byType(CharacterSelectionDialog), findsOneWidget); + }); }); - }); - group('CharacterSelectionView', () { - testWidgets('renders correctly', (tester) async { - const titleText = 'Choose your character!'; + testWidgets('selecting a new character calls characterSelected on cubit', + (tester) async { await tester.pumpApp( - CharacterSelectionView(), + const CharacterSelectionDialog(), characterThemeCubit: characterThemeCubit, ); - - expect(find.text(titleText), findsOneWidget); - expect(find.byType(CharacterImageButton), findsNWidgets(4)); - expect(find.byType(TextButton), findsOneWidget); + await tester.tap(find.byKey(const Key('sparky_character_selection'))); + await tester.pumpAndSettle(); + verify( + () => characterThemeCubit.characterSelected(const SparkyTheme()), + ).called(1); }); - testWidgets('calls characterSelected when a character image is tapped', - (tester) async { - const sparkyButtonKey = Key('characterSelectionPage_sparkyButton'); - + testWidgets( + 'tapping the select button dismisses the character ' + 'dialog and shows the how to play dialog', (tester) async { await tester.pumpApp( - CharacterSelectionView(), + const CharacterSelectionDialog(), characterThemeCubit: characterThemeCubit, ); - - await tester.tap(find.byKey(sparkyButtonKey)); - - verify(() => characterThemeCubit.characterSelected(SparkyTheme())) - .called(1); - }); - - group('HowToPlayDialog', () { - testWidgets( - 'is displayed for 3 seconds when start is tapped', - (tester) async { - await tester.pumpApp( - Scaffold( - body: Builder( - builder: (context) { - return ElevatedButton( - onPressed: () { - Navigator.of(context) - .push(CharacterSelectionDialog.route()); - }, - child: Text('Tap me'), - ); - }, - ), - ), - characterThemeCubit: characterThemeCubit, - ); - await tester.tap(find.text('Tap me')); - await tester.pumpAndSettle(); - await tester.ensureVisible(find.byType(TextButton)); - await tester.tap(find.byType(TextButton)); - await tester.pumpAndSettle(); - expect(find.byType(HowToPlayDialog), findsOneWidget); - await tester.pump(Duration(seconds: 3)); - await tester.pumpAndSettle(); - expect(find.byType(HowToPlayDialog), findsNothing); - }, - ); - - testWidgets( - 'can be dismissed manually before 3 seconds have passed', - (tester) async { - await tester.pumpApp( - Scaffold( - body: Builder( - builder: (context) { - return ElevatedButton( - onPressed: () { - Navigator.of(context) - .push(CharacterSelectionDialog.route()); - }, - child: Text('Tap me'), - ); - }, - ), - ), - characterThemeCubit: characterThemeCubit, - ); - await tester.tap(find.text('Tap me')); - await tester.pumpAndSettle(); - await tester.ensureVisible(find.byType(TextButton)); - await tester.tap(find.byType(TextButton)); - await tester.pumpAndSettle(); - expect(find.byType(HowToPlayDialog), findsOneWidget); - await tester.tapAt(Offset(1, 1)); - await tester.pumpAndSettle(); - expect(find.byType(HowToPlayDialog), findsNothing); - }, - ); + await tester.tap(find.byType(PinballButton)); + await tester.pumpAndSettle(); + expect(find.byType(CharacterSelectionDialog), findsNothing); + expect(find.byType(HowToPlayDialog), findsOneWidget); }); }); - - testWidgets('CharacterImageButton renders correctly', (tester) async { - await tester.pumpApp( - CharacterImageButton(DashTheme()), - characterThemeCubit: characterThemeCubit, - ); - - expect(find.byType(Image), findsOneWidget); - }); } diff --git a/test/start_game/widgets/how_to_play_dialog_test.dart b/test/start_game/widgets/how_to_play_dialog_test.dart index 1de4c2ad..cf8b4971 100644 --- a/test/start_game/widgets/how_to_play_dialog_test.dart +++ b/test/start_game/widgets/how_to_play_dialog_test.dart @@ -55,14 +55,31 @@ void main() { expect(find.text(l10n.tapAndHoldRocket), findsOneWidget); expect(find.text(l10n.tapLeftRightScreen), findsOneWidget); }); - }); - group('KeyButton', () { - testWidgets('renders correctly', (tester) async { + testWidgets('disappears after 3 seconds', (tester) async { await tester.pumpApp( - KeyButton(control: Control.a), + Builder( + builder: (context) { + return TextButton( + onPressed: () => showHowToPlayDialog(context), + child: const Text('test'), + ); + }, + ), ); + expect(find.byType(HowToPlayDialog), findsNothing); + await tester.tap(find.text('test')); + await tester.pumpAndSettle(); + expect(find.byType(HowToPlayDialog), findsOneWidget); + await tester.pump(const Duration(seconds: 4)); + await tester.pumpAndSettle(); + expect(find.byType(HowToPlayDialog), findsNothing); + }); + }); + group('KeyButton', () { + testWidgets('renders correctly', (tester) async { + await tester.pumpApp(KeyButton(control: Control.a)); expect(find.text('A'), findsOneWidget); }); });