feat: tests

pull/377/head
Erick Zanardo 3 years ago
parent 4f4f1cdd87
commit f88dbb7fb8

@ -9,6 +9,7 @@ import 'package:pinball/game/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;
import 'package:platform_helper/platform_helper.dart';
/// {@template backbox}
/// The [Backbox] of the pinball machine.
@ -17,16 +18,20 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
/// {@macro backbox}
Backbox({
required LeaderboardRepository leaderboardRepository,
}) : _bloc = BackboxBloc(leaderboardRepository: leaderboardRepository);
}) : _bloc = BackboxBloc(leaderboardRepository: leaderboardRepository),
_platformHelper = PlatformHelper();
/// {@macro backbox}
@visibleForTesting
Backbox.test({
required BackboxBloc bloc,
}) : _bloc = bloc;
required PlatformHelper platformHelper,
}) : _bloc = bloc,
_platformHelper = platformHelper;
late final Component _display;
final BackboxBloc _bloc;
final PlatformHelper _platformHelper;
late StreamSubscription<BackboxState> _subscription;
@override
@ -59,8 +64,9 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
} else if (state is LeaderboardSuccessState) {
_display.add(LeaderboardDisplay(entries: state.entries));
} else if (state is InitialsFormState) {
// TODO check
if (_platformHelper.isMobile) {
gameRef.overlays.add(PinballGame.mobileControlsOverlay);
}
_display.add(
InitialsInputDisplay(
score: state.score,

@ -8,7 +8,6 @@ import 'package:flame_bloc/flame_bloc.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
@ -117,16 +116,6 @@ class PinballGame extends PinballForge2DGame
final focusedBoardSide = <int, BoardSide>{};
void triggerVirtualKeyUp(LogicalKeyboardKey key) {
final keyControllers = descendants().whereType<KeyboardInputController>();
for (final controller in keyControllers) {
if (!controller.onVirtualKeyUp(key)) {
break;
}
}
}
@override
void onTapDown(int pointerId, TapDownInfo info) {
if (info.raw.kind == PointerDeviceKind.touch) {

@ -12,6 +12,7 @@ import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_ui/pinball_ui.dart';
class PinballGamePage extends StatelessWidget {
@ -143,29 +144,7 @@ class PinballGameLoadedView extends StatelessWidget {
bottom: 0,
left: 0,
right: 0,
child: MobileControls(
onTapUp: () {
game.triggerVirtualKeyUp(LogicalKeyboardKey.arrowUp);
},
onTapDown: () {
game.triggerVirtualKeyUp(
LogicalKeyboardKey.arrowDown,
);
},
onTapLeft: () {
game.triggerVirtualKeyUp(
LogicalKeyboardKey.arrowLeft,
);
},
onTapRight: () {
game.triggerVirtualKeyUp(
LogicalKeyboardKey.arrowRight,
);
},
onTapEnter: () {
game.triggerVirtualKeyUp(LogicalKeyboardKey.enter);
},
),
child: MobileControls(game: game),
);
},
},

@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// {@template mobile_controls}
@ -7,43 +10,35 @@ import 'package:pinball_ui/pinball_ui.dart';
/// {@endtemplate}
class MobileControls extends StatelessWidget {
/// {@macro mobile_controls}
const MobileControls({Key? key,
required this.onTapUp,
required this.onTapDown,
required this.onTapLeft,
required this.onTapRight,
required this.onTapEnter,
const MobileControls({
Key? key,
required this.game,
}) : super(key: key);
/// Called when dpad up is pressed
final VoidCallback onTapUp;
/// Called when dpad down is pressed
final VoidCallback onTapDown;
/// Called when dpad left is pressed
final VoidCallback onTapLeft;
/// Called when dpad right is pressed
final VoidCallback onTapRight;
/// Called when enter is pressed
final VoidCallback onTapEnter;
/// Game instance
final PinballGame game;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MobileDpad(
onTapUp: onTapUp,
onTapDown: onTapUp,
onTapLeft: onTapLeft,
onTapRight: onTapRight,
onTapUp: () => game.triggerVirtualKeyUp(LogicalKeyboardKey.arrowUp),
onTapDown: () => game.triggerVirtualKeyUp(
LogicalKeyboardKey.arrowDown,
),
onTapLeft: () => game.triggerVirtualKeyUp(
LogicalKeyboardKey.arrowLeft,
),
onTapRight: () => game.triggerVirtualKeyUp(
LogicalKeyboardKey.arrowRight,
),
),
PinballButton(
text: 'Enter', // TODO l10n
onTap: onTapEnter,
text: l10n.enter,
onTap: () => game.triggerVirtualKeyUp(LogicalKeyboardKey.enter),
),
],
);

@ -167,5 +167,9 @@
"ioPinball": "I/O Pinball",
"@ioPinball": {
"description": "I/O Pinball - Name of the game"
},
"enter": "Enter",
"@enter": {
"description": "Enter show on the mobile controls button"
}
}

@ -1,4 +1,5 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/services.dart';
/// The signature for a key handle function
@ -44,3 +45,18 @@ class KeyboardInputController extends Component with KeyboardHandler {
return true;
}
}
/// Add the ability to virtually trigger key events to a [FlameGame]'s
/// [KeyboardInputController].
extension VirtualKeyEvents on FlameGame {
/// Trigger a key up
void triggerVirtualKeyUp(LogicalKeyboardKey key) {
final keyControllers = descendants().whereType<KeyboardInputController>();
for (final controller in keyControllers) {
if (!controller.onVirtualKeyUp(key)) {
break;
}
}
}
}

@ -1,11 +1,36 @@
// ignore_for_file: cascade_invocations, one_member_abstracts
import 'package:flame/game.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:mocktail/mocktail.dart';
import 'package:pinball_flame/pinball_flame.dart';
class _TestGame extends FlameGame {
bool pressed = false;
@override
Future<void>? onLoad() async {
await super.onLoad();
await add(
KeyboardInputController(
keyUp: {
LogicalKeyboardKey.enter: () {
pressed = true;
return true;
},
LogicalKeyboardKey.escape: () {
return false;
},
},
),
);
}
}
abstract class _KeyCall {
bool onCall();
}
@ -75,4 +100,15 @@ void main() {
},
);
});
group('VirtualKeyEvents', () {
final flameTester = FlameTester(_TestGame.new);
group('onVirtualKeyUp', () {
flameTester.test('triggers the event', (game) async {
await game.ready();
game.triggerVirtualKeyUp(LogicalKeyboardKey.enter);
expect(game.pressed, isTrue);
});
});
});
}

