refactor: adapt bloc events and models to what leaderboard repository has

pull/57/head
RuiAlonso 4 years ago
parent 343b7fae91
commit 93c343e3ac

@ -8,26 +8,52 @@ part 'leaderboard_event.dart';
part 'leaderboard_state.dart'; part 'leaderboard_state.dart';
class LeaderboardRepository { class LeaderboardRepository {
Future<List<Competitor>> fetchRanking() { Future<List<LeaderboardEntry>> fetchTop10Leaderboard() {
return Future.value([]); return Future.value([]);
} }
Future<LeaderboardRanking> addLeaderboardEntry(LeaderboardEntry entry) {
return Future.value();
}
} }
class LeaderboardBloc extends Bloc<LeaderboardEvent, LeaderboardState> { class LeaderboardBloc extends Bloc<LeaderboardEvent, LeaderboardState> {
LeaderboardBloc(this._leaderboardRepository) LeaderboardBloc(this._leaderboardRepository)
: super(const LeaderboardState()) { : super(const LeaderboardState()) {
on<LeaderboardRequested>(_onLeaderboardRequested); on<Top10Fetched>(_onTop10Fetched);
on<LeaderboardEntryAdded>(_onLeaderboardEntryAdded);
} }
final LeaderboardRepository _leaderboardRepository; final LeaderboardRepository _leaderboardRepository;
FutureOr<void> _onLeaderboardRequested( FutureOr<void> _onTop10Fetched(
LeaderboardRequested event, Top10Fetched event,
Emitter<LeaderboardState> 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<void> _onLeaderboardEntryAdded(
LeaderboardEntryAdded event,
Emitter<LeaderboardState> emit, Emitter<LeaderboardState> emit,
) async { ) async {
emit(state.copyWith(status: LeaderboardStatus.loading)); emit(state.copyWith(status: LeaderboardStatus.loading));
try { try {
final ranking = await _leaderboardRepository.fetchRanking(); final ranking =
await _leaderboardRepository.addLeaderboardEntry(event.entry);
emit( emit(
state.copyWith( state.copyWith(
status: LeaderboardStatus.success, status: LeaderboardStatus.success,

@ -7,6 +7,15 @@ abstract class LeaderboardEvent extends Equatable {
List<Object?> get props => []; List<Object?> get props => [];
} }
class LeaderboardRequested extends LeaderboardEvent { class Top10Fetched extends LeaderboardEvent {
const LeaderboardRequested(); const Top10Fetched();
}
class LeaderboardEntryAdded extends LeaderboardEvent {
const LeaderboardEntryAdded({required this.entry});
final LeaderboardEntry entry;
@override
List<Object?> get props => [entry];
} }

@ -5,39 +5,59 @@ enum LeaderboardStatus { loading, success, error }
class LeaderboardState extends Equatable { class LeaderboardState extends Equatable {
const LeaderboardState({ const LeaderboardState({
this.status = LeaderboardStatus.loading, this.status = LeaderboardStatus.loading,
this.ranking = const [], this.ranking = const LeaderboardRanking(
ranking: 0,
outOf: 0,
),
this.leaderboard = const [],
}); });
final LeaderboardStatus status; final LeaderboardStatus status;
final List<Competitor> ranking; final LeaderboardRanking ranking;
final List<LeaderboardEntry> leaderboard;
@override @override
List<Object> get props => [status, ranking]; List<Object> get props => [status, ranking, leaderboard];
LeaderboardState copyWith({ LeaderboardState copyWith({
LeaderboardStatus? status, LeaderboardStatus? status,
List<Competitor>? ranking, LeaderboardRanking? ranking,
List<LeaderboardEntry>? leaderboard,
}) { }) {
return LeaderboardState( return LeaderboardState(
status: status ?? this.status, status: status ?? this.status,
ranking: ranking ?? this.ranking, ranking: ranking ?? this.ranking,
leaderboard: leaderboard ?? this.leaderboard,
); );
} }
} }
class Competitor extends Equatable { enum CharacterType { dash, sparky, android, dino }
const Competitor({
required this.rank, class LeaderboardEntry extends Equatable {
required this.characterTheme, const LeaderboardEntry({
required this.initials, required this.playerInitials,
required this.score, required this.score,
required this.character,
}); });
final int rank; final String playerInitials;
final CharacterTheme characterTheme;
final String initials;
final int score; final int score;
final CharacterType character;
@override
List<Object?> get props => [playerInitials, character, score];
}
class LeaderboardRanking extends Equatable {
const LeaderboardRanking({
required this.ranking,
required this.outOf,
});
final int ranking;
final int outOf;
@override @override
List<Object?> get props => [rank, characterTheme, initials, score]; List<Object?> get props => [ranking, outOf];
} }

@ -4,7 +4,6 @@ import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball/leaderboard/leaderboard.dart'; import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball_theme/pinball_theme.dart';
class MockLeaderboardRepository extends Mock implements LeaderboardRepository {} class MockLeaderboardRepository extends Mock implements LeaderboardRepository {}
@ -16,50 +15,50 @@ void main() {
leaderboardRepository = MockLeaderboardRepository(); 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); final bloc = LeaderboardBloc(leaderboardRepository);
expect(bloc.state.status, equals(LeaderboardStatus.loading)); 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', () { group('Top10Fetched', () {
final ranking = <Competitor>[ final top10Scores = [
Competitor( 2500,
rank: 1, 2200,
characterTheme: DashTheme(), 2200,
initials: 'ABC', 2000,
score: 100, 1800,
), 1400,
Competitor( 1300,
rank: 2, 1000,
characterTheme: SparkyTheme(), 600,
initials: 'DEF', 300,
score: 200, 100,
),
Competitor(
rank: 3,
characterTheme: AndroidTheme(),
initials: 'GHI',
score: 300,
),
Competitor(
rank: 4,
characterTheme: DinoTheme(),
initials: 'JKL',
score: 400,
),
]; ];
final top10Leaderboard = top10Scores
.map(
(score) => LeaderboardEntry(
playerInitials: 'user$score',
score: score,
character: CharacterType.dash,
),
)
.toList();
blocTest<LeaderboardBloc, LeaderboardState>( blocTest<LeaderboardBloc, LeaderboardState>(
'emits [loading, success] statuses ' 'emits [loading, success] statuses '
'when fetchRanking succeeds', 'when fetchTop10Leaderboard succeeds',
setUp: () { setUp: () {
when(() => leaderboardRepository.fetchRanking()).thenAnswer( when(() => leaderboardRepository.fetchTop10Leaderboard()).thenAnswer(
(_) async => ranking, (_) async => top10Leaderboard,
); );
}, },
build: () => LeaderboardBloc(leaderboardRepository), build: () => LeaderboardBloc(leaderboardRepository),
act: (bloc) => bloc.add(LeaderboardRequested()), act: (bloc) => bloc.add(Top10Fetched()),
expect: () => [ expect: () => [
const LeaderboardState(), const LeaderboardState(),
isA<LeaderboardState>() isA<LeaderboardState>()
@ -69,31 +68,96 @@ void main() {
equals(LeaderboardStatus.success), equals(LeaderboardStatus.success),
) )
..having( ..having(
(element) => element.ranking.length, (element) => element.leaderboard.length,
'ranking', 'leaderboard',
equals(ranking.length), equals(top10Leaderboard.length),
) )
], ],
verify: (_) => verify: (_) =>
verify(() => leaderboardRepository.fetchRanking()).called(1), verify(() => leaderboardRepository.fetchTop10Leaderboard())
.called(1),
); );
blocTest<LeaderboardBloc, LeaderboardState>( blocTest<LeaderboardBloc, LeaderboardState>(
'emits [loading, error] statuses ' 'emits [loading, error] statuses '
'when fetchRanking fails', 'when fetchTop10Leaderboard fails',
setUp: () { setUp: () {
when(() => leaderboardRepository.fetchRanking()).thenThrow( when(() => leaderboardRepository.fetchTop10Leaderboard()).thenThrow(
Exception(), Exception(),
); );
}, },
build: () => LeaderboardBloc(leaderboardRepository), build: () => LeaderboardBloc(leaderboardRepository),
act: (bloc) => bloc.add(LeaderboardRequested()), act: (bloc) => bloc.add(Top10Fetched()),
expect: () => <LeaderboardState>[ expect: () => <LeaderboardState>[
const LeaderboardState(), const LeaderboardState(),
const LeaderboardState(status: LeaderboardStatus.error), const LeaderboardState(status: LeaderboardStatus.error),
], ],
verify: (_) => verify: (_) =>
verify(() => leaderboardRepository.fetchRanking()).called(1), verify(() => leaderboardRepository.fetchTop10Leaderboard())
.called(1),
errors: () => [isA<Exception>()],
);
});
group('LeaderboardEntryAdded', () {
final leaderboardEntry = LeaderboardEntry(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
);
final ranking = LeaderboardRanking(ranking: 3, outOf: 4);
blocTest<LeaderboardBloc, LeaderboardState>(
'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<LeaderboardState>()
..having(
(element) => element.status,
'status',
equals(LeaderboardStatus.success),
)
..having(
(element) => element.ranking,
'ranking',
equals(ranking),
)
],
verify: (_) => verify(
() => leaderboardRepository.addLeaderboardEntry(leaderboardEntry),
).called(1),
);
blocTest<LeaderboardBloc, LeaderboardState>(
'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: () => <LeaderboardState>[
const LeaderboardState(),
const LeaderboardState(status: LeaderboardStatus.error),
],
verify: (_) => verify(
() => leaderboardRepository.addLeaderboardEntry(leaderboardEntry),
).called(1),
errors: () => [isA<Exception>()], errors: () => [isA<Exception>()],
); );
}); });

@ -1,17 +1,38 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/leaderboard/leaderboard.dart'; import 'package:pinball/leaderboard/leaderboard.dart';
void main() { void main() {
group('GameEvent', () { 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', () { test('can be instantiated', () {
expect(const LeaderboardRequested(), isNotNull); expect(const LeaderboardEntryAdded(entry: leaderboardEntry), isNotNull);
}); });
test('supports value equality', () { test('supports value equality', () {
expect( expect(
LeaderboardRequested(), LeaderboardEntryAdded(entry: leaderboardEntry),
equals(const LeaderboardRequested()), equals(const LeaderboardEntryAdded(entry: leaderboardEntry)),
); );
}); });
}); });

