feat: character selection for desktop and mobile (#280)

pull/281/head
Jorge Coca 3 years ago committed by GitHub
parent 97193c63f3
commit 3fbb2564cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,24 +22,9 @@ class PlayButtonOverlay extends StatelessWidget {
return Center( return Center(
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () async {
_game.gameFlowController.start(); _game.gameFlowController.start();
showDialog<void>( await showCharacterSelectionDialog(context);
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(),
),
);
},
);
}, },
child: Text(l10n.play), child: Text(l10n.play),
), ),

@ -46,19 +46,19 @@
}, },
"select": "Select", "select": "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": "Space",
"@space": { "@space": {
"description": "Text displayed on space control button" "description": "Text displayed on space control button"
}, },
"characterSelectionTitle": "Choose your character!", "characterSelectionTitle": "Select a Character",
"@characterSelectionTitle": { "@characterSelectionTitle": {
"description": "Title text displayed on the character selection page" "description": "Title text displayed on the character selection dialog"
}, },
"characterSelectionSubtitle": "Theres no wrong answer", "characterSelectionSubtitle": "Theres no wrong choice!",
"@characterSelectionSubtitle": { "@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": "Game Over",
"@gameOver": { "@gameOver": {
@ -124,4 +124,4 @@
"@footerGoogleIOText": { "@footerGoogleIOText": {
"description": "Text shown on the footer which mentions Google I/O" "description": "Text shown on the footer which mentions Google I/O"
} }
} }

@ -10,6 +10,14 @@ class CharacterThemeState extends Equatable {
final CharacterTheme characterTheme; 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 @override
List<Object> get props => [characterTheme]; List<Object> get props => [characterTheme];
} }

