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 b3b1fb003..a74a4d3f4 100644 --- a/api/packages/game_domain/lib/src/models/leaderboard_player.dart +++ b/api/packages/game_domain/lib/src/models/leaderboard_player.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:game_domain/src/models/mascots.dart'; import 'package:json_annotation/json_annotation.dart'; part 'leaderboard_player.g.dart'; @@ -13,6 +14,8 @@ class LeaderboardPlayer extends Equatable { required this.userId, required this.initials, required this.score, + required this.streak, + required this.mascot, }); /// {@macro leaderboard_player} @@ -28,13 +31,21 @@ class LeaderboardPlayer extends Equatable { @JsonKey() final int score; + /// Number of streaks. + @JsonKey() + final int streak; + /// Initials of the player. @JsonKey() final String initials; + /// The player mascot. + @JsonKey() + final Mascots mascot; + /// Returns a json representation from this instance. Map toJson() => _$LeaderboardPlayerToJson(this); @override - List get props => [userId, score, initials]; + List get props => [userId, score, initials, streak, mascot]; } diff --git a/api/packages/game_domain/lib/src/models/leaderboard_player.g.dart b/api/packages/game_domain/lib/src/models/leaderboard_player.g.dart index 7cd486de2..ca43437ca 100644 --- a/api/packages/game_domain/lib/src/models/leaderboard_player.g.dart +++ b/api/packages/game_domain/lib/src/models/leaderboard_player.g.dart @@ -11,11 +11,22 @@ LeaderboardPlayer _$LeaderboardPlayerFromJson(Map json) => userId: json['userId'] as String, initials: json['initials'] as String, score: json['score'] as int, + streak: json['streak'] as int, + mascot: $enumDecode(_$MascotsEnumMap, json['mascot']), ); Map _$LeaderboardPlayerToJson(LeaderboardPlayer instance) => { 'userId': instance.userId, 'score': instance.score, + 'streak': instance.streak, 'initials': instance.initials, + 'mascot': _$MascotsEnumMap[instance.mascot]!, }; + +const _$MascotsEnumMap = { + Mascots.dash: 'dash', + Mascots.sparky: 'sparky', + Mascots.dino: 'dino', + Mascots.android: 'android', +}; 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 3362feaa8..b34cab82d 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 @@ -11,6 +11,8 @@ void main() { userId: 'id', initials: 'TST', score: 10, + mascot: Mascots.android, + streak: 2, ), isNotNull, ); @@ -20,6 +22,8 @@ void main() { userId: 'id', initials: 'TST', score: 20, + mascot: Mascots.android, + streak: 2, ); test('toJson returns the instance as json', () { @@ -29,16 +33,20 @@ void main() { 'userId': 'id', 'initials': 'TST', 'score': 20, + 'mascot': Mascots.android.name, + 'streak': 2, }), ); }); test('fromJson returns the correct instance', () { expect( - LeaderboardPlayer.fromJson(const { + LeaderboardPlayer.fromJson({ 'userId': 'id', 'initials': 'TST', 'score': 20, + 'mascot': Mascots.android.name, + 'streak': 2, }), equals(leaderboardPlayer), ); @@ -46,8 +54,22 @@ void main() { test('supports equality', () { expect( - LeaderboardPlayer(userId: '', initials: 'TST', score: 20), - equals(LeaderboardPlayer(userId: '', initials: 'TST', score: 20)), + LeaderboardPlayer( + userId: '', + initials: 'TST', + score: 20, + mascot: Mascots.android, + streak: 2, + ), + equals( + LeaderboardPlayer( + userId: '', + initials: 'TST', + score: 20, + mascot: Mascots.android, + streak: 2, + ), + ), ); expect( @@ -55,6 +77,8 @@ void main() { userId: '', initials: 'TST', score: 20, + mascot: Mascots.android, + streak: 2, ), isNot( equals(leaderboardPlayer), @@ -66,6 +90,34 @@ void main() { userId: 'id', initials: 'WOW', score: 20, + mascot: Mascots.android, + streak: 2, + ), + isNot( + equals(leaderboardPlayer), + ), + ); + + expect( + LeaderboardPlayer( + userId: 'id', + initials: 'TST', + score: 20, + mascot: Mascots.dash, + streak: 2, + ), + isNot( + equals(leaderboardPlayer), + ), + ); + + expect( + LeaderboardPlayer( + userId: 'id', + initials: 'TST', + score: 20, + mascot: Mascots.android, + streak: 3, ), isNot( equals(leaderboardPlayer), diff --git a/api/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart b/api/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart index 1b25f35b0..a15385849 100644 --- a/api/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart +++ b/api/packages/leaderboard_repository/test/src/leaderboard_repository_test.dart @@ -44,11 +44,15 @@ void main() { userId: 'id', initials: 'AAA', score: 20, + mascot: Mascots.android, + streak: 3, ); const playerTwo = LeaderboardPlayer( userId: 'id2', initials: 'BBB', score: 10, + mascot: Mascots.dash, + streak: 2, ); when(() => dbClient.orderBy('leaderboard', 'score')) @@ -56,17 +60,11 @@ void main() { return [ DbEntityRecord( id: 'id', - data: { - 'initials': playerOne.initials, - 'score': 20, - }, + data: playerOne.toJson()..remove('userId'), ), DbEntityRecord( id: 'id2', - data: { - 'initials': playerTwo.initials, - 'score': 10, - }, + data: playerTwo.toJson()..remove('userId'), ), ]; }); @@ -93,14 +91,13 @@ void main() { userId: 'user-id', initials: 'initials', score: 40, + mascot: Mascots.dash, + streak: 2, ); final record = DbEntityRecord( id: 'user-id', - data: { - 'initials': 'initials', - 'score': 40, - }, + data: leaderboardPlayer.toJson()..remove('userId'), ); when(() => dbClient.set('leaderboard', record)) diff --git a/api/test/routes/game/leaderboard/initials/index_test.dart b/api/test/routes/game/leaderboard/initials/index_test.dart index 6dde9751e..8ad7adda2 100644 --- a/api/test/routes/game/leaderboard/initials/index_test.dart +++ b/api/test/routes/game/leaderboard/initials/index_test.dart @@ -45,6 +45,8 @@ void main() { userId: 'user-id', initials: 'AAA', score: 10, + mascot: Mascots.dash, + streak: 2, ); when( @@ -71,6 +73,8 @@ void main() { userId: 'user-id', initials: 'AAA', score: 10, + mascot: Mascots.dash, + streak: 2, ); when(() => request.method).thenReturn(HttpMethod.post); @@ -87,6 +91,32 @@ void main() { expect(response.statusCode, equals(HttpStatus.noContent)); }); + test('responds with a 400 when mascot is not correct', () async { + final leaderboardPlayer = LeaderboardPlayer( + userId: 'user-id', + initials: 'AAA', + score: 10, + mascot: Mascots.dash, + streak: 2, + ); + + when(() => request.method).thenReturn(HttpMethod.post); + + when( + () => leaderboardRepository.addPlayerToLeaderboard( + leaderboardPlayer: leaderboardPlayer, + ), + ).thenAnswer((_) async {}); + + when(request.json).thenAnswer( + (_) async => leaderboardPlayer.toJson() + ..update('mascot', (value) => 'no-real-mascot'), + ); + + final response = await route.onRequest(context); + expect(response.statusCode, equals(HttpStatus.badRequest)); + }); + test('responds with a 400 when request is invalid', () async { when(() => request.method).thenReturn(HttpMethod.post); when(request.json).thenAnswer((_) async => {'test': 'test'}); @@ -102,6 +132,8 @@ void main() { 'userId': 'user-id', 'initials': 'CCC', 'score': 10, + 'mascot': 'dash', + 'streak': 2, }, ); @@ -117,6 +149,8 @@ void main() { 'userId': 'user-id', 'initials': 'ccc', 'score': 10, + 'mascot': 'dash', + 'streak': 2, }, ); @@ -133,6 +167,8 @@ void main() { 'userId': 'user-id', 'initials': 'aa', 'score': 10, + 'mascot': 'dash', + 'streak': 2, }, ); @@ -150,6 +186,8 @@ void main() { 'userId': 'user-id', 'initials': 'aaaa', 'score': 10, + 'mascot': 'dash', + 'streak': 2, }, ); diff --git a/api/test/routes/game/leaderboard/results/index_test.dart b/api/test/routes/game/leaderboard/results/index_test.dart index 3a881cfba..70c7f950c 100644 --- a/api/test/routes/game/leaderboard/results/index_test.dart +++ b/api/test/routes/game/leaderboard/results/index_test.dart @@ -26,16 +26,22 @@ void main() { userId: 'id', score: 1, initials: 'AAA', + mascot: Mascots.dash, + streak: 2, ), LeaderboardPlayer( userId: 'id2', score: 2, initials: 'BBB', + mascot: Mascots.android, + streak: 3, ), LeaderboardPlayer( userId: 'id3', score: 3, initials: 'CCC', + mascot: Mascots.sparky, + streak: 4, ), ]; @@ -88,16 +94,22 @@ void main() { 'userId': 'id', 'score': 1, 'initials': 'AAA', + 'mascot': 'dash', + 'streak': 2, }, { 'userId': 'id2', 'score': 2, 'initials': 'BBB', + 'mascot': 'android', + 'streak': 3, }, { 'userId': 'id3', 'score': 3, 'initials': 'CCC', + 'mascot': 'sparky', + 'streak': 4, }, ], }), diff --git a/lib/leaderboard/bloc/leaderboard_bloc.dart b/lib/leaderboard/bloc/leaderboard_bloc.dart index 739df2c41..6a38d5aba 100644 --- a/lib/leaderboard/bloc/leaderboard_bloc.dart +++ b/lib/leaderboard/bloc/leaderboard_bloc.dart @@ -34,6 +34,8 @@ class LeaderboardBloc extends Bloc { userId: '', initials: 'AAA', score: 0, + streak: 0, + mascot: Mascots.dash, ), ), ), diff --git a/lib/leaderboard/view/leaderboard_success.dart b/lib/leaderboard/view/leaderboard_success.dart index bc8e6603b..bf1c7f9b5 100644 --- a/lib/leaderboard/view/leaderboard_success.dart +++ b/lib/leaderboard/view/leaderboard_success.dart @@ -171,12 +171,13 @@ class UserLeaderboardRanking extends StatelessWidget { @override Widget build(BuildContext context) { - // TODO(Ayad): show the correct style based on the players team - // https://very-good-ventures-team.monday.com/boards/6004820050/pulses/6400331391 final style = IoPlayerAliasStyle( - backgroundColor: rank.isOdd - ? IoCrosswordColors.androidGreen - : IoCrosswordColors.flutterBlue, + backgroundColor: switch (player.mascot) { + Mascots.dash => IoCrosswordColors.flutterBlue, + Mascots.sparky => IoCrosswordColors.sparkyYellow, + Mascots.dino => IoCrosswordColors.chromeRed, + Mascots.android => IoCrosswordColors.androidGreen, + }, textStyle: const TextStyle( color: Colors.black, fontWeight: FontWeight.w700, @@ -204,11 +205,10 @@ class UserLeaderboardRanking extends StatelessWidget { ], ), ), - - // TODO(Ayad): add missing streak in [LeaderboardPlayer] - // https://very-good-ventures-team.monday.com/boards/6004820050/pulses/6400331391 Expanded( - child: Text(1524.toDisplayNumber()), + child: Text( + player.streak.toDisplayNumber(), + ), ), Expanded( child: Text( diff --git a/packages/api_client/test/src/resources/leaderboard_resource_test.dart b/packages/api_client/test/src/resources/leaderboard_resource_test.dart index bac4314ac..55b5a1dcf 100644 --- a/packages/api_client/test/src/resources/leaderboard_resource_test.dart +++ b/packages/api_client/test/src/resources/leaderboard_resource_test.dart @@ -36,6 +36,8 @@ void main() { userId: 'id', score: 10, initials: 'TST', + mascot: Mascots.dash, + streak: 3, ); when(() => response.statusCode).thenReturn(HttpStatus.ok); @@ -157,6 +159,8 @@ void main() { userId: 'id', score: 10, initials: 'TST', + mascot: Mascots.dash, + streak: 3, ); setUp(() { diff --git a/test/leaderboard/bloc/leaderboard_bloc_test.dart b/test/leaderboard/bloc/leaderboard_bloc_test.dart index 70853fdda..30e5dd05b 100644 --- a/test/leaderboard/bloc/leaderboard_bloc_test.dart +++ b/test/leaderboard/bloc/leaderboard_bloc_test.dart @@ -41,6 +41,8 @@ void main() { userId: '', initials: 'AAA', score: 0, + streak: 0, + mascot: Mascots.dash, ), ), ), @@ -52,9 +54,27 @@ void main() { setUp: () { when(() => leaderboardResource.getLeaderboardResults()).thenAnswer( (_) async => [ - LeaderboardPlayer(userId: '1', initials: 'AAA', score: 100), - LeaderboardPlayer(userId: '2', initials: 'BBB', score: 80), - LeaderboardPlayer(userId: '3', initials: 'CCC', score: 60), + 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, + ), ], ); }, @@ -64,9 +84,27 @@ void main() { LeaderboardState( status: LeaderboardStatus.success, players: [ - LeaderboardPlayer(userId: '1', initials: 'AAA', score: 100), - LeaderboardPlayer(userId: '2', initials: 'BBB', score: 80), - LeaderboardPlayer(userId: '3', initials: 'CCC', score: 60), + 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, + ), ], ), ], diff --git a/test/leaderboard/bloc/leaderboard_state_test.dart b/test/leaderboard/bloc/leaderboard_state_test.dart index d0624f10f..984ca04d2 100644 --- a/test/leaderboard/bloc/leaderboard_state_test.dart +++ b/test/leaderboard/bloc/leaderboard_state_test.dart @@ -18,7 +18,13 @@ void main() { expect( LeaderboardState( players: [ - LeaderboardPlayer(userId: '1', initials: 'AAA', score: 100), + LeaderboardPlayer( + userId: '1', + initials: 'AAA', + score: 100, + streak: 2, + mascot: Mascots.dash, + ), ], ), isNot(equals(LeaderboardState())), @@ -37,13 +43,25 @@ void main() { expect( LeaderboardState().copyWith( players: [ - LeaderboardPlayer(userId: '1', initials: 'AAA', score: 100), + LeaderboardPlayer( + userId: '1', + initials: 'AAA', + score: 100, + streak: 2, + mascot: Mascots.dash, + ), ], ), equals( LeaderboardState( players: [ - LeaderboardPlayer(userId: '1', initials: 'AAA', score: 100), + LeaderboardPlayer( + userId: '1', + initials: 'AAA', + score: 100, + streak: 2, + mascot: Mascots.dash, + ), ], ), ), diff --git a/test/leaderboard/view/leaderboard_success_test.dart b/test/leaderboard/view/leaderboard_success_test.dart index fa591f4bb..7c51b5f77 100644 --- a/test/leaderboard/view/leaderboard_success_test.dart +++ b/test/leaderboard/view/leaderboard_success_test.dart @@ -80,6 +80,8 @@ void main() { userId: '', initials: 'AAA', score: 50, + streak: 2, + mascot: Mascots.dash, ), ), ), @@ -104,6 +106,8 @@ void main() { userId: '', initials: 'AAA', score: 50, + streak: 20, + mascot: Mascots.dash, ), ), ), @@ -172,6 +176,8 @@ void main() { userId: '123', initials: 'ABC', score: 200, + streak: 2, + mascot: Mascots.dash, ); setUpAll(() async { @@ -209,6 +215,17 @@ void main() { userId: '123', initials: 'ABC', score: 200, + streak: 25, + mascot: Mascots.dash, + ); + + testWidgets( + 'displays streak', + (tester) async { + await tester.pumpApp(CurrentUserPosition(player: player, rank: 7)); + + expect(find.text('25'), findsOneWidget); + }, ); testWidgets( @@ -245,6 +262,8 @@ void main() { userId: '123', initials: 'ABC', score: 23700, + streak: 2, + mascot: Mascots.dash, ); await tester.pumpApp(CurrentUserPosition(player: player, rank: 4)); @@ -263,5 +282,57 @@ void main() { expect(find.text('C'), findsOneWidget); }, ); + + for (final mascot in [ + _MascotTester( + mascot: Mascots.dash, + color: IoCrosswordColors.flutterBlue, + ), + _MascotTester( + mascot: Mascots.sparky, + color: IoCrosswordColors.sparkyYellow, + ), + _MascotTester( + mascot: Mascots.dino, + color: IoCrosswordColors.chromeRed, + ), + _MascotTester( + mascot: Mascots.android, + color: IoCrosswordColors.androidGreen, + ), + ]) { + testWidgets( + 'displays color for ${mascot.mascot}', + (tester) async { + final player = LeaderboardPlayer( + userId: '123', + initials: 'ABC', + score: 23700, + streak: 2, + mascot: mascot.mascot, + ); + + await tester.pumpApp(CurrentUserPosition(player: player, rank: 4)); + + expect( + tester + .widget(find.byType(IoPlayerAlias)) + .style + .backgroundColor, + equals(mascot.color), + ); + }, + ); + } + }); +} + +class _MascotTester { + const _MascotTester({ + required this.mascot, + required this.color, }); + + final Mascots mascot; + final Color color; }