From 93c343e3ac0423b7bebe12f4a3e191c9e094e4dd Mon Sep 17 00:00:00 2001 From: RuiAlonso Date: Thu, 17 Mar 2022 09:27:22 +0100 Subject: [PATCH] refactor: adapt bloc events and models to what leaderboard repository has --- lib/leaderboard/bloc/leaderboard_bloc.dart | 36 ++++- lib/leaderboard/bloc/leaderboard_event.dart | 13 +- lib/leaderboard/bloc/leaderboard_state.dart | 46 ++++-- .../bloc/leaderboard_bloc_test.dart | 146 +++++++++++++----- .../bloc/leaderboard_event_test.dart | 29 +++- .../bloc/leaderboard_state_test.dart | 23 ++- 6 files changed, 216 insertions(+), 77 deletions(-) diff --git a/lib/leaderboard/bloc/leaderboard_bloc.dart b/lib/leaderboard/bloc/leaderboard_bloc.dart index 3fd65635..9a4742f0 100644 --- a/lib/leaderboard/bloc/leaderboard_bloc.dart +++ b/lib/leaderboard/bloc/leaderboard_bloc.dart @@ -8,26 +8,52 @@ part 'leaderboard_event.dart'; part 'leaderboard_state.dart'; class LeaderboardRepository { - Future> fetchRanking() { + Future> fetchTop10Leaderboard() { return Future.value([]); } + + Future addLeaderboardEntry(LeaderboardEntry entry) { + return Future.value(); + } } class LeaderboardBloc extends Bloc { LeaderboardBloc(this._leaderboardRepository) : super(const LeaderboardState()) { - on(_onLeaderboardRequested); + on(_onTop10Fetched); + on(_onLeaderboardEntryAdded); } final LeaderboardRepository _leaderboardRepository; - FutureOr _onLeaderboardRequested( - LeaderboardRequested event, + FutureOr _onTop10Fetched( + Top10Fetched event, + Emitter emit, + ) async { + emit(state.copyWith(status: LeaderboardStatus.loading)); + try { + final top10Leaderboard = + await _leaderboardRepository.fetchTop10Leaderboard(); + emit( + state.copyWith( + status: LeaderboardStatus.success, + leaderboard: top10Leaderboard, + ), + ); + } catch (error, _) { + emit(state.copyWith(status: LeaderboardStatus.error)); + addError(error); + } + } + + FutureOr _onLeaderboardEntryAdded( + LeaderboardEntryAdded event, Emitter emit, ) async { emit(state.copyWith(status: LeaderboardStatus.loading)); try { - final ranking = await _leaderboardRepository.fetchRanking(); + final ranking = + await _leaderboardRepository.addLeaderboardEntry(event.entry); emit( state.copyWith( status: LeaderboardStatus.success, diff --git a/lib/leaderboard/bloc/leaderboard_event.dart b/lib/leaderboard/bloc/leaderboard_event.dart index 066922e7..4c06eabd 100644 --- a/lib/leaderboard/bloc/leaderboard_event.dart +++ b/lib/leaderboard/bloc/leaderboard_event.dart @@ -7,6 +7,15 @@ abstract class LeaderboardEvent extends Equatable { List get props => []; } -class LeaderboardRequested extends LeaderboardEvent { - const LeaderboardRequested(); +class Top10Fetched extends LeaderboardEvent { + const Top10Fetched(); +} + +class LeaderboardEntryAdded extends LeaderboardEvent { + const LeaderboardEntryAdded({required this.entry}); + + final LeaderboardEntry entry; + + @override + List get props => [entry]; } diff --git a/lib/leaderboard/bloc/leaderboard_state.dart b/lib/leaderboard/bloc/leaderboard_state.dart index 7d2a43b8..8b254496 100644 --- a/lib/leaderboard/bloc/leaderboard_state.dart +++ b/lib/leaderboard/bloc/leaderboard_state.dart @@ -5,39 +5,59 @@ enum LeaderboardStatus { loading, success, error } class LeaderboardState extends Equatable { const LeaderboardState({ this.status = LeaderboardStatus.loading, - this.ranking = const [], + this.ranking = const LeaderboardRanking( + ranking: 0, + outOf: 0, + ), + this.leaderboard = const [], }); final LeaderboardStatus status; - final List ranking; + final LeaderboardRanking ranking; + final List leaderboard; @override - List get props => [status, ranking]; + List get props => [status, ranking, leaderboard]; LeaderboardState copyWith({ LeaderboardStatus? status, - List? ranking, + LeaderboardRanking? ranking, + List? leaderboard, }) { return LeaderboardState( status: status ?? this.status, ranking: ranking ?? this.ranking, + leaderboard: leaderboard ?? this.leaderboard, ); } } -class Competitor extends Equatable { - const Competitor({ - required this.rank, - required this.characterTheme, - required this.initials, +enum CharacterType { dash, sparky, android, dino } + +class LeaderboardEntry extends Equatable { + const LeaderboardEntry({ + required this.playerInitials, required this.score, + required this.character, }); - final int rank; - final CharacterTheme characterTheme; - final String initials; + final String playerInitials; final int score; + final CharacterType character; + + @override + List get props => [playerInitials, character, score]; +} + +class LeaderboardRanking extends Equatable { + const LeaderboardRanking({ + required this.ranking, + required this.outOf, + }); + + final int ranking; + final int outOf; @override - List get props => [rank, characterTheme, initials, score]; + List get props => [ranking, outOf]; } diff --git a/test/leaderboard/bloc/leaderboard_bloc_test.dart b/test/leaderboard/bloc/leaderboard_bloc_test.dart index f8ff8fd6..a509e61e 100644 --- a/test/leaderboard/bloc/leaderboard_bloc_test.dart +++ b/test/leaderboard/bloc/leaderboard_bloc_test.dart @@ -4,7 +4,6 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pinball/leaderboard/leaderboard.dart'; -import 'package:pinball_theme/pinball_theme.dart'; class MockLeaderboardRepository extends Mock implements LeaderboardRepository {} @@ -16,50 +15,50 @@ void main() { leaderboardRepository = MockLeaderboardRepository(); }); - test('initial state has state loading and empty ranking', () { + test('initial state has state loading no ranking and empty leaderboard', + () { final bloc = LeaderboardBloc(leaderboardRepository); expect(bloc.state.status, equals(LeaderboardStatus.loading)); - expect(bloc.state.ranking.isEmpty, isTrue); + expect(bloc.state.ranking.ranking, equals(0)); + expect(bloc.state.ranking.outOf, equals(0)); + expect(bloc.state.leaderboard.isEmpty, isTrue); }); - group('LeaderboardRequested', () { - final ranking = [ - Competitor( - rank: 1, - characterTheme: DashTheme(), - initials: 'ABC', - score: 100, - ), - Competitor( - rank: 2, - characterTheme: SparkyTheme(), - initials: 'DEF', - score: 200, - ), - Competitor( - rank: 3, - characterTheme: AndroidTheme(), - initials: 'GHI', - score: 300, - ), - Competitor( - rank: 4, - characterTheme: DinoTheme(), - initials: 'JKL', - score: 400, - ), + group('Top10Fetched', () { + final top10Scores = [ + 2500, + 2200, + 2200, + 2000, + 1800, + 1400, + 1300, + 1000, + 600, + 300, + 100, ]; + final top10Leaderboard = top10Scores + .map( + (score) => LeaderboardEntry( + playerInitials: 'user$score', + score: score, + character: CharacterType.dash, + ), + ) + .toList(); + blocTest( 'emits [loading, success] statuses ' - 'when fetchRanking succeeds', + 'when fetchTop10Leaderboard succeeds', setUp: () { - when(() => leaderboardRepository.fetchRanking()).thenAnswer( - (_) async => ranking, + when(() => leaderboardRepository.fetchTop10Leaderboard()).thenAnswer( + (_) async => top10Leaderboard, ); }, build: () => LeaderboardBloc(leaderboardRepository), - act: (bloc) => bloc.add(LeaderboardRequested()), + act: (bloc) => bloc.add(Top10Fetched()), expect: () => [ const LeaderboardState(), isA() @@ -69,31 +68,96 @@ void main() { equals(LeaderboardStatus.success), ) ..having( - (element) => element.ranking.length, - 'ranking', - equals(ranking.length), + (element) => element.leaderboard.length, + 'leaderboard', + equals(top10Leaderboard.length), ) ], verify: (_) => - verify(() => leaderboardRepository.fetchRanking()).called(1), + verify(() => leaderboardRepository.fetchTop10Leaderboard()) + .called(1), ); blocTest( 'emits [loading, error] statuses ' - 'when fetchRanking fails', + 'when fetchTop10Leaderboard fails', setUp: () { - when(() => leaderboardRepository.fetchRanking()).thenThrow( + when(() => leaderboardRepository.fetchTop10Leaderboard()).thenThrow( Exception(), ); }, build: () => LeaderboardBloc(leaderboardRepository), - act: (bloc) => bloc.add(LeaderboardRequested()), + act: (bloc) => bloc.add(Top10Fetched()), expect: () => [ const LeaderboardState(), const LeaderboardState(status: LeaderboardStatus.error), ], verify: (_) => - verify(() => leaderboardRepository.fetchRanking()).called(1), + verify(() => leaderboardRepository.fetchTop10Leaderboard()) + .called(1), + errors: () => [isA()], + ); + }); + + group('LeaderboardEntryAdded', () { + final leaderboardEntry = LeaderboardEntry( + playerInitials: 'ABC', + score: 1500, + character: CharacterType.dash, + ); + + final ranking = LeaderboardRanking(ranking: 3, outOf: 4); + + blocTest( + 'emits [loading, success] statuses ' + 'when addLeaderboardEntry succeeds', + setUp: () { + when( + () => leaderboardRepository.addLeaderboardEntry(leaderboardEntry), + ).thenAnswer( + (_) async => ranking, + ); + }, + build: () => LeaderboardBloc(leaderboardRepository), + act: (bloc) => bloc.add(LeaderboardEntryAdded(entry: leaderboardEntry)), + expect: () => [ + const LeaderboardState(), + isA() + ..having( + (element) => element.status, + 'status', + equals(LeaderboardStatus.success), + ) + ..having( + (element) => element.ranking, + 'ranking', + equals(ranking), + ) + ], + verify: (_) => verify( + () => leaderboardRepository.addLeaderboardEntry(leaderboardEntry), + ).called(1), + ); + + blocTest( + 'emits [loading, error] statuses ' + 'when addLeaderboardEntry fails', + setUp: () { + when( + () => leaderboardRepository.addLeaderboardEntry(leaderboardEntry), + ).thenThrow( + Exception(), + ); + }, + build: () => LeaderboardBloc(leaderboardRepository), + act: (bloc) => bloc.add(LeaderboardEntryAdded(entry: leaderboardEntry)), + expect: () => [ + const LeaderboardState(), + const LeaderboardState(status: LeaderboardStatus.error), + ], + verify: (_) => verify( + () => leaderboardRepository.addLeaderboardEntry(leaderboardEntry), + ).called(1), errors: () => [isA()], ); }); diff --git a/test/leaderboard/bloc/leaderboard_event_test.dart b/test/leaderboard/bloc/leaderboard_event_test.dart index 1589dd1c..ae85fd7f 100644 --- a/test/leaderboard/bloc/leaderboard_event_test.dart +++ b/test/leaderboard/bloc/leaderboard_event_test.dart @@ -1,17 +1,38 @@ +// ignore_for_file: prefer_const_constructors + import 'package:flutter_test/flutter_test.dart'; import 'package:pinball/leaderboard/leaderboard.dart'; void main() { group('GameEvent', () { - group('LeaderboardRequested', () { + group('Top10Fetched', () { + test('can be instantiated', () { + expect(const Top10Fetched(), isNotNull); + }); + + test('supports value equality', () { + expect( + Top10Fetched(), + equals(const Top10Fetched()), + ); + }); + }); + + group('LeaderboardEntryAdded', () { + const leaderboardEntry = LeaderboardEntry( + playerInitials: 'ABC', + score: 1500, + character: CharacterType.dash, + ); + test('can be instantiated', () { - expect(const LeaderboardRequested(), isNotNull); + expect(const LeaderboardEntryAdded(entry: leaderboardEntry), isNotNull); }); test('supports value equality', () { expect( - LeaderboardRequested(), - equals(const LeaderboardRequested()), + LeaderboardEntryAdded(entry: leaderboardEntry), + equals(const LeaderboardEntryAdded(entry: leaderboardEntry)), ); }); }); diff --git a/test/leaderboard/bloc/leaderboard_state_test.dart b/test/leaderboard/bloc/leaderboard_state_test.dart index 95cafe3c..13983506 100644 --- a/test/leaderboard/bloc/leaderboard_state_test.dart +++ b/test/leaderboard/bloc/leaderboard_state_test.dart @@ -2,7 +2,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:pinball/leaderboard/leaderboard.dart'; -import 'package:pinball_theme/pinball_theme.dart'; void main() { group('LeaderboardState', () { @@ -25,6 +24,12 @@ void main() { }); group('copyWith', () { + const leaderboardEntry = LeaderboardEntry( + playerInitials: 'ABC', + score: 1500, + character: CharacterType.dash, + ); + test( 'copies correctly ' 'when no argument specified', @@ -44,22 +49,16 @@ void main() { const leaderboardState = LeaderboardState(); final otherLeaderboardState = LeaderboardState( status: LeaderboardStatus.success, - ranking: const [ - Competitor( - rank: 1, - characterTheme: DashTheme(), - initials: 'ABC', - score: 10, - ), - ], + ranking: LeaderboardRanking(ranking: 0, outOf: 0), + leaderboard: const [leaderboardEntry], ); expect(leaderboardState, isNot(equals(otherLeaderboardState))); expect( leaderboardState.copyWith( - status: otherLeaderboardState.status, - ranking: otherLeaderboardState.ranking, - ), + status: otherLeaderboardState.status, + ranking: otherLeaderboardState.ranking, + leaderboard: otherLeaderboardState.leaderboard), equals(otherLeaderboardState), ); },