feat: add character selection (#20)

* chore: lock file

* feat: character selection page

* fix: ignore generated asset coverage

* chore: add suggestions

* feat: tint ball with theme color

* refactor: decrease theme cubit scope

* chore: minimize changes

* chore: typos and readability

* refactor: use extension for initial pinball game

* fix: tests from merge

* refactor: ignore docs for views

* refactor: revert to ignoring for file

* fix: todo analyzer warning
pull/33/head
Allison Ryan 2 years ago committed by GitHub
parent 07db88d355
commit ffef053678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,4 +15,5 @@ jobs:
build:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
with:
working_directory: packages/pinball_theme
working_directory: packages/pinball_theme
coverage_excludes: "lib/src/generated/*.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<PinballGame, SpriteComponent>
with BlocComponent<GameBloc, GameState> {
class Ball extends PositionBodyComponent<PinballGame, SpriteComponent> {
/// {@macro ball}
Ball({
required Vector2 position,
@ -27,7 +25,8 @@ class Ball extends PositionBodyComponent<PinballGame, SpriteComponent>
Future<void> 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

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

@ -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<void>(
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<PinballGameView> createState() => _PinballGameViewState();
@ -42,7 +47,7 @@ class _PinballGameViewState extends State<PinballGameView> {
// 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

@ -1,3 +1,2 @@
export 'game_hud.dart';
export 'pinball_game_page.dart';
export 'widgets/widgets.dart';

@ -1 +1,2 @@
export 'game_hud.dart';
export 'game_over_dialog.dart';

@ -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"
}
}

@ -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"
}
}

@ -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<void>(PinballGamePage.route()),
Navigator.of(context).push<void>(CharacterSelectionPage.route()),
child: Text(l10n.play),
),
),

@ -1 +1,2 @@
export 'cubit/theme_cubit.dart';
export 'view/view.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<void>(
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<void>(
PinballGamePage.route(
theme: context.read<ThemeCubit>().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<ThemeCubit, CharacterTheme>(
(cubit) => cubit.state.theme.characterTheme,
);
return GestureDetector(
onTap: () => context.read<ThemeCubit>().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(),
),
),
);
}
}

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

@ -1 +1,4 @@
include: package:very_good_analysis/analysis_options.2.4.0.yaml
include: package:very_good_analysis/analysis_options.2.4.0.yaml
analyzer:
exclude:
- lib/**/*.gen.dart

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

@ -1,4 +1,5 @@
library pinball_theme;
export 'src/generated/generated.dart';
export 'src/pinball_theme.dart';
export 'src/themes/themes.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;
}

@ -10,4 +10,7 @@ class AndroidTheme extends CharacterTheme {
@override
Color get ballColor => Colors.green;
@override
AssetGenImage get characterAsset => Assets.images.android;
}

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

@ -10,4 +10,7 @@ class DashTheme extends CharacterTheme {
@override
Color get ballColor => Colors.blue;
@override
AssetGenImage get characterAsset => Assets.images.dash;
}

@ -10,4 +10,7 @@ class DinoTheme extends CharacterTheme {
@override
Color get ballColor => Colors.grey;
@override
AssetGenImage get characterAsset => Assets.images.dino;
}

@ -10,4 +10,7 @@ class SparkyTheme extends CharacterTheme {
@override
Color get ballColor => Colors.orange;
@override
AssetGenImage get characterAsset => Assets.images.sparky;
}

@ -14,4 +14,16 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
very_good_analysis: ^2.4.0
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

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

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

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

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

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

@ -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<GameState>.empty(),
@ -96,11 +95,7 @@ void main() {
);
});
final tester = flameBlocTester(
gameBlocBuilder: () {
return gameBloc;
},
);
final tester = flameBlocTester(gameBloc: gameBloc);
tester.widgetTest(
'adds BallLost to GameBloc',

@ -12,7 +12,7 @@ import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGame.new);
final flameTester = FlameTester(PinballGameX.initial);
group(
'Flipper',
() {

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

@ -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<GameState>.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 '

@ -32,7 +32,7 @@ void main() {
},
);
});
final flameTester = FlameTester(PinballGame.new);
final flameTester = FlameTester(PinballGameX.initial);
flameTester.test(
'loads correctly',

@ -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:

@ -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<void>(PinballGamePage.route());
Navigator.of(context)
.push<void>(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<PinballGame>),
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(

@ -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<PinballGame> flameBlocTester({
required GameBloc Function() gameBlocBuilder,
required GameBloc gameBloc,
}) {
return FlameTester<PinballGame>(
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(),
),
);
}

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

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

@ -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<void>(any())).thenAnswer((_) async {});

@ -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<ThemeState>.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<void>(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<void>(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<void>(any())).called(1);
});
});
testWidgets('CharacterImageButton renders correctly', (tester) async {
await tester.pumpApp(
CharacterImageButton(DashTheme()),
themeCubit: themeCubit,
);
expect(find.byType(Image), findsOneWidget);
});
}
Loading…
Cancel
Save