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';
class LeaderboardRepository {
Future<List<Competitor>> fetchRanking() {
Future<List<LeaderboardEntry>> fetchTop10Leaderboard() {
return Future.value([]);
}
Future<LeaderboardRanking> addLeaderboardEntry(LeaderboardEntry entry) {
return Future.value();
}
}
class LeaderboardBloc extends Bloc<LeaderboardEvent, LeaderboardState> {
LeaderboardBloc(this._leaderboardRepository)
: super(const LeaderboardState()) {
on<LeaderboardRequested>(_onLeaderboardRequested);
on<Top10Fetched>(_onTop10Fetched);
on<LeaderboardEntryAdded>(_onLeaderboardEntryAdded);
}
final LeaderboardRepository _leaderboardRepository;
FutureOr<void> _onLeaderboardRequested(
LeaderboardRequested event,
FutureOr<void> _onTop10Fetched(
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,
) 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,

@ -7,6 +7,15 @@ abstract class LeaderboardEvent extends Equatable {
List<Object?> 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<Object?> get props => [entry];
}

@ -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<Competitor> ranking;
final LeaderboardRanking ranking;
final List<LeaderboardEntry> leaderboard;
@override
List<Object> get props => [status, ranking];
List<Object> get props => [status, ranking, leaderboard];
LeaderboardState copyWith({
LeaderboardStatus? status,
List<Competitor>? ranking,
LeaderboardRanking? ranking,
List<LeaderboardEntry>? 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<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
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: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>[
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<LeaderboardBloc, LeaderboardState>(
'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<LeaderboardState>()
@ -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<LeaderboardBloc, LeaderboardState>(
'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: () => <LeaderboardState>[
const LeaderboardState(),
const LeaderboardState(status: LeaderboardStatus.error),
],
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>()],
);
});

@ -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)),
);
});
});

@ -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),
);
},

Loading…
Cancel
Save