feat: adding mobile controls (#377)

* feat: adding mobile controls

* adding tests for pinball dpad button

* feat: tests

* lint

* Apply suggestions from code review

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

* suggestions

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
pull/389/head
Erick 2 years ago committed by GitHub
parent bba5316dfd
commit 9d184fedf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,27 +5,33 @@ 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/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.
/// {@endtemplate}
class Backbox extends PositionComponent with ZIndex {
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
@ -58,6 +64,9 @@ class Backbox extends PositionComponent with ZIndex {
} else if (state is LeaderboardSuccessState) {
_display.add(LeaderboardDisplay(entries: state.entries));
} else if (state is InitialsFormState) {
if (_platformHelper.isMobile) {
gameRef.overlays.add(PinballGame.mobileControlsOverlay);
}
_display.add(
InitialsInputDisplay(
score: state.score,

@ -37,6 +37,9 @@ class PinballGame extends PinballForge2DGame
/// Identifier of the play button overlay
static const playButtonOverlay = 'play_button';
/// Identifier of the mobile controls overlay
static const mobileControlsOverlay = 'mobile_controls';
@override
Color backgroundColor() => Colors.transparent;

@ -122,6 +122,14 @@ class PinballGameLoadedView extends StatelessWidget {
child: PlayButtonOverlay(),
);
},
PinballGame.mobileControlsOverlay: (context, game) {
return Positioned(
bottom: 0,
left: 0,
right: 0,
child: MobileControls(game: game),
);
},
},
),
),

@ -0,0 +1,46 @@
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}
/// Widget with the controls used to enable the user initials input on mobile.
/// {@endtemplate}
class MobileControls extends StatelessWidget {
/// {@macro mobile_controls}
const MobileControls({
Key? key,
required this.game,
}) : super(key: key);
/// Game instance
final PinballGame game;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MobileDpad(
onTapUp: () => game.triggerVirtualKeyUp(LogicalKeyboardKey.arrowUp),
onTapDown: () => game.triggerVirtualKeyUp(
LogicalKeyboardKey.arrowDown,
),
onTapLeft: () => game.triggerVirtualKeyUp(
LogicalKeyboardKey.arrowLeft,
),
onTapRight: () => game.triggerVirtualKeyUp(
LogicalKeyboardKey.arrowRight,
),
),
PinballButton(
text: l10n.enter,
onTap: () => game.triggerVirtualKeyUp(LogicalKeyboardKey.enter),
),
],
);
}
}

@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// {@template mobile_dpad}
/// Widget rendering 4 directional input arrows.
/// {@endtemplate}
class MobileDpad extends StatelessWidget {
/// {@template mobile_dpad}
const MobileDpad({
Key? key,
required this.onTapUp,
required this.onTapDown,
required this.onTapLeft,
required this.onTapRight,
}) : super(key: key);
static const _size = 180.0;
/// 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;
@override
Widget build(BuildContext context) {
return SizedBox(
width: _size,
height: _size,
child: Column(
children: [
Row(
children: [
const Spacer(),
PinballDpadButton(
direction: PinballDpadDirection.up,
onTap: onTapUp,
),
const Spacer(),
],
),
Row(
children: [
PinballDpadButton(
direction: PinballDpadDirection.left,
onTap: onTapLeft,
),
const Spacer(),
PinballDpadButton(
direction: PinballDpadDirection.right,
onTap: onTapRight,
),
],
),
Row(
children: [
const Spacer(),
PinballDpadButton(
direction: PinballDpadDirection.down,
onTap: onTapDown,
),
const Spacer(),
],
),
],
),
);
}
}

@ -1,5 +1,7 @@
export 'bonus_animation.dart';
export 'game_hud.dart';
export 'mobile_controls.dart';
export 'mobile_dpad.dart';
export 'play_button_overlay.dart';
export 'round_count_display.dart';
export 'score_view.dart';

