feat: user initials submission (#341)

* feat: initial implementation

* feat: tests

* fix: lint

* fix: removing wrong commited images

* feat: pr suggestions

* feat: PR suggestions

* Apply suggestions from code review

Co-authored-by: Alejandro Santiago <dev@alestiago.com>

* Apply suggestions from code review

* Apply suggestions from code review

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* fix

* pr suggestions

* fixing comments

* pr suggestions

* lint

Co-authored-by: Alejandro Santiago <dev@alestiago.com>
Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
pull/320/head
Erick 2 years ago committed by GitHub
parent 2ad0196e44
commit e9b902355d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,38 +1,90 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart';
import 'package:pinball/game/components/backbox/displays/displays.dart';
import 'package:pinball/game/pinball_game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' hide Assets;
/// {@template backbox}
/// The [Backbox] of the pinball machine.
/// {@endtemplate}
class Backbox extends PositionComponent with HasGameRef, ZIndex {
class Backbox extends PositionComponent with HasGameRef<PinballGame>, ZIndex {
/// {@macro backbox}
Backbox()
: super(
position: Vector2(0, -87),
anchor: Anchor.bottomCenter,
children: [
_BackboxSpriteComponent(),
],
) {
Backbox({
required LeaderboardRepository leaderboardRepository,
}) : _bloc = BackboxBloc(leaderboardRepository: leaderboardRepository);
/// {@macro backbox}
@visibleForTesting
Backbox.test({
required BackboxBloc bloc,
}) : _bloc = bloc;
late final Component _display;
final BackboxBloc _bloc;
late StreamSubscription<BackboxState> _subscription;
@override
Future<void> onLoad() async {
position = Vector2(0, -87);
anchor = Anchor.bottomCenter;
zIndex = ZIndexes.backbox;
await add(_BackboxSpriteComponent());
await add(_display = Component());
_subscription = _bloc.stream.listen((state) {
_display.children.removeWhere((_) => true);
_build(state);
});
}
@override
void onRemove() {
super.onRemove();
_subscription.cancel();
}
void _build(BackboxState state) {
if (state is LoadingState) {
_display.add(LoadingDisplay());
} else if (state is InitialsFormState) {
_display.add(
InitialsInputDisplay(
score: state.score,
characterIconPath: state.character.leaderboardIcon.keyName,
onSubmit: (initials) {
_bloc.add(
PlayerInitialsSubmitted(
score: state.score,
initials: initials,
character: state.character,
),
);
},
),
);
} else if (state is InitialsSuccessState) {
_display.add(InitialsSubmissionSuccessDisplay());
} else if (state is InitialsFailureState) {
_display.add(InitialsSubmissionFailureDisplay());
}
}
/// Puts [InitialsInputDisplay] on the [Backbox].
Future<void> initialsInput({
void requestInitials({
required int score,
required String characterIconPath,
InitialsOnSubmit? onSubmit,
}) async {
removeAll(children.where((child) => child is! _BackboxSpriteComponent));
await add(
InitialsInputDisplay(
required CharacterTheme character,
}) {
_bloc.add(
PlayerInitialsRequested(
score: score,
characterIconPath: characterIconPath,
onSubmit: onSubmit,
character: character,
),
);
}

@ -0,0 +1,56 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/leaderboard/models/leader_board_entry.dart';
import 'package:pinball_theme/pinball_theme.dart';
part 'backbox_event.dart';
part 'backbox_state.dart';
/// {@template backbox_bloc}
/// Bloc which manages the Backbox display.
/// {@endtemplate}
class BackboxBloc extends Bloc<BackboxEvent, BackboxState> {
/// {@macro backbox_bloc}
BackboxBloc({
required LeaderboardRepository leaderboardRepository,
}) : _leaderboardRepository = leaderboardRepository,
super(LoadingState()) {
on<PlayerInitialsRequested>(_onPlayerInitialsRequested);
on<PlayerInitialsSubmitted>(_onPlayerInitialsSubmitted);
}
final LeaderboardRepository _leaderboardRepository;
void _onPlayerInitialsRequested(
PlayerInitialsRequested event,
Emitter<BackboxState> emit,
) {
emit(
InitialsFormState(
score: event.score,
character: event.character,
),
);
}
Future<void> _onPlayerInitialsSubmitted(
PlayerInitialsSubmitted event,
Emitter<BackboxState> emit,
) async {
try {
emit(LoadingState());
await _leaderboardRepository.addLeaderboardEntry(
LeaderboardEntryData(
playerInitials: event.initials,
score: event.score,
character: event.character.toType,
),
);
emit(InitialsSuccessState());
} catch (error, stackTrace) {
addError(error, stackTrace);
emit(InitialsFailureState());
}
}
}

@ -0,0 +1,53 @@
part of 'backbox_bloc.dart';
/// {@template backbox_event}
/// Base class for backbox events.
/// {@endtemplate}
abstract class BackboxEvent extends Equatable {
/// {@macro backbox_event}
const BackboxEvent();
}
/// {@template player_initials_requested}
/// Event that triggers the user initials display.
/// {@endtemplate}
class PlayerInitialsRequested extends BackboxEvent {
/// {@macro player_initials_requested}
const PlayerInitialsRequested({
required this.score,
required this.character,
});
/// Player's score.
final int score;
/// Player's character.
final CharacterTheme character;
@override
List<Object?> get props => [score, character];
}
/// {@template player_initials_submitted}
/// Event that submits the user score and initials.
/// {@endtemplate}
class PlayerInitialsSubmitted extends BackboxEvent {
/// {@macro player_initials_submitted}
const PlayerInitialsSubmitted({
required this.score,
required this.initials,
required this.character,
});
/// Player's score.
final int score;
/// Player's initials.
final String initials;
/// Player's character.
final CharacterTheme character;
@override
List<Object?> get props => [score, initials, character];
}

@ -0,0 +1,59 @@
part of 'backbox_bloc.dart';
/// {@template backbox_state}
/// The base state for all [BackboxState].
/// {@endtemplate backbox_state}
abstract class BackboxState extends Equatable {
/// {@macro backbox_state}
const BackboxState();
}
/// Loading state for the backbox.
class LoadingState extends BackboxState {
@override
List<Object?> get props => [];
}
/// State when the leaderboard was successfully loaded.
class LeaderboardSuccessState extends BackboxState {
@override
List<Object?> get props => [];
}
/// State when the leaderboard failed to load.
class LeaderboardFailureState extends BackboxState {
@override
List<Object?> get props => [];
}
/// {@template initials_form_state}
/// State when the user is inputting their initials.
/// {@endtemplate}
class InitialsFormState extends BackboxState {
/// {@macro initials_form_state}
const InitialsFormState({
required this.score,
required this.character,
}) : super();
/// Player's score.
final int score;
/// Player's character.
final CharacterTheme character;
@override
List<Object?> get props => [score, character];
}
/// State when the leaderboard was successfully loaded.
class InitialsSuccessState extends BackboxState {
@override
List<Object?> get props => [];
}
/// State when the initials submission failed.
class InitialsFailureState extends BackboxState {
@override
List<Object?> get props => [];
}

@ -1 +1,4 @@
export 'initials_input_display.dart';
export 'initials_submission_failure_display.dart';
export 'initials_submission_success_display.dart';
export 'loading_display.dart';

@ -0,0 +1,28 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_ui/pinball_ui.dart';
final _bodyTextPaint = TextPaint(
style: const TextStyle(
fontSize: 3,
color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans,
),
);
/// {@template initials_submission_failure_display}
/// [Backbox] display for when a failure occurs during initials submission.
/// {@endtemplate}
class InitialsSubmissionFailureDisplay extends TextComponent
with HasGameRef<PinballGame> {
@override
Future<void> onLoad() async {
await super.onLoad();
position = Vector2(0, -10);
anchor = Anchor.center;
text = 'Failure!';
textRenderer = _bodyTextPaint;
}
}

@ -0,0 +1,28 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_ui/pinball_ui.dart';
final _bodyTextPaint = TextPaint(
style: const TextStyle(
fontSize: 3,
color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans,
),
);
/// {@template initials_submission_success_display}
/// [Backbox] display for initials successfully submitted.
/// {@endtemplate}
class InitialsSubmissionSuccessDisplay extends TextComponent
with HasGameRef<PinballGame> {
@override
Future<void> onLoad() async {
await super.onLoad();
position = Vector2(0, -10);
anchor = Anchor.center;
text = 'Success!';
textRenderer = _bodyTextPaint;
}
}

@ -0,0 +1,48 @@
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_ui/pinball_ui.dart';
final _bodyTextPaint = TextPaint(
style: const TextStyle(
fontSize: 3,
color: PinballColors.white,
fontFamily: PinballFonts.pixeloidSans,
),
);
/// {@template loading_display}
/// Display used to show the loading animation.
/// {@endtemplate}
class LoadingDisplay extends TextComponent with HasGameRef<PinballGame> {
/// {@template loading_display}
LoadingDisplay();
late final String _label;
@override
Future<void> onLoad() async {
await super.onLoad();
position = Vector2(0, -10);
anchor = Anchor.center;
text = _label = gameRef.l10n.loading;
textRenderer = _bodyTextPaint;
await add(
TimerComponent(
period: 1,
repeat: true,
onTick: () {
final index = text.indexOf('.');
if (index != -1 && text.substring(index).length == 3) {
text = _label;
} else {
text = '$text.';
}
},
),
);
}
}

@ -22,9 +22,9 @@ class GameBlocStatusListener extends Component
gameRef.overlays.remove(PinballGame.playButtonOverlay);
break;
case GameStatus.gameOver:
gameRef.descendants().whereType<Backbox>().first.initialsInput(
gameRef.descendants().whereType<Backbox>().first.requestInitials(
score: state.displayScore,
characterIconPath: gameRef.characterTheme.leaderboardIcon.keyName,
character: gameRef.characterTheme,
);
gameRef.firstChild<CameraController>()!.focusOnGameOverBackbox();
break;

@ -7,6 +7,7 @@ import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
@ -23,6 +24,7 @@ class PinballGame extends PinballForge2DGame
MultiTouchTapDetector {
PinballGame({
required this.characterTheme,
required this.leaderboardRepository,
required this.l10n,
required this.player,
}) : super(gravity: Vector2(0, 30)) {
@ -40,6 +42,8 @@ class PinballGame extends PinballForge2DGame
final PinballPlayer player;
final LeaderboardRepository leaderboardRepository;
final AppLocalizations l10n;
@override
@ -49,7 +53,7 @@ class PinballGame extends PinballForge2DGame
final machine = [
BoardBackgroundSpriteComponent(),
Boundaries(),
Backbox(),
Backbox(leaderboardRepository: leaderboardRepository),
];
final decals = [
GoogleWord(position: Vector2(-4.25, 1.8)),
@ -184,11 +188,13 @@ class _GameBallsController extends ComponentController<PinballGame>
class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
DebugPinballGame({
required CharacterTheme characterTheme,
required LeaderboardRepository leaderboardRepository,
required AppLocalizations l10n,
required PinballPlayer player,
}) : super(
characterTheme: characterTheme,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: l10n,
) {
controller = _GameBallsController(this);

@ -4,6 +4,7 @@ import 'package:flame/game.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/assets_manager/assets_manager.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
@ -37,23 +38,25 @@ class PinballGamePage extends StatelessWidget {
final characterTheme =
context.read<CharacterThemeCubit>().state.characterTheme;
final player = context.read<PinballPlayer>();
final pinballAudio = context.read<PinballPlayer>();
final leaderboardRepository = context.read<LeaderboardRepository>();
final game = isDebugMode
? DebugPinballGame(
characterTheme: characterTheme,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: context.l10n,
)
: PinballGame(
characterTheme: characterTheme,
player: player,
leaderboardRepository: leaderboardRepository,
l10n: context.l10n,
);
final loadables = [
...game.preLoadAssets(),
...pinballAudio.load(),
...player.load(),
...BonusAnimation.loadAssets(),
...SelectedCharacter.loadAssets(),
];

@ -1,3 +1,4 @@
import 'package:equatable/equatable.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball_theme/pinball_theme.dart';
@ -6,9 +7,9 @@ import 'package:pinball_theme/pinball_theme.dart';
/// player's initials, score, and chosen character.
///
/// {@endtemplate}
class LeaderboardEntry {
class LeaderboardEntry extends Equatable {
/// {@macro leaderboard_entry}
LeaderboardEntry({
const LeaderboardEntry({
required this.rank,
required this.playerInitials,
required this.score,
@ -26,6 +27,9 @@ class LeaderboardEntry {
/// [CharacterTheme] for [LeaderboardEntry].
final AssetGenImage character;
@override
List<Object?> get props => [rank, playerInitials, score, character];
}
/// Converts [LeaderboardEntryData] from repository to [LeaderboardEntry].

@ -1,10 +1,17 @@
// ignore_for_file: cascade_invocations
// ignore_for_file: cascade_invocations, prefer_const_constructors
import 'dart:async';
import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/backbox/displays/initials_input_display.dart';
import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart';
import 'package:pinball/game/components/backbox/displays/displays.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/pinball_components.dart';
@ -12,6 +19,24 @@ import 'package:pinball_theme/pinball_theme.dart' as theme;
import '../../../helpers/helpers.dart';
class _MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return super.toString();
}
}
RawKeyUpEvent _mockKeyUp(LogicalKeyboardKey key) {
final event = _MockRawKeyUpEvent();
when(() => event.logicalKey).thenReturn(key);
return event;
}
class _MockBackboxBloc extends Mock implements BackboxBloc {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get score => '';
@ -33,13 +58,16 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get toSubmit => '';
@override
String get loading => '';
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final characterIconPath = theme.Assets.images.dash.leaderboardIcon.keyName;
const character = theme.AndroidTheme();
final assets = [
characterIconPath,
character.leaderboardIcon.keyName,
Assets.images.backbox.marquee.keyName,
Assets.images.backbox.displayDivider.keyName,
];
@ -50,11 +78,22 @@ void main() {
),
);
late BackboxBloc bloc;
setUp(() {
bloc = _MockBackboxBloc();
whenListen(
bloc,
Stream.value(LoadingState()),
initialState: LoadingState(),
);
});
group('Backbox', () {
flameTester.test(
'loads correctly',
(game) async {
final backbox = Backbox();
final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox);
expect(game.children, contains(backbox));
@ -68,7 +107,9 @@ void main() {
game.camera
..followVector2(Vector2(0, -130))
..zoom = 6;
await game.ensureAdd(Backbox());
await game.ensureAdd(
Backbox.test(bloc: bloc),
);
await tester.pump();
},
verify: (game, tester) async {
@ -80,18 +121,124 @@ void main() {
);
flameTester.test(
'initialsInput adds InitialsInputDisplay',
'requestInitials adds InitialsInputDisplay',
(game) async {
final backbox = Backbox();
final backbox = Backbox.test(
bloc: BackboxBloc(
leaderboardRepository: _MockLeaderboardRepository(),
),
);
await game.ensureAdd(backbox);
await backbox.initialsInput(
backbox.requestInitials(
score: 0,
characterIconPath: characterIconPath,
onSubmit: (_) {},
character: character,
);
await game.ready();
expect(backbox.firstChild<InitialsInputDisplay>(), isNotNull);
expect(
backbox.descendants().whereType<InitialsInputDisplay>().length,
equals(1),
);
},
);
flameTester.test(
'adds PlayerInitialsSubmitted when initials are submitted',
(game) async {
final bloc = _MockBackboxBloc();
final state = InitialsFormState(
score: 10,
character: theme.AndroidTheme(),
);
whenListen(
bloc,
Stream.value(state),
initialState: state,
);
final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox);
game.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.enter), {});
verify(
() => bloc.add(
PlayerInitialsSubmitted(
score: 10,
initials: 'AAA',
character: theme.AndroidTheme(),
),
),
).called(1);
},
);
flameTester.test(
'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState',
(game) async {
whenListen(
bloc,
Stream.value(InitialsSuccessState()),
initialState: InitialsSuccessState(),
);
final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox);
expect(
game
.descendants()
.whereType<InitialsSubmissionSuccessDisplay>()
.length,
equals(1),
);
},
);
flameTester.test(
'adds InitialsSubmissionFailureDisplay on InitialsFailureState',
(game) async {
whenListen(
bloc,
Stream.value(InitialsFailureState()),
initialState: InitialsFailureState(),
);
final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox);
expect(
game
.descendants()
.whereType<InitialsSubmissionFailureDisplay>()
.length,
equals(1),
);
},
);
flameTester.test(
'closes the subscription when it is removed',
(game) async {
final streamController = StreamController<BackboxState>();
whenListen(
bloc,
streamController.stream,
initialState: LoadingState(),
);
final backbox = Backbox.test(bloc: bloc);
await game.ensureAdd(backbox);
backbox.removeFromParent();
await game.ready();
streamController.add(InitialsFailureState());
await game.ready();
expect(
backbox
.descendants()
.whereType<InitialsSubmissionFailureDisplay>()
.isEmpty,
isTrue,
);
},
);
});

@ -0,0 +1,92 @@
// ignore_for_file: prefer_const_constructors
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart';
import 'package:pinball_theme/pinball_theme.dart';
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
void main() {
late LeaderboardRepository leaderboardRepository;
group('BackboxBloc', () {
blocTest<BackboxBloc, BackboxState>(
'adds InitialsFormState on PlayerInitialsRequested',
setUp: () {
leaderboardRepository = _MockLeaderboardRepository();
},
build: () => BackboxBloc(leaderboardRepository: leaderboardRepository),
act: (bloc) => bloc.add(
PlayerInitialsRequested(
score: 100,
character: AndroidTheme(),
),
),
expect: () => [
InitialsFormState(score: 100, character: AndroidTheme()),
],
);
group('PlayerInitialsSubmitted', () {
blocTest<BackboxBloc, BackboxState>(
'adds [LoadingState, InitialsSuccessState] when submission succeeds',
setUp: () {
leaderboardRepository = _MockLeaderboardRepository();
when(
() => leaderboardRepository.addLeaderboardEntry(
LeaderboardEntryData(
playerInitials: 'AAA',
score: 10,
character: CharacterType.dash,
),
),
).thenAnswer((_) async {});
},
build: () => BackboxBloc(leaderboardRepository: leaderboardRepository),
act: (bloc) => bloc.add(
PlayerInitialsSubmitted(
score: 10,
initials: 'AAA',
character: DashTheme(),
),
),
expect: () => [
LoadingState(),
InitialsSuccessState(),
],
);
blocTest<BackboxBloc, BackboxState>(
'adds [LoadingState, InitialsFailureState] when submission fails',
setUp: () {
leaderboardRepository = _MockLeaderboardRepository();
when(
() => leaderboardRepository.addLeaderboardEntry(
LeaderboardEntryData(
playerInitials: 'AAA',
score: 10,
character: CharacterType.dash,
),
),
).thenThrow(Exception('Error'));
},
build: () => BackboxBloc(leaderboardRepository: leaderboardRepository),
act: (bloc) => bloc.add(
PlayerInitialsSubmitted(
score: 10,
initials: 'AAA',
character: DashTheme(),
),
),
expect: () => [
LoadingState(),
InitialsFailureState(),
],
);
});
});
}

@ -0,0 +1,126 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart';
import 'package:pinball_theme/pinball_theme.dart';
void main() {
group('BackboxEvent', () {
group('PlayerInitialsRequested', () {
test('can be instantiated', () {
expect(
PlayerInitialsRequested(score: 0, character: AndroidTheme()),
isNotNull,
);
});
test('supports value comparison', () {
expect(
PlayerInitialsRequested(score: 0, character: AndroidTheme()),
equals(
PlayerInitialsRequested(score: 0, character: AndroidTheme()),
),
);
expect(
PlayerInitialsRequested(score: 0, character: AndroidTheme()),
isNot(
equals(
PlayerInitialsRequested(score: 1, character: AndroidTheme()),
),
),
);
expect(
PlayerInitialsRequested(score: 0, character: AndroidTheme()),
isNot(
equals(
PlayerInitialsRequested(score: 0, character: SparkyTheme()),
),
),
);
});
});
group('PlayerInitialsSubmitted', () {
test('can be instantiated', () {
expect(
PlayerInitialsSubmitted(
score: 0,
initials: 'AAA',
character: AndroidTheme(),
),
isNotNull,
);
});
test('supports value comparison', () {
expect(
PlayerInitialsSubmitted(
score: 0,
initials: 'AAA',
character: AndroidTheme(),
),
equals(
PlayerInitialsSubmitted(
score: 0,
initials: 'AAA',
character: AndroidTheme(),
),
),
);
expect(
PlayerInitialsSubmitted(
score: 0,
initials: 'AAA',
character: AndroidTheme(),
),
isNot(
equals(
PlayerInitialsSubmitted(
score: 1,
initials: 'AAA',
character: AndroidTheme(),
),
),
),
);
expect(
PlayerInitialsSubmitted(
score: 0,
initials: 'AAA',
character: AndroidTheme(),
),
isNot(
equals(
PlayerInitialsSubmitted(
score: 0,
initials: 'AAA',
character: SparkyTheme(),
),
),
),
);
expect(
PlayerInitialsSubmitted(
score: 0,
initials: 'AAA',
character: AndroidTheme(),
),
isNot(
equals(
PlayerInitialsSubmitted(
score: 0,
initials: 'BBB',
character: AndroidTheme(),
),
),
),
);
});
});
});
}

@ -0,0 +1,116 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/components/backbox/bloc/backbox_bloc.dart';
import 'package:pinball_theme/pinball_theme.dart';
void main() {
group('BackboxState', () {
group('LoadingState', () {
test('can be instantiated', () {
expect(LoadingState(), isNotNull);
});
test('supports value comparison', () {
expect(LoadingState(), equals(LoadingState()));
});
});
group('LeaderboardSuccessState', () {
test('can be instantiated', () {
expect(LeaderboardSuccessState(), isNotNull);
});
test('supports value comparison', () {
expect(LeaderboardSuccessState(), equals(LeaderboardSuccessState()));
});
});
group('LeaderboardFailureState', () {
test('can be instantiated', () {
expect(LeaderboardFailureState(), isNotNull);
});
test('supports value comparison', () {
expect(LeaderboardFailureState(), equals(LeaderboardFailureState()));
});
});
group('InitialsFormState', () {
test('can be instantiated', () {
expect(
InitialsFormState(
score: 0,
character: AndroidTheme(),
),
isNotNull,
);
});
test('supports value comparison', () {
expect(
InitialsFormState(
score: 0,
character: AndroidTheme(),
),
equals(
InitialsFormState(
score: 0,
character: AndroidTheme(),
),
),
);
expect(
InitialsFormState(
score: 0,
character: AndroidTheme(),
),
isNot(
equals(
InitialsFormState(
score: 1,
character: AndroidTheme(),
),
),
),
);
expect(
InitialsFormState(
score: 0,
character: AndroidTheme(),
),
isNot(
equals(
InitialsFormState(
score: 0,
character: SparkyTheme(),
),
),
),
);
});
});
group('InitialsSuccessState', () {
test('can be instantiated', () {
expect(InitialsSuccessState(), isNotNull);
});
test('supports value comparison', () {
expect(InitialsSuccessState(), equals(InitialsSuccessState()));
});
group('InitialsFailureState', () {
test('can be instantiated', () {
expect(InitialsFailureState(), isNotNull);
});
test('supports value comparison', () {
expect(InitialsFailureState(), equals(InitialsFailureState()));
});
});
});
});
}

@ -0,0 +1,22 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/components/backbox/displays/initials_submission_failure_display.dart';
import '../../../../helpers/helpers.dart';
void main() {
group('InitialsSubmissionFailureDisplay', () {
final flameTester = FlameTester(EmptyKeyboardPinballTestGame.new);
flameTester.test('renders correctly', (game) async {
await game.ensureAdd(InitialsSubmissionFailureDisplay());
final component = game.firstChild<TextComponent>();
expect(component, isNotNull);
expect(component?.text, equals('Failure!'));
});
});
}

@ -0,0 +1,22 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/components/backbox/displays/initials_submission_success_display.dart';
import '../../../../helpers/helpers.dart';
void main() {
group('InitialsSubmissionSuccessDisplay', () {
final flameTester = FlameTester(EmptyKeyboardPinballTestGame.new);
flameTester.test('renders correctly', (game) async {
await game.ensureAdd(InitialsSubmissionSuccessDisplay());
final component = game.firstChild<TextComponent>();
expect(component, isNotNull);
expect(component?.text, equals('Success!'));
});
});
}

@ -0,0 +1,54 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/components/backbox/displays/loading_display.dart';
import 'package:pinball/l10n/l10n.dart';
import '../../../../helpers/helpers.dart';
class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get loading => 'Loading';
}
void main() {
group('LoadingDisplay', () {
final flameTester = FlameTester(
() => EmptyPinballTestGame(
l10n: _MockAppLocalizations(),
),
);
flameTester.test('renders correctly', (game) async {
await game.ensureAdd(LoadingDisplay());
final component = game.firstChild<TextComponent>();
expect(component, isNotNull);
expect(component?.text, equals('Loading'));
});
flameTester.test('use ellipses as animation', (game) async {
await game.ensureAdd(LoadingDisplay());
final component = game.firstChild<TextComponent>();
expect(component?.text, equals('Loading'));
final timer = component?.firstChild<TimerComponent>();
timer?.update(1.1);
expect(component?.text, equals('Loading.'));
timer?.update(1.1);
expect(component?.text, equals('Loading..'));
timer?.update(1.1);
expect(component?.text, equals('Loading...'));
timer?.update(1.1);
expect(component?.text, equals('Loading'));
});
});
}

@ -20,6 +20,10 @@ class _MockPinballPlayer extends Mock implements PinballPlayer {}
void main() {
group('GameBlocStatusListener', () {
setUpAll(() {
registerFallbackValue(AndroidTheme());
});
group('listenWhen', () {
test('is true when the game over state has changed', () {
final state = GameState(
@ -58,10 +62,9 @@ void main() {
gameFlowController.mockGameRef(game);
when(
() => backbox.initialsInput(
() => backbox.requestInitials(
score: any(named: 'score'),
characterIconPath: any(named: 'characterIconPath'),
onSubmit: any(named: 'onSubmit'),
character: any(named: 'character'),
),
).thenAnswer((_) async {});
when(cameraController.focusOnWaitingBackbox).thenAnswer((_) async {});
@ -92,10 +95,9 @@ void main() {
gameFlowController.onNewState(state);
verify(
() => backbox.initialsInput(
score: state.displayScore,
characterIconPath: any(named: 'characterIconPath'),
onSubmit: any(named: 'onSubmit'),
() => backbox.requestInitials(
score: any(named: 'score'),
character: any(named: 'character'),
),
).called(1);
verify(cameraController.focusOnGameOverBackbox).called(1);

@ -5,6 +5,7 @@ import 'dart:async';
import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
@ -15,6 +16,9 @@ class _MockPinballPlayer extends Mock implements PinballPlayer {}
class _MockAppLocalizations extends Mock implements AppLocalizations {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
}
class TestGame extends Forge2DGame with FlameBloc {
TestGame() {
images.prefix = '';
@ -25,11 +29,14 @@ class PinballTestGame extends PinballGame {
PinballTestGame({
List<String>? assets,
PinballPlayer? player,
LeaderboardRepository? leaderboardRepository,
CharacterTheme? theme,
AppLocalizations? l10n,
}) : _assets = assets,
super(
player: player ?? _MockPinballPlayer(),
leaderboardRepository:
leaderboardRepository ?? _MockLeaderboardRepository(),
characterTheme: theme ?? const DashTheme(),
l10n: l10n ?? _MockAppLocalizations(),
);
@ -48,11 +55,14 @@ class DebugPinballTestGame extends DebugPinballGame {
DebugPinballTestGame({
List<String>? assets,
PinballPlayer? player,
LeaderboardRepository? leaderboardRepository,
CharacterTheme? theme,
AppLocalizations? l10n,
}) : _assets = assets,
super(
player: player ?? _MockPinballPlayer(),
leaderboardRepository:
leaderboardRepository ?? _MockLeaderboardRepository(),
characterTheme: theme ?? const DashTheme(),
l10n: l10n ?? _MockAppLocalizations(),
);

@ -0,0 +1,42 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/leaderboard/models/leader_board_entry.dart';
import 'package:pinball_theme/pinball_theme.dart';
void main() {
group('LeaderboardEntry', () {
group('toEntry', () {
test('returns the correct from a to entry data', () {
expect(
LeaderboardEntryData.empty.toEntry(1),
LeaderboardEntry(
rank: '1',
playerInitials: '',
score: 0,
character: CharacterType.dash.toTheme.leaderboardIcon,
),
);
});
});
group('CharacterType', () {
test('toTheme returns the correct theme', () {
expect(CharacterType.dash.toTheme, equals(DashTheme()));
expect(CharacterType.sparky.toTheme, equals(SparkyTheme()));
expect(CharacterType.android.toTheme, equals(AndroidTheme()));
expect(CharacterType.dino.toTheme, equals(DinoTheme()));
});
});
group('CharacterTheme', () {
test('toType returns the correct type', () {
expect(DashTheme().toType, equals(CharacterType.dash));
expect(SparkyTheme().toType, equals(CharacterType.sparky));
expect(AndroidTheme().toType, equals(CharacterType.android));
expect(DinoTheme().toType, equals(CharacterType.dino));
});
});
});
}
Loading…
Cancel
Save