diff --git a/api/packages/game_domain/lib/src/models/leaderboard_player.dart b/api/packages/game_domain/lib/src/models/leaderboard_player.dart index a74a4d3f4..62a420f39 100644 --- a/api/packages/game_domain/lib/src/models/leaderboard_player.dart +++ b/api/packages/game_domain/lib/src/models/leaderboard_player.dart @@ -22,6 +22,15 @@ class LeaderboardPlayer extends Equatable { factory LeaderboardPlayer.fromJson(Map json) => _$LeaderboardPlayerFromJson(json); + /// Creates empty [LeaderboardPlayer]. + static const empty = LeaderboardPlayer( + userId: '', + initials: '', + score: 0, + streak: 0, + mascot: Mascots.dash, + ); + /// Unique identifier of the leaderboard player object /// and session id for the player. @JsonKey() diff --git a/api/packages/game_domain/test/src/models/leaderboard_player_test.dart b/api/packages/game_domain/test/src/models/leaderboard_player_test.dart index b34cab82d..4cfd9107e 100644 --- a/api/packages/game_domain/test/src/models/leaderboard_player_test.dart +++ b/api/packages/game_domain/test/src/models/leaderboard_player_test.dart @@ -5,6 +5,21 @@ import 'package:test/test.dart'; void main() { group('LeaderboardPlayer', () { + test('empty', () { + expect( + LeaderboardPlayer.empty, + equals( + LeaderboardPlayer( + userId: '', + initials: '', + score: 0, + streak: 0, + mascot: Mascots.dash, + ), + ), + ); + }); + test('can be instantiated', () { expect( LeaderboardPlayer( diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 245eaeb0a..33fc3f1ab 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -9,6 +9,7 @@ import 'package:io_crossword/challenge/challenge.dart'; import 'package:io_crossword/crossword/crossword.dart'; import 'package:io_crossword/game_intro/game_intro.dart'; import 'package:io_crossword/l10n/l10n.dart'; +import 'package:io_crossword/player/player.dart'; import 'package:io_crossword_ui/io_crossword_ui.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; import 'package:provider/provider.dart'; @@ -51,6 +52,13 @@ class App extends StatelessWidget { crosswordResource: crosswordResource, ), ), + BlocProvider( + // coverage:ignore-start + create: (_) => PlayerBloc( + leaderboardRepository: leaderboardRepository, + )..add(PlayerLoaded(userId: user.id)), + // coverage:ignore-end + ), BlocProvider( create: (context) => ChallengeBloc( boardInfoRepository: context.read(), diff --git a/lib/leaderboard/bloc/leaderboard_bloc.dart b/lib/leaderboard/bloc/leaderboard_bloc.dart index 4b3eef1ec..e50889e13 100644 --- a/lib/leaderboard/bloc/leaderboard_bloc.dart +++ b/lib/leaderboard/bloc/leaderboard_bloc.dart @@ -45,37 +45,12 @@ class LeaderboardBloc extends Bloc { return; } - final foundCurrentUser = - players.where((player) => player.userId == event.userId); - - // In this case we don't need to search for the player position - // because its in top 10 leaderboard. - if (foundCurrentUser.isNotEmpty) { - emit( - state.copyWith( - status: LeaderboardStatus.success, - players: players, - ), - ); - } else { - return emit.forEach( - _leaderboardRepository.getPlayerRanked(event.userId), - onData: (data) { - return state.copyWith( - currentPlayer: data.$1, - currentUserPosition: data.$2, - status: LeaderboardStatus.success, - players: players, - ); - }, - onError: (error, stackTrace) { - addError(error, stackTrace); - return state.copyWith( - status: LeaderboardStatus.failure, - ); - }, - ); - } + emit( + state.copyWith( + status: LeaderboardStatus.success, + players: players, + ), + ); } catch (error, stackTrace) { addError(error, stackTrace); emit(state.copyWith(status: LeaderboardStatus.failure)); diff --git a/lib/leaderboard/bloc/leaderboard_state.dart b/lib/leaderboard/bloc/leaderboard_state.dart index a94ce638b..f44cc208e 100644 --- a/lib/leaderboard/bloc/leaderboard_state.dart +++ b/lib/leaderboard/bloc/leaderboard_state.dart @@ -10,33 +10,21 @@ class LeaderboardState extends Equatable { const LeaderboardState({ this.status = LeaderboardStatus.initial, this.players = const [], - this.currentUserPosition = 0, - this.currentPlayer, }); final LeaderboardStatus status; final List players; - // TODO(Ayad): Remove currentUserPosition and currentPlayer - // https://very-good-ventures-team.monday.com/boards/6004820050/pulses/6445638020 - final int currentUserPosition; - final LeaderboardPlayer? currentPlayer; - LeaderboardState copyWith({ LeaderboardStatus? status, List? players, - int? currentUserPosition, - LeaderboardPlayer? currentPlayer, }) { return LeaderboardState( status: status ?? this.status, players: players ?? this.players, - currentUserPosition: currentUserPosition ?? this.currentUserPosition, - currentPlayer: currentPlayer ?? this.currentPlayer, ); } @override - List get props => - [status, players, currentUserPosition, currentPlayer]; + List get props => [status, players]; } diff --git a/lib/leaderboard/view/leaderboard_page.dart b/lib/leaderboard/view/leaderboard_page.dart index 569a5ac7f..f7f6dedea 100644 --- a/lib/leaderboard/view/leaderboard_page.dart +++ b/lib/leaderboard/view/leaderboard_page.dart @@ -6,6 +6,7 @@ import 'package:game_domain/game_domain.dart'; import 'package:io_crossword/extensions/extensions.dart'; import 'package:io_crossword/l10n/l10n.dart'; import 'package:io_crossword/leaderboard/bloc/leaderboard_bloc.dart'; +import 'package:io_crossword/player/player.dart'; import 'package:io_crossword_ui/io_crossword_ui.dart'; import 'package:leaderboard_repository/leaderboard_repository.dart'; diff --git a/lib/leaderboard/view/leaderboard_success.dart b/lib/leaderboard/view/leaderboard_success.dart index 4b35dfc0d..b5a76ce89 100644 --- a/lib/leaderboard/view/leaderboard_success.dart +++ b/lib/leaderboard/view/leaderboard_success.dart @@ -115,15 +115,14 @@ class CurrentPlayerNotTopRank extends StatelessWidget { @override Widget build(BuildContext context) { - final player = - context.select((LeaderboardBloc bloc) => bloc.state.currentPlayer); - - if (player == null) return const SizedBox.shrink(); + final player = context.select((PlayerBloc bloc) => bloc.state.player); final rank = context.select( - (LeaderboardBloc bloc) => bloc.state.currentUserPosition, + (PlayerBloc bloc) => bloc.state.rank, ); + if (rank <= 10) return const SizedBox(); + return Padding( padding: const EdgeInsets.only(top: 8, bottom: 16), child: CurrentUserPosition( diff --git a/lib/player/bloc/player_bloc.dart b/lib/player/bloc/player_bloc.dart new file mode 100644 index 000000000..8b082b0dc --- /dev/null +++ b/lib/player/bloc/player_bloc.dart @@ -0,0 +1,40 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:game_domain/game_domain.dart'; +import 'package:leaderboard_repository/leaderboard_repository.dart'; + +part 'player_event.dart'; +part 'player_state.dart'; + +class PlayerBloc extends Bloc { + PlayerBloc({ + required LeaderboardRepository leaderboardRepository, + }) : _leaderboardRepository = leaderboardRepository, + super(const PlayerState()) { + on(_onPlayerLoaded); + } + + final LeaderboardRepository _leaderboardRepository; + + Future _onPlayerLoaded( + PlayerLoaded event, + Emitter emit, + ) { + return emit.forEach( + _leaderboardRepository.getPlayerRanked(event.userId), + onData: (data) { + return PlayerState( + status: PlayerStatus.playing, + player: data.$1, + rank: data.$2, + ); + }, + onError: (error, stackTrace) { + addError(error, stackTrace); + return state.copyWith( + status: PlayerStatus.failure, + ); + }, + ); + } +} diff --git a/lib/player/bloc/player_event.dart b/lib/player/bloc/player_event.dart new file mode 100644 index 000000000..47a1c3866 --- /dev/null +++ b/lib/player/bloc/player_event.dart @@ -0,0 +1,14 @@ +part of 'player_bloc.dart'; + +abstract class PlayerEvent extends Equatable { + const PlayerEvent(); +} + +class PlayerLoaded extends PlayerEvent { + const PlayerLoaded({required this.userId}); + + final String userId; + + @override + List get props => [userId]; +} diff --git a/lib/player/bloc/player_state.dart b/lib/player/bloc/player_state.dart new file mode 100644 index 000000000..6b590e0a4 --- /dev/null +++ b/lib/player/bloc/player_state.dart @@ -0,0 +1,35 @@ +part of 'player_bloc.dart'; + +enum PlayerStatus { + onboarding, + loading, + playing, + failure, +} + +class PlayerState extends Equatable { + const PlayerState({ + this.status = PlayerStatus.onboarding, + this.player = LeaderboardPlayer.empty, + this.rank = 0, + }); + + PlayerState copyWith({ + PlayerStatus? status, + LeaderboardPlayer? player, + int? rank, + }) { + return PlayerState( + status: status ?? this.status, + player: player ?? this.player, + rank: rank ?? this.rank, + ); + } + + final PlayerStatus status; + final LeaderboardPlayer player; + final int rank; + + @override + List get props => [status, player, rank]; +} diff --git a/lib/player/player.dart b/lib/player/player.dart new file mode 100644 index 000000000..a188d0bbe --- /dev/null +++ b/lib/player/player.dart @@ -0,0 +1 @@ +export 'bloc/player_bloc.dart'; diff --git a/test/leaderboard/bloc/leaderboard_bloc_test.dart b/test/leaderboard/bloc/leaderboard_bloc_test.dart index cec6f9c1d..ddf17cdd2 100644 --- a/test/leaderboard/bloc/leaderboard_bloc_test.dart +++ b/test/leaderboard/bloc/leaderboard_bloc_test.dart @@ -54,7 +54,7 @@ void main() { blocTest( 'emits [success] when getLeaderboardResults returns players ' - 'with current user in top 10 rank', + 'with the top 10 rank', setUp: () { when(() => leaderboardRepository.getLeaderboardResults('1')) .thenAnswer( @@ -117,138 +117,6 @@ void main() { ], ); - blocTest( - 'emits [success] when getLeaderboardResults returns players and ' - 'getPlayerRanked gets users position and information', - setUp: () { - when(() => leaderboardRepository.getLeaderboardResults('400')) - .thenAnswer( - (_) async => [ - LeaderboardPlayer( - userId: '1', - initials: 'AAA', - score: 100, - streak: 20, - mascot: Mascots.dash, - ), - LeaderboardPlayer( - userId: '2', - initials: 'BBB', - score: 80, - streak: 10, - mascot: Mascots.android, - ), - LeaderboardPlayer( - userId: '3', - initials: 'CCC', - score: 60, - streak: 5, - mascot: Mascots.sparky, - ), - ], - ); - - when(() => leaderboardRepository.getPlayerRanked('400')).thenAnswer( - (invocation) => Stream.value( - ( - LeaderboardPlayer( - userId: '3', - initials: 'CCC', - score: 60, - streak: 5, - mascot: Mascots.sparky, - ), - 50, - ), - ), - ); - }, - build: () => bloc, - act: (bloc) => bloc.add( - LoadRequestedLeaderboardEvent(userId: '400'), - ), - expect: () => [ - LeaderboardState( - status: LeaderboardStatus.success, - players: [ - LeaderboardPlayer( - userId: '1', - initials: 'AAA', - score: 100, - streak: 20, - mascot: Mascots.dash, - ), - LeaderboardPlayer( - userId: '2', - initials: 'BBB', - score: 80, - streak: 10, - mascot: Mascots.android, - ), - LeaderboardPlayer( - userId: '3', - initials: 'CCC', - score: 60, - streak: 5, - mascot: Mascots.sparky, - ), - ], - currentPlayer: LeaderboardPlayer( - userId: '3', - initials: 'CCC', - score: 60, - streak: 5, - mascot: Mascots.sparky, - ), - currentUserPosition: 50, - ), - ], - ); - - blocTest( - 'emits [failure] when getPlayerRanked throws error', - setUp: () { - when(() => leaderboardRepository.getLeaderboardResults('400')) - .thenAnswer( - (_) async => [ - LeaderboardPlayer( - userId: '1', - initials: 'AAA', - score: 100, - streak: 20, - mascot: Mascots.dash, - ), - LeaderboardPlayer( - userId: '2', - initials: 'BBB', - score: 80, - streak: 10, - mascot: Mascots.android, - ), - LeaderboardPlayer( - userId: '3', - initials: 'CCC', - score: 60, - streak: 5, - mascot: Mascots.sparky, - ), - ], - ); - - when(() => leaderboardRepository.getPlayerRanked('400')) - .thenAnswer((invocation) => Stream.error(Exception())); - }, - build: () => bloc, - act: (bloc) => bloc.add( - LoadRequestedLeaderboardEvent(userId: '400'), - ), - expect: () => [ - LeaderboardState( - status: LeaderboardStatus.failure, - ), - ], - ); - blocTest( 'emits [failure] when getLeaderboardResults throws exception', setUp: () { diff --git a/test/leaderboard/view/leaderboard_page_test.dart b/test/leaderboard/view/leaderboard_page_test.dart index 6c8f96fa7..109f95e79 100644 --- a/test/leaderboard/view/leaderboard_page_test.dart +++ b/test/leaderboard/view/leaderboard_page_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:io_crossword/l10n/l10n.dart'; import 'package:io_crossword/leaderboard/bloc/leaderboard_bloc.dart'; import 'package:io_crossword/leaderboard/view/leaderboard_page.dart'; +import 'package:io_crossword/player/bloc/player_bloc.dart'; import 'package:io_crossword_ui/io_crossword_ui.dart'; import 'package:mockingjay/mockingjay.dart'; @@ -15,6 +16,9 @@ import '../../helpers/helpers.dart'; class _MockLeaderboardBloc extends MockBloc implements LeaderboardBloc {} +class _MockPlayerBloc extends MockBloc + implements PlayerBloc {} + void main() { group('LeaderboardPage', () { testWidgets( @@ -29,6 +33,7 @@ void main() { group('LeaderboardView', () { late LeaderboardBloc leaderboardBloc; + late PlayerBloc playerBloc; late Widget widget; late AppLocalizations l10n; @@ -39,9 +44,17 @@ void main() { setUp(() { leaderboardBloc = _MockLeaderboardBloc(); - - widget = BlocProvider( - create: (_) => leaderboardBloc, + playerBloc = _MockPlayerBloc(); + + widget = MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => leaderboardBloc, + ), + BlocProvider( + create: (context) => playerBloc, + ), + ], child: LeaderboardView(), ); }); @@ -86,6 +99,7 @@ void main() { (tester) async { when(() => leaderboardBloc.state) .thenReturn(LeaderboardState(status: LeaderboardStatus.success)); + when(() => playerBloc.state).thenReturn(PlayerState()); await tester.pumpApp(widget); diff --git a/test/leaderboard/view/leaderboard_success_test.dart b/test/leaderboard/view/leaderboard_success_test.dart index a7be130c4..694c1e10f 100644 --- a/test/leaderboard/view/leaderboard_success_test.dart +++ b/test/leaderboard/view/leaderboard_success_test.dart @@ -9,6 +9,8 @@ import 'package:game_domain/game_domain.dart'; import 'package:io_crossword/l10n/l10n.dart'; import 'package:io_crossword/leaderboard/bloc/leaderboard_bloc.dart'; import 'package:io_crossword/leaderboard/view/leaderboard_page.dart'; +import 'package:io_crossword/player/bloc/player_bloc.dart'; +import 'package:io_crossword/player/player.dart'; import 'package:io_crossword_ui/io_crossword_ui.dart'; import 'package:mockingjay/mockingjay.dart'; @@ -17,9 +19,13 @@ import '../../helpers/helpers.dart'; class _MockLeaderboardBloc extends MockBloc implements LeaderboardBloc {} +class _MockPlayerBloc extends MockBloc + implements PlayerBloc {} + void main() { group('LeaderboardSuccess', () { late LeaderboardBloc leaderboardBloc; + late PlayerBloc playerBloc; late Widget widget; const user = User(id: 'user-id'); @@ -31,9 +37,17 @@ void main() { setUp(() { leaderboardBloc = _MockLeaderboardBloc(); + playerBloc = _MockPlayerBloc(); - widget = BlocProvider( - create: (_) => leaderboardBloc, + widget = MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => leaderboardBloc, + ), + BlocProvider( + create: (context) => playerBloc, + ), + ], child: LeaderboardSuccess(), ); }); @@ -42,6 +56,7 @@ void main() { 'displays rank title', (tester) async { when(() => leaderboardBloc.state).thenReturn(LeaderboardState()); + when(() => playerBloc.state).thenReturn(PlayerState()); await tester.pumpApp(widget); @@ -53,6 +68,7 @@ void main() { 'displays streak title', (tester) async { when(() => leaderboardBloc.state).thenReturn(LeaderboardState()); + when(() => playerBloc.state).thenReturn(PlayerState()); await tester.pumpApp(widget); @@ -64,6 +80,7 @@ void main() { 'displays score title', (tester) async { when(() => leaderboardBloc.state).thenReturn(LeaderboardState()); + when(() => playerBloc.state).thenReturn(PlayerState()); await tester.pumpApp(widget); @@ -75,6 +92,7 @@ void main() { 'renders CurrentPlayerNotTopRank', (tester) async { when(() => leaderboardBloc.state).thenReturn(LeaderboardState()); + when(() => playerBloc.state).thenReturn(PlayerState()); await tester.pumpApp(widget); @@ -86,6 +104,14 @@ void main() { 'renders CurrentUserPosition with one CurrentUserPosition ' 'with the user in the top 10', (tester) async { + final currentPlayer = LeaderboardPlayer( + userId: user.id, + initials: 'BBB', + score: 500, + streak: 2, + mascot: Mascots.android, + ); + when(() => leaderboardBloc.state).thenReturn( LeaderboardState( players: List.generate( @@ -97,44 +123,13 @@ void main() { streak: 2, mascot: Mascots.dash, ), - )..insert( - 2, - LeaderboardPlayer( - userId: user.id, - initials: 'BBB', - score: 500, - streak: 2, - mascot: Mascots.android, - ), - ), + )..insert(2, currentPlayer), ), ); - - await tester.pumpApp( - widget, - user: user, - ); - - expect(find.byType(UserLeaderboardRanking), findsNWidgets(10)); - expect(find.byType(CurrentUserPosition), findsOneWidget); - }, - ); - - testWidgets( - 'renders UserLeaderboardRanking', - (tester) async { - when(() => leaderboardBloc.state).thenReturn( - LeaderboardState( - players: List.generate( - 10, - (index) => LeaderboardPlayer( - userId: '', - initials: 'AAA', - score: 50, - streak: 2, - mascot: Mascots.dash, - ), - ), + when(() => playerBloc.state).thenReturn( + PlayerState( + rank: 3, + player: currentPlayer, ), ); @@ -144,11 +139,12 @@ void main() { ); expect(find.byType(UserLeaderboardRanking), findsNWidgets(10)); + expect(find.byType(CurrentUserPosition), findsOneWidget); }, ); testWidgets( - 'displays rank positions based on number of players', + 'displays rank position based on number of players', (tester) async { const numberOfPlayers = 10; @@ -166,6 +162,7 @@ void main() { ), ), ); + when(() => playerBloc.state).thenReturn(PlayerState()); await tester.pumpApp( widget, @@ -186,6 +183,7 @@ void main() { addTearDown(tester.view.resetPhysicalSize); when(() => leaderboardBloc.state).thenReturn(LeaderboardState()); + when(() => playerBloc.state).thenReturn(PlayerState()); await tester.pumpApp(widget); @@ -198,6 +196,7 @@ void main() { 'displays playAgain and icon in button', (tester) async { when(() => leaderboardBloc.state).thenReturn(LeaderboardState()); + when(() => playerBloc.state).thenReturn(PlayerState()); await tester.pumpApp(widget); @@ -214,6 +213,7 @@ void main() { when(mockNavigator.canPop).thenReturn(true); when(() => leaderboardBloc.state).thenReturn(LeaderboardState()); + when(() => playerBloc.state).thenReturn(PlayerState()); await tester.pumpApp( widget, @@ -228,63 +228,74 @@ void main() { group('CurrentPlayerNotTopRank', () { setUp(() { - widget = BlocProvider( - create: (_) => leaderboardBloc, + widget = BlocProvider( + create: (_) => playerBloc, child: CurrentPlayerNotTopRank(), ); }); - testWidgets( - 'does not render CurrentUserPosition when there are no currentPlayer', - (tester) async { - when(() => leaderboardBloc.state).thenReturn( - LeaderboardState( - currentUserPosition: 50, - ), - ); + for (var i = 1; i <= 10; i++) { + testWidgets( + 'does not render CurrentUserPosition with player in $i position', + (tester) async { + final player = LeaderboardPlayer( + userId: '1234', + initials: 'AAA', + score: 50, + streak: 2, + mascot: Mascots.dash, + ); + + when(() => playerBloc.state).thenReturn( + PlayerState( + player: player, + rank: i, + ), + ); - await tester.pumpApp(widget); + await tester.pumpApp(widget); - expect(find.byType(CurrentUserPosition), findsNothing); - }, - ); + expect(find.byType(CurrentUserPosition), findsNothing); + }, + ); + } testWidgets( - 'renders CurrentUserPosition when there is a currentPlayer', + 'renders CurrentUserPosition with the players information', (tester) async { - when(() => leaderboardBloc.state).thenReturn( - LeaderboardState( - currentPlayer: LeaderboardPlayer( - userId: '', - initials: 'AAA', - score: 50, - streak: 2, - mascot: Mascots.dash, - ), - currentUserPosition: 50, + final player = LeaderboardPlayer( + userId: '1234', + initials: 'AAA', + score: 50, + streak: 2, + mascot: Mascots.dash, + ); + + when(() => playerBloc.state).thenReturn( + PlayerState( + player: player, + rank: 11, ), ); await tester.pumpApp(widget); expect(find.byType(CurrentUserPosition), findsOneWidget); + expect( + tester + .widget(find.byType(CurrentUserPosition)) + .player, + equals(player), + ); }, ); testWidgets( - 'renders CurrentUserPosition when there is a currentPlayer ' - 'with the ranking', + 'renders CurrentUserPosition with ranking', (tester) async { - when(() => leaderboardBloc.state).thenReturn( - LeaderboardState( - currentPlayer: LeaderboardPlayer( - userId: '', - initials: 'AAA', - score: 50, - streak: 2, - mascot: Mascots.dash, - ), - currentUserPosition: 50, + when(() => playerBloc.state).thenReturn( + PlayerState( + rank: 50, ), ); diff --git a/test/player/bloc/player_bloc_test.dart b/test/player/bloc/player_bloc_test.dart new file mode 100644 index 000000000..5b1c38772 --- /dev/null +++ b/test/player/bloc/player_bloc_test.dart @@ -0,0 +1,67 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_domain/game_domain.dart'; +import 'package:io_crossword/player/bloc/player_bloc.dart'; +import 'package:leaderboard_repository/leaderboard_repository.dart'; +import 'package:mocktail/mocktail.dart'; + +class _MockLeaderboardRepository extends Mock + implements LeaderboardRepository {} + +void main() { + group('$PlayerBloc', () { + late LeaderboardRepository leaderboardRepository; + late PlayerBloc bloc; + + const player = LeaderboardPlayer( + userId: 'user-id', + mascot: Mascots.android, + streak: 5, + initials: 'ABC', + score: 1200, + ); + + setUp(() { + leaderboardRepository = _MockLeaderboardRepository(); + bloc = PlayerBloc( + leaderboardRepository: leaderboardRepository, + ); + }); + + group('$PlayerLoaded', () { + blocTest( + 'emits [playing] with the player and ranking position', + setUp: () { + when(() => leaderboardRepository.getPlayerRanked('user-id')) + .thenAnswer((_) => Stream.value((player, 3))); + }, + build: () => bloc, + act: (bloc) => bloc.add(PlayerLoaded(userId: 'user-id')), + expect: () => [ + PlayerState( + status: PlayerStatus.playing, + rank: 3, + player: player, + ), + ], + ); + + blocTest( + 'emits [failure] when getPlayerRanked throws error', + setUp: () { + when(() => leaderboardRepository.getPlayerRanked('user-id')) + .thenAnswer((_) => Stream.error(Exception())); + }, + build: () => bloc, + act: (bloc) => bloc.add(PlayerLoaded(userId: 'user-id')), + expect: () => [ + PlayerState( + status: PlayerStatus.failure, + ), + ], + ); + }); + }); +} diff --git a/test/player/bloc/player_event_test.dart b/test/player/bloc/player_event_test.dart new file mode 100644 index 000000000..1cb7922d5 --- /dev/null +++ b/test/player/bloc/player_event_test.dart @@ -0,0 +1,22 @@ +// ignore_for_file: prefer_const_constructors + +import 'package:flutter_test/flutter_test.dart'; +import 'package:io_crossword/player/bloc/player_bloc.dart'; + +void main() { + group('$PlayerEvent', () { + group('$PlayerLoaded', () { + test('checks equality', () { + expect( + PlayerLoaded(userId: 'user-id'), + equals(PlayerLoaded(userId: 'user-id')), + ); + + expect( + PlayerLoaded(userId: 'user-id'), + isNot(equals(PlayerLoaded(userId: 'user-id-1'))), + ); + }); + }); + }); +} diff --git a/test/player/bloc/player_state_test.dart b/test/player/bloc/player_state_test.dart new file mode 100644 index 000000000..cabc166f0 --- /dev/null +++ b/test/player/bloc/player_state_test.dart @@ -0,0 +1,81 @@ +// ignore_for_file: prefer_const_constructors +// ignore_for_file: prefer_const_literals_to_create_immutables + +import 'package:flutter_test/flutter_test.dart'; +import 'package:game_domain/game_domain.dart'; +import 'package:io_crossword/player/bloc/player_bloc.dart'; + +void main() { + group('$PlayerState', () { + test('supports value comparisons', () { + expect( + PlayerState(rank: 4, status: PlayerStatus.failure), + equals(PlayerState(rank: 4, status: PlayerStatus.failure)), + ); + + expect( + PlayerState(status: PlayerStatus.failure), + isNot(equals(PlayerState())), + ); + + expect( + PlayerState( + player: LeaderboardPlayer( + userId: '1', + initials: 'AAA', + score: 100, + streak: 2, + mascot: Mascots.dash, + ), + ), + isNot(equals(PlayerState())), + ); + + expect( + PlayerState(rank: 4), + isNot(equals(PlayerState())), + ); + }); + + group('copyWith', () { + test('updates status', () { + expect( + PlayerState().copyWith(status: PlayerStatus.playing), + equals(PlayerState(status: PlayerStatus.playing)), + ); + }); + + test('updates status', () { + expect( + PlayerState().copyWith(rank: 5), + equals(PlayerState(rank: 5)), + ); + }); + + test('updates players', () { + expect( + PlayerState().copyWith( + player: LeaderboardPlayer( + userId: '1', + initials: 'AAA', + score: 100, + streak: 2, + mascot: Mascots.dash, + ), + ), + equals( + PlayerState( + player: LeaderboardPlayer( + userId: '1', + initials: 'AAA', + score: 100, + streak: 2, + mascot: Mascots.dash, + ), + ), + ), + ); + }); + }); + }); +}