@ -1,139 +1,160 @@
// ignore_for_file: public_member_api_docs
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/l10n/l10n.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/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.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}
/// Dialog used to select the playing character of the game.
/// {@endtemplate character_selection_dialog}
class CharacterSelectionDialog extends StatelessWidget { class CharacterSelectionDialog extends StatelessWidget {
/// {@macro character_selection_dialog}
const CharacterSelectionDialog({Key? key}) : super(key: key); const CharacterSelectionDialog({Key? key}) : super(key: key);
static Route route() {
return MaterialPageRoute<void>(
builder: (_) => const CharacterSelectionDialog(),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( final l10n = context.l10n;
create: (_) => CharacterThemeCubit(), return PinballDialog(
child: const CharacterSelectionView(), 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 { class _SelectCharacterButton extends StatelessWidget {
const CharacterSelectionView({Key? key}) : super(key: key); const _SelectCharacterButton({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
return PinballButton(
onTap: () async {
Navigator.of(context).pop();
await showHowToPlayDialog(context);
},
text: l10n.select,
);
}
}
return PixelatedDecoration( class _CharacterGrid extends StatelessWidget {
header: Text( @override
l10n.characterSelectionTitle, Widget build(BuildContext context) {
style: Theme.of(context).textTheme.headline3, return BlocBuilder<CharacterThemeCubit, CharacterThemeState>(
), builder: (context, state) {
body: SingleChildScrollView( return Row(
child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const _CharacterSelectionGridView(), Column(
const SizedBox(height: 20), children: [
TextButton( _Character(
onPressed: () { key: const Key('sparky_character_selection'),
Navigator.of(context).pop(); character: const SparkyTheme(),
// TODO(arturplaczek): remove after merge StarBlocListener isSelected: state.isSparkySelected,
final height = MediaQuery.of(context).size.height * 0.5; ),
showDialog<void>( const SizedBox(height: 6),
context: context, _Character(
builder: (_) => Center( key: const Key('android_character_selection'),
child: SizedBox( character: const AndroidTheme(),
height: height, isSelected: state.isAndroidSelected,
width: height * 1.4, ),
child: HowToPlayDialog(), ],
), ),
), const SizedBox(width: 6),
); Column(
}, children: [
child: Text(l10n.start), _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 { class _CharacterPreview extends StatelessWidget {
const _CharacterSelectionGridView({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return BlocBuilder<CharacterThemeCubit, CharacterThemeState>(
padding: const EdgeInsets.all(20), builder: (context, state) {
child: GridView.count( return Column(
shrinkWrap: true, mainAxisAlignment: MainAxisAlignment.center,
crossAxisCount: 2, children: [
mainAxisSpacing: 20, Text(
crossAxisSpacing: 20, state.characterTheme.name,
children: const [ style: Theme.of(context).textTheme.headline2,
CharacterImageButton( overflow: TextOverflow.ellipsis,
DashTheme(), textAlign: TextAlign.center,
key: Key('characterSelectionPage_dashButton'), ),
), const SizedBox(height: 10),
CharacterImageButton( Expanded(child: state.characterTheme.icon.image()),
SparkyTheme(), ],
key: Key('characterSelectionPage_sparkyButton'), );
), },
CharacterImageButton(
AndroidTheme(),
key: Key('characterSelectionPage_androidButton'),
),
CharacterImageButton(
DinoTheme(),
key: Key('characterSelectionPage_dinoButton'),
),
],
),
); );
} }
} }
// TODO(allisonryan0002): remove visibility when adding final UI. class _Character extends StatelessWidget {
@visibleForTesting const _Character({
class CharacterImageButton extends StatelessWidget {
const CharacterImageButton(
this.characterTheme, {
Key? key, Key? key,
required this.character,
required this.isSelected,
}) : super(key: key); }) : super(key: key);
final CharacterTheme characterTheme; final CharacterTheme character;
final bool isSelected;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final currentCharacterTheme = return Expanded(
context.select<CharacterThemeCubit, CharacterTheme>( child: Opacity(
(cubit) => cubit.state.characterTheme, opacity: isSelected ? 1 : 0.3,
); child: InkWell(
onTap: () =>
return GestureDetector( context.read<CharacterThemeCubit>().characterSelected(character),
onTap: () => child: character.icon.image(fit: BoxFit.contain),
context.read<CharacterThemeCubit>().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(),
), ),
), ),
); );

@ -50,6 +50,22 @@ extension on Control {
} }
} }
Future<void> showHowToPlayDialog(BuildContext context) {
final height = MediaQuery.of(context).size.height * 0.5;
return showDialog<void>(
context: context,
builder: (context) {
return Center(
child: SizedBox(
height: height,
width: height * 1.4,
child: HowToPlayDialog(),
),
);
},
);
}
class HowToPlayDialog extends StatefulWidget { class HowToPlayDialog extends StatefulWidget {
HowToPlayDialog({ HowToPlayDialog({
Key? key, Key? key,
@ -70,7 +86,7 @@ class _HowToPlayDialogState extends State<HowToPlayDialog> {
super.initState(); super.initState();
closeTimer = Timer(const Duration(seconds: 3), () { closeTimer = Timer(const Duration(seconds: 3), () {
if (mounted) { if (mounted) {
Navigator.of(context).maybePop(); Navigator.of(context).pop();
} }
}); });
} }
@ -121,7 +137,10 @@ class _MobileLaunchControls extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
final textStyle = Theme.of(context).textTheme.headline3; final textStyle = Theme.of(context)
.textTheme
.headline3!
.copyWith(color: PinballColors.white);
return Column( return Column(
children: [ children: [
Text( Text(
@ -137,7 +156,7 @@ class _MobileLaunchControls extends StatelessWidget {
), ),
TextSpan( TextSpan(
text: l10n.launch, text: l10n.launch,
style: textStyle?.copyWith(color: PinballColors.blue), style: textStyle.copyWith(color: PinballColors.blue),
), ),
], ],
), ),
@ -153,7 +172,10 @@ class _MobileFlipperControls extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
final textStyle = Theme.of(context).textTheme.headline3; final textStyle = Theme.of(context)
.textTheme
.headline3!
.copyWith(color: PinballColors.white);
return Column( return Column(
children: [ children: [
Text( Text(
@ -169,7 +191,7 @@ class _MobileFlipperControls extends StatelessWidget {
), ),
TextSpan( TextSpan(
text: l10n.flip, text: l10n.flip,
style: textStyle?.copyWith(color: PinballColors.orange), style: textStyle.copyWith(color: PinballColors.orange),
), ),
], ],
), ),

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -10,9 +10,18 @@ import 'package:flutter/widgets.dart';
class $AssetsImagesGen { class $AssetsImagesGen {
const $AssetsImagesGen(); const $AssetsImagesGen();
$AssetsImagesButtonGen get button => const $AssetsImagesButtonGen();
$AssetsImagesDialogGen get dialog => const $AssetsImagesDialogGen(); $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 { class $AssetsImagesDialogGen {
const $AssetsImagesDialogGen(); const $AssetsImagesDialogGen();

@ -6,3 +6,4 @@ export 'package:url_launcher_platform_interface/url_launcher_platform_interface.
export 'src/dialog/dialog.dart'; export 'src/dialog/dialog.dart';
export 'src/external_links/external_links.dart'; export 'src/external_links/external_links.dart';
export 'src/theme/theme.dart'; export 'src/theme/theme.dart';
export 'src/widgets/widgets.dart';

@ -1 +1,2 @@
export 'pinball_dialog.dart';
export 'pixelated_decoration.dart'; export 'pixelated_decoration.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),
),
],
);
}
}

@ -19,10 +19,11 @@ abstract class PinballTextStyle {
fontSize: 24, fontSize: 24,
package: _fontPackage, package: _fontPackage,
fontFamily: _primaryFontFamily, fontFamily: _primaryFontFamily,
color: PinballColors.white,
); );
static const headline3 = TextStyle( static const headline3 = TextStyle(
color: PinballColors.white, color: PinballColors.darkBlue,
fontSize: 20, fontSize: 20,
package: _fontPackage, package: _fontPackage,
fontFamily: _primaryFontFamily, fontFamily: _primaryFontFamily,

@ -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),
),
),
),
),
);
}
}

@ -0,0 +1 @@
export 'pinball_button.dart';

@ -23,6 +23,7 @@ flutter:
generate: true generate: true
assets: assets:
- assets/images/dialog/ - assets/images/dialog/
- assets/images/button/
fonts: fonts:
- family: PixeloidSans - family: PixeloidSans
fonts: fonts:

@ -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);
});
});
});
}

@ -14,10 +14,10 @@ void main() {
expect(style.fontSize, 24); 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; const style = PinballTextStyle.headline3;
expect(style.fontSize, 20); expect(style.fontSize, 20);
expect(style.color, PinballColors.white); expect(style.color, PinballColors.darkBlue);
}); });
test('headline4 has fontSize 16 and white color', () { test('headline4 has fontSize 16 and white color', () {

@ -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);
});
});
}