@ -2,7 +2,6 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/leaderboard/leaderboard.dart'; import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball_theme/pinball_theme.dart';
void main() { void main() {
group('LeaderboardState', () { group('LeaderboardState', () {
@ -25,6 +24,12 @@ void main() {
}); });
group('copyWith', () { group('copyWith', () {
const leaderboardEntry = LeaderboardEntry(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
);
test( test(
'copies correctly ' 'copies correctly '
'when no argument specified', 'when no argument specified',
@ -44,22 +49,16 @@ void main() {
const leaderboardState = LeaderboardState(); const leaderboardState = LeaderboardState();
final otherLeaderboardState = LeaderboardState( final otherLeaderboardState = LeaderboardState(
status: LeaderboardStatus.success, status: LeaderboardStatus.success,
ranking: const [ ranking: LeaderboardRanking(ranking: 0, outOf: 0),
Competitor( leaderboard: const [leaderboardEntry],
rank: 1,
characterTheme: DashTheme(),
initials: 'ABC',
score: 10,
),
],
); );
expect(leaderboardState, isNot(equals(otherLeaderboardState))); expect(leaderboardState, isNot(equals(otherLeaderboardState)));
expect( expect(
leaderboardState.copyWith( leaderboardState.copyWith(
status: otherLeaderboardState.status, status: otherLeaderboardState.status,
ranking: otherLeaderboardState.ranking, ranking: otherLeaderboardState.ranking,
), leaderboard: otherLeaderboardState.leaderboard),
equals(otherLeaderboardState), equals(otherLeaderboardState),
); );
}, },

Loading…
Cancel
Save