@ -6,8 +6,7 @@ import 'package:flutter/foundation.dart';
class PlatformHelper {
/// {@macro platform_helper}
bool get isMobile {
return true;
//return defaultTargetPlatform == TargetPlatform.iOS ||
// defaultTargetPlatform == TargetPlatform.android;
return defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.android;
}
}

@ -19,6 +19,7 @@ import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart' as theme;
import 'package:platform_helper/platform_helper.dart';
class _TestGame extends Forge2DGame with HasKeyboardHandlerComponents {
final character = theme.DashTheme();
@ -64,6 +65,8 @@ RawKeyUpEvent _mockKeyUp(LogicalKeyboardKey key) {
return event;
}
class _MockPlatformHelper extends Mock implements PlatformHelper {}
class _MockBackboxBloc extends Mock implements BackboxBloc {}
class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
@ -104,21 +107,27 @@ void main() {
final flameTester = FlameTester(_TestGame.new);
late BackboxBloc bloc;
late PlatformHelper platformHelper;
setUp(() {
bloc = _MockBackboxBloc();
platformHelper = _MockPlatformHelper();
whenListen(
bloc,
Stream<BackboxState>.empty(),
initialState: LoadingState(),
);
when(() => platformHelper.isMobile).thenReturn(false);
});
group('Backbox', () {
flameTester.test(
'loads correctly',
(game) async {
final backbox = Backbox.test(bloc: bloc);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(game.descendants(), contains(backbox));
},
@ -127,7 +136,10 @@ void main() {
flameTester.test(
'adds LeaderboardRequested when loaded',
(game) async {
final backbox = Backbox.test(bloc: bloc);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
verify(() => bloc.add(LeaderboardRequested())).called(1);
@ -142,7 +154,10 @@ void main() {
..followVector2(Vector2(0, -130))
..zoom = 6;
await game.pump(
Backbox.test(bloc: bloc),
Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
),
);
await tester.pump();
},
@ -161,6 +176,7 @@ void main() {
bloc: BackboxBloc(
leaderboardRepository: _MockLeaderboardRepository(),
),
platformHelper: platformHelper,
);
await game.pump(backbox);
backbox.requestInitials(
@ -189,7 +205,10 @@ void main() {
Stream<BackboxState>.empty(),
initialState: state,
);
final backbox = Backbox.test(bloc: bloc);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
game.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.enter), {});
@ -205,6 +224,34 @@ void main() {
},
);
flameTester.test(
'adds the mobile controls overlay when it is mobile',
(game) async {
final bloc = _MockBackboxBloc();
final platformHelper = _MockPlatformHelper();
final state = InitialsFormState(
score: 10,
character: game.character,
);
whenListen(
bloc,
Stream<BackboxState>.empty(),
initialState: state,
);
when(() => platformHelper.isMobile).thenReturn(true);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.overlays.value,
contains(PinballGame.mobileControlsOverlay),
);
},
);
flameTester.test(
'adds InitialsSubmissionSuccessDisplay on InitialsSuccessState',
(game) async {
@ -213,7 +260,10 @@ void main() {
Stream<BackboxState>.empty(),
initialState: InitialsSuccessState(),
);
final backbox = Backbox.test(bloc: bloc);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
@ -234,7 +284,10 @@ void main() {
Stream<BackboxState>.empty(),
initialState: InitialsFailureState(),
);
final backbox = Backbox.test(bloc: bloc);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
@ -256,7 +309,10 @@ void main() {
initialState: LeaderboardSuccessState(entries: const []),
);
final backbox = Backbox.test(bloc: bloc);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
@ -276,7 +332,10 @@ void main() {
initialState: LoadingState(),
);
final backbox = Backbox.test(bloc: bloc);
final backbox = Backbox.test(
bloc: bloc,
platformHelper: platformHelper,
);
await game.pump(backbox);
backbox.removeFromParent();

@ -331,5 +331,19 @@ void main() {
expect(game.focusNode.hasFocus, isTrue);
});
testWidgets('mobile controls when the overlay is added', (tester) async {
await tester.pumpApp(
PinballGameView(game: game),
gameBloc: gameBloc,
startGameBloc: startGameBloc,
);
game.overlays.add(PinballGame.mobileControlsOverlay);
await tester.pump();
expect(find.byType(MobileControls), findsOneWidget);
});
});
}

@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_ui/pinball_ui.dart';
class _MockPinballGame extends Mock implements PinballGame {}
extension _WidgetTesterX on WidgetTester {
Future<void> pumpMobileControls(PinballGame game) async {
await pumpWidget(
MaterialApp(
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
],
home: Scaffold(
body: MobileControls(game: game),
),
),
);
}
}
extension _CommonFindersX on CommonFinders {
Finder byPinballDpadDirection(PinballDpadDirection direction) {
return byWidgetPredicate((widget) {
return widget is PinballDpadButton && widget.direction == direction;
});
}
}
void main() {
group('MobileControls', () {
testWidgets('renders', (tester) async {
await tester.pumpMobileControls(_MockPinballGame());
expect(find.byType(PinballButton), findsOneWidget);
expect(find.byType(MobileDpad), findsOneWidget);
});
testWidgets('correctly triggers the arrow up', (tester) async {
var pressed = false;
final component = KeyboardInputController(
keyUp: {
LogicalKeyboardKey.arrowUp: () => pressed = true,
},
);
final game = _MockPinballGame();
when(game.descendants).thenReturn([component]);
await tester.pumpMobileControls(game);
await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.up));
await tester.pump();
expect(pressed, isTrue);
});
testWidgets('correctly triggers the arrow down', (tester) async {
var pressed = false;
final component = KeyboardInputController(
keyUp: {
LogicalKeyboardKey.arrowDown: () => pressed = true,
},
);
final game = _MockPinballGame();
when(game.descendants).thenReturn([component]);
await tester.pumpMobileControls(game);
await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.down));
await tester.pump();
expect(pressed, isTrue);
});
testWidgets('correctly triggers the arrow right', (tester) async {
var pressed = false;
final component = KeyboardInputController(
keyUp: {
LogicalKeyboardKey.arrowRight: () => pressed = true,
},
);
final game = _MockPinballGame();
when(game.descendants).thenReturn([component]);
await tester.pumpMobileControls(game);
await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.right));
await tester.pump();
expect(pressed, isTrue);
});
testWidgets('correctly triggers the arrow left', (tester) async {
var pressed = false;
final component = KeyboardInputController(
keyUp: {
LogicalKeyboardKey.arrowLeft: () => pressed = true,
},
);
final game = _MockPinballGame();
when(game.descendants).thenReturn([component]);
await tester.pumpMobileControls(game);
await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.left));
await tester.pump();
expect(pressed, isTrue);
});
testWidgets('correctly triggers the enter', (tester) async {
var pressed = false;
final component = KeyboardInputController(
keyUp: {
LogicalKeyboardKey.enter: () => pressed = true,
},
);
final game = _MockPinballGame();
when(game.descendants).thenReturn([component]);
await tester.pumpMobileControls(game);
await tester.tap(find.byType(PinballButton));
await tester.pump();
expect(pressed, isTrue);
});
});
}

