Merge branch 'main' into feat/adjusting-component-rendering

pull/282/head
Alejandro Santiago 3 years ago committed by GitHub
commit 3dc66e5309
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -0,0 +1 @@
export 'widgets/widgets.dart';

@ -8,7 +8,6 @@ import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart';
@visibleForTesting
enum Control {
left,
right,
@ -50,6 +49,14 @@ extension on Control {
}
}
Future<void> showHowToPlayDialog(BuildContext context) {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => HowToPlayDialog(),
);
}
class HowToPlayDialog extends StatefulWidget {
HowToPlayDialog({
Key? key,
@ -70,7 +77,7 @@ class _HowToPlayDialogState extends State<HowToPlayDialog> {
super.initState();
closeTimer = Timer(const Duration(seconds: 3), () {
if (mounted) {
Navigator.of(context).maybePop();
Navigator.of(context).pop();
}
});
}
@ -84,9 +91,11 @@ class _HowToPlayDialogState extends State<HowToPlayDialog> {
@override
Widget build(BuildContext context) {
final isMobile = widget.platformHelper.isMobile;
return PixelatedDecoration(
header: const _HowToPlayHeader(),
body: isMobile ? const _MobileBody() : const _DesktopBody(),
final l10n = context.l10n;
return PinballDialog(
title: l10n.howToPlay,
subtitle: l10n.tipsForFlips,
child: isMobile ? const _MobileBody() : const _DesktopBody(),
);
}
}
@ -121,23 +130,20 @@ class _MobileLaunchControls extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final textStyle = Theme.of(context).textTheme.headline3;
final headline3 = Theme.of(context)
.textTheme
.headline3!
.copyWith(color: PinballColors.white);
return Column(
children: [
Text(
l10n.tapAndHoldRocket,
style: textStyle,
),
Text(l10n.tapAndHoldRocket, style: headline3),
Text.rich(
TextSpan(
children: [
TextSpan(
text: '${l10n.to} ',
style: textStyle,
),
TextSpan(text: '${l10n.to} ', style: headline3),
TextSpan(
text: l10n.launch,
style: textStyle?.copyWith(color: PinballColors.blue),
style: headline3.copyWith(color: PinballColors.blue),
),
],
),
@ -153,23 +159,20 @@ class _MobileFlipperControls extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final textStyle = Theme.of(context).textTheme.headline3;
final headline3 = Theme.of(context)
.textTheme
.headline3!
.copyWith(color: PinballColors.white);
return Column(
children: [
Text(
l10n.tapLeftRightScreen,
style: textStyle,
),
Text(l10n.tapLeftRightScreen, style: headline3),
Text.rich(
TextSpan(
children: [
TextSpan(
text: '${l10n.to} ',
style: textStyle,
),
TextSpan(text: '${l10n.to} ', style: headline3),
TextSpan(
text: l10n.flip,
style: textStyle?.copyWith(color: PinballColors.orange),
style: headline3.copyWith(color: PinballColors.orange),
),
],
),
@ -184,55 +187,23 @@ class _DesktopBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
const spacing = SizedBox(height: 16);
return ListView(
children: const [
spacing,
SizedBox(height: 16),
_DesktopLaunchControls(),
spacing,
SizedBox(height: 16),
_DesktopFlipperControls(),
],
);
}
}
class _HowToPlayHeader extends StatelessWidget {
const _HowToPlayHeader({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final textStyle = Theme.of(context).textTheme.headline3?.copyWith(
color: PinballColors.darkBlue,
);
return FittedBox(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
l10n.howToPlay,
style: textStyle?.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
l10n.tipsForFlips,
style: textStyle,
),
],
),
);
}
}
class _DesktopLaunchControls extends StatelessWidget {
const _DesktopLaunchControls({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
const spacing = SizedBox(width: 10);
return Column(
children: [
Text(
@ -242,11 +213,11 @@ class _DesktopLaunchControls extends StatelessWidget {
const SizedBox(height: 10),
Wrap(
children: const [
KeyButton(control: Control.down),
spacing,
KeyButton(control: Control.space),
spacing,
KeyButton(control: Control.s),
_KeyButton(control: Control.down),
SizedBox(width: 10),
_KeyButton(control: Control.space),
SizedBox(width: 10),
_KeyButton(control: Control.s),
],
)
],
@ -260,8 +231,6 @@ class _DesktopFlipperControls extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
const rowSpacing = SizedBox(width: 20);
return Column(
children: [
Text(
@ -275,17 +244,17 @@ class _DesktopFlipperControls extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: const [
KeyButton(control: Control.left),
rowSpacing,
KeyButton(control: Control.right),
_KeyButton(control: Control.left),
SizedBox(width: 20),
_KeyButton(control: Control.right),
],
),
const SizedBox(height: 8),
Wrap(
children: const [
KeyButton(control: Control.a),
rowSpacing,
KeyButton(control: Control.d),
_KeyButton(control: Control.a),
SizedBox(width: 20),
_KeyButton(control: Control.d),
],
)
],
@ -295,29 +264,24 @@ class _DesktopFlipperControls extends StatelessWidget {
}
}
@visibleForTesting
class KeyButton extends StatelessWidget {
const KeyButton({
Key? key,
required Control control,
}) : _control = control,
super(key: key);
class _KeyButton extends StatelessWidget {
const _KeyButton({Key? key, required this.control}) : super(key: key);
final Control _control;
final Control control;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final textStyle =
_control.isArrow ? textTheme.headline1 : textTheme.headline3;
control.isArrow ? textTheme.headline1 : textTheme.headline3;
const height = 60.0;
final width = _control.isSpace ? height * 2.83 : height;
final width = control.isSpace ? height * 2.83 : height;
return DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image: AssetImage(
_control.isSpace
control.isSpace
? Assets.images.components.space.keyName
: Assets.images.components.key.keyName,
),
@ -328,9 +292,9 @@ class KeyButton extends StatelessWidget {
height: height,
child: Center(
child: RotatedBox(
quarterTurns: _control.isDown ? 1 : 0,
quarterTurns: control.isDown ? 1 : 0,
child: Text(
_control.getCharacter(context),
control.getCharacter(context),
style: textStyle?.copyWith(color: PinballColors.white),
),
),

@ -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": "Theres no wrong answer",
"characterSelectionSubtitle": "Theres 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"
}
}
}

@ -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<Object> get props => [characterTheme];
}

@ -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/how_to_play/how_to_play.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<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 {
/// {@macro character_selection_dialog}
const CharacterSelectionDialog({Key? key}) : super(key: key);
static Route route() {
return MaterialPageRoute<void>(
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<CharacterThemeCubit, CharacterThemeState>(
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<void>(
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<CharacterThemeCubit, CharacterThemeState>(
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<CharacterThemeCubit, CharacterTheme>(
(cubit) => cubit.state.characterTheme,
);
return GestureDetector(
onTap: () =>
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(),
return Expanded(
child: Opacity(
opacity: isSelected ? 1 : 0.3,
child: InkWell(
onTap: () =>
context.read<CharacterThemeCubit>().characterSelected(character),
child: character.icon.image(fit: BoxFit.contain),
),
),
);

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

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

@ -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';

@ -1 +1,2 @@
export 'pinball_dialog.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,
package: _fontPackage,
fontFamily: _primaryFontFamily,
color: PinballColors.white,
);
static const headline3 = TextStyle(
color: PinballColors.white,
color: PinballColors.darkBlue,
fontSize: 20,
package: _fontPackage,
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
assets:
- assets/images/dialog/
- assets/images/button/
fonts:
- family: PixeloidSans
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);
});
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', () {

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

@ -1,13 +1,11 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/how_to_play/how_to_play.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:platform_helper/platform_helper.dart';
import '../../helpers/helpers.dart';
import '../helpers/helpers.dart';
class MockPlatformHelper extends Mock implements PlatformHelper {}
@ -17,7 +15,7 @@ void main() {
late PlatformHelper platformHelper;
setUp(() async {
l10n = await AppLocalizations.delegate.load(Locale('en'));
l10n = await AppLocalizations.delegate.load(const Locale('en'));
platformHelper = MockPlatformHelper();
});
@ -40,7 +38,7 @@ void main() {
expect(find.text(l10n.tipsForFlips), findsOneWidget);
expect(find.text(l10n.launchControls), findsOneWidget);
expect(find.text(l10n.flipperControls), findsOneWidget);
expect(find.byType(KeyButton), findsNWidgets(7));
expect(find.byType(RotatedBox), findsNWidgets(7)); // controls
});
testWidgets('displays content for mobile', (tester) async {
@ -55,15 +53,25 @@ 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.text('A'), findsOneWidget);
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);
});
});
}

@ -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/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_theme/pinball_theme.dart';
import 'package:pinball_ui/pinball_ui.dart';
import '../../helpers/helpers.dart';
@ -22,141 +19,54 @@ void main() {
const Stream<CharacterThemeState>.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<void>(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<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);
},
);
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);
});
}

Loading…
Cancel
Save