mirror of https://github.com/flutter/pinball.git
feat: implementing the new game start flow (#160)
* feat: implementing the new game start flow * fix: coverage * fix: lint * fix: lint * Apply suggestions from code review Co-authored-by: Alejandro Santiago <dev@alestiago.com> Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> * feat: PR suggestions * feat: improve test docs * feaT: pr suggestions * feat: adding backboard assets to the pre fetch * feat: pr suggestions * feat: pr suggestions * fix: lint * fix: ci * fix: tests Co-authored-by: Alejandro Santiago <dev@alestiago.com> Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>pull/171/head
parent
31afa08fc1
commit
bfba65823f
@ -0,0 +1,80 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:pinball/flame/flame.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// Adds helpers methods to Flame's [Camera]
|
||||
extension CameraX on Camera {
|
||||
/// Instantly apply the point of focus to the [Camera]
|
||||
void snapToFocus(FocusData data) {
|
||||
followVector2(data.position);
|
||||
zoom = data.zoom;
|
||||
}
|
||||
|
||||
/// Returns a [CameraZoom] that can be added to a [FlameGame]
|
||||
CameraZoom focusToCameraZoom(FocusData data) {
|
||||
final zoom = CameraZoom(value: data.zoom);
|
||||
zoom.completed.then((_) {
|
||||
moveTo(data.position);
|
||||
});
|
||||
return zoom;
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template focus_data}
|
||||
/// Model class that defines a focus point of the camera
|
||||
/// {@endtemplate}
|
||||
class FocusData {
|
||||
/// {@template focus_data}
|
||||
FocusData({
|
||||
required this.zoom,
|
||||
required this.position,
|
||||
});
|
||||
|
||||
/// The amount of zoom
|
||||
final double zoom;
|
||||
|
||||
/// The position of the camera
|
||||
final Vector2 position;
|
||||
}
|
||||
|
||||
/// {@template camera_controller}
|
||||
/// A [Component] that controls its game camera focus
|
||||
/// {@endtemplate}
|
||||
class CameraController extends ComponentController<FlameGame> {
|
||||
/// {@macro camera_controller}
|
||||
CameraController(FlameGame component) : super(component) {
|
||||
final gameZoom = component.size.y / 16;
|
||||
final backboardZoom = component.size.y / 18;
|
||||
|
||||
gameFocus = FocusData(
|
||||
zoom: gameZoom,
|
||||
position: Vector2(0, -7.8),
|
||||
);
|
||||
backboardFocus = FocusData(
|
||||
zoom: backboardZoom,
|
||||
position: Vector2(0, -100.8),
|
||||
);
|
||||
|
||||
// Game starts with the camera focused on the panel
|
||||
component.camera
|
||||
..speed = 100
|
||||
..snapToFocus(backboardFocus);
|
||||
}
|
||||
|
||||
/// Holds the data for the game focus point
|
||||
late final FocusData gameFocus;
|
||||
|
||||
/// Holds the data for the backboard focus point
|
||||
late final FocusData backboardFocus;
|
||||
|
||||
/// Move the camera focus to the game board
|
||||
void focusOnGame() {
|
||||
component.add(component.camera.focusToCameraZoom(gameFocus));
|
||||
}
|
||||
|
||||
/// Move the camera focus to the backboard
|
||||
void focusOnBackboard() {
|
||||
component.add(component.camera.focusToCameraZoom(backboardFocus));
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_bloc/flame_bloc.dart';
|
||||
import 'package:pinball/flame/flame.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template game_flow_controller}
|
||||
/// A [Component] that controls the game over and game restart logic
|
||||
/// {@endtemplate}
|
||||
class GameFlowController extends ComponentController<PinballGame>
|
||||
with BlocComponent<GameBloc, GameState> {
|
||||
/// {@macro game_flow_controller}
|
||||
GameFlowController(PinballGame component) : super(component);
|
||||
|
||||
@override
|
||||
bool listenWhen(GameState? previousState, GameState newState) {
|
||||
return previousState?.isGameOver != newState.isGameOver;
|
||||
}
|
||||
|
||||
@override
|
||||
void onNewState(GameState state) {
|
||||
if (state.isGameOver) {
|
||||
gameOver();
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
/// Puts the game on a game over state
|
||||
void gameOver() {
|
||||
component.firstChild<Backboard>()?.gameOverMode();
|
||||
component.firstChild<CameraController>()?.focusOnBackboard();
|
||||
}
|
||||
|
||||
/// Puts the game on a playing state
|
||||
void start() {
|
||||
component.firstChild<Backboard>()?.waitingMode();
|
||||
component.firstChild<CameraController>()?.focusOnGame();
|
||||
component.overlays.remove(PinballGame.playButtonOverlay);
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball/leaderboard/leaderboard.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart';
|
||||
|
||||
/// {@template game_over_dialog}
|
||||
/// [Dialog] displayed when the [PinballGame] is over.
|
||||
/// {@endtemplate}
|
||||
class GameOverDialog extends StatelessWidget {
|
||||
/// {@macro game_over_dialog}
|
||||
const GameOverDialog({Key? key, required this.score, required this.theme})
|
||||
: super(key: key);
|
||||
|
||||
/// Score achieved by the current user.
|
||||
final int score;
|
||||
|
||||
/// Theme of the current user.
|
||||
final CharacterTheme theme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LeaderboardBloc(
|
||||
context.read<LeaderboardRepository>(),
|
||||
),
|
||||
child: GameOverDialogView(score: score, theme: theme),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// {@template game_over_dialog_view}
|
||||
/// View for showing final score when the game is finished.
|
||||
/// {@endtemplate}
|
||||
@visibleForTesting
|
||||
class GameOverDialogView extends StatefulWidget {
|
||||
/// {@macro game_over_dialog_view}
|
||||
const GameOverDialogView({
|
||||
Key? key,
|
||||
required this.score,
|
||||
required this.theme,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Score achieved by the current user.
|
||||
final int score;
|
||||
|
||||
/// Theme of the current user.
|
||||
final CharacterTheme theme;
|
||||
|
||||
@override
|
||||
State<GameOverDialogView> createState() => _GameOverDialogViewState();
|
||||
}
|
||||
|
||||
class _GameOverDialogViewState extends State<GameOverDialogView> {
|
||||
final playerInitialsInputController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
playerInitialsInputController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
||||
// TODO(ruimiguel): refactor this view once UI design finished.
|
||||
return Dialog(
|
||||
child: SizedBox(
|
||||
width: 200,
|
||||
height: 250,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.gameOver,
|
||||
style: Theme.of(context).textTheme.headline4,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
'${l10n.yourScore} ${widget.score}',
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
TextField(
|
||||
key: const Key('player_initials_text_field'),
|
||||
controller: playerInitialsInputController,
|
||||
textCapitalization: TextCapitalization.characters,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: l10n.enterInitials,
|
||||
),
|
||||
maxLength: 3,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
_GameOverDialogActions(
|
||||
score: widget.score,
|
||||
theme: widget.theme,
|
||||
playerInitialsInputController:
|
||||
playerInitialsInputController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GameOverDialogActions extends StatelessWidget {
|
||||
const _GameOverDialogActions({
|
||||
Key? key,
|
||||
required this.score,
|
||||
required this.theme,
|
||||
required this.playerInitialsInputController,
|
||||
}) : super(key: key);
|
||||
|
||||
final int score;
|
||||
final CharacterTheme theme;
|
||||
final TextEditingController playerInitialsInputController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
||||
return BlocBuilder<LeaderboardBloc, LeaderboardState>(
|
||||
builder: (context, state) {
|
||||
switch (state.status) {
|
||||
case LeaderboardStatus.loading:
|
||||
return TextButton(
|
||||
onPressed: () {
|
||||
context.read<LeaderboardBloc>().add(
|
||||
LeaderboardEntryAdded(
|
||||
entry: LeaderboardEntryData(
|
||||
playerInitials:
|
||||
playerInitialsInputController.text.toUpperCase(),
|
||||
score: score,
|
||||
character: theme.toType,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(l10n.addUser),
|
||||
);
|
||||
case LeaderboardStatus.success:
|
||||
return TextButton(
|
||||
onPressed: () => Navigator.of(context).push<void>(
|
||||
LeaderboardPage.route(theme: theme),
|
||||
),
|
||||
child: Text(l10n.leaderboard),
|
||||
);
|
||||
case LeaderboardStatus.error:
|
||||
return Text(l10n.error);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pinball/game/pinball_game.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
|
||||
/// {@template play_button_overlay}
|
||||
/// [Widget] that renders the button responsible to starting the game
|
||||
/// {@endtemplate}
|
||||
class PlayButtonOverlay extends StatelessWidget {
|
||||
/// {@macro play_button_overlay}
|
||||
const PlayButtonOverlay({
|
||||
Key? key,
|
||||
required PinballGame game,
|
||||
}) : _game = game,
|
||||
super(key: key);
|
||||
|
||||
final PinballGame _game;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: _game.gameFlowController.start,
|
||||
child: Text(l10n.play),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,2 +1,2 @@
|
||||
export 'game_hud.dart';
|
||||
export 'game_over_dialog.dart';
|
||||
export 'play_button_overlay.dart';
|
||||
|
After Width: | Height: | Size: 955 KiB |
After Width: | Height: | Size: 2.4 MiB |
@ -0,0 +1,40 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
/// {@template backboard}
|
||||
/// The [Backboard] of the pinball machine.
|
||||
/// {@endtemplate}
|
||||
class Backboard extends SpriteComponent with HasGameRef {
|
||||
/// {@macro backboard}
|
||||
Backboard({
|
||||
required Vector2 position,
|
||||
}) : super(
|
||||
// TODO(erickzanardo): remove multiply after
|
||||
// https://github.com/flame-engine/flame/pull/1506 is merged
|
||||
position: position..clone().multiply(Vector2(1, -1)),
|
||||
anchor: Anchor.bottomCenter,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await waitingMode();
|
||||
}
|
||||
|
||||
/// Puts the Backboard in waiting mode, where the scoreboard is shown.
|
||||
Future<void> waitingMode() async {
|
||||
final sprite = await gameRef.loadSprite(
|
||||
Assets.images.backboard.backboardScores.keyName,
|
||||
);
|
||||
size = sprite.originalSize / 10;
|
||||
this.sprite = sprite;
|
||||
}
|
||||
|
||||
/// Puts the Backboard in game over mode, where the score input is shown.
|
||||
Future<void> gameOverMode() async {
|
||||
final sprite = await gameRef.loadSprite(
|
||||
Assets.images.backboard.backboardGameOver.keyName,
|
||||
);
|
||||
size = sprite.originalSize / 10;
|
||||
this.sprite = sprite;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
// ignore_for_file: unawaited_futures
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
group('Backboard', () {
|
||||
final tester = FlameTester(TestGame.new);
|
||||
|
||||
group('on waitingMode', () {
|
||||
tester.testGameWidget(
|
||||
'renders correctly',
|
||||
setUp: (game, tester) async {
|
||||
game.camera.zoom = 2;
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
await game.ensureAdd(Backboard(position: Vector2(0, 15)));
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/backboard/waiting.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('on gameOverMode', () {
|
||||
tester.testGameWidget(
|
||||
'renders correctly',
|
||||
setUp: (game, tester) async {
|
||||
game.camera.zoom = 2;
|
||||
game.camera.followVector2(Vector2.zero());
|
||||
final backboard = Backboard(position: Vector2(0, 15));
|
||||
await game.ensureAdd(backboard);
|
||||
|
||||
await backboard.gameOverMode();
|
||||
await game.ready();
|
||||
await tester.pump();
|
||||
},
|
||||
verify: (game, tester) async {
|
||||
await expectLater(
|
||||
find.byGame<TestGame>(),
|
||||
matchesGoldenFile('golden/backboard/game_over.png'),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
After Width: | Height: | Size: 427 KiB |
After Width: | Height: | Size: 894 KiB |
@ -0,0 +1,85 @@
|
||||
// ignore_for_file: cascade_invocations
|
||||
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pinball/game/components/camera_controller.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
void main() {
|
||||
group('CameraController', () {
|
||||
late FlameGame game;
|
||||
late CameraController controller;
|
||||
|
||||
setUp(() async {
|
||||
game = FlameGame()..onGameResize(Vector2(100, 200));
|
||||
|
||||
controller = CameraController(game);
|
||||
await game.ensureAdd(controller);
|
||||
});
|
||||
|
||||
test('loads correctly', () async {
|
||||
expect(game.firstChild<CameraController>(), isNotNull);
|
||||
});
|
||||
|
||||
test('correctly calculates the zooms', () async {
|
||||
expect(controller.gameFocus.zoom.toInt(), equals(12));
|
||||
expect(controller.backboardFocus.zoom.toInt(), equals(11));
|
||||
});
|
||||
|
||||
test('correctly sets the initial zoom and position', () async {
|
||||
expect(game.camera.zoom, equals(controller.backboardFocus.zoom));
|
||||
expect(game.camera.follow, equals(controller.backboardFocus.position));
|
||||
});
|
||||
|
||||
group('focusOnBoard', () {
|
||||
test('changes the zoom', () async {
|
||||
controller.focusOnGame();
|
||||
|
||||
await game.ready();
|
||||
final zoom = game.firstChild<CameraZoom>();
|
||||
expect(zoom, isNotNull);
|
||||
expect(zoom?.value, equals(controller.gameFocus.zoom));
|
||||
});
|
||||
|
||||
test('moves the camera after the zoom is completed', () async {
|
||||
controller.focusOnGame();
|
||||
await game.ready();
|
||||
final cameraZoom = game.firstChild<CameraZoom>()!;
|
||||
final future = cameraZoom.completed;
|
||||
|
||||
game.update(10);
|
||||
game.update(0); // Ensure that the component was removed
|
||||
|
||||
await future;
|
||||
|
||||
expect(game.camera.position, Vector2(-4, -108.8));
|
||||
});
|
||||
});
|
||||
|
||||
group('focusOnBackboard', () {
|
||||
test('changes the zoom', () async {
|
||||
controller.focusOnBackboard();
|
||||
|
||||
await game.ready();
|
||||
final zoom = game.firstChild<CameraZoom>();
|
||||
expect(zoom, isNotNull);
|
||||
expect(zoom?.value, equals(controller.backboardFocus.zoom));
|
||||
});
|
||||
|
||||
test('moves the camera after the zoom is completed', () async {
|
||||
controller.focusOnBackboard();
|
||||
await game.ready();
|
||||
final cameraZoom = game.firstChild<CameraZoom>()!;
|
||||
final future = cameraZoom.completed;
|
||||
|
||||
game.update(10);
|
||||
game.update(0); // Ensure that the component was removed
|
||||
|
||||
await future;
|
||||
|
||||
expect(game.camera.position, Vector2(-4.5, -109.8));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
// ignore_for_file: type_annotate_public_apis, prefer_const_constructors
|
||||
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball_components/pinball_components.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
group('GameFlowController', () {
|
||||
group('listenWhen', () {
|
||||
test('is true when the game over state has changed', () {
|
||||
final state = GameState(
|
||||
score: 10,
|
||||
balls: 0,
|
||||
activatedBonusLetters: const [],
|
||||
bonusHistory: const [],
|
||||
activatedDashNests: const {},
|
||||
);
|
||||
|
||||
final previous = GameState.initial();
|
||||
expect(
|
||||
GameFlowController(MockPinballGame()).listenWhen(previous, state),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('onNewState', () {
|
||||
late PinballGame game;
|
||||
late Backboard backboard;
|
||||
late CameraController cameraController;
|
||||
late GameFlowController gameFlowController;
|
||||
late ActiveOverlaysNotifier overlays;
|
||||
|
||||
setUp(() {
|
||||
game = MockPinballGame();
|
||||
backboard = MockBackboard();
|
||||
cameraController = MockCameraController();
|
||||
gameFlowController = GameFlowController(game);
|
||||
overlays = MockActiveOverlaysNotifier();
|
||||
|
||||
when(backboard.gameOverMode).thenAnswer((_) async {});
|
||||
when(backboard.waitingMode).thenAnswer((_) async {});
|
||||
when(cameraController.focusOnBackboard).thenAnswer((_) async {});
|
||||
when(cameraController.focusOnGame).thenAnswer((_) async {});
|
||||
|
||||
when(() => overlays.remove(any())).thenAnswer((_) => true);
|
||||
|
||||
when(game.firstChild<Backboard>).thenReturn(backboard);
|
||||
when(game.firstChild<CameraController>).thenReturn(cameraController);
|
||||
when(() => game.overlays).thenReturn(overlays);
|
||||
});
|
||||
|
||||
test(
|
||||
'changes the backboard and camera correctly when it is a game over',
|
||||
() {
|
||||
gameFlowController.onNewState(
|
||||
GameState(
|
||||
score: 10,
|
||||
balls: 0,
|
||||
activatedBonusLetters: const [],
|
||||
bonusHistory: const [],
|
||||
activatedDashNests: const {},
|
||||
),
|
||||
);
|
||||
|
||||
verify(backboard.gameOverMode).called(1);
|
||||
verify(cameraController.focusOnBackboard).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'changes the backboard and camera correctly when it is not a game over',
|
||||
() {
|
||||
gameFlowController.onNewState(GameState.initial());
|
||||
|
||||
verify(backboard.waitingMode).called(1);
|
||||
verify(cameraController.focusOnGame).called(1);
|
||||
verify(() => overlays.remove(PinballGame.playButtonOverlay))
|
||||
.called(1);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
group('PlayButtonOverlay', () {
|
||||
late PinballGame game;
|
||||
late GameFlowController gameFlowController;
|
||||
|
||||
setUp(() {
|
||||
game = MockPinballGame();
|
||||
gameFlowController = MockGameFlowController();
|
||||
|
||||
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));
|
||||
|
||||
await tester.tap(find.text('Play'));
|
||||
await tester.pump();
|
||||
|
||||
verify(gameFlowController.start).called(1);
|
||||
});
|
||||
});
|
||||
}
|
@ -1,195 +0,0 @@
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
import 'package:mockingjay/mockingjay.dart';
|
||||
import 'package:pinball/game/game.dart';
|
||||
import 'package:pinball/l10n/l10n.dart';
|
||||
import 'package:pinball/leaderboard/leaderboard.dart';
|
||||
import 'package:pinball_theme/pinball_theme.dart';
|
||||
|
||||
import '../../../helpers/helpers.dart';
|
||||
|
||||
void main() {
|
||||
group('GameOverDialog', () {
|
||||
testWidgets('renders GameOverDialogView', (tester) async {
|
||||
await tester.pumpApp(
|
||||
GameOverDialog(
|
||||
score: 1000,
|
||||
theme: DashTheme(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(GameOverDialogView), findsOneWidget);
|
||||
});
|
||||
|
||||
group('GameOverDialogView', () {
|
||||
late LeaderboardBloc leaderboardBloc;
|
||||
|
||||
final leaderboard = [
|
||||
LeaderboardEntry(
|
||||
rank: '1',
|
||||
playerInitials: 'ABC',
|
||||
score: 5000,
|
||||
character: DashTheme().characterAsset,
|
||||
),
|
||||
];
|
||||
final entryData = LeaderboardEntryData(
|
||||
playerInitials: 'VGV',
|
||||
score: 10000,
|
||||
character: CharacterType.dash,
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
leaderboardBloc = MockLeaderboardBloc();
|
||||
whenListen(
|
||||
leaderboardBloc,
|
||||
const Stream<LeaderboardState>.empty(),
|
||||
initialState: const LeaderboardState.initial(),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('renders input text view when bloc emits [loading]',
|
||||
(tester) async {
|
||||
final l10n = await AppLocalizations.delegate.load(Locale('en'));
|
||||
|
||||
await tester.pumpApp(
|
||||
BlocProvider.value(
|
||||
value: leaderboardBloc,
|
||||
child: GameOverDialogView(
|
||||
score: entryData.score,
|
||||
theme: entryData.character.toTheme,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.widgetWithText(TextButton, l10n.addUser), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders error view when bloc emits [error]', (tester) async {
|
||||
final l10n = await AppLocalizations.delegate.load(Locale('en'));
|
||||
|
||||
whenListen(
|
||||
leaderboardBloc,
|
||||
const Stream<LeaderboardState>.empty(),
|
||||
initialState: LeaderboardState.initial()
|
||||
.copyWith(status: LeaderboardStatus.error),
|
||||
);
|
||||
|
||||
await tester.pumpApp(
|
||||
BlocProvider.value(
|
||||
value: leaderboardBloc,
|
||||
child: GameOverDialogView(
|
||||
score: entryData.score,
|
||||
theme: entryData.character.toTheme,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text(l10n.error), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders success view when bloc emits [success]',
|
||||
(tester) async {
|
||||
final l10n = await AppLocalizations.delegate.load(Locale('en'));
|
||||
|
||||
whenListen(
|
||||
leaderboardBloc,
|
||||
const Stream<LeaderboardState>.empty(),
|
||||
initialState: LeaderboardState(
|
||||
status: LeaderboardStatus.success,
|
||||
ranking: LeaderboardRanking(ranking: 1, outOf: 2),
|
||||
leaderboard: leaderboard,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpApp(
|
||||
BlocProvider.value(
|
||||
value: leaderboardBloc,
|
||||
child: GameOverDialogView(
|
||||
score: entryData.score,
|
||||
theme: entryData.character.toTheme,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
find.widgetWithText(TextButton, l10n.leaderboard),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('adds LeaderboardEntryAdded when tap on add user button',
|
||||
(tester) async {
|
||||
final l10n = await AppLocalizations.delegate.load(Locale('en'));
|
||||
|
||||
whenListen(
|
||||
leaderboardBloc,
|
||||
const Stream<LeaderboardState>.empty(),
|
||||
initialState: LeaderboardState.initial(),
|
||||
);
|
||||
|
||||
await tester.pumpApp(
|
||||
BlocProvider.value(
|
||||
value: leaderboardBloc,
|
||||
child: GameOverDialogView(
|
||||
score: entryData.score,
|
||||
theme: entryData.character.toTheme,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.enterText(
|
||||
find.byKey(const Key('player_initials_text_field')),
|
||||
entryData.playerInitials,
|
||||
);
|
||||
|
||||
final button = find.widgetWithText(TextButton, l10n.addUser);
|
||||
await tester.ensureVisible(button);
|
||||
await tester.tap(button);
|
||||
|
||||
verify(
|
||||
() => leaderboardBloc.add(LeaderboardEntryAdded(entry: entryData)),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
testWidgets('navigates to LeaderboardPage when tap on leaderboard button',
|
||||
(tester) async {
|
||||
final l10n = await AppLocalizations.delegate.load(Locale('en'));
|
||||
final navigator = MockNavigator();
|
||||
when(() => navigator.push<void>(any())).thenAnswer((_) async {});
|
||||
whenListen(
|
||||
leaderboardBloc,
|
||||
const Stream<LeaderboardState>.empty(),
|
||||
initialState: LeaderboardState(
|
||||
status: LeaderboardStatus.success,
|
||||
ranking: LeaderboardRanking(ranking: 1, outOf: 2),
|
||||
leaderboard: leaderboard,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpApp(
|
||||
BlocProvider.value(
|
||||
value: leaderboardBloc,
|
||||
child: GameOverDialogView(
|
||||
score: entryData.score,
|
||||
theme: entryData.character.toTheme,
|
||||
),
|
||||
),
|
||||
navigator: navigator,
|
||||
);
|
||||
|
||||
final button = find.widgetWithText(TextButton, l10n.leaderboard);
|
||||
await tester.ensureVisible(button);
|
||||
await tester.tap(button);
|
||||
|
||||
verify(() => navigator.push<void>(any())).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in new issue