@ -151,5 +151,9 @@
"ioPinball": "I/O Pinball",
"@ioPinball": {
"description": "I/O Pinball - Name of the game"
},
"enter": "Enter",
"@enter": {
"description": "Text shown on the mobile controls enter 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
@ -18,6 +19,17 @@ class KeyboardInputController extends Component with KeyboardHandler {
final Map<LogicalKeyboardKey, KeyHandlerCallback> _keyUp;
final Map<LogicalKeyboardKey, KeyHandlerCallback> _keyDown;
/// Trigger a virtual key up event.
bool onVirtualKeyUp(LogicalKeyboardKey key) {
final handler = _keyUp[key];
if (handler != null) {
return handler();
}
return true;
}
@override
bool onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
final isUp = event is RawKeyUpEvent;
@ -32,3 +44,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);
});
});
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -3,8 +3,6 @@
/// FlutterGen
/// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
import 'package:flutter/widgets.dart';
class $AssetsImagesGen {
@ -17,7 +15,14 @@ class $AssetsImagesGen {
class $AssetsImagesButtonGen {
const $AssetsImagesButtonGen();
/// File path: assets/images/button/pinball_button.png
AssetGenImage get dpadDown =>
const AssetGenImage('assets/images/button/dpad_down.png');
AssetGenImage get dpadLeft =>
const AssetGenImage('assets/images/button/dpad_left.png');
AssetGenImage get dpadRight =>
const AssetGenImage('assets/images/button/dpad_right.png');
AssetGenImage get dpadUp =>
const AssetGenImage('assets/images/button/dpad_up.png');
AssetGenImage get pinballButton =>
const AssetGenImage('assets/images/button/pinball_button.png');
}
@ -25,7 +30,6 @@ class $AssetsImagesButtonGen {
class $AssetsImagesDialogGen {
const $AssetsImagesDialogGen();
/// File path: assets/images/dialog/background.png
AssetGenImage get background =>
const AssetGenImage('assets/images/dialog/background.png');
}

@ -3,14 +3,9 @@
/// FlutterGen
/// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
class FontFamily {
FontFamily._();
/// Font family: PixeloidMono
static const String pixeloidMono = 'PixeloidMono';
/// Font family: PixeloidSans
static const String pixeloidSans = 'PixeloidSans';
}

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:pinball_ui/gen/gen.dart';
import 'package:pinball_ui/pinball_ui.dart';
/// Enum with all possibile directions of a [PinballDpadButton].
enum PinballDpadDirection {
/// Up
up,
/// Down
down,
/// Left
left,
/// Right
right,
}
extension _PinballDpadDirectionX on PinballDpadDirection {
String toAsset() {
switch (this) {
case PinballDpadDirection.up:
return Assets.images.button.dpadUp.keyName;
case PinballDpadDirection.down:
return Assets.images.button.dpadDown.keyName;
case PinballDpadDirection.left:
return Assets.images.button.dpadLeft.keyName;
case PinballDpadDirection.right:
return Assets.images.button.dpadRight.keyName;
}
}
}
/// {@template pinball_dpad_button}
/// Widget that renders a Dpad button with a given direction.
/// {@endtemplate}
class PinballDpadButton extends StatelessWidget {
/// {@macro pinball_dpad_button}
const PinballDpadButton({
Key? key,
required this.direction,
required this.onTap,
}) : super(key: key);
/// Which [PinballDpadDirection] this button is.
final PinballDpadDirection direction;
/// The function executed when the button is pressed.
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Material(
color: PinballColors.transparent,
child: InkWell(
onTap: onTap,
child: Image.asset(
direction.toAsset(),
width: 60,
height: 60,
),
),
);
}
}

@ -1,4 +1,5 @@
export 'animated_ellipsis_text.dart';
export 'crt_background.dart';
export 'pinball_button.dart';
export 'pinball_dpad_button.dart';
export 'pinball_loading_indicator.dart';

@ -0,0 +1,122 @@
// ignore_for_file: one_member_abstracts
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_ui/gen/gen.dart';
import 'package:pinball_ui/pinball_ui.dart';
extension _WidgetTesterX on WidgetTester {
Future<void> pumpButton({
required PinballDpadDirection direction,
required VoidCallback onTap,
}) async {
await pumpWidget(
MaterialApp(
home: Scaffold(
body: PinballDpadButton(
direction: direction,
onTap: onTap,
),
),
),
);
}
}
extension _CommonFindersX on CommonFinders {
Finder byImagePath(String path) {
return find.byWidgetPredicate(
(widget) {
if (widget is Image) {
final image = widget.image;
if (image is AssetImage) {
return image.keyName == path;
}
return false;
}
return false;
},
);
}
}
abstract class _VoidCallbackStubBase {
void onCall();
}
class _VoidCallbackStub extends Mock implements _VoidCallbackStubBase {}
void main() {
group('PinballDpadButton', () {
testWidgets('can be tapped', (tester) async {
final stub = _VoidCallbackStub();
await tester.pumpButton(
direction: PinballDpadDirection.up,
onTap: stub.onCall,
);
await tester.tap(find.byType(Image));
verify(stub.onCall).called(1);
});
group('up', () {
testWidgets('renders the correct image', (tester) async {
await tester.pumpButton(
direction: PinballDpadDirection.up,
onTap: () {},
);
expect(
find.byImagePath(Assets.images.button.dpadUp.keyName),
findsOneWidget,
);
});
});
group('down', () {
testWidgets('renders the correct image', (tester) async {
await tester.pumpButton(
direction: PinballDpadDirection.down,
onTap: () {},
);
expect(
find.byImagePath(Assets.images.button.dpadDown.keyName),
findsOneWidget,
);
});
});
group('left', () {
testWidgets('renders the correct image', (tester) async {
await tester.pumpButton(
direction: PinballDpadDirection.left,
onTap: () {},
);
expect(
find.byImagePath(Assets.images.button.dpadLeft.keyName),
findsOneWidget,
);
});
});
group('right', () {
testWidgets('renders the correct image', (tester) async {
await tester.pumpButton(
direction: PinballDpadDirection.right,
onTap: () {},
);
expect(
find.byImagePath(Assets.images.button.dpadRight.keyName),
findsOneWidget,
);
});
});
});
}

@ -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 platform 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();

@ -335,6 +335,20 @@ 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);
});
group('info icon', () {
testWidgets('renders on game over', (tester) async {
final gameState = GameState.initial().copyWith(

@ -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,113 @@
// ignore_for_file: one_member_abstracts
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