Merge branch 'main' into fix/spaceship-ramp-logic

pull/416/head
RuiAlonso 3 years ago
commit ff925cb035

@ -9,6 +9,7 @@ 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_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
class App extends StatelessWidget {
@ -18,16 +19,19 @@ class App extends StatelessWidget {
required LeaderboardRepository leaderboardRepository,
required ShareRepository shareRepository,
required PinballAudioPlayer pinballAudioPlayer,
required PlatformHelper platformHelper,
}) : _authenticationRepository = authenticationRepository,
_leaderboardRepository = leaderboardRepository,
_shareRepository = shareRepository,
_pinballAudioPlayer = pinballAudioPlayer,
_platformHelper = platformHelper,
super(key: key);
final AuthenticationRepository _authenticationRepository;
final LeaderboardRepository _leaderboardRepository;
final ShareRepository _shareRepository;
final PinballAudioPlayer _pinballAudioPlayer;
final PlatformHelper _platformHelper;
@override
Widget build(BuildContext context) {
@ -37,6 +41,7 @@ class App extends StatelessWidget {
RepositoryProvider.value(value: _leaderboardRepository),
RepositoryProvider.value(value: _shareRepository),
RepositoryProvider.value(value: _pinballAudioPlayer),
RepositoryProvider.value(value: _platformHelper),
],
child: MultiBlocProvider(
providers: [

@ -2,6 +2,8 @@ import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:platform_helper/platform_helper.dart';
/// Updates the [ArcadeBackground] and launch [Ball] to reflect character
/// selections.
@ -11,12 +13,14 @@ class CharacterSelectionBehavior extends Component
HasGameRef {
@override
void onNewState(CharacterThemeState state) {
gameRef
.descendants()
.whereType<ArcadeBackground>()
.single
.bloc
.onCharacterSelected(state.characterTheme);
if (!readProvider<PlatformHelper>().isMobile) {
gameRef
.descendants()
.whereType<ArcadeBackground>()
.single
.bloc
.onCharacterSelected(state.characterTheme);
}
gameRef
.descendants()
.whereType<Ball>()

@ -27,23 +27,19 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
leaderboardRepository: leaderboardRepository,
initialEntries: entries,
),
_shareRepository = shareRepository,
_platformHelper = PlatformHelper();
_shareRepository = shareRepository;
/// {@macro backbox}
@visibleForTesting
Backbox.test({
required BackboxBloc bloc,
required ShareRepository shareRepository,
required PlatformHelper platformHelper,
}) : _bloc = bloc,
_shareRepository = shareRepository,
_platformHelper = platformHelper;
_shareRepository = shareRepository;
final ShareRepository _shareRepository;
late final Component _display;
final BackboxBloc _bloc;
final PlatformHelper _platformHelper;
late StreamSubscription<BackboxState> _subscription;
@override
@ -76,7 +72,7 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
} else if (state is LeaderboardFailureState) {
_display.add(LeaderboardFailureDisplay());
} else if (state is InitialsFormState) {
if (_platformHelper.isMobile) {
if (readProvider<PlatformHelper>().isMobile) {
gameRef.overlays.add(PinballGame.mobileControlsOverlay);
}
_display.add(
@ -109,7 +105,7 @@ class Backbox extends PositionComponent with ZIndex, HasGameRef {
ShareDisplay(
onShare: (platform) {
final message = readProvider<AppLocalizations>()
.iGotScoreAtPinball(state.score);
.iGotScoreAtPinball(state.score.formatScore());
final url = _shareRepository.shareText(
value: message,
platform: platform,

@ -212,7 +212,7 @@ class GoogleIOLinkComponent extends TextComponent with HasGameRef, Tappable {
);
@override
bool onTapDown(TapDownInfo info) {
bool onTapUp(TapUpInfo info) {
openLink(ShareRepository.googleIOEvent);
return true;
}

@ -140,7 +140,7 @@ class FacebookButtonComponent extends SpriteComponent
final OnSocialShareTap? _onTap;
@override
bool onTapDown(TapDownInfo info) {
bool onTapUp(TapUpInfo info) {
_onTap?.call(SharePlatform.facebook);
return true;
}
@ -172,7 +172,7 @@ class TwitterButtonComponent extends SpriteComponent with HasGameRef, Tappable {
final OnSocialShareTap? _onTap;
@override
bool onTapDown(TapDownInfo info) {
bool onTapUp(TapUpInfo info) {
_onTap?.call(SharePlatform.twitter);
return true;
}

@ -1,5 +1,6 @@
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
@ -17,6 +18,7 @@ class GoogleWordBonusBehavior extends Component {
readBloc<GameBloc, GameState>()
.add(const BonusActivated(GameBonus.googleWord));
readBloc<GoogleWordCubit, GoogleWordState>().onBonusAwarded();
add(BonusBallSpawningBehavior());
},
),
);

@ -11,7 +11,8 @@ class MultiballsBehavior extends Component
bool listenWhen(GameState? previousState, GameState newState) {
final hasChanged = previousState?.bonusHistory != newState.bonusHistory;
final lastBonusIsMultiball = newState.bonusHistory.isNotEmpty &&
newState.bonusHistory.last == GameBonus.dashNest;
(newState.bonusHistory.last == GameBonus.dashNest ||
newState.bonusHistory.last == GameBonus.googleWord);
return hasChanged && lastBonusIsMultiball;
}

@ -12,7 +12,7 @@ extension PinballGameAssetsX on PinballGame {
const androidTheme = AndroidTheme();
const dinoTheme = DinoTheme();
return [
final gameAssets = [
images.load(components.Assets.images.boardBackground.keyName),
images.load(components.Assets.images.ball.flameEffect.keyName),
images.load(components.Assets.images.signpost.inactive.keyName),
@ -148,17 +148,21 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.displayArrows.arrowLeft.keyName),
images.load(components.Assets.images.displayArrows.arrowRight.keyName),
images.load(androidTheme.leaderboardIcon.keyName),
images.load(androidTheme.background.keyName),
images.load(androidTheme.ball.keyName),
images.load(dashTheme.leaderboardIcon.keyName),
images.load(dashTheme.background.keyName),
images.load(dashTheme.ball.keyName),
images.load(dinoTheme.leaderboardIcon.keyName),
images.load(dinoTheme.background.keyName),
images.load(dinoTheme.ball.keyName),
images.load(sparkyTheme.leaderboardIcon.keyName),
images.load(sparkyTheme.background.keyName),
images.load(sparkyTheme.ball.keyName),
];
return (platformHelper.isMobile) ? gameAssets : gameAssets
..addAll([
images.load(androidTheme.background.keyName),
images.load(dashTheme.background.keyName),
images.load(dinoTheme.background.keyName),
images.load(sparkyTheme.background.keyName),
]);
}
}

@ -14,6 +14,7 @@ import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
class PinballGame extends PinballForge2DGame
@ -25,6 +26,7 @@ class PinballGame extends PinballForge2DGame
required GameBloc gameBloc,
required AppLocalizations l10n,
required PinballAudioPlayer audioPlayer,
required this.platformHelper,
}) : focusNode = FocusNode(),
_gameBloc = gameBloc,
_audioPlayer = audioPlayer,
@ -57,6 +59,8 @@ class PinballGame extends PinballForge2DGame
final AppLocalizations _l10n;
final PlatformHelper platformHelper;
final GameBloc _gameBloc;
List<LeaderboardEntryData>? _entries;
@ -90,6 +94,7 @@ class PinballGame extends PinballForge2DGame
FlameProvider<LeaderboardRepository>.value(leaderboardRepository),
FlameProvider<ShareRepository>.value(shareRepository),
FlameProvider<AppLocalizations>.value(_l10n),
FlameProvider<PlatformHelper>.value(platformHelper),
],
children: [
BonusNoiseBehavior(),
@ -106,7 +111,7 @@ class PinballGame extends PinballForge2DGame
children: [
ZCanvasComponent(
children: [
ArcadeBackground(),
if (!platformHelper.isMobile) ArcadeBackground(),
BoardBackgroundSpriteComponent(),
Boundaries(),
Backbox(
@ -197,6 +202,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
required ShareRepository shareRepository,
required AppLocalizations l10n,
required PinballAudioPlayer audioPlayer,
required PlatformHelper platformHelper,
required GameBloc gameBloc,
}) : super(
characterThemeBloc: characterThemeBloc,
@ -204,6 +210,7 @@ class DebugPinballGame extends PinballGame with FPSCounter, PanDetector {
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
l10n: l10n,
platformHelper: platformHelper,
gameBloc: gameBloc,
);

@ -11,6 +11,7 @@ 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_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
class PinballGamePage extends StatelessWidget {
@ -27,6 +28,7 @@ class PinballGamePage extends StatelessWidget {
final audioPlayer = context.read<PinballAudioPlayer>();
final leaderboardRepository = context.read<LeaderboardRepository>();
final shareRepository = context.read<ShareRepository>();
final platformHelper = context.read<PlatformHelper>();
final gameBloc = context.read<GameBloc>();
final game = isDebugMode
? DebugPinballGame(
@ -35,6 +37,7 @@ class PinballGamePage extends StatelessWidget {
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
l10n: context.l10n,
platformHelper: platformHelper,
gameBloc: gameBloc,
)
: PinballGame(
@ -43,6 +46,7 @@ class PinballGamePage extends StatelessWidget {
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
l10n: context.l10n,
platformHelper: platformHelper,
gameBloc: gameBloc,
);

@ -50,14 +50,11 @@ extension on Control {
}
class HowToPlayDialog extends StatefulWidget {
HowToPlayDialog({
const HowToPlayDialog({
Key? key,
required this.onDismissCallback,
@visibleForTesting PlatformHelper? platformHelper,
}) : platformHelper = platformHelper ?? PlatformHelper(),
super(key: key);
}) : super(key: key);
final PlatformHelper platformHelper;
final VoidCallback onDismissCallback;
@override
@ -85,7 +82,7 @@ class _HowToPlayDialogState extends State<HowToPlayDialog> {
@override
Widget build(BuildContext context) {
final isMobile = widget.platformHelper.isMobile;
final isMobile = context.read<PlatformHelper>().isMobile;
final l10n = context.l10n;
return WillPopScope(

@ -218,7 +218,7 @@
"description": "Text to share score on Social Network",
"placeholders": {
"score": {
"type": "int"
"type": "String"
}
}
}

@ -6,6 +6,7 @@ import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/app/app.dart';
import 'package:pinball/bootstrap.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
void main() {
@ -15,6 +16,7 @@ void main() {
ShareRepository(appUrl: ShareRepository.pinballGameUrl);
final authenticationRepository = AuthenticationRepository(firebaseAuth);
final pinballAudioPlayer = PinballAudioPlayer();
final platformHelper = PlatformHelper();
unawaited(
Firebase.initializeApp().then(
(_) => authenticationRepository.authenticateAnonymously(),
@ -25,6 +27,7 @@ void main() {
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
pinballAudioPlayer: pinballAudioPlayer,
platformHelper: platformHelper,
);
});
}

@ -204,7 +204,7 @@ class _MadeWithFlutterAndFirebase extends StatelessWidget {
abstract class _MoreInformationUrl {
static const flutterWebsite = 'https://flutter.dev';
static const firebaseWebsite = 'https://firebase.google.com';
static const openSourceCode = 'https://github.com/VGVentures/pinball';
static const openSourceCode = 'https://github.com/flutter/pinball';
static const googleIOEvent = 'https://events.google.com/io/';
static const flutterGamesWebsite = 'http://flutter.dev/games';
static const howItsMadeArticle =

@ -59,7 +59,7 @@ typedef CreateAudioPool = Future<AudioPool> Function(
typedef PlaySingleAudio = Future<void> Function(String);
/// Defines the contract for looping a single audio.
typedef LoopSingleAudio = Future<void> Function(String);
typedef LoopSingleAudio = Future<void> Function(String, {double volume});
/// Defines the contract for pre fetching an audio.
typedef PreCacheSingleAudio = Future<void> Function(String);
@ -101,18 +101,20 @@ class _LoopAudio extends _Audio {
required this.preCacheSingleAudio,
required this.loopSingleAudio,
required this.path,
this.volume,
});
final PreCacheSingleAudio preCacheSingleAudio;
final LoopSingleAudio loopSingleAudio;
final String path;
final double? volume;
@override
Future<void> load() => preCacheSingleAudio(prefixFile(path));
@override
void play() {
loopSingleAudio(prefixFile(path));
loopSingleAudio(prefixFile(path), volume: volume ?? 1);
}
}
@ -121,10 +123,12 @@ class _SingleLoopAudio extends _LoopAudio {
required PreCacheSingleAudio preCacheSingleAudio,
required LoopSingleAudio loopSingleAudio,
required String path,
double? volume,
}) : super(
preCacheSingleAudio: preCacheSingleAudio,
loopSingleAudio: loopSingleAudio,
path: path,
volume: volume,
);
bool _playing = false;
@ -296,6 +300,7 @@ class PinballAudioPlayer {
preCacheSingleAudio: _preCacheSingleAudio,
loopSingleAudio: _loopSingleAudio,
path: Assets.music.background,
volume: .6,
),
};
}

@ -33,7 +33,7 @@ class _MockPlaySingleAudio extends Mock {
}
class _MockLoopSingleAudio extends Mock {
Future<void> onCall(String url);
Future<void> onCall(String url, {double volume});
}
abstract class _PreCacheSingleAudio {
@ -77,7 +77,8 @@ void main() {
when(() => playSingleAudio.onCall(any())).thenAnswer((_) async {});
loopSingleAudio = _MockLoopSingleAudio();
when(() => loopSingleAudio.onCall(any())).thenAnswer((_) async {});
when(() => loopSingleAudio.onCall(any(), volume: any(named: 'volume')))
.thenAnswer((_) async {});
preCacheSingleAudio = _MockPreCacheSingleAudio();
when(() => preCacheSingleAudio.onCall(any())).thenAnswer((_) async {});
@ -443,8 +444,10 @@ void main() {
audioPlayer.play(PinballAudio.backgroundMusic);
verify(
() => loopSingleAudio
.onCall('packages/pinball_audio/${Assets.music.background}'),
() => loopSingleAudio.onCall(
'packages/pinball_audio/${Assets.music.background}',
volume: .6,
),
).called(1);
});
@ -455,8 +458,10 @@ void main() {
..play(PinballAudio.backgroundMusic);
verify(
() => loopSingleAudio
.onCall('packages/pinball_audio/${Assets.music.background}'),
() => loopSingleAudio.onCall(
'packages/pinball_audio/${Assets.music.background}',
volume: .6,
),
).called(1);
});
});

@ -9,9 +9,9 @@ import 'package:pinball_flame/pinball_flame.dart';
class MultiballBlinkingBehavior extends TimerComponent
with ParentIsA<Multiball> {
/// {@macro multiball_blinking_behavior}
MultiballBlinkingBehavior() : super(period: 0.1);
MultiballBlinkingBehavior() : super(period: 0.18);
final _maxBlinks = 10;
final _maxBlinks = 28;
int _blinksCounter = 0;

@ -3,7 +3,7 @@ import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
const _path =
'https://github.com/VGVentures/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/';
'https://github.com/flutter/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/';
extension StoryAddGame on Story {
void addGame({

@ -21,7 +21,7 @@ void main() {
'MultiballBlinkingBehavior',
() {
flameTester.testGameWidget(
'calls onBlink every 0.1 seconds when animation state is animated',
'calls onBlink every 0.18 seconds when animation state is animated',
setUp: (game, tester) async {
final behavior = MultiballBlinkingBehavior();
final bloc = _MockMultiballCubit();
@ -48,7 +48,7 @@ void main() {
verify(bloc.onBlink).called(1);
await tester.pump();
game.update(0.1);
game.update(0.18);
await streamController.close();
verify(bloc.onBlink).called(1);
@ -124,7 +124,7 @@ void main() {
);
flameTester.testGameWidget(
'onTick stops after 10 blinks repetitions',
'onTick stops after 28 blinks',
setUp: (game, tester) async {
final behavior = MultiballBlinkingBehavior();
final bloc = _MockMultiballCubit();
@ -148,7 +148,7 @@ void main() {
);
await tester.pump();
for (var i = 0; i < 10; i++) {
for (var i = 0; i < 28; i++) {
behavior.onTick();
}

@ -12,13 +12,13 @@ class ShareRepository {
final String _appUrl;
/// Url to the Github Open Source Pinball project.
static const openSourceCode = 'https://github.com/VGVentures/pinball';
static const openSourceCode = 'https://github.com/flutter/pinball';
/// Url to the Google IO Event.
static const googleIOEvent = 'https://events.google.com/io/';
/// Url to the Pinball game.
static const pinballGameUrl = 'https://ashehwkdkdjruejdnensjsjdne.web.app/#/';
static const pinballGameUrl = 'https://pinball.flutter.dev';
/// Returns a url to share the [value] on the given [platform].
///

@ -5,6 +5,7 @@ import 'package:mocktail/mocktail.dart';
import 'package:pinball/app/app.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_audio/pinball_audio.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
class _MockAuthenticationRepository extends Mock
@ -17,18 +18,25 @@ class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
class _MockShareRepository extends Mock implements ShareRepository {}
class _MockPlatformHelper extends Mock implements PlatformHelper {
@override
bool get isMobile => false;
}
void main() {
group('App', () {
late AuthenticationRepository authenticationRepository;
late LeaderboardRepository leaderboardRepository;
late ShareRepository shareRepository;
late PinballAudioPlayer pinballAudioPlayer;
late PlatformHelper platformHelper;
setUp(() {
authenticationRepository = _MockAuthenticationRepository();
leaderboardRepository = _MockLeaderboardRepository();
shareRepository = _MockShareRepository();
pinballAudioPlayer = _MockPinballAudioPlayer();
platformHelper = _MockPlatformHelper();
when(pinballAudioPlayer.load).thenAnswer((_) => [Future.value()]);
});
@ -39,6 +47,7 @@ void main() {
leaderboardRepository: leaderboardRepository,
shareRepository: shareRepository,
pinballAudioPlayer: pinballAudioPlayer,
platformHelper: platformHelper,
),
);
await tester.pump(const Duration(milliseconds: 1100));

@ -12,6 +12,7 @@ import 'package:pinball/select_character/select_character.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 {
@override
@ -28,11 +29,17 @@ class _TestGame extends Forge2DGame {
Future<void> pump(
List<Component> children, {
CharacterThemeCubit? characterThemeBloc,
PlatformHelper? platformHelper,
}) async {
await ensureAdd(
FlameBlocProvider<CharacterThemeCubit, CharacterThemeState>.value(
value: characterThemeBloc ?? CharacterThemeCubit(),
children: children,
children: [
FlameProvider.value(
platformHelper ?? _MockPlatformHelper(),
children: children,
),
],
),
);
}
@ -43,6 +50,8 @@ class _MockBallCubit extends Mock implements BallCubit {}
class _MockArcadeBackgroundCubit extends Mock implements ArcadeBackgroundCubit {
}
class _MockPlatformHelper extends Mock implements PlatformHelper {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@ -68,8 +77,45 @@ void main() {
);
flameTester.test(
'onNewState calls onCharacterSelected on the arcade background bloc',
'onNewState does not call onCharacterSelected on the arcade background '
'bloc when platform is mobile',
(game) async {
final platformHelper = _MockPlatformHelper();
when(() => platformHelper.isMobile).thenAnswer((_) => true);
final arcadeBackgroundBloc = _MockArcadeBackgroundCubit();
whenListen(
arcadeBackgroundBloc,
const Stream<ArcadeBackgroundState>.empty(),
initialState: const ArcadeBackgroundState.initial(),
);
final behavior = CharacterSelectionBehavior();
await game.pump(
[
behavior,
ZCanvasComponent(),
Plunger.test(compressionDistance: 10),
Ball.test(),
],
platformHelper: platformHelper,
);
const dinoThemeState = CharacterThemeState(theme.DinoTheme());
behavior.onNewState(dinoThemeState);
await game.ready();
verifyNever(
() => arcadeBackgroundBloc
.onCharacterSelected(dinoThemeState.characterTheme),
);
},
);
flameTester.test(
'onNewState calls onCharacterSelected on the arcade background '
'bloc when platform is not mobile',
(game) async {
final platformHelper = _MockPlatformHelper();
when(() => platformHelper.isMobile).thenAnswer((_) => false);
final arcadeBackgroundBloc = _MockArcadeBackgroundCubit();
whenListen(
arcadeBackgroundBloc,
@ -79,13 +125,16 @@ void main() {
final arcadeBackground =
ArcadeBackground.test(bloc: arcadeBackgroundBloc);
final behavior = CharacterSelectionBehavior();
await game.pump([
arcadeBackground,
behavior,
ZCanvasComponent(),
Plunger.test(compressionDistance: 10),
Ball.test(),
]);
await game.pump(
[
arcadeBackground,
behavior,
ZCanvasComponent(),
Plunger.test(compressionDistance: 10),
Ball.test(),
],
platformHelper: platformHelper,
);
const dinoThemeState = CharacterThemeState(theme.DinoTheme());
behavior.onNewState(dinoThemeState);
@ -101,6 +150,8 @@ void main() {
flameTester.test(
'onNewState calls onCharacterSelected on the ball bloc',
(game) async {
final platformHelper = _MockPlatformHelper();
when(() => platformHelper.isMobile).thenAnswer((_) => false);
final ballBloc = _MockBallCubit();
whenListen(
ballBloc,
@ -109,13 +160,16 @@ void main() {
);
final ball = Ball.test(bloc: ballBloc);
final behavior = CharacterSelectionBehavior();
await game.pump([
ball,
behavior,
ZCanvasComponent(),
Plunger.test(compressionDistance: 10),
ArcadeBackground.test(),
]);
await game.pump(
[
ball,
behavior,
ZCanvasComponent(),
Plunger.test(compressionDistance: 10),
ArcadeBackground.test(),
],
platformHelper: platformHelper,
);
const dinoThemeState = CharacterThemeState(theme.DinoTheme());
behavior.onNewState(dinoThemeState);

@ -47,7 +47,10 @@ class _TestGame extends Forge2DGame
]);
}
Future<void> pump(Backbox component) async {
Future<void> pump(
Backbox component, {
PlatformHelper? platformHelper,
}) async {
// Not needed once https://github.com/flame-engine/flame/issues/1607
// is fixed
await onLoad();
@ -55,8 +58,15 @@ class _TestGame extends Forge2DGame
FlameBlocProvider<GameBloc, GameState>.value(
value: GameBloc(),
children: [
FlameProvider.value(
_MockAppLocalizations(),
MultiFlameProvider(
providers: [
FlameProvider<AppLocalizations>.value(
_MockAppLocalizations(),
),
FlameProvider<PlatformHelper>.value(
platformHelper ?? _MockPlatformHelper(),
),
],
children: [component],
),
],
@ -89,6 +99,8 @@ class _MockShareRepository extends Mock implements ShareRepository {}
class _MockTapDownInfo extends Mock implements TapDownInfo {}
class _MockTapUpInfo extends Mock implements TapUpInfo {}
class _MockUrlLauncher extends Mock
with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {}
@ -161,7 +173,7 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
String get leaderboardErrorMessage => '';
@override
String iGotScoreAtPinball(int _) => '';
String iGotScoreAtPinball(String _) => '';
}
void main() {
@ -191,9 +203,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(game.descendants(), contains(backbox));
},
);
@ -209,8 +223,8 @@ void main() {
Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
platformHelper: platformHelper,
),
platformHelper: platformHelper,
);
await tester.pump();
},
@ -231,9 +245,11 @@ void main() {
initialEntries: [LeaderboardEntryData.empty],
),
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
backbox.requestInitials(
score: 0,
character: game.character,
@ -263,9 +279,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
game.onKeyEvent(_mockKeyUp(LogicalKeyboardKey.enter), {});
verify(
@ -292,9 +310,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.descendants().whereType<GameOverInfoDisplay>().length,
@ -322,9 +342,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.overlays.value,
@ -349,9 +371,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.overlays.value,
@ -372,9 +396,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.descendants().whereType<GameOverInfoDisplay>().length,
@ -395,9 +421,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
final shareLink =
game.descendants().whereType<ShareLinkComponent>().first;
@ -425,9 +453,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game
@ -457,9 +487,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.descendants().whereType<ShareDisplay>().length,
@ -504,13 +536,15 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: shareRepository,
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
final facebookButton =
game.descendants().whereType<FacebookButtonComponent>().first;
facebookButton.onTapDown(_MockTapDownInfo());
facebookButton.onTapUp(_MockTapUpInfo());
await game.ready();
@ -558,13 +592,15 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: shareRepository,
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
final facebookButton =
game.descendants().whereType<TwitterButtonComponent>().first;
facebookButton.onTapDown(_MockTapDownInfo());
facebookButton.onTapUp(_MockTapUpInfo());
await game.ready();
@ -590,9 +626,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.descendants().whereType<LeaderboardDisplay>().length,
@ -613,9 +651,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
expect(
game.descendants().whereType<LeaderboardFailureDisplay>().length,
@ -637,9 +677,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
backbox.removeFromParent();
await game.ready();
@ -678,9 +720,11 @@ void main() {
final backbox = Backbox.test(
bloc: bloc,
shareRepository: _MockShareRepository(),
);
await game.pump(
backbox,
platformHelper: platformHelper,
);
await game.pump(backbox);
game.update(4);
verify(

@ -68,6 +68,8 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
class _MockTapDownInfo extends Mock implements TapDownInfo {}
class _MockTapUpInfo extends Mock implements TapUpInfo {}
class _MockUrlLauncher extends Mock
with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {}
@ -135,7 +137,7 @@ void main() {
final googleLink =
component.descendants().whereType<GoogleIOLinkComponent>().first;
googleLink.onTapDown(_MockTapDownInfo());
googleLink.onTapUp(_MockTapUpInfo());
await game.ready();

@ -52,7 +52,7 @@ class _MockAppLocalizations extends Mock implements AppLocalizations {
String get socialMediaAccount => '';
}
class _MockTapDownInfo extends Mock implements TapDownInfo {}
class _MockTapUpInfo extends Mock implements TapUpInfo {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@ -74,7 +74,7 @@ void main() {
(game) async {
var tapped = false;
final tapDownInfo = _MockTapDownInfo();
final tapUpInfo = _MockTapUpInfo();
final component = ShareDisplay(
onShare: (_) => tapped = true,
);
@ -83,7 +83,7 @@ void main() {
final facebookButton =
component.descendants().whereType<FacebookButtonComponent>().first;
facebookButton.onTapDown(tapDownInfo);
facebookButton.onTapUp(tapUpInfo);
expect(tapped, isTrue);
},
@ -94,7 +94,7 @@ void main() {
(game) async {
var tapped = false;
final tapDownInfo = _MockTapDownInfo();
final tapUpInfo = _MockTapUpInfo();
final component = ShareDisplay(
onShare: (_) => tapped = true,
);
@ -103,7 +103,7 @@ void main() {
final twitterButton =
component.descendants().whereType<TwitterButtonComponent>().first;
twitterButton.onTapDown(tapDownInfo);
twitterButton.onTapUp(tapUpInfo);
expect(tapped, isTrue);
},

@ -15,6 +15,7 @@ import 'package:pinball_audio/pinball_audio.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';
import 'package:share_repository/share_repository.dart';
class _TestGame extends Forge2DGame with HasTappables {
@ -55,6 +56,9 @@ class _TestGame extends Forge2DGame with HasTappables {
FlameProvider<AppLocalizations>.value(
_MockAppLocalizations(),
),
FlameProvider<PlatformHelper>.value(
_MockPlatformHelper(),
),
],
children: children,
),
@ -71,6 +75,11 @@ class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
class _MockShareRepository extends Mock implements ShareRepository {}
class _MockPlatformHelper extends Mock implements PlatformHelper {
@override
bool get isMobile => false;
}
class _MockAppLocalizations extends Mock implements AppLocalizations {
@override
String get score => '';

@ -8,6 +8,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/behaviors/behaviors.dart';
import 'package:pinball/game/components/google_gallery/behaviors/behaviors.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
@ -116,5 +117,51 @@ void main() {
verify(googleWordBloc.onBonusAwarded).called(1);
},
);
flameTester.testGameWidget(
'adds BonusBallSpawningBehavior to the game when all letters '
'in google word are activated',
setUp: (game, tester) async {
final behavior = GoogleWordBonusBehavior();
final parent = GoogleGallery.test();
final googleWord = GoogleWord(position: Vector2.zero());
final googleWordBloc = _MockGoogleWordCubit();
final streamController = StreamController<GoogleWordState>();
whenListen(
googleWordBloc,
streamController.stream,
initialState: GoogleWordState.initial(),
);
await parent.add(googleWord);
await game.pump(
parent,
gameBloc: gameBloc,
googleWordBloc: googleWordBloc,
);
await parent.ensureAdd(behavior);
streamController.add(
const GoogleWordState(
letterSpriteStates: {
0: GoogleLetterSpriteState.lit,
1: GoogleLetterSpriteState.lit,
2: GoogleLetterSpriteState.lit,
3: GoogleLetterSpriteState.lit,
4: GoogleLetterSpriteState.lit,
5: GoogleLetterSpriteState.lit,
},
),
);
await tester.pump();
await game.ready();
expect(
game.descendants().whereType<BonusBallSpawningBehavior>().length,
equals(1),
);
},
);
});
}

@ -78,8 +78,24 @@ void main() {
);
test(
'is false when the bonusHistory has changed '
'with a bonus different than GameBonus.dashNest', () {
'is true when the bonusHistory has changed '
'with a new GameBonus.googleWord',
() {
final previous = GameState.initial();
final state = previous.copyWith(
bonusHistory: [GameBonus.googleWord],
);
expect(
MultiballsBehavior().listenWhen(previous, state),
isTrue,
);
},
);
test(
'is false when the bonusHistory has changed with a bonus other than '
'GameBonus.dashNest or GameBonus.googleWord', () {
final previous =
GameState.initial().copyWith(bonusHistory: [GameBonus.dashNest]);
final state = previous.copyWith(

@ -16,6 +16,7 @@ import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/src/pinball_audio.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
class _TestPinballGame extends PinballGame {
@ -27,6 +28,7 @@ class _TestPinballGame extends PinballGame {
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
audioPlayer: _MockPinballAudioPlayer(),
platformHelper: _MockPlatformHelper(),
);
@override
@ -47,6 +49,7 @@ class _TestDebugPinballGame extends DebugPinballGame {
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
audioPlayer: _MockPinballAudioPlayer(),
platformHelper: _MockPlatformHelper(),
);
@override
@ -88,6 +91,11 @@ class _MockShareRepository extends Mock implements ShareRepository {}
class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {}
class _MockPlatformHelper extends Mock implements PlatformHelper {
@override
bool get isMobile => false;
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();

@ -15,6 +15,7 @@ import 'package:pinball/more_information/more_information.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:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
import '../../helpers/helpers.dart';
@ -28,6 +29,7 @@ class _TestPinballGame extends PinballGame {
gameBloc: GameBloc(),
l10n: _MockAppLocalizations(),
audioPlayer: _MockPinballAudioPlayer(),
platformHelper: _MockPlatformHelper(),
);
@override
@ -63,6 +65,11 @@ class _MockLeaderboardRepository extends Mock implements LeaderboardRepository {
class _MockShareRepository extends Mock implements ShareRepository {}
class _MockPlatformHelper extends Mock implements PlatformHelper {
@override
bool get isMobile => false;
}
void main() {
final game = _TestPinballGame();

@ -12,6 +12,7 @@ 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_ui/pinball_ui.dart';
import 'package:platform_helper/platform_helper.dart';
import 'package:share_repository/share_repository.dart';
class _MockAssetsManagerCubit extends Mock implements AssetsManagerCubit {}
@ -29,6 +30,8 @@ class _MockStartGameBloc extends Mock implements StartGameBloc {}
class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {}
class _MockPlatformHelper extends Mock implements PlatformHelper {}
PinballAudioPlayer _buildDefaultPinballAudioPlayer() {
final audioPlayer = _MockPinballAudioPlayer();
when(audioPlayer.load).thenAnswer((_) => [Future.value()]);
@ -60,6 +63,7 @@ extension PumpApp on WidgetTester {
LeaderboardRepository? leaderboardRepository,
ShareRepository? shareRepository,
PinballAudioPlayer? pinballAudioPlayer,
PlatformHelper? platformHelper,
}) {
return runAsync(() {
return pumpWidget(
@ -74,6 +78,9 @@ extension PumpApp on WidgetTester {
RepositoryProvider.value(
value: pinballAudioPlayer ?? _buildDefaultPinballAudioPlayer(),
),
RepositoryProvider.value(
value: platformHelper ?? _MockPlatformHelper(),
),
],
child: MultiBlocProvider(
providers: [

@ -17,27 +17,23 @@ void main() {
setUp(() async {
l10n = await AppLocalizations.delegate.load(const Locale('en'));
platformHelper = _MockPlatformHelper();
when(() => platformHelper.isMobile).thenAnswer((_) => false);
});
testWidgets(
'can be instantiated without passing in a platform helper',
(tester) async {
await tester.pumpApp(
HowToPlayDialog(
onDismissCallback: () {},
),
);
expect(find.byType(HowToPlayDialog), findsOneWidget);
},
);
test('can be instantiated', () {
expect(
HowToPlayDialog(onDismissCallback: () {}),
isA<HowToPlayDialog>(),
);
});
testWidgets('displays content for desktop', (tester) async {
when(() => platformHelper.isMobile).thenAnswer((_) => false);
await tester.pumpApp(
HowToPlayDialog(
platformHelper: platformHelper,
onDismissCallback: () {},
),
platformHelper: platformHelper,
);
expect(find.text(l10n.howToPlay), findsOneWidget);
expect(find.text(l10n.tipsForFlips), findsOneWidget);
@ -50,9 +46,9 @@ void main() {
when(() => platformHelper.isMobile).thenAnswer((_) => true);
await tester.pumpApp(
HowToPlayDialog(
platformHelper: platformHelper,
onDismissCallback: () {},
),
platformHelper: platformHelper,
);
expect(find.text(l10n.howToPlay), findsOneWidget);
expect(find.text(l10n.tipsForFlips), findsOneWidget);
@ -75,6 +71,7 @@ void main() {
);
},
),
platformHelper: platformHelper,
);
expect(find.byType(HowToPlayDialog), findsNothing);
await tester.tap(find.text('test'));
@ -100,6 +97,7 @@ void main() {
);
},
),
platformHelper: platformHelper,
);
expect(find.byType(HowToPlayDialog), findsNothing);
await tester.tap(find.text('test'));

@ -138,7 +138,7 @@ void main() {
);
<String, String>{
'Open Source Code': 'https://github.com/VGVentures/pinball',
'Open Source Code': 'https://github.com/flutter/pinball',
'Google I/O': 'https://events.google.com/io/',
'Flutter Games': 'http://flutter.dev/games',
'How its made':

@ -7,6 +7,7 @@ import 'package:pinball/how_to_play/how_to_play.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:platform_helper/platform_helper.dart';
import '../../helpers/helpers.dart';
@ -18,10 +19,16 @@ class _MockCharacterThemeCubit extends Mock implements CharacterThemeCubit {}
class _MockPinballAudioPlayer extends Mock implements PinballAudioPlayer {}
class _MockPlatformHelper extends Mock implements PlatformHelper {
@override
bool get isMobile => false;
}
void main() {
late StartGameBloc startGameBloc;
late PinballAudioPlayer pinballAudioPlayer;
late CharacterThemeCubit characterThemeCubit;
late PlatformHelper platformHelper;
group('StartGameListener', () {
setUp(() async {
@ -30,6 +37,7 @@ void main() {
startGameBloc = _MockStartGameBloc();
pinballAudioPlayer = _MockPinballAudioPlayer();
characterThemeCubit = _MockCharacterThemeCubit();
platformHelper = _MockPlatformHelper();
});
group('on selectCharacter status', () {
@ -121,6 +129,7 @@ void main() {
child: SizedBox.shrink(),
),
startGameBloc: startGameBloc,
platformHelper: platformHelper,
);
await tester.pumpAndSettle();
@ -213,6 +222,7 @@ void main() {
child: SizedBox.shrink(),
),
startGameBloc: startGameBloc,
platformHelper: platformHelper,
);
await tester.pumpAndSettle();
@ -244,6 +254,7 @@ void main() {
),
startGameBloc: startGameBloc,
pinballAudioPlayer: pinballAudioPlayer,
platformHelper: platformHelper,
);
await tester.pumpAndSettle();

Loading…
Cancel
Save