diff --git a/lib/start_game/bloc/start_game_bloc.dart b/lib/start_game/bloc/start_game_bloc.dart new file mode 100644 index 00000000..ba44d88c --- /dev/null +++ b/lib/start_game/bloc/start_game_bloc.dart @@ -0,0 +1,58 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:pinball/game/game.dart'; + +part 'start_game_event.dart'; +part 'start_game_state.dart'; + +/// {@template start_game_bloc} +/// Bloc that manages the app flow before the game starts. +/// {@endtemplate} +class StartGameBloc extends Bloc { + /// {@macro start_game_bloc} + StartGameBloc({ + required PinballGame game, + }) : _game = game, + super(const StartGameState.initial()) { + on(_onPlayTapped); + on(_onCharacterSelected); + on(_onHowToPlayFinished); + } + + final PinballGame _game; + + void _onPlayTapped( + PlayTapped event, + Emitter emit, + ) { + _game.gameFlowController.start(); + + emit( + state.copyWith( + status: StartGameStatus.selectCharacter, + ), + ); + } + + void _onCharacterSelected( + CharacterSelected event, + Emitter emit, + ) { + emit( + state.copyWith( + status: StartGameStatus.howToPlay, + ), + ); + } + + void _onHowToPlayFinished( + HowToPlayFinished event, + Emitter emit, + ) { + emit( + state.copyWith( + status: StartGameStatus.play, + ), + ); + } +} diff --git a/lib/start_game/bloc/start_game_event.dart b/lib/start_game/bloc/start_game_event.dart new file mode 100644 index 00000000..ce164e97 --- /dev/null +++ b/lib/start_game/bloc/start_game_event.dart @@ -0,0 +1,42 @@ +part of 'start_game_bloc.dart'; + +/// {@template start_game_event} +/// Event added during the start game flow. +/// {@endtemplate} +abstract class StartGameEvent extends Equatable { + /// {@macro start_game_event} + const StartGameEvent(); +} + +/// {@template play_tapped} +/// Play tapped event. +/// {@endtemplate} +class PlayTapped extends StartGameEvent { + /// {@macro play_tapped} + const PlayTapped(); + + @override + List get props => []; +} + +/// {@template character_selected} +/// Character selected event. +/// {@endtemplate} +class CharacterSelected extends StartGameEvent { + /// {@macro character_selected} + const CharacterSelected(); + + @override + List get props => []; +} + +/// {@template how_to_play_finished} +/// How to play finished event. +/// {@endtemplate} +class HowToPlayFinished extends StartGameEvent { + /// {@macro how_to_play_finished} + const HowToPlayFinished(); + + @override + List get props => []; +} diff --git a/lib/start_game/bloc/start_game_state.dart b/lib/start_game/bloc/start_game_state.dart new file mode 100644 index 00000000..ad7c7cbe --- /dev/null +++ b/lib/start_game/bloc/start_game_state.dart @@ -0,0 +1,44 @@ +part of 'start_game_bloc.dart'; + +/// Defines status of start game flow. +enum StartGameStatus { + /// Initial status. + initial, + + /// Selection characters status. + selectCharacter, + + /// How to play status. + howToPlay, + + /// Play status. + play, +} + +/// {@template start_game_state} +/// Represents the state of flow before the game starts. +/// {@endtemplate} +class StartGameState extends Equatable { + /// {@macro start_game_state} + const StartGameState({ + required this.status, + }); + + /// Initial [StartGameState]. + const StartGameState.initial() : this(status: StartGameStatus.initial); + + /// Status of [StartGameState]. + final StartGameStatus status; + + /// Creates a copy of [StartGameState]. + StartGameState copyWith({ + StartGameStatus? status, + }) { + return StartGameState( + status: status ?? this.status, + ); + } + + @override + List get props => [status]; +} diff --git a/lib/start_game/start_game.dart b/lib/start_game/start_game.dart new file mode 100644 index 00000000..7171c66d --- /dev/null +++ b/lib/start_game/start_game.dart @@ -0,0 +1 @@ +export 'bloc/start_game_bloc.dart'; diff --git a/test/start_game/bloc/start_game_bloc_test.dart b/test/start_game/bloc/start_game_bloc_test.dart new file mode 100644 index 00000000..ec1b3ced --- /dev/null +++ b/test/start_game/bloc/start_game_bloc_test.dart @@ -0,0 +1,62 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball/start_game/bloc/start_game_bloc.dart'; + +import '../../helpers/helpers.dart'; + +void main() { + late PinballGame pinballGame; + + setUp(() { + pinballGame = MockPinballGame(); + + when( + () => pinballGame.gameFlowController, + ).thenReturn( + MockGameFlowController(), + ); + }); + + group('StartGameBloc', () { + blocTest( + 'on PlayTapped changes status to selectCharacter', + build: () => StartGameBloc( + game: pinballGame, + ), + act: (bloc) => bloc.add(const PlayTapped()), + expect: () => [ + const StartGameState( + status: StartGameStatus.selectCharacter, + ) + ], + ); + + blocTest( + 'on CharacterSelected changes status to howToPlay', + build: () => StartGameBloc( + game: pinballGame, + ), + act: (bloc) => bloc.add(const CharacterSelected()), + expect: () => [ + const StartGameState( + status: StartGameStatus.howToPlay, + ) + ], + ); + + blocTest( + 'on HowToPlayFinished changes status to play', + build: () => StartGameBloc( + game: pinballGame, + ), + act: (bloc) => bloc.add(const HowToPlayFinished()), + expect: () => [ + const StartGameState( + status: StartGameStatus.play, + ) + ], + ); + }); +} diff --git a/test/start_game/bloc/start_game_event_test.dart b/test/start_game/bloc/start_game_event_test.dart new file mode 100644 index 00000000..cf481d9f --- /dev/null +++ b/test/start_game/bloc/start_game_event_test.dart @@ -0,0 +1,29 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/start_game/bloc/start_game_bloc.dart'; + +void main() { + group('StartGameEvent', () { + test('PlayTapped supports value equality', () { + expect( + PlayTapped(), + equals(PlayTapped()), + ); + }); + + test('CharacterSelected supports value equality', () { + expect( + CharacterSelected(), + equals(CharacterSelected()), + ); + }); + + test('HowToPlayFinished supports value equality', () { + expect( + HowToPlayFinished(), + equals(HowToPlayFinished()), + ); + }); + }); +} diff --git a/test/start_game/bloc/start_game_state_test.dart b/test/start_game/bloc/start_game_state_test.dart new file mode 100644 index 00000000..7ede696d --- /dev/null +++ b/test/start_game/bloc/start_game_state_test.dart @@ -0,0 +1,43 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/start_game/bloc/start_game_bloc.dart'; + +void main() { + group('StartGameState', () { + final testState = StartGameState( + status: StartGameStatus.selectCharacter, + ); + + test('initial state has correct values', () { + final state = StartGameState( + status: StartGameStatus.initial, + ); + + expect(state, StartGameState.initial()); + }); + + test('supports value equality', () { + final secondState = StartGameState( + status: StartGameStatus.selectCharacter, + ); + + expect(testState, secondState); + }); + + test('supports copyWith', () { + final secondState = testState.copyWith(); + + expect(testState, secondState); + }); + + test('has correct props', () { + expect( + testState.props, + equals([ + StartGameStatus.selectCharacter, + ]), + ); + }); + }); +}