@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_ui/pinball_ui.dart';
extension _WidgetTesterX on WidgetTester {
Future<void> pumpDpad({
required VoidCallback onTapUp,
required VoidCallback onTapDown,
required VoidCallback onTapLeft,
required VoidCallback onTapRight,
}) async {
await pumpWidget(
MaterialApp(
home: Scaffold(
body: MobileDpad(
onTapUp: onTapUp,
onTapDown: onTapDown,
onTapLeft: onTapLeft,
onTapRight: onTapRight,
),
),
),
);
}
}
extension _CommonFindersX on CommonFinders {
Finder byPinballDpadDirection(PinballDpadDirection direction) {
return byWidgetPredicate((widget) {
return widget is PinballDpadButton && widget.direction == direction;
});
}
}
abstract class _VoidCallbackStubBase {
void onCall();
}
class _VoidCallbackStub extends Mock implements _VoidCallbackStubBase {}
void main() {
group('MobileDpad', () {
testWidgets('renders correctly', (tester) async {
await tester.pumpDpad(
onTapUp: () {},
onTapDown: () {},
onTapLeft: () {},
onTapRight: () {},
);
expect(
find.byType(PinballDpadButton),
findsNWidgets(4),
);
});
testWidgets('can tap up', (tester) async {
final stub = _VoidCallbackStub();
await tester.pumpDpad(
onTapUp: stub.onCall,
onTapDown: () {},
onTapLeft: () {},
onTapRight: () {},
);
await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.up));
verify(stub.onCall).called(1);
});
testWidgets('can tap down', (tester) async {
final stub = _VoidCallbackStub();
await tester.pumpDpad(
onTapUp: () {},
onTapDown: stub.onCall,
onTapLeft: () {},
onTapRight: () {},
);
await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.down));
verify(stub.onCall).called(1);
});
testWidgets('can tap left', (tester) async {
final stub = _VoidCallbackStub();
await tester.pumpDpad(
onTapUp: () {},
onTapDown: () {},
onTapLeft: stub.onCall,
onTapRight: () {},
);
await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.left));
verify(stub.onCall).called(1);
});
testWidgets('can tap left', (tester) async {
final stub = _VoidCallbackStub();
await tester.pumpDpad(
onTapUp: () {},
onTapDown: () {},
onTapLeft: () {},
onTapRight: stub.onCall,
);
await tester.tap(find.byPinballDpadDirection(PinballDpadDirection.right));
verify(stub.onCall).called(1);
});
});
}
Loading…
Cancel
Save