@ -1,3 +1,4 @@
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';
@ -9,37 +10,46 @@ void main() {
group('PlayButtonOverlay', () { group('PlayButtonOverlay', () {
late PinballGame game; late PinballGame game;
late GameFlowController gameFlowController; late GameFlowController gameFlowController;
late CharacterThemeCubit characterThemeCubit;
setUp(() { setUp(() {
game = MockPinballGame(); game = MockPinballGame();
gameFlowController = MockGameFlowController(); gameFlowController = MockGameFlowController();
characterThemeCubit = MockCharacterThemeCubit();
whenListen(
characterThemeCubit,
const Stream<CharacterThemeState>.empty(),
initialState: const CharacterThemeState.initial(),
);
when(() => characterThemeCubit.state)
.thenReturn(const CharacterThemeState.initial());
when(() => game.gameFlowController).thenReturn(gameFlowController); when(() => game.gameFlowController).thenReturn(gameFlowController);
when(gameFlowController.start).thenAnswer((_) {}); when(gameFlowController.start).thenAnswer((_) {});
}); });
testWidgets('renders correctly', (tester) async { testWidgets('renders correctly', (tester) async {
await tester.pumpApp(PlayButtonOverlay(game: game)); await tester.pumpApp(PlayButtonOverlay(game: game));
expect(find.text('Play'), findsOneWidget); expect(find.text('Play'), findsOneWidget);
}); });
testWidgets('calls gameFlowController.start when taped', (tester) async { testWidgets('calls gameFlowController.start 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.tap(find.text('Play'));
await tester.pump(); await tester.pump();
verify(gameFlowController.start).called(1); verify(gameFlowController.start).called(1);
}); });
testWidgets('displays CharacterSelectionDialog when tapped', testWidgets('displays CharacterSelectionDialog when tapped',
(tester) async { (tester) async {
await tester.pumpApp(PlayButtonOverlay(game: game)); await tester.pumpApp(
PlayButtonOverlay(game: game),
characterThemeCubit: characterThemeCubit,
);
await tester.tap(find.text('Play')); await tester.tap(find.text('Play'));
await tester.pump(); await tester.pumpAndSettle();
expect(find.byType(CharacterSelectionDialog), findsOneWidget); expect(find.byType(CharacterSelectionDialog), findsOneWidget);
}); });
}); });

@ -1,14 +1,11 @@
// ignore_for_file: prefer_const_constructors
import 'dart:async';
import 'package:bloc_test/bloc_test.dart'; 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:mockingjay/mockingjay.dart'; import 'package:mocktail/mocktail.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/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 '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
@ -22,141 +19,54 @@ void main() {
const Stream<CharacterThemeState>.empty(), const Stream<CharacterThemeState>.empty(),
initialState: const CharacterThemeState.initial(), initialState: const CharacterThemeState.initial(),
); );
when(() => characterThemeCubit.state)
.thenReturn(const CharacterThemeState.initial());
}); });
group('CharacterSelectionPage', () { group('CharacterSelectionDialog', () {
testWidgets('renders CharacterSelectionView', (tester) async { group('showCharacterSelectionDialog', () {
await tester.pumpApp( testWidgets('inflates the dialog', (tester) async {
CharacterSelectionDialog(), await tester.pumpApp(
characterThemeCubit: characterThemeCubit, Builder(
);
expect(find.byType(CharacterSelectionView), findsOneWidget);
});
testWidgets('route returns a valid navigation route', (tester) async {
await tester.pumpApp(
Scaffold(
body: Builder(
builder: (context) { builder: (context) {
return ElevatedButton( return TextButton(
onPressed: () { onPressed: () => showCharacterSelectionDialog(context),
Navigator.of(context) child: const Text('test'),
.push<void>(CharacterSelectionDialog.route());
},
child: Text('Tap me'),
); );
}, },
), ),
), characterThemeCubit: characterThemeCubit,
characterThemeCubit: characterThemeCubit, );
); await tester.tap(find.text('test'));
await tester.pumpAndSettle();
await tester.tap(find.text('Tap me')); expect(find.byType(CharacterSelectionDialog), findsOneWidget);
await tester.pumpAndSettle(); });
expect(find.byType(CharacterSelectionDialog), findsOneWidget);
}); });
});
group('CharacterSelectionView', () { testWidgets('selecting a new character calls characterSelected on cubit',
testWidgets('renders correctly', (tester) async { (tester) async {
const titleText = 'Choose your character!';
await tester.pumpApp( await tester.pumpApp(
CharacterSelectionView(), const CharacterSelectionDialog(),
characterThemeCubit: characterThemeCubit, characterThemeCubit: characterThemeCubit,
); );
await tester.tap(find.byKey(const Key('sparky_character_selection')));
expect(find.text(titleText), findsOneWidget); await tester.pumpAndSettle();
expect(find.byType(CharacterImageButton), findsNWidgets(4)); verify(
expect(find.byType(TextButton), findsOneWidget); () => characterThemeCubit.characterSelected(const SparkyTheme()),
).called(1);
}); });
testWidgets('calls characterSelected when a character image is tapped', testWidgets(
(tester) async { 'tapping the select button dismisses the character '
const sparkyButtonKey = Key('characterSelectionPage_sparkyButton'); 'dialog and shows the how to play dialog', (tester) async {
await tester.pumpApp( await tester.pumpApp(
CharacterSelectionView(), const CharacterSelectionDialog(),
characterThemeCubit: characterThemeCubit, characterThemeCubit: characterThemeCubit,
); );
await tester.tap(find.byType(PinballButton));
await tester.tap(find.byKey(sparkyButtonKey)); await tester.pumpAndSettle();
expect(find.byType(CharacterSelectionDialog), findsNothing);
verify(() => characterThemeCubit.characterSelected(SparkyTheme())) expect(find.byType(HowToPlayDialog), findsOneWidget);
.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<void>(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<void>(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);
},
);
}); });
}); });
testWidgets('CharacterImageButton renders correctly', (tester) async {
await tester.pumpApp(
CharacterImageButton(DashTheme()),
characterThemeCubit: characterThemeCubit,
);
expect(find.byType(Image), findsOneWidget);
});
} }

@ -55,14 +55,31 @@ void main() {
expect(find.text(l10n.tapAndHoldRocket), findsOneWidget); expect(find.text(l10n.tapAndHoldRocket), findsOneWidget);
expect(find.text(l10n.tapLeftRightScreen), findsOneWidget); expect(find.text(l10n.tapLeftRightScreen), findsOneWidget);
}); });
});
group('KeyButton', () { testWidgets('disappears after 3 seconds', (tester) async {
testWidgets('renders correctly', (tester) async {
await tester.pumpApp( 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); expect(find.text('A'), findsOneWidget);
}); });
}); });

Loading…
Cancel
Save