diff --git a/.github/workflows/pinball_theme.yaml b/.github/workflows/pinball_theme.yaml index f6fa14aa..83206de5 100644 --- a/.github/workflows/pinball_theme.yaml +++ b/.github/workflows/pinball_theme.yaml @@ -15,4 +15,5 @@ jobs: build: uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1 with: - working_directory: packages/pinball_theme \ No newline at end of file + working_directory: packages/pinball_theme + coverage_excludes: "lib/src/generated/*.dart" \ No newline at end of file diff --git a/lib/game/components/ball.dart b/lib/game/components/ball.dart index cdd1296c..20aa924e 100644 --- a/lib/game/components/ball.dart +++ b/lib/game/components/ball.dart @@ -1,5 +1,4 @@ import 'package:flame/components.dart'; -import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; @@ -7,8 +6,7 @@ import 'package:pinball/game/game.dart'; /// A solid, [BodyType.dynamic] sphere that rolls and bounces along the /// [PinballGame]. /// {@endtemplate} -class Ball extends PositionBodyComponent - with BlocComponent { +class Ball extends PositionBodyComponent { /// {@macro ball} Ball({ required Vector2 position, @@ -27,7 +25,8 @@ class Ball extends PositionBodyComponent Future onLoad() async { await super.onLoad(); final sprite = await gameRef.loadSprite(spritePath); - positionComponent = SpriteComponent(sprite: sprite, size: size); + final tint = gameRef.theme.characterTheme.ballColor.withOpacity(0.5); + positionComponent = SpriteComponent(sprite: sprite, size: size)..tint(tint); } @override diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 1cf2b997..b27e305d 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -5,9 +5,14 @@ import 'package:flame/input.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_theme/pinball_theme.dart'; class PinballGame extends Forge2DGame with FlameBloc, HasKeyboardHandlerComponents { + PinballGame({required this.theme}); + + final PinballTheme theme; + // TODO(erickzanardo): Change to the plumber position late final ballStartingPosition = screenToWorld( Vector2( diff --git a/lib/game/view/pinball_game_page.dart b/lib/game/view/pinball_game_page.dart index a49ff0c1..95997832 100644 --- a/lib/game/view/pinball_game_page.dart +++ b/lib/game/view/pinball_game_page.dart @@ -4,16 +4,19 @@ import 'package:flame/game.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_theme/pinball_theme.dart'; class PinballGamePage extends StatelessWidget { - const PinballGamePage({Key? key}) : super(key: key); + const PinballGamePage({Key? key, required this.theme}) : super(key: key); - static Route route() { + final PinballTheme theme; + + static Route route({required PinballTheme theme}) { return MaterialPageRoute( builder: (_) { return BlocProvider( create: (_) => GameBloc(), - child: const PinballGamePage(), + child: PinballGamePage(theme: theme), ); }, ); @@ -21,12 +24,14 @@ class PinballGamePage extends StatelessWidget { @override Widget build(BuildContext context) { - return const PinballGameView(); + return PinballGameView(theme: theme); } } class PinballGameView extends StatefulWidget { - const PinballGameView({Key? key}) : super(key: key); + const PinballGameView({Key? key, required this.theme}) : super(key: key); + + final PinballTheme theme; @override State createState() => _PinballGameViewState(); @@ -42,7 +47,7 @@ class _PinballGameViewState extends State { // TODO(erickzanardo): Revisit this when we start to have more assets // this could expose a Stream (maybe even a cubit?) so we could show the // the loading progress with some fancy widgets. - _game = PinballGame()..preLoadAssets(); + _game = PinballGame(theme: widget.theme)..preLoadAssets(); } @override diff --git a/lib/game/view/view.dart b/lib/game/view/view.dart index 26b700d3..53d3813a 100644 --- a/lib/game/view/view.dart +++ b/lib/game/view/view.dart @@ -1,3 +1,2 @@ -export 'game_hud.dart'; export 'pinball_game_page.dart'; export 'widgets/widgets.dart'; diff --git a/lib/game/view/game_hud.dart b/lib/game/view/widgets/game_hud.dart similarity index 100% rename from lib/game/view/game_hud.dart rename to lib/game/view/widgets/game_hud.dart diff --git a/lib/game/view/widgets/widgets.dart b/lib/game/view/widgets/widgets.dart index 9c457b1c..aa473c64 100644 --- a/lib/game/view/widgets/widgets.dart +++ b/lib/game/view/widgets/widgets.dart @@ -1 +1,2 @@ +export 'game_hud.dart'; export 'game_over_dialog.dart'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index fa732ac7..f12ccf7d 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,4 +1,15 @@ { "@@locale": "en", - "play": "Play" + "play": "Play", + "@play": { + "description": "Text displayed on the landing page play button" + }, + "start": "Start", + "@start": { + "description": "Text displayed on the character selection page start button" + }, + "characterSelectionTitle": "Choose your character!", + "@characterSelectionTitle": { + "description": "Title text displayed on the character selection page" + } } \ No newline at end of file diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index 405ef51f..597a39d8 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -1,4 +1,15 @@ { "@@locale": "es", - "play": "Jugar" + "play": "Jugar", + "@play": { + "description": "Text displayed on the landing page play button" + }, + "start": "Comienzo", + "@start": { + "description": "Text displayed on the character selection page start button" + }, + "characterSelectionTitle": "¡Elige a tu personaje!", + "@characterSelectionTitle": { + "description": "Title text displayed on the character selection page" + } } \ No newline at end of file diff --git a/lib/landing/view/landing_page.dart b/lib/landing/view/landing_page.dart index c705f084..38951da6 100644 --- a/lib/landing/view/landing_page.dart +++ b/lib/landing/view/landing_page.dart @@ -1,8 +1,8 @@ // ignore_for_file: public_member_api_docs import 'package:flutter/material.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/theme/theme.dart'; class LandingPage extends StatelessWidget { const LandingPage({Key? key}) : super(key: key); @@ -10,11 +10,12 @@ class LandingPage extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; + return Scaffold( body: Center( child: TextButton( onPressed: () => - Navigator.of(context).push(PinballGamePage.route()), + Navigator.of(context).push(CharacterSelectionPage.route()), child: Text(l10n.play), ), ), diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index fcf5d9ee..f6318400 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -1 +1,2 @@ export 'cubit/theme_cubit.dart'; +export 'view/view.dart'; diff --git a/lib/theme/view/character_selection_page.dart b/lib/theme/view/character_selection_page.dart new file mode 100644 index 00000000..9569760a --- /dev/null +++ b/lib/theme/view/character_selection_page.dart @@ -0,0 +1,130 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/theme/theme.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +class CharacterSelectionPage extends StatelessWidget { + const CharacterSelectionPage({Key? key}) : super(key: key); + + static Route route() { + return MaterialPageRoute( + builder: (_) => const CharacterSelectionPage(), + ); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => ThemeCubit(), + child: const CharacterSelectionView(), + ); + } +} + +class CharacterSelectionView extends StatelessWidget { + const CharacterSelectionView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + return Scaffold( + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 80), + Text( + l10n.characterSelectionTitle, + style: Theme.of(context).textTheme.headline3, + ), + const SizedBox(height: 80), + const _CharacterSelectionGridView(), + const SizedBox(height: 20), + TextButton( + onPressed: () => Navigator.of(context).push( + PinballGamePage.route( + theme: context.read().state.theme, + ), + ), + child: Text(l10n.start), + ), + ], + ), + ), + ); + } +} + +class _CharacterSelectionGridView extends StatelessWidget { + const _CharacterSelectionGridView({Key? key}) : super(key: key); + + @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'), + ), + ], + ), + ); + } +} + +// TODO(allisonryan0002): remove visibility when adding final UI. +@visibleForTesting +class CharacterImageButton extends StatelessWidget { + const CharacterImageButton( + this.characterTheme, { + Key? key, + }) : super(key: key); + + final CharacterTheme characterTheme; + + @override + Widget build(BuildContext context) { + final currentCharacterTheme = context.select( + (cubit) => cubit.state.theme.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.characterAsset.image(), + ), + ), + ); + } +} diff --git a/lib/theme/view/view.dart b/lib/theme/view/view.dart new file mode 100644 index 00000000..1af489b5 --- /dev/null +++ b/lib/theme/view/view.dart @@ -0,0 +1 @@ +export 'character_selection_page.dart'; diff --git a/packages/pinball_theme/analysis_options.yaml b/packages/pinball_theme/analysis_options.yaml index 3742fc3d..5e587410 100644 --- a/packages/pinball_theme/analysis_options.yaml +++ b/packages/pinball_theme/analysis_options.yaml @@ -1 +1,4 @@ -include: package:very_good_analysis/analysis_options.2.4.0.yaml \ No newline at end of file +include: package:very_good_analysis/analysis_options.2.4.0.yaml +analyzer: + exclude: + - lib/**/*.gen.dart \ No newline at end of file diff --git a/packages/pinball_theme/assets/images/android.png b/packages/pinball_theme/assets/images/android.png new file mode 100644 index 00000000..23f677a5 Binary files /dev/null and b/packages/pinball_theme/assets/images/android.png differ diff --git a/packages/pinball_theme/assets/images/dash.png b/packages/pinball_theme/assets/images/dash.png new file mode 100644 index 00000000..43c074a3 Binary files /dev/null and b/packages/pinball_theme/assets/images/dash.png differ diff --git a/packages/pinball_theme/assets/images/dino.png b/packages/pinball_theme/assets/images/dino.png new file mode 100644 index 00000000..9e5dbf86 Binary files /dev/null and b/packages/pinball_theme/assets/images/dino.png differ diff --git a/packages/pinball_theme/assets/images/sparky.png b/packages/pinball_theme/assets/images/sparky.png new file mode 100644 index 00000000..8e484f26 Binary files /dev/null and b/packages/pinball_theme/assets/images/sparky.png differ diff --git a/packages/pinball_theme/lib/pinball_theme.dart b/packages/pinball_theme/lib/pinball_theme.dart index 0206fa7b..139a70dc 100644 --- a/packages/pinball_theme/lib/pinball_theme.dart +++ b/packages/pinball_theme/lib/pinball_theme.dart @@ -1,4 +1,5 @@ library pinball_theme; +export 'src/generated/generated.dart'; export 'src/pinball_theme.dart'; export 'src/themes/themes.dart'; diff --git a/packages/pinball_theme/lib/src/generated/assets.gen.dart b/packages/pinball_theme/lib/src/generated/assets.gen.dart new file mode 100644 index 00000000..9dc5c029 --- /dev/null +++ b/packages/pinball_theme/lib/src/generated/assets.gen.dart @@ -0,0 +1,71 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +import 'package:flutter/widgets.dart'; + +class $AssetsImagesGen { + const $AssetsImagesGen(); + + AssetGenImage get android => const AssetGenImage('assets/images/android.png'); + AssetGenImage get dash => const AssetGenImage('assets/images/dash.png'); + AssetGenImage get dino => const AssetGenImage('assets/images/dino.png'); + AssetGenImage get sparky => const AssetGenImage('assets/images/sparky.png'); +} + +class Assets { + Assets._(); + + static const $AssetsImagesGen images = $AssetsImagesGen(); +} + +class AssetGenImage extends AssetImage { + const AssetGenImage(String assetName) + : super(assetName, package: 'pinball_theme'); + + Image image({ + Key? key, + ImageFrameBuilder? frameBuilder, + ImageLoadingBuilder? loadingBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? width, + double? height, + Color? color, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + bool isAntiAlias = false, + FilterQuality filterQuality = FilterQuality.low, + }) { + return Image( + key: key, + image: this, + frameBuilder: frameBuilder, + loadingBuilder: loadingBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + width: width, + height: height, + color: color, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + filterQuality: filterQuality, + ); + } + + String get path => assetName; +} diff --git a/packages/pinball_theme/lib/src/generated/generated.dart b/packages/pinball_theme/lib/src/generated/generated.dart new file mode 100644 index 00000000..e7ad4c54 --- /dev/null +++ b/packages/pinball_theme/lib/src/generated/generated.dart @@ -0,0 +1 @@ +export 'assets.gen.dart'; diff --git a/packages/pinball_theme/lib/src/themes/android_theme.dart b/packages/pinball_theme/lib/src/themes/android_theme.dart index 59c16bd9..f6605f52 100644 --- a/packages/pinball_theme/lib/src/themes/android_theme.dart +++ b/packages/pinball_theme/lib/src/themes/android_theme.dart @@ -10,4 +10,7 @@ class AndroidTheme extends CharacterTheme { @override Color get ballColor => Colors.green; + + @override + AssetGenImage get characterAsset => Assets.images.android; } diff --git a/packages/pinball_theme/lib/src/themes/character_theme.dart b/packages/pinball_theme/lib/src/themes/character_theme.dart index 8f81486a..9478f954 100644 --- a/packages/pinball_theme/lib/src/themes/character_theme.dart +++ b/packages/pinball_theme/lib/src/themes/character_theme.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:pinball_theme/pinball_theme.dart'; /// {@template character_theme} /// Base class for creating character themes. @@ -14,6 +15,9 @@ abstract class CharacterTheme extends Equatable { /// Ball color for this theme. Color get ballColor; + /// Asset for the theme character. + AssetGenImage get characterAsset; + @override List get props => [ballColor]; } diff --git a/packages/pinball_theme/lib/src/themes/dash_theme.dart b/packages/pinball_theme/lib/src/themes/dash_theme.dart index e4875a11..1b5b357e 100644 --- a/packages/pinball_theme/lib/src/themes/dash_theme.dart +++ b/packages/pinball_theme/lib/src/themes/dash_theme.dart @@ -10,4 +10,7 @@ class DashTheme extends CharacterTheme { @override Color get ballColor => Colors.blue; + + @override + AssetGenImage get characterAsset => Assets.images.dash; } diff --git a/packages/pinball_theme/lib/src/themes/dino_theme.dart b/packages/pinball_theme/lib/src/themes/dino_theme.dart index 07776771..564cbea0 100644 --- a/packages/pinball_theme/lib/src/themes/dino_theme.dart +++ b/packages/pinball_theme/lib/src/themes/dino_theme.dart @@ -10,4 +10,7 @@ class DinoTheme extends CharacterTheme { @override Color get ballColor => Colors.grey; + + @override + AssetGenImage get characterAsset => Assets.images.dino; } diff --git a/packages/pinball_theme/lib/src/themes/sparky_theme.dart b/packages/pinball_theme/lib/src/themes/sparky_theme.dart index 5264bad6..b4181a8c 100644 --- a/packages/pinball_theme/lib/src/themes/sparky_theme.dart +++ b/packages/pinball_theme/lib/src/themes/sparky_theme.dart @@ -10,4 +10,7 @@ class SparkyTheme extends CharacterTheme { @override Color get ballColor => Colors.orange; + + @override + AssetGenImage get characterAsset => Assets.images.sparky; } diff --git a/packages/pinball_theme/pubspec.yaml b/packages/pinball_theme/pubspec.yaml index e9b3f215..7d745422 100644 --- a/packages/pinball_theme/pubspec.yaml +++ b/packages/pinball_theme/pubspec.yaml @@ -14,4 +14,16 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - very_good_analysis: ^2.4.0 \ No newline at end of file + very_good_analysis: ^2.4.0 + +flutter: + uses-material-design: true + generate: true + assets: + - assets/images/ + +flutter_gen: + assets: + package_parameter_enabled: true + output: lib/src/generated/ + line_length: 80 \ No newline at end of file diff --git a/packages/pinball_theme/test/src/themes/android_theme_test.dart b/packages/pinball_theme/test/src/themes/android_theme_test.dart index a6148042..24186c35 100644 --- a/packages/pinball_theme/test/src/themes/android_theme_test.dart +++ b/packages/pinball_theme/test/src/themes/android_theme_test.dart @@ -17,5 +17,9 @@ void main() { test('ballColor is correct', () { expect(AndroidTheme().ballColor, equals(Colors.green)); }); + + test('characterAsset is correct', () { + expect(AndroidTheme().characterAsset, equals(Assets.images.android)); + }); }); } diff --git a/packages/pinball_theme/test/src/themes/dash_theme_test.dart b/packages/pinball_theme/test/src/themes/dash_theme_test.dart index 0d5c8293..2fb429e0 100644 --- a/packages/pinball_theme/test/src/themes/dash_theme_test.dart +++ b/packages/pinball_theme/test/src/themes/dash_theme_test.dart @@ -17,5 +17,9 @@ void main() { test('ballColor is correct', () { expect(DashTheme().ballColor, equals(Colors.blue)); }); + + test('characterAsset is correct', () { + expect(DashTheme().characterAsset, equals(Assets.images.dash)); + }); }); } diff --git a/packages/pinball_theme/test/src/themes/dino_theme_test.dart b/packages/pinball_theme/test/src/themes/dino_theme_test.dart index 6efd8cbd..673cccf6 100644 --- a/packages/pinball_theme/test/src/themes/dino_theme_test.dart +++ b/packages/pinball_theme/test/src/themes/dino_theme_test.dart @@ -17,5 +17,9 @@ void main() { test('ballColor is correct', () { expect(DinoTheme().ballColor, equals(Colors.grey)); }); + + test('characterAsset is correct', () { + expect(DinoTheme().characterAsset, equals(Assets.images.dino)); + }); }); } diff --git a/packages/pinball_theme/test/src/themes/sparky_theme_test.dart b/packages/pinball_theme/test/src/themes/sparky_theme_test.dart index 513ca219..d0d96566 100644 --- a/packages/pinball_theme/test/src/themes/sparky_theme_test.dart +++ b/packages/pinball_theme/test/src/themes/sparky_theme_test.dart @@ -17,5 +17,9 @@ void main() { test('ballColor is correct', () { expect(SparkyTheme().ballColor, equals(Colors.orange)); }); + + test('characterAsset is correct', () { + expect(SparkyTheme().characterAsset, equals(Assets.images.sparky)); + }); }); } diff --git a/test/game/components/anchor_test.dart b/test/game/components/anchor_test.dart index a5aa7d2d..e0cfd645 100644 --- a/test/game/components/anchor_test.dart +++ b/test/game/components/anchor_test.dart @@ -5,11 +5,13 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball/game/game.dart'; +import '../../helpers/helpers.dart'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Anchor', () { - final flameTester = FlameTester(PinballGame.new); + final flameTester = FlameTester(PinballGameX.initial); flameTester.test( 'loads correctly', diff --git a/test/game/components/ball_test.dart b/test/game/components/ball_test.dart index bd2cbcfc..98e0d3fb 100644 --- a/test/game/components/ball_test.dart +++ b/test/game/components/ball_test.dart @@ -13,7 +13,7 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Ball', () { - final flameTester = FlameTester(PinballGame.new); + final flameTester = FlameTester(PinballGameX.initial); flameTester.test( 'loads correctly', @@ -85,10 +85,9 @@ void main() { }); group('resetting a ball', () { - late GameBloc gameBloc; + final gameBloc = MockGameBloc(); setUp(() { - gameBloc = MockGameBloc(); whenListen( gameBloc, const Stream.empty(), @@ -96,11 +95,7 @@ void main() { ); }); - final tester = flameBlocTester( - gameBlocBuilder: () { - return gameBloc; - }, - ); + final tester = flameBlocTester(gameBloc: gameBloc); tester.widgetTest( 'adds BallLost to GameBloc', diff --git a/test/game/components/flipper_test.dart b/test/game/components/flipper_test.dart index b9894d9a..6537ee54 100644 --- a/test/game/components/flipper_test.dart +++ b/test/game/components/flipper_test.dart @@ -12,7 +12,7 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(PinballGame.new); + final flameTester = FlameTester(PinballGameX.initial); group( 'Flipper', () { diff --git a/test/game/components/pathway_test.dart b/test/game/components/pathway_test.dart index 036309b1..482f6d57 100644 --- a/test/game/components/pathway_test.dart +++ b/test/game/components/pathway_test.dart @@ -6,9 +6,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball/game/game.dart'; +import '../../helpers/helpers.dart'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(PinballGame.new); + final flameTester = FlameTester(PinballGameX.initial); group('Pathway', () { const width = 50.0; diff --git a/test/game/components/plunger_test.dart b/test/game/components/plunger_test.dart index 835bbc65..17bc275e 100644 --- a/test/game/components/plunger_test.dart +++ b/test/game/components/plunger_test.dart @@ -10,7 +10,7 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(PinballGame.new); + final flameTester = FlameTester(PinballGameX.initial); group('Plunger', () { flameTester.test( @@ -127,12 +127,12 @@ void main() { }); group('PlungerAnchorPrismaticJointDef', () { - late GameBloc gameBloc; late Plunger plunger; late Anchor anchor; + final gameBloc = MockGameBloc(); + setUp(() { - gameBloc = MockGameBloc(); whenListen( gameBloc, const Stream.empty(), @@ -142,11 +142,7 @@ void main() { anchor = Anchor(position: Vector2(0, -1)); }); - final flameTester = flameBlocTester( - gameBlocBuilder: () { - return gameBloc; - }, - ); + final flameTester = flameBlocTester(gameBloc: gameBloc); flameTester.test( 'throws AssertionError ' diff --git a/test/game/components/wall_test.dart b/test/game/components/wall_test.dart index 7f3c1c9c..c19f20e8 100644 --- a/test/game/components/wall_test.dart +++ b/test/game/components/wall_test.dart @@ -32,7 +32,7 @@ void main() { }, ); }); - final flameTester = FlameTester(PinballGame.new); + final flameTester = FlameTester(PinballGameX.initial); flameTester.test( 'loads correctly', diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index 4dc93b7f..34493cc7 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -5,10 +5,12 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball/game/game.dart'; +import '../helpers/helpers.dart'; + void main() { group('PinballGame', () { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(PinballGame.new); + final flameTester = FlameTester(PinballGameX.initial); // TODO(alestiago): test if [PinballGame] registers // [BallScorePointsCallback] once the following issue is resolved: diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index 746dc2c7..fcfbe149 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -3,10 +3,13 @@ import 'package:flame/game.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_theme/pinball_theme.dart'; import '../../helpers/helpers.dart'; void main() { + const theme = PinballTheme(characterTheme: DashTheme()); + group('PinballGamePage', () { testWidgets('renders PinballGameView', (tester) async { final gameBloc = MockGameBloc(); @@ -16,7 +19,10 @@ void main() { initialState: const GameState.initial(), ); - await tester.pumpApp(const PinballGamePage(), gameBloc: gameBloc); + await tester.pumpApp( + const PinballGamePage(theme: theme), + gameBloc: gameBloc, + ); expect(find.byType(PinballGameView), findsOneWidget); }); @@ -27,7 +33,8 @@ void main() { builder: (context) { return ElevatedButton( onPressed: () { - Navigator.of(context).push(PinballGamePage.route()); + Navigator.of(context) + .push(PinballGamePage.route(theme: theme)); }, child: const Text('Tap me'), ); @@ -56,7 +63,10 @@ void main() { initialState: const GameState.initial(), ); - await tester.pumpApp(const PinballGameView(), gameBloc: gameBloc); + await tester.pumpApp( + const PinballGameView(theme: theme), + gameBloc: gameBloc, + ); expect( find.byWidgetPredicate((w) => w is GameWidget), findsOneWidget, @@ -78,7 +88,10 @@ void main() { initialState: state, ); - await tester.pumpApp(const PinballGameView(), gameBloc: gameBloc); + await tester.pumpApp( + const PinballGameView(theme: theme), + gameBloc: gameBloc, + ); await tester.pump(); expect( diff --git a/test/helpers/builders.dart b/test/helpers/builders.dart index e124052e..8ae3c546 100644 --- a/test/helpers/builders.dart +++ b/test/helpers/builders.dart @@ -1,19 +1,28 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball_theme/pinball_theme.dart'; FlameTester flameBlocTester({ - required GameBloc Function() gameBlocBuilder, + required GameBloc gameBloc, }) { return FlameTester( - PinballGame.new, + PinballGameX.initial, pumpWidget: (gameWidget, tester) async { await tester.pumpWidget( BlocProvider.value( - value: gameBlocBuilder(), + value: gameBloc, child: gameWidget, ), ); }, ); } + +extension PinballGameX on PinballGame { + static PinballGame initial() => PinballGame( + theme: const PinballTheme( + characterTheme: DashTheme(), + ), + ); +} diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index da9fd537..44e78afe 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/game/game.dart'; +import 'package:pinball/theme/theme.dart'; class MockPinballGame extends Mock implements PinballGame {} @@ -16,6 +17,8 @@ class MockContact extends Mock implements Contact {} class MockGameBloc extends Mock implements GameBloc {} +class MockThemeCubit extends Mock implements ThemeCubit {} + class MockRawKeyDownEvent extends Mock implements RawKeyDownEvent { @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { diff --git a/test/helpers/pump_app.dart b/test/helpers/pump_app.dart index 2c1efd9f..e0b953d2 100644 --- a/test/helpers/pump_app.dart +++ b/test/helpers/pump_app.dart @@ -12,6 +12,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockingjay/mockingjay.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball/l10n/l10n.dart'; +import 'package:pinball/theme/theme.dart'; import 'helpers.dart'; @@ -20,17 +21,25 @@ extension PumpApp on WidgetTester { Widget widget, { MockNavigator? navigator, GameBloc? gameBloc, + ThemeCubit? themeCubit, }) { return pumpWidget( - MaterialApp( - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, + MultiBlocProvider( + providers: [ + BlocProvider.value( + value: themeCubit ?? MockThemeCubit(), + ), + BlocProvider.value( + value: gameBloc ?? MockGameBloc(), + ), ], - supportedLocales: AppLocalizations.supportedLocales, - home: BlocProvider.value( - value: gameBloc ?? MockGameBloc(), - child: navigator != null + child: MaterialApp( + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + home: navigator != null ? MockNavigatorProvider(navigator: navigator, child: widget) : widget, ), diff --git a/test/landing/view/landing_page_test.dart b/test/landing/view/landing_page_test.dart index d754864c..ab036f9c 100644 --- a/test/landing/view/landing_page_test.dart +++ b/test/landing/view/landing_page_test.dart @@ -12,7 +12,7 @@ void main() { expect(find.byType(TextButton), findsOneWidget); }); - testWidgets('tapping on TextButton navigates to PinballGamePage', + testWidgets('tapping on TextButton navigates to CharacterSelectionPage', (tester) async { final navigator = MockNavigator(); when(() => navigator.push(any())).thenAnswer((_) async {}); diff --git a/test/theme/view/character_selection_page_test.dart b/test/theme/view/character_selection_page_test.dart new file mode 100644 index 00000000..eeac690f --- /dev/null +++ b/test/theme/view/character_selection_page_test.dart @@ -0,0 +1,110 @@ +// ignore_for_file: prefer_const_constructors + +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:pinball/theme/theme.dart'; +import 'package:pinball_theme/pinball_theme.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + late ThemeCubit themeCubit; + + setUp(() { + themeCubit = MockThemeCubit(); + whenListen( + themeCubit, + const Stream.empty(), + initialState: const ThemeState.initial(), + ); + }); + + group('CharacterSelectionPage', () { + testWidgets('renders CharacterSelectionView', (tester) async { + await tester.pumpApp( + CharacterSelectionPage(), + themeCubit: themeCubit, + ); + expect(find.byType(CharacterSelectionView), findsOneWidget); + }); + + testWidgets('route returns a valid navigation route', (tester) async { + await tester.pumpApp( + Scaffold( + body: Builder( + builder: (context) { + return ElevatedButton( + onPressed: () { + Navigator.of(context) + .push(CharacterSelectionPage.route()); + }, + child: Text('Tap me'), + ); + }, + ), + ), + themeCubit: themeCubit, + ); + + await tester.tap(find.text('Tap me')); + await tester.pumpAndSettle(); + + expect(find.byType(CharacterSelectionPage), findsOneWidget); + }); + }); + + group('CharacterSelectionView', () { + testWidgets('renders correctly', (tester) async { + const titleText = 'Choose your character!'; + await tester.pumpApp( + CharacterSelectionView(), + themeCubit: themeCubit, + ); + + expect(find.text(titleText), findsOneWidget); + expect(find.byType(CharacterImageButton), findsNWidgets(4)); + expect(find.byType(TextButton), findsOneWidget); + }); + + testWidgets('calls characterSelected when a character image is tapped', + (tester) async { + const sparkyButtonKey = Key('characterSelectionPage_sparkyButton'); + + await tester.pumpApp( + CharacterSelectionView(), + themeCubit: themeCubit, + ); + + await tester.tap(find.byKey(sparkyButtonKey)); + + verify(() => themeCubit.characterSelected(SparkyTheme())).called(1); + }); + + testWidgets('navigates to PinballGamePage when start is tapped', + (tester) async { + final navigator = MockNavigator(); + when(() => navigator.push(any())).thenAnswer((_) async {}); + + await tester.pumpApp( + CharacterSelectionView(), + themeCubit: themeCubit, + navigator: navigator, + ); + await tester.ensureVisible(find.byType(TextButton)); + await tester.tap(find.byType(TextButton)); + + verify(() => navigator.push(any())).called(1); + }); + }); + + testWidgets('CharacterImageButton renders correctly', (tester) async { + await tester.pumpApp( + CharacterImageButton(DashTheme()), + themeCubit: themeCubit, + ); + + expect(find.byType(Image), findsOneWidget); + }); +}