diff --git a/lib/models/ui/comment_details.dart b/lib/models/ui/comment_details.dart index f68fa7fd..9f496df4 100644 --- a/lib/models/ui/comment_details.dart +++ b/lib/models/ui/comment_details.dart @@ -1,12 +1,16 @@ import "package:flutter/foundation.dart"; import "package:proxima/models/database/comment/comment_data.dart"; import "package:proxima/models/database/user/user_firestore.dart"; +import "package:proxima/models/database/user/user_id_firestore.dart"; +/// This class contains all the details of a comment +/// that are needed to display it in the UI. @immutable class CommentDetails { final String content; final String ownerDisplayName; final String ownerUsername; + final UserIdFirestore ownerUserID; final int ownerCentauriPoints; final DateTime publicationDate; @@ -14,6 +18,7 @@ class CommentDetails { required this.content, required this.ownerDisplayName, required this.ownerUsername, + required this.ownerUserID, required this.ownerCentauriPoints, required this.publicationDate, }); @@ -26,6 +31,7 @@ class CommentDetails { other.content == content && other.ownerDisplayName == ownerDisplayName && other.ownerUsername == ownerUsername && + other.ownerUserID == ownerUserID && other.ownerCentauriPoints == ownerCentauriPoints && other.publicationDate == publicationDate; } @@ -36,6 +42,7 @@ class CommentDetails { content, ownerDisplayName, ownerUsername, + ownerUserID, ownerCentauriPoints, publicationDate, ); @@ -53,6 +60,7 @@ class CommentDetails { content: commentData.content, ownerDisplayName: ownerData.displayName, ownerUsername: ownerData.username, + ownerUserID: owner.uid, ownerCentauriPoints: ownerData.centauriPoints, publicationDate: DateTime.fromMillisecondsSinceEpoch( commentData.publicationTime.millisecondsSinceEpoch, diff --git a/lib/models/ui/post_details.dart b/lib/models/ui/post_details.dart index 8b2e2ac8..80d27906 100644 --- a/lib/models/ui/post_details.dart +++ b/lib/models/ui/post_details.dart @@ -3,6 +3,7 @@ import "package:geoflutterfire_plus/geoflutterfire_plus.dart"; import "package:proxima/models/database/post/post_firestore.dart"; import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/database/user/user_firestore.dart"; +import "package:proxima/models/database/user/user_id_firestore.dart"; @immutable @@ -15,6 +16,7 @@ class PostDetails { final int commentNumber; final String ownerDisplayName; final String ownerUsername; + final UserIdFirestore ownerUserID; final int ownerCentauriPoints; final DateTime publicationDate; final int distance; // in meters @@ -28,6 +30,7 @@ class PostDetails { required this.commentNumber, required this.ownerDisplayName, required this.ownerUsername, + required this.ownerUserID, required this.ownerCentauriPoints, required this.publicationDate, required this.distance, @@ -46,6 +49,7 @@ class PostDetails { other.commentNumber == commentNumber && other.ownerDisplayName == ownerDisplayName && other.ownerUsername == ownerUsername && + other.ownerUserID == ownerUserID && other.ownerCentauriPoints == ownerCentauriPoints && other.publicationDate == publicationDate && other.distance == distance && @@ -62,6 +66,7 @@ class PostDetails { commentNumber, ownerDisplayName, ownerUsername, + ownerUserID, ownerCentauriPoints, publicationDate, distance, @@ -86,6 +91,7 @@ class PostDetails { commentNumber: postFirestore.data.commentCount, ownerDisplayName: userFirestore.data.displayName, ownerUsername: userFirestore.data.username, + ownerUserID: userFirestore.uid, ownerCentauriPoints: userFirestore.data.centauriPoints, publicationDate: postFirestore.data.publicationTime.toDate(), distance: (geoFirePoint.distanceBetweenInKm( diff --git a/lib/models/ui/ranking/ranking_element_details.dart b/lib/models/ui/ranking/ranking_element_details.dart index 5ea5fcd3..05038fd2 100644 --- a/lib/models/ui/ranking/ranking_element_details.dart +++ b/lib/models/ui/ranking/ranking_element_details.dart @@ -1,4 +1,5 @@ import "package:flutter/foundation.dart"; +import "package:proxima/models/database/user/user_id_firestore.dart"; /// A class that stores data for each ranking UI element. /// The [userRank] is nullable to allow the current user to not have a rank. @@ -7,6 +8,7 @@ class RankingElementDetails { const RankingElementDetails({ required this.userDisplayName, required this.userUserName, + required this.userID, required this.centauriPoints, required this.userRank, }); @@ -17,6 +19,9 @@ class RankingElementDetails { /// Username of the user. final String userUserName; + /// ID of the user. + final UserIdFirestore userID; + /// Centauri points of the user. final int centauriPoints; @@ -30,6 +35,7 @@ class RankingElementDetails { return other is RankingElementDetails && other.userDisplayName == userDisplayName && other.userUserName == userUserName && + other.userID == userID && other.centauriPoints == centauriPoints && other.userRank == userRank; } @@ -38,6 +44,7 @@ class RankingElementDetails { int get hashCode => Object.hash( userDisplayName, userUserName, + userID, centauriPoints, userRank, ); diff --git a/lib/models/ui/user_avatar_details.dart b/lib/models/ui/user_avatar_details.dart index 14b24ade..3f182fcb 100644 --- a/lib/models/ui/user_avatar_details.dart +++ b/lib/models/ui/user_avatar_details.dart @@ -1,27 +1,29 @@ import "package:flutter/foundation.dart"; -import "package:proxima/models/database/user/user_data.dart"; +import "package:proxima/models/database/user/user_firestore.dart"; /// A class that stores data for a user avatar UI. @immutable class UserAvatarDetails { final String displayName; - final int? userCentauriPoints; + final int? centauriPoints; /// Creates a [UserAvatarDetails] object. /// [displayName] is the user's display name, of which /// the first letter is displayed on the avatar. - /// [userCentauriPoints] is the user's centauri points, - /// which is used to color the avatar background. + /// [centauriPoints] is the user's centauri points. + /// The [centauriPoints] parameter can be null, which is useful for loading states. const UserAvatarDetails({ required this.displayName, - required this.userCentauriPoints, + required this.centauriPoints, }); - /// Converts a [UserData] object, [userData], to a [UserAvatarDetails] object. - factory UserAvatarDetails.fromUserData(UserData userData) { + /// Converts a [UserFirestore] object, [user], to a [UserAvatarDetails] object. + factory UserAvatarDetails.fromUser( + UserFirestore user, + ) { return UserAvatarDetails( - displayName: userData.displayName, - userCentauriPoints: userData.centauriPoints, + displayName: user.data.displayName, + centauriPoints: user.data.centauriPoints, ); } @@ -29,14 +31,14 @@ class UserAvatarDetails { bool operator ==(Object other) { return other is UserAvatarDetails && other.displayName == displayName && - other.userCentauriPoints == userCentauriPoints; + other.centauriPoints == centauriPoints; } @override int get hashCode { return Object.hash( displayName, - userCentauriPoints, + centauriPoints, ); } } diff --git a/lib/utils/user_avatar_color.dart b/lib/utils/user_avatar_color.dart new file mode 100644 index 00000000..3ffd5a36 --- /dev/null +++ b/lib/utils/user_avatar_color.dart @@ -0,0 +1,46 @@ +import "package:flutter/material.dart"; +import "package:proxima/models/ui/linear_segmented_hsv_colormap.dart"; +import "package:proxima/services/database/challenge_repository_service.dart"; + +/// Stops for the colormap used to color the user's avatar based on their centauri points. +/// The stops are defined as the number of challenges completed. +const _challengesStops = [ + // sqrt(10) ~= 3, which is the approximate step between each stop + 0, + 10, // ~ 3 days of daily challenge + 30, + 100, + 300, // ~ 3 months of daily challenge + 1000, + 3000, // ~ 3 years of daily challenge + 10000, +]; + +/// Color used when the user's centauri points are null. +const loadingUserAvatarColor = Colors.transparent; + +/// Converts some amount of [centauriPoints] to a color, based on a uniform +/// [LinearSegmentedHSVColormap] (defined by _challengesStop). [brightness] +/// defines the value and saturation of the colormap. +/// If [centauriPoints] is null, the color is transparent. +Color centauriToUserAvatarColor(int? centauriPoints, Brightness brightness) { + if (centauriPoints == null) return loadingUserAvatarColor; + + final hsvValue = switch (brightness) { + Brightness.light => 0.9, + Brightness.dark => 0.5, + }; + final hsvSaturation = switch (brightness) { + Brightness.light => 0.4, + Brightness.dark => 0.8, + }; + + const chalReward = ChallengeRepositoryService.soloChallengeReward; + final colorMap = LinearSegmentedHSVColormap.uniform( + _challengesStops.map((nChallenges) => nChallenges * chalReward).toList(), + value: hsvValue, + saturation: hsvSaturation, + ); + + return colorMap(centauriPoints).toColor(); +} diff --git a/lib/viewmodels/challenge_view_model.dart b/lib/viewmodels/challenge_view_model.dart index 6663f171..497eb5c7 100644 --- a/lib/viewmodels/challenge_view_model.dart +++ b/lib/viewmodels/challenge_view_model.dart @@ -5,6 +5,7 @@ import "package:proxima/models/ui/challenge_details.dart"; import "package:proxima/services/database/challenge_repository_service.dart"; import "package:proxima/services/database/post_repository_service.dart"; import "package:proxima/services/sensors/geolocation_service.dart"; +import "package:proxima/viewmodels/dynamic_user_avatar_view_model.dart"; import "package:proxima/viewmodels/login_view_model.dart"; import "package:proxima/viewmodels/map/map_pin_view_model.dart"; @@ -98,6 +99,13 @@ class ChallengeViewModel // Refresh the map pins after challenge completion ref.read(mapPinViewModelProvider.notifier).refresh(); + + // Refresh the user centauri points after challenge completion + // Note: null is the current user id as represented in dynamicUserAvatarViewModelProvider + // So we have to refresh both [currentUser] and the null user + for (final user in [null, currentUser]) { + ref.read(dynamicUserAvatarViewModelProvider(user).notifier).refresh(); + } } return pointsAwarded; diff --git a/lib/viewmodels/comments_view_model.dart b/lib/viewmodels/comments_view_model.dart index aa05babd..9b2a67c8 100644 --- a/lib/viewmodels/comments_view_model.dart +++ b/lib/viewmodels/comments_view_model.dart @@ -6,6 +6,7 @@ import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/ui/comment_details.dart"; import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/services/database/user_repository_service.dart"; +import "package:proxima/viewmodels/post_comment_count_view_model.dart"; /// This view model is used to fetch the comments of a post. /// It fetches the comments under the post with the id [arg] and returns @@ -21,6 +22,9 @@ class CommentsViewModel extends AutoDisposeFamilyAsyncNotifier< final commentsFirestore = await commentRepository.getPostComments(arg); + // Update the post comment count + ref.read(postCommentCountProvider(arg).notifier).refresh(); + final commentOwnersId = commentsFirestore.map((comment) => comment.data.ownerId).toSet(); diff --git a/lib/viewmodels/dynamic_user_avatar_view_model.dart b/lib/viewmodels/dynamic_user_avatar_view_model.dart index d42e2be4..63c8298a 100644 --- a/lib/viewmodels/dynamic_user_avatar_view_model.dart +++ b/lib/viewmodels/dynamic_user_avatar_view_model.dart @@ -1,87 +1,53 @@ import "dart:async"; -import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/user/user_firestore.dart"; import "package:proxima/models/database/user/user_id_firestore.dart"; -import "package:proxima/models/ui/linear_segmented_hsv_colormap.dart"; import "package:proxima/models/ui/user_avatar_details.dart"; -import "package:proxima/services/database/challenge_repository_service.dart"; import "package:proxima/services/database/user_repository_service.dart"; import "package:proxima/viewmodels/login_view_model.dart"; +// TODO: Remove the fact that the current user is "null" and accept a non-nullable user id. /// View model for the dynamic user avatar. -/// This view model is used to fetch the user's display name given its id. -/// If the id is null, the current user's display name is fetched. +/// This view model is used to fetch the user's display name and centauri points +/// given its id. If the id is null, the current user's information is fetched. class DynamicUserAvatarViewModel extends AutoDisposeFamilyAsyncNotifier< UserAvatarDetails, UserIdFirestore?> { DynamicUserAvatarViewModel(); - /// Stops for the colormap used to color the user's avatar based on their centauri points. - /// The stops are defined as the number of challenges completed. - static const _challengesStops = [ - // sqrt(10) ~= 3, which is the approximate step between each stop - 0, - 10, // ~ 3 days of daily challenge - 30, - 100, - 300, // ~ 3 months of daily challenge - 1000, - 3000, // ~ 3 years of daily challenge - 10000, - ]; - - /// Converts some amount of [centauriPoints] to a color, based on a uniform - /// [LinearSegmentedHSVColormap] (defined by _challengesStop). [brightness] - /// defines the value and saturation of the colormap. - /// If [centauriPoints] is null, the color is transparent. - static Color centauriToColor(int? centauriPoints, Brightness brightness) { - if (centauriPoints == null) return Colors.transparent; - - final hsvValue = switch (brightness) { - Brightness.light => 0.9, - Brightness.dark => 0.5, - }; - final hsvSaturation = switch (brightness) { - Brightness.light => 0.4, - Brightness.dark => 0.8, - }; - - const chalReward = ChallengeRepositoryService.soloChallengeReward; - final colorMap = LinearSegmentedHSVColormap.uniform( - _challengesStops.map((nChallenges) => nChallenges * chalReward).toList(), - value: hsvValue, - saturation: hsvSaturation, - ); - - return colorMap(centauriPoints).toColor(); - } - @override Future build(UserIdFirestore? arg) async { - final userID = arg; final currentUID = ref.watch(loggedInUserIdProvider); final userDataBase = ref.watch(userRepositoryServiceProvider); late final UserFirestore user; + late final UserIdFirestore userID; - if (userID == null) { + if (arg == null) { if (currentUID == null) { throw Exception("User is not logged in."); } - - user = await userDataBase.getUser(currentUID); + userID = currentUID; } else { - user = await userDataBase.getUser(userID); + userID = arg; } - return UserAvatarDetails.fromUserData(user.data); + user = await userDataBase.getUser(userID); + + return UserAvatarDetails.fromUser(user); + } + + /// Refresh the user's information. + Future refresh() async { + state = const AsyncValue.loading(); + + state = await AsyncValue.guard(() => build(arg)); } } //TODO: Extend to fetch the user's avatar image. -/// Flexible provider allowing to retrieve the user's display name given its id. -/// If the id is null, the current user's display name is fetched. +/// Flexible provider allowing to retrieve the user's display name and centauri points +/// given its id. If the id is null, the current user's information is fetched. final dynamicUserAvatarViewModelProvider = AsyncNotifierProvider.autoDispose .family( () => DynamicUserAvatarViewModel(), diff --git a/lib/viewmodels/post_comment_count_view_model.dart b/lib/viewmodels/post_comment_count_view_model.dart new file mode 100644 index 00000000..85ee31ad --- /dev/null +++ b/lib/viewmodels/post_comment_count_view_model.dart @@ -0,0 +1,28 @@ +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/services/database/post_repository_service.dart"; + +/// This view model is used to keep in memory the number of comments of a post. +/// It is refreshed every time the post comment list is refreshed, to stay consistent +/// with it. It is also refreshed when a comment is deleted. +/// This cannot be auto-dispose because, otherwise, it might get unmounted in the middle +/// of its refresh method. See https://github.com/rrousselGit/riverpod/discussions/2502. +class PostCommentCountViewModel + extends FamilyAsyncNotifier { + @override + Future build(PostIdFirestore arg) async { + final postRepo = ref.watch(postRepositoryServiceProvider); + final post = await postRepo.getPost(arg); + return post.data.commentCount; + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() => build(arg)); + } +} + +final postCommentCountProvider = AsyncNotifierProvider.family< + PostCommentCountViewModel, int, PostIdFirestore>( + () => PostCommentCountViewModel(), +); diff --git a/lib/viewmodels/user_comments_view_model.dart b/lib/viewmodels/user_comments_view_model.dart index 066655d4..8b0227e3 100644 --- a/lib/viewmodels/user_comments_view_model.dart +++ b/lib/viewmodels/user_comments_view_model.dart @@ -5,6 +5,7 @@ import "package:proxima/models/database/post/post_id_firestore.dart"; import "package:proxima/models/ui/user_comment_details.dart"; import "package:proxima/services/database/comment/comment_repository_service.dart"; import "package:proxima/viewmodels/login_view_model.dart"; +import "package:proxima/viewmodels/post_comment_count_view_model.dart"; import "package:proxima/viewmodels/posts_feed_view_model.dart"; typedef UserCommentsState = List; @@ -53,6 +54,8 @@ class UserCommentViewModel extends AutoDisposeAsyncNotifier { // Refresh the home feed after comment deletion, that way the comment // count will be updated ref.read(postsFeedViewModelProvider.notifier).refresh(); + // Also update the comment count for the post + ref.read(postCommentCountProvider(postId).notifier).refresh(); } /// Refresh the list of posts diff --git a/lib/viewmodels/users_ranking_view_model.dart b/lib/viewmodels/users_ranking_view_model.dart index 85ad99c7..5ad10edc 100644 --- a/lib/viewmodels/users_ranking_view_model.dart +++ b/lib/viewmodels/users_ranking_view_model.dart @@ -3,6 +3,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/ui/ranking/ranking_details.dart"; import "package:proxima/models/ui/ranking/ranking_element_details.dart"; import "package:proxima/services/database/user_repository_service.dart"; +import "package:proxima/viewmodels/dynamic_user_avatar_view_model.dart"; import "package:proxima/viewmodels/login_view_model.dart"; /// Provides a refreshable async list of users that are sorted by @@ -37,6 +38,7 @@ class UsersRankingViewModel extends AutoDisposeAsyncNotifier { final userRankingDetails = RankingElementDetails( userDisplayName: userData.displayName, userUserName: userData.username, + userID: user.uid, centauriPoints: userData.centauriPoints, userRank: userRank, ); @@ -44,7 +46,8 @@ class UsersRankingViewModel extends AutoDisposeAsyncNotifier { return userRankingDetails; }).toList(); - final currentUserData = futures.$2.data; + final currentUser = futures.$2; + final currentUserData = currentUser.data; // If the current user is in the leaderboard, also provide its rank final currentUserRank = topUsers.firstWhereOrNull( @@ -54,6 +57,7 @@ class UsersRankingViewModel extends AutoDisposeAsyncNotifier { final currentUserRankingDetails = RankingElementDetails( userDisplayName: currentUserData.displayName, userUserName: currentUserData.username, + userID: currentUser.uid, centauriPoints: currentUserData.centauriPoints, userRank: currentUserRank?.userRank, ); @@ -64,6 +68,19 @@ class UsersRankingViewModel extends AutoDisposeAsyncNotifier { userRankElementDetails: currentUserRankingDetails, ); + // Get the list of user ids to refresh, and refresh the avatars. + // Note that we also need to refresh the null user avatar + // which represents the current user. + final userIds = + ([currentUser, null] + topUsersFromDb).map((user) => user?.uid); + + for (final userId in userIds) { + ref + .read( + dynamicUserAvatarViewModelProvider(userId).notifier, + ) + .refresh(); + } return state; } diff --git a/lib/views/components/content/user_avatar/dynamic_user_avatar.dart b/lib/views/components/content/user_avatar/dynamic_user_avatar.dart index 8b4580d5..27649958 100644 --- a/lib/views/components/content/user_avatar/dynamic_user_avatar.dart +++ b/lib/views/components/content/user_avatar/dynamic_user_avatar.dart @@ -13,7 +13,7 @@ import "package:proxima/views/components/content/user_avatar/user_avatar.dart"; class DynamicUserAvatar extends ConsumerWidget { static const _loadingDetails = UserAvatarDetails( displayName: "", - userCentauriPoints: null, + centauriPoints: null, ); final UserIdFirestore? uid; diff --git a/lib/views/components/content/user_avatar/user_avatar.dart b/lib/views/components/content/user_avatar/user_avatar.dart index efbc4949..937e3dc4 100644 --- a/lib/views/components/content/user_avatar/user_avatar.dart +++ b/lib/views/components/content/user_avatar/user_avatar.dart @@ -1,12 +1,13 @@ import "package:flutter/material.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/ui/user_avatar_details.dart"; -import "package:proxima/viewmodels/dynamic_user_avatar_view_model.dart"; +import "package:proxima/utils/user_avatar_color.dart"; /// A widget that displays the user's avatar. /// It provides a [onTap] parameter to handle the user's tap, /// which adds an InkWell response. /// The [radius] parameter is the radius of the avatar. -class UserAvatar extends StatelessWidget { +class UserAvatar extends ConsumerWidget { static const initialDisplayNameKey = Key("initialDisplayName"); const UserAvatar({ @@ -25,36 +26,40 @@ class UserAvatar extends StatelessWidget { // working and displays the user's profile picture behind the InkWell response. @override - Widget build(BuildContext context) { - return CircleAvatar( - radius: radius, - backgroundColor: DynamicUserAvatarViewModel.centauriToColor( - details.userCentauriPoints, - Theme.of(context).brightness, - ), - child: Stack( - children: [ - Center( - child: Text( - key: initialDisplayNameKey, - // Display the first letter of the [displayName] parameter (user's initial). - // If the display name is empty, display an empty string (because - // it causes an error if it is empty). - details.displayName.isEmpty - ? "" - : details.displayName.substring(0, 1), - ), + Widget build(BuildContext context, WidgetRef ref) { + final content = Stack( + children: [ + Center( + child: Text( + key: initialDisplayNameKey, + // Display the first letter of the [displayName] parameter (user's initial). + // If the display name is empty, display an empty string (because + // it causes an error if it is empty). + details.displayName.isEmpty + ? "" + : details.displayName.substring(0, 1), ), - Material( - shape: const CircleBorder(), - clipBehavior: Clip.hardEdge, - color: Colors.transparent, - child: InkWell( - onTap: onTap, - ), + ), + Material( + shape: const CircleBorder(), + clipBehavior: Clip.hardEdge, + color: Colors.transparent, + child: InkWell( + onTap: onTap, ), - ], - ), + ), + ], + ); + + final backgroundColor = centauriToUserAvatarColor( + details.centauriPoints, + Theme.of(context).brightness, + ); + + return CircleAvatar( + radius: radius, + backgroundColor: backgroundColor, + child: content, ); } } diff --git a/lib/views/components/content/user_profile_pop_up.dart b/lib/views/components/content/user_profile_pop_up.dart index eb911d6a..f607b2f8 100644 --- a/lib/views/components/content/user_profile_pop_up.dart +++ b/lib/views/components/content/user_profile_pop_up.dart @@ -1,6 +1,6 @@ import "package:flutter/material.dart"; -import "package:proxima/models/ui/user_avatar_details.dart"; -import "package:proxima/views/components/content/user_avatar/user_avatar.dart"; +import "package:proxima/models/database/user/user_id_firestore.dart"; +import "package:proxima/views/components/content/user_avatar/dynamic_user_avatar.dart"; import "package:proxima/views/helpers/key_value_list_builder.dart"; /// A pop-up displaying a user's profile, i.e. their display name, username, @@ -10,12 +10,14 @@ import "package:proxima/views/helpers/key_value_list_builder.dart"; class UserProfilePopUp extends StatelessWidget { final String displayName; final String username; + final UserIdFirestore userID; final int centauriPoints; const UserProfilePopUp({ super.key, required this.displayName, required this.username, + required this.userID, required this.centauriPoints, }); @@ -26,16 +28,15 @@ class UserProfilePopUp extends StatelessWidget { .addPair("Username", username) .addPair("Score", "$centauriPoints Centauri"); + final userAvatar = DynamicUserAvatar( + uid: userID, + radius: 15, + ); + return AlertDialog( title: Row( children: [ - UserAvatar( - details: UserAvatarDetails( - displayName: displayName, - userCentauriPoints: centauriPoints, - ), - radius: 15, - ), + userAvatar, const SizedBox(width: 12), Text( displayName, diff --git a/lib/views/pages/home/content/feed/components/comment_count.dart b/lib/views/pages/home/content/feed/components/comment_count.dart index bf5bafcf..e622b6f8 100644 --- a/lib/views/pages/home/content/feed/components/comment_count.dart +++ b/lib/views/pages/home/content/feed/components/comment_count.dart @@ -1,17 +1,23 @@ import "package:flutter/material.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/viewmodels/post_comment_count_view_model.dart"; /// This widget is used to display the comment number in the post card. /// It contains the comment icon and the number of comments. -class CommentCount extends StatelessWidget { - final int count; +class CommentCount extends ConsumerWidget { + final PostIdFirestore postId; const CommentCount({ super.key, - required this.count, + required this.postId, }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final asyncCount = ref.watch(postCommentCountProvider(postId)); + final count = asyncCount.value ?? 0; + const icon = Icon(Icons.comment, size: 20); final countText = Text(count.toString()); diff --git a/lib/views/pages/home/content/feed/components/post_card.dart b/lib/views/pages/home/content/feed/components/post_card.dart index da25ec41..6efdee77 100644 --- a/lib/views/pages/home/content/feed/components/post_card.dart +++ b/lib/views/pages/home/content/feed/components/post_card.dart @@ -79,7 +79,7 @@ class PostCard extends ConsumerWidget { onTap: () => _onPostSelect(context, postDetails, ref), child: CommentCount( key: postCardCommentsNumberKey, - count: postDetails.commentNumber, + postId: postDetails.postId, ), ), ], @@ -113,6 +113,7 @@ class PostCard extends ConsumerWidget { key: postCardUserKey, posterUsername: postDetails.ownerUsername, posterDisplayName: postDetails.ownerDisplayName, + posterUserID: postDetails.ownerUserID, posterCentauriPoints: postDetails.ownerCentauriPoints, publicationDate: postDetails.publicationDate, ), diff --git a/lib/views/pages/home/content/feed/components/post_card_header.dart b/lib/views/pages/home/content/feed/components/post_card_header.dart index 6f8ead0b..80f7d4a0 100644 --- a/lib/views/pages/home/content/feed/components/post_card_header.dart +++ b/lib/views/pages/home/content/feed/components/post_card_header.dart @@ -1,8 +1,8 @@ import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:proxima/models/ui/user_avatar_details.dart"; +import "package:proxima/models/database/user/user_id_firestore.dart"; import "package:proxima/services/conversion/human_time_service.dart"; -import "package:proxima/views/components/content/user_avatar/user_avatar.dart"; +import "package:proxima/views/components/content/user_avatar/dynamic_user_avatar.dart"; import "package:proxima/views/components/content/user_profile_pop_up.dart"; /// This widget is used to display the info bar in the post card. @@ -17,12 +17,14 @@ class PostCardHeader extends ConsumerWidget { super.key, required this.posterDisplayName, required this.posterUsername, + required this.posterUserID, required this.posterCentauriPoints, required this.publicationDate, }); final String posterDisplayName; final String posterUsername; + final UserIdFirestore posterUserID; final int posterCentauriPoints; final DateTime publicationDate; @@ -32,6 +34,7 @@ class PostCardHeader extends ConsumerWidget { builder: (BuildContext context) => UserProfilePopUp( displayName: posterDisplayName, username: posterUsername, + userID: posterUserID, centauriPoints: posterCentauriPoints, ), ); @@ -78,11 +81,8 @@ class PostCardHeader extends ConsumerWidget { return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - UserAvatar( - details: UserAvatarDetails( - displayName: posterDisplayName, - userCentauriPoints: posterCentauriPoints, - ), + DynamicUserAvatar( + uid: posterUserID, radius: 12, onTap: () => onTap(context), key: avatarKey, diff --git a/lib/views/pages/home/content/ranking/components/ranking_card.dart b/lib/views/pages/home/content/ranking/components/ranking_card.dart index da83338b..c227de5d 100644 --- a/lib/views/pages/home/content/ranking/components/ranking_card.dart +++ b/lib/views/pages/home/content/ranking/components/ranking_card.dart @@ -1,7 +1,6 @@ import "package:flutter/material.dart"; import "package:proxima/models/ui/ranking/ranking_element_details.dart"; -import "package:proxima/models/ui/user_avatar_details.dart"; -import "package:proxima/views/components/content/user_avatar/user_avatar.dart"; +import "package:proxima/views/components/content/user_avatar/dynamic_user_avatar.dart"; import "package:proxima/views/components/content/user_profile_pop_up.dart"; /// A widget that displays a ranking card. @@ -65,6 +64,7 @@ class RankingCard extends StatelessWidget { builder: (BuildContext context) => UserProfilePopUp( displayName: rankingElementDetails.userDisplayName, username: rankingElementDetails.userUserName, + userID: rankingElementDetails.userID, centauriPoints: rankingElementDetails.centauriPoints, ), ), @@ -96,17 +96,13 @@ class RankingCard extends StatelessWidget { ), ); - final userDetails = UserAvatarDetails( - displayName: rankingElementDetails.userDisplayName, - userCentauriPoints: rankingElementDetails.centauriPoints, - ); final userInfo = Padding( padding: const EdgeInsets.all(8), child: Row( children: [ - UserAvatar( + DynamicUserAvatar( key: userRankAvatarKey, - details: userDetails, + uid: rankingElementDetails.userID, radius: 22, ), Flexible(child: userDisplayNameText), diff --git a/lib/views/pages/post/components/comment/comment_post_widget.dart b/lib/views/pages/post/components/comment/comment_post_widget.dart index 0a061a65..c5f0d630 100644 --- a/lib/views/pages/post/components/comment/comment_post_widget.dart +++ b/lib/views/pages/post/components/comment/comment_post_widget.dart @@ -24,6 +24,7 @@ class CommentPostWidget extends StatelessWidget { key: commentUserWidgetKey, posterDisplayName: commentPost.ownerDisplayName, posterUsername: commentPost.ownerUsername, + posterUserID: commentPost.ownerUserID, posterCentauriPoints: commentPost.ownerCentauriPoints, publicationDate: commentPost.publicationDate, ), diff --git a/lib/views/pages/post/components/complete_post.dart b/lib/views/pages/post/components/complete_post.dart index c53ad584..93c08082 100644 --- a/lib/views/pages/post/components/complete_post.dart +++ b/lib/views/pages/post/components/complete_post.dart @@ -46,6 +46,7 @@ class CompletePost extends StatelessWidget { key: postUserBarKey, posterDisplayName: post.ownerDisplayName, posterUsername: post.ownerUsername, + posterUserID: post.ownerUserID, posterCentauriPoints: post.ownerCentauriPoints, publicationDate: post.publicationDate, ), diff --git a/lib/views/pages/profile/components/profile_app_bar.dart b/lib/views/pages/profile/components/profile_app_bar.dart index 9cea519e..3c2a76bf 100644 --- a/lib/views/pages/profile/components/profile_app_bar.dart +++ b/lib/views/pages/profile/components/profile_app_bar.dart @@ -1,20 +1,20 @@ import "package:flutter/material.dart"; -import "package:proxima/models/database/user/user_data.dart"; +import "package:proxima/models/database/user/user_firestore.dart"; import "package:proxima/views/navigation/leading_back_button/leading_back_button.dart"; import "package:proxima/views/pages/profile/components/logout_button.dart"; import "package:proxima/views/pages/profile/components/user_account.dart"; +// This widget displays the profile of a user as an app bar. class ProfileAppBar extends AppBar { - final UserData userData; + // The user to display + final UserFirestore user; ProfileAppBar({ super.key, - required this.userData, + required this.user, }) : super( leading: const LeadingBackButton(), - title: UserAccount( - userData: userData, - ), + title: UserAccount(user: user), actions: [ const LogoutButton(), ], diff --git a/lib/views/pages/profile/components/user_account.dart b/lib/views/pages/profile/components/user_account.dart index 6558e96b..5ecab36a 100644 --- a/lib/views/pages/profile/components/user_account.dart +++ b/lib/views/pages/profile/components/user_account.dart @@ -1,7 +1,6 @@ import "package:flutter/material.dart"; -import "package:proxima/models/database/user/user_data.dart"; -import "package:proxima/models/ui/user_avatar_details.dart"; -import "package:proxima/views/components/content/user_avatar/user_avatar.dart"; +import "package:proxima/models/database/user/user_firestore.dart"; +import "package:proxima/views/components/content/user_avatar/dynamic_user_avatar.dart"; /// This widget display the user info in the profile page class UserAccount extends StatelessWidget { @@ -10,15 +9,18 @@ class UserAccount extends StatelessWidget { const UserAccount({ super.key, - required this.userData, + required this.user, }); - final UserData userData; + // The user to display + final UserFirestore user; @override Widget build(BuildContext context) { final textStyle = Theme.of(context).textTheme.titleSmall; + final userData = user.data; + final displayName = Text( userData.displayName, style: textStyle, @@ -31,8 +33,8 @@ class UserAccount extends StatelessWidget { "${userData.username} ยท ${userData.centauriPoints} Centauri", ); - final profilePicture = UserAvatar( - details: UserAvatarDetails.fromUserData(userData), + final profilePicture = DynamicUserAvatar( + uid: user.uid, radius: 20, ); diff --git a/lib/views/pages/profile/profile_page.dart b/lib/views/pages/profile/profile_page.dart index 3678da50..e5933e2a 100644 --- a/lib/views/pages/profile/profile_page.dart +++ b/lib/views/pages/profile/profile_page.dart @@ -44,10 +44,11 @@ class ProfilePage extends ConsumerWidget { return CircularValue( future: asyncUserData, builder: (context, value) { + final user = value.firestoreUser; return DefaultTabController( length: 2, child: Scaffold( - appBar: ProfileAppBar(userData: value.firestoreUser.data), + appBar: ProfileAppBar(user: user), body: Container( padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), child: const Column( diff --git a/test/end2end/app_actions.dart b/test/end2end/app_actions.dart index 154b45c9..92038ef3 100644 --- a/test/end2end/app_actions.dart +++ b/test/end2end/app_actions.dart @@ -322,11 +322,21 @@ class AppActions { await tester.pumpAndSettle(); // Check that the post content is displayed - expect(find.text(comment), findsOneWidget); + final commentText = find.text(comment); + expect(commentText, findsOneWidget); - // Find the delete button on card - final deleteButton = find.byKey(ProfileInfoCard.deleteButtonCardKey); - expect(deleteButton, findsOneWidget); + // Find and tap the delete button on the card + final commentCard = find.ancestor( + of: commentText, + matching: find.byKey(ProfileInfoCard.infoCardKey), + ); + expect(commentCard, findsOne); + + final deleteButton = find.descendant( + of: commentCard, + matching: find.byKey(ProfileInfoCard.deleteButtonCardKey), + ); + expect(deleteButton, findsOne); await tester.tap(deleteButton); await tester.pumpAndSettle(delayNeededForAsyncFunctionExecution); diff --git a/test/end2end/app_test.dart b/test/end2end/app_test.dart index 3a6175fc..ed3ea66f 100644 --- a/test/end2end/app_test.dart +++ b/test/end2end/app_test.dart @@ -11,7 +11,6 @@ import "package:proxima/services/database/firestore_service.dart"; import "package:proxima/services/sensors/geolocation_service.dart"; import "package:proxima/views/navigation/bottom_navigation_bar/navigation_bar_routes.dart"; import "package:proxima/views/pages/home/content/challenge/challenge_list.dart"; -import "package:proxima/views/pages/home/content/feed/post_feed.dart"; import "package:proxima/views/pages/profile/components/profile_app_bar.dart"; import "package:proxima/views/proxima_app.dart"; @@ -164,17 +163,19 @@ void main() { await AppActions.loginToCreateAccount(tester); await AppActions.createAccountToHome(tester); await AppActions.createPost(tester, testPostTitle, testPostDescription); - await AppActions.openPost(tester, testPostTitle); - const comment = "I like turtles too!"; - await AppActions.addComment(tester, comment); - - // back to feed - await AppActions.navigateBack(tester); - await AppActions.flingRefresh(tester, find.byType(PostFeed)); + await AppActions.expectCommentCount(tester, testPostTitle, 0); + + // Add two comments + const comments = ["I like turtles too!", "I prefer elephants"]; + for (final (i, comment) in comments.indexed) { + await AppActions.openPost(tester, testPostTitle); + await AppActions.addComment(tester, comment); + await AppActions.navigateBack(tester); + await AppActions.expectCommentCount(tester, testPostTitle, i + 1); + } - // expect comment count to be correct - AppActions.expectCommentCount(tester, testPostTitle, 1); - await AppActions.deleteComment(tester, comment); - AppActions.expectCommentCount(tester, testPostTitle, 0); + // Deleting a comment should reduce the comment count + await AppActions.deleteComment(tester, comments.first); + await AppActions.expectCommentCount(tester, testPostTitle, 1); }); } diff --git a/test/mocks/data/post_comment.dart b/test/mocks/data/post_comment.dart index cd32a8a0..6e08d192 100644 --- a/test/mocks/data/post_comment.dart +++ b/test/mocks/data/post_comment.dart @@ -1,3 +1,4 @@ +import "package:proxima/models/database/user/user_id_firestore.dart"; import "package:proxima/models/ui/comment_details.dart"; import "datetime.dart"; @@ -8,6 +9,7 @@ final List testComments = [ "I totally agree! It's the little things in life that make all the difference.", ownerDisplayName: "SunnySkyWalker", ownerUsername: "SunnySkyWalkerUsername", + ownerUserID: const UserIdFirestore(value: "SunnySkyWalkerID"), ownerCentauriPoints: 12, publicationDate: publicationDate1, ), @@ -16,6 +18,7 @@ final List testComments = [ "Just what I needed to read today. Sometimes, simplicity is key to happiness.", ownerDisplayName: "BookishBee", ownerUsername: "BookishBeeUsername", + ownerUserID: const UserIdFirestore(value: "BookishBeeID"), ownerCentauriPoints: 123, publicationDate: publicationDate1, ), @@ -24,6 +27,7 @@ final List testComments = [ "Love this reminder! I try to start each day with a grateful heart.", ownerDisplayName: "MilesOfSmiles", ownerUsername: "MilesOfSmilesUsername", + ownerUserID: const UserIdFirestore(value: "MilesOfSmilesID"), ownerCentauriPoints: 12333, publicationDate: publicationDate1, ), @@ -32,6 +36,7 @@ final List testComments = [ "Couldn't agree more! Walking my dog and enjoying nature always lifts my mood.", ownerDisplayName: "SimpleJoys247", ownerUsername: "SimpleJoys247Username", + ownerUserID: const UserIdFirestore(value: "SimpleJoys247ID"), ownerCentauriPoints: 1729, publicationDate: publicationDate1, ), @@ -40,6 +45,7 @@ final List testComments = [ "This is so true. Just being outdoors makes me feel at peace and genuinely happy.", ownerDisplayName: "HappyHiker42", ownerUsername: "HappyHiker42Username", + ownerUserID: const UserIdFirestore(value: "HappyHiker42ID"), ownerCentauriPoints: 31415, publicationDate: publicationDate1, ), @@ -50,6 +56,7 @@ final List unequalComments = [ content: "content1", ownerDisplayName: "username1", ownerUsername: "username1username", + ownerUserID: const UserIdFirestore(value: "username1ID"), ownerCentauriPoints: 2, publicationDate: publicationDate1, ), @@ -58,6 +65,7 @@ final List unequalComments = [ content: "content2", ownerDisplayName: "username1", ownerUsername: "username1username", + ownerUserID: const UserIdFirestore(value: "username1ID"), ownerCentauriPoints: 0, publicationDate: publicationDate1, ), @@ -65,7 +73,8 @@ final List unequalComments = [ CommentDetails( content: "content1", ownerDisplayName: "username2", - ownerUsername: "username2username", + ownerUsername: "username1username", + ownerUserID: const UserIdFirestore(value: "username1ID"), ownerCentauriPoints: 2, publicationDate: publicationDate1, ), @@ -75,7 +84,18 @@ final List unequalComments = [ content: "content1", ownerDisplayName: "username1", ownerUsername: "username1username", + ownerUserID: const UserIdFirestore(value: "username1ID"), ownerCentauriPoints: 2, publicationDate: publicationDate1.add(const Duration(days: 1)), ), + + // Different ownerUserID + CommentDetails( + content: "content1", + ownerDisplayName: "username1", + ownerUsername: "username1username", + ownerUserID: const UserIdFirestore(value: "username2ID"), + ownerCentauriPoints: 2, + publicationDate: publicationDate1, + ), ]; diff --git a/test/mocks/data/post_overview.dart b/test/mocks/data/post_overview.dart index 65bcb0bd..75d0202b 100644 --- a/test/mocks/data/post_overview.dart +++ b/test/mocks/data/post_overview.dart @@ -1,4 +1,5 @@ import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/models/database/user/user_id_firestore.dart"; import "package:proxima/models/ui/post_details.dart"; import "datetime.dart"; @@ -14,6 +15,7 @@ final List testPosts = [ // to simplify testing ownerDisplayName: "Proxima", ownerUsername: "Elephant", + ownerUserID: const UserIdFirestore(value: "ProximaUserID"), ownerCentauriPoints: 32, publicationDate: publicationDate1, distance: 20, @@ -27,6 +29,7 @@ final List testPosts = [ commentNumber: 5, ownerDisplayName: "Proxima", ownerUsername: "ProximaUsername", + ownerUserID: const UserIdFirestore(value: "ProximaUserID"), ownerCentauriPoints: 7, publicationDate: publicationDate1, distance: 20, @@ -40,6 +43,7 @@ final List testPosts = [ commentNumber: 829, ownerDisplayName: "Proxima", ownerUsername: "ProximaUsername", + ownerUserID: const UserIdFirestore(value: "ProximaUserID"), ownerCentauriPoints: 218281828, publicationDate: publicationDate1, distance: 20, @@ -55,6 +59,7 @@ final timeDistancePost = PostDetails( commentNumber: 5, ownerDisplayName: "owner", ownerUsername: "ownerUsername", + ownerUserID: const UserIdFirestore(value: "ownerID"), ownerCentauriPoints: 911, publicationDate: DateTime.utc(1999), distance: 100, diff --git a/test/mocks/data/ranking_data.dart b/test/mocks/data/ranking_data.dart index 93bdef21..d2eaaeb8 100644 --- a/test/mocks/data/ranking_data.dart +++ b/test/mocks/data/ranking_data.dart @@ -1,4 +1,5 @@ import "package:collection/collection.dart"; +import "package:proxima/models/database/user/user_id_firestore.dart"; import "package:proxima/models/ui/ranking/ranking_details.dart"; import "package:proxima/models/ui/ranking/ranking_element_details.dart"; @@ -23,6 +24,7 @@ final List mockRankingElementDetailsList = (index, user) => RankingElementDetails( userDisplayName: user.keys.first, userUserName: "u_${user.keys.first}", + userID: UserIdFirestore(value: "uid_${user.keys.first}"), centauriPoints: user.values.first, userRank: index + 1, ), @@ -41,6 +43,7 @@ final RankingElementDetails testUserRankingElementDetails = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 0, userRank: 9, ); diff --git a/test/mocks/overrides/override_dynamic_user_avatar_view_model.dart b/test/mocks/overrides/override_dynamic_user_avatar_view_model.dart index c737137f..f34f030c 100644 --- a/test/mocks/overrides/override_dynamic_user_avatar_view_model.dart +++ b/test/mocks/overrides/override_dynamic_user_avatar_view_model.dart @@ -1,27 +1,33 @@ import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/models/database/user/user_id_firestore.dart"; import "package:proxima/models/ui/user_avatar_details.dart"; -import "package:proxima/services/database/challenge_repository_service.dart"; import "package:proxima/viewmodels/dynamic_user_avatar_view_model.dart"; import "../data/firebase_auth_user.dart"; +import "../data/firestore_user.dart"; /// A mock implementation of the [DynamicUserAvatarViewModel] class. class MockDynamicUserAvatarViewModel extends AutoDisposeFamilyAsyncNotifier implements DynamicUserAvatarViewModel { final Future Function(UserIdFirestore? arg) _build; + final Future Function() _onRefresh; MockDynamicUserAvatarViewModel({ Future Function(UserIdFirestore? arg)? build, - }) : _build = build ?? + Future Function()? onRefresh, + }) : _build = build ?? ((_) async => const UserAvatarDetails( displayName: "", - userCentauriPoints: null, - )); + centauriPoints: null, + )), + _onRefresh = onRefresh ?? (() async {}); @override Future build(UserIdFirestore? arg) => _build(arg); + + @override + Future refresh() => _onRefresh(); } final mockDynamicUserAvatarViewModelTestLoginUserOverride = [ @@ -29,7 +35,7 @@ final mockDynamicUserAvatarViewModelTestLoginUserOverride = [ () => MockDynamicUserAvatarViewModel( build: (userUID) async => UserAvatarDetails( displayName: testingLoginUser.displayName!, - userCentauriPoints: ChallengeRepositoryService.soloChallengeReward * 50, + centauriPoints: testingUserData.centauriPoints, ), ), ), @@ -40,7 +46,7 @@ final mockDynamicUserAvatarViewModelEmptyDisplayNameOverride = [ () => MockDynamicUserAvatarViewModel( build: (userUID) async => const UserAvatarDetails( displayName: "", - userCentauriPoints: null, + centauriPoints: null, ), ), ), diff --git a/test/mocks/overrides/override_post_comment_count_view_model.dart b/test/mocks/overrides/override_post_comment_count_view_model.dart new file mode 100644 index 00000000..7782a9fe --- /dev/null +++ b/test/mocks/overrides/override_post_comment_count_view_model.dart @@ -0,0 +1,30 @@ +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/viewmodels/post_comment_count_view_model.dart"; + +/// A mock implementation of the [PostCommentCountViewModel] class. +/// Its state is always the same and can be set in the constructor. +class MockPostCommentCountViewModel + extends FamilyAsyncNotifier + implements PostCommentCountViewModel { + final int count; + + /// Creates a new [MockPostCommentCountViewModel] with the given [count], + /// which is the value that will always be returned by this view-model. + MockPostCommentCountViewModel({this.count = 0}); + + @override + Future build(PostIdFirestore arg) async { + return count; + } + + @override + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() => build(arg)); + } +} + +final zeroPostCommentCountOverride = [ + postCommentCountProvider.overrideWith(() => MockPostCommentCountViewModel()), +]; diff --git a/test/mocks/overrides/override_posts_feed_view_model.dart b/test/mocks/overrides/override_posts_feed_view_model.dart index bf426fda..7e0c4123 100644 --- a/test/mocks/overrides/override_posts_feed_view_model.dart +++ b/test/mocks/overrides/override_posts_feed_view_model.dart @@ -4,6 +4,7 @@ import "package:proxima/models/ui/post_details.dart"; import "package:proxima/viewmodels/posts_feed_view_model.dart"; import "../data/post_overview.dart"; +import "override_post_comment_count_view_model.dart"; /// A mock implementation of the [PostsFeedViewModel] class. /// This class is particularly useful for the UI tests where we want to expose @@ -35,6 +36,8 @@ final mockNonEmptyHomeViewModelOverride = [ postsFeedViewModelProvider.overrideWith( () => MockPostsFeedViewModel(build: () async => testPosts), ), + // The posts don't exist in the database, so we also override their comment count to 0 + ...zeroPostCommentCountOverride, ]; final mockLoadingHomeViewModelOverride = [ diff --git a/test/mocks/providers/provider_ranking.dart b/test/mocks/providers/provider_ranking.dart index 076f2c8c..18335e7b 100644 --- a/test/mocks/providers/provider_ranking.dart +++ b/test/mocks/providers/provider_ranking.dart @@ -1,4 +1,5 @@ import "package:fake_cloud_firestore/fake_cloud_firestore.dart"; +import "package:firebase_core/firebase_core.dart"; import "package:flutter/material.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/services/database/firestore_service.dart"; @@ -6,6 +7,8 @@ import "package:proxima/viewmodels/login_view_model.dart"; import "package:proxima/views/pages/home/content/ranking/ranking_page.dart"; import "../data/firestore_user.dart"; +import "../overrides/override_dynamic_user_avatar_view_model.dart"; +import "../services/setup_firebase_mocks.dart"; /// Returns a testing [ProviderContainer] which purpose is to test the ranking /// view model. @@ -13,10 +16,13 @@ import "../data/firestore_user.dart"; Future rankingProviderContainerWithTestingUser( FakeFirebaseFirestore fakeFirebaseFirestore, ) async { + setupFirebaseAuthMocks(); + await Firebase.initializeApp(); await setUserFirestore(fakeFirebaseFirestore, testingUserFirestore); return ProviderContainer( overrides: [ + ...mockDynamicUserAvatarViewModelTestLoginUserOverride, firestoreProvider.overrideWithValue(fakeFirebaseFirestore), validLoggedInUserIdProvider.overrideWithValue(testingUserFirestoreId), ], @@ -25,16 +31,16 @@ Future rankingProviderContainerWithTestingUser( /// Returns a testable [RankingPage] already overriden by providers /// of [rankingProviderContainerWithTestingUser]. -Future rankingPageMockApp( +Future rankingPageMockApp( FakeFirebaseFirestore fakeFirebaseFirestore, ) async { final rankingContainer = await rankingProviderContainerWithTestingUser(fakeFirebaseFirestore); - return MaterialApp( - home: UncontrolledProviderScope( - container: rankingContainer, - child: const RankingPage(), + return UncontrolledProviderScope( + container: rankingContainer, + child: const MaterialApp( + home: RankingPage(), ), ); } diff --git a/test/models/ui/comment_details_test.dart b/test/models/ui/comment_details_test.dart index ab4bdb6b..eed9e0fe 100644 --- a/test/models/ui/comment_details_test.dart +++ b/test/models/ui/comment_details_test.dart @@ -11,6 +11,7 @@ void main() { commentPost.content, commentPost.ownerDisplayName, commentPost.ownerUsername, + commentPost.ownerUserID, commentPost.ownerCentauriPoints, commentPost.publicationDate, ); @@ -31,21 +32,28 @@ void main() { final commentPost = unequalComments[0]; final commentPostOther = unequalComments[1]; - expect(commentPost != commentPostOther, true); + expect(commentPost, isNot(equals(commentPostOther))); }); test("inequality test on username", () { final commentPost = unequalComments[0]; final commentPostOther = unequalComments[2]; - expect(commentPost != commentPostOther, true); + expect(commentPost, isNot(equals(commentPostOther))); }); test("inequality test on publication time", () { final commentPost = unequalComments[0]; final commentPostOther = unequalComments[3]; - expect(commentPost != commentPostOther, true); + expect(commentPost, isNot(equals(commentPostOther))); + }); + + test("inequality test on owner ID", () { + final commentPost = unequalComments[0]; + final commentPostOther = unequalComments[4]; + + expect(commentPost, isNot(equals(commentPostOther))); }); }); } diff --git a/test/models/ui/post_details_test.dart b/test/models/ui/post_details_test.dart index 4da7267c..7df2a570 100644 --- a/test/models/ui/post_details_test.dart +++ b/test/models/ui/post_details_test.dart @@ -15,6 +15,7 @@ void main() { postDetails.commentNumber, postDetails.ownerDisplayName, postDetails.ownerUsername, + postDetails.ownerUserID, postDetails.ownerCentauriPoints, postDetails.publicationDate, postDetails.distance, diff --git a/test/models/ui/ranking/ranking_details_test.dart b/test/models/ui/ranking/ranking_details_test.dart index 847fa82d..3dd12570 100644 --- a/test/models/ui/ranking/ranking_details_test.dart +++ b/test/models/ui/ranking/ranking_details_test.dart @@ -10,23 +10,26 @@ void main() { late List listRankingElementDetails; setUp(() { - final usersList = FirestoreUserGenerator.generateUserData(5); - listRankingElementDetails = usersList - .mapIndexed( - (index, user) => RankingElementDetails( - userDisplayName: user.displayName, - userUserName: user.username, - centauriPoints: user.centauriPoints, - userRank: index + 1, - ), - ) - .toList(); + final usersList = FirestoreUserGenerator.generateUserFirestore(5); + listRankingElementDetails = usersList.mapIndexed( + (index, user) { + final userData = user.data; + return RankingElementDetails( + userDisplayName: userData.displayName, + userUserName: userData.username, + userID: user.uid, + centauriPoints: userData.centauriPoints, + userRank: index + 1, + ); + }, + ).toList(); }); test("hash", () { final userRankingElementDetail = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 6, ); @@ -50,6 +53,7 @@ void main() { final userRankingElementDetail = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 6, ); @@ -71,6 +75,7 @@ void main() { final userRankingElementDetail1 = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 6, ); @@ -83,6 +88,7 @@ void main() { final userRankingElementDetail2 = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 12, userRank: 7, ); @@ -99,6 +105,7 @@ void main() { final userRankingElementDetail = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 6, ); @@ -108,17 +115,19 @@ void main() { rankElementDetailsList: listRankingElementDetails, ); - final userList2 = FirestoreUserGenerator.generateUserData(3); - final listRankingElementDetails2 = userList2 - .mapIndexed( - (index, user) => RankingElementDetails( - userDisplayName: user.displayName, - userUserName: user.username, - centauriPoints: user.centauriPoints, - userRank: index + 1, - ), - ) - .toList(); + final userList2 = FirestoreUserGenerator.generateUserFirestore(3); + final listRankingElementDetails2 = userList2.mapIndexed( + (index, user) { + final userData = user.data; + return RankingElementDetails( + userDisplayName: userData.displayName, + userUserName: userData.username, + userID: user.uid, + centauriPoints: userData.centauriPoints, + userRank: index + 1, + ); + }, + ).toList(); final rankingDetails2 = RankingDetails( userRankElementDetails: userRankingElementDetail, @@ -135,21 +144,24 @@ void main() { final userRankingElementDetail = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 6, ); - final usersList = FirestoreUserGenerator.generateUserData(5); - final listRankingElementDetails = usersList - .mapIndexed( - (index, user) => RankingElementDetails( - userDisplayName: user.displayName, - userUserName: user.username, - centauriPoints: user.centauriPoints, - userRank: index == 1 ? null : index + 1, - ), - ) - .toList(); + final usersList = FirestoreUserGenerator.generateUserFirestore(5); + final listRankingElementDetails = usersList.mapIndexed( + (index, user) { + final userData = user.data; + return RankingElementDetails( + userDisplayName: userData.displayName, + userUserName: userData.username, + userID: user.uid, + centauriPoints: userData.centauriPoints, + userRank: index == 1 ? null : index + 1, + ); + }, + ).toList(); expect( () => { diff --git a/test/models/ui/ranking/ranking_element_details_test.dart b/test/models/ui/ranking/ranking_element_details_test.dart index 797c824f..e1e1c6a1 100644 --- a/test/models/ui/ranking/ranking_element_details_test.dart +++ b/test/models/ui/ranking/ranking_element_details_test.dart @@ -1,4 +1,5 @@ import "package:flutter_test/flutter_test.dart"; +import "package:proxima/models/database/user/user_id_firestore.dart"; import "package:proxima/models/ui/ranking/ranking_element_details.dart"; import "../../../mocks/data/firestore_user.dart"; @@ -9,6 +10,7 @@ void main() { final rankingElementDetail = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 1, ); @@ -16,6 +18,7 @@ void main() { final expectedHash = Object.hash( rankingElementDetail.userDisplayName, rankingElementDetail.userUserName, + rankingElementDetail.userID, rankingElementDetail.centauriPoints, rankingElementDetail.userRank, ); @@ -29,6 +32,7 @@ void main() { final rankingElementDetail1 = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 1, ); @@ -36,17 +40,19 @@ void main() { final rankingElementDetail2 = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 1, ); - expect(rankingElementDetail1 == rankingElementDetail2, isTrue); + expect(rankingElementDetail1, rankingElementDetail2); }); test("!= userDisplayName", () { final rankingElementDetail1 = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 1, ); @@ -54,17 +60,19 @@ void main() { final rankingElementDetail2 = RankingElementDetails( userDisplayName: "${testingUserData.displayName}_2", userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 1, ); - expect(rankingElementDetail1 == rankingElementDetail2, isFalse); + expect(rankingElementDetail1, isNot(equals(rankingElementDetail2))); }); test("!= centauriPoints", () { final rankingElementDetail1 = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 1, ); @@ -72,17 +80,19 @@ void main() { final rankingElementDetail2 = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 12, userRank: 1, ); - expect(rankingElementDetail1 == rankingElementDetail2, isFalse); + expect(rankingElementDetail1, isNot(equals(rankingElementDetail2))); }); test("!= userRank", () { final rankingElementDetail1 = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 1, ); @@ -90,11 +100,32 @@ void main() { final rankingElementDetail2 = RankingElementDetails( userDisplayName: testingUserData.displayName, userUserName: testingUserData.username, + userID: testingUserFirestoreId, centauriPoints: 10, userRank: 2, ); - expect(rankingElementDetail1 == rankingElementDetail2, isFalse); + expect(rankingElementDetail1, isNot(equals(rankingElementDetail2))); + }); + + test("!= userID", () { + final rankingElementDetail1 = RankingElementDetails( + userDisplayName: testingUserData.displayName, + userUserName: testingUserData.username, + userID: testingUserFirestoreId, + centauriPoints: 10, + userRank: 1, + ); + + final rankingElementDetail2 = RankingElementDetails( + userDisplayName: testingUserData.displayName, + userUserName: testingUserData.username, + userID: const UserIdFirestore(value: "not_the_same_uid"), + centauriPoints: 10, + userRank: 1, + ); + + expect(rankingElementDetail1, isNot(equals(rankingElementDetail2))); }); }); } diff --git a/test/models/ui/user_avatar_details_test.dart b/test/models/ui/user_avatar_details_test.dart index 96a49f1f..9adb8f5a 100644 --- a/test/models/ui/user_avatar_details_test.dart +++ b/test/models/ui/user_avatar_details_test.dart @@ -8,13 +8,13 @@ void main() { late UserAvatarDetails userAvatarDetails; setUp(() { - userAvatarDetails = UserAvatarDetails.fromUserData(testingUserData); + userAvatarDetails = UserAvatarDetails.fromUser(testingUserFirestore); }); test("Hash overrides correctly", () { final expectedHash = Object.hash( userAvatarDetails.displayName, - userAvatarDetails.userCentauriPoints, + userAvatarDetails.centauriPoints, ); final actualHash = userAvatarDetails.hashCode; @@ -25,7 +25,7 @@ void main() { // Copy made by the other constructor on purpose final userAvatarDetailsCopy = UserAvatarDetails( displayName: testingUserData.displayName, - userCentauriPoints: testingUserData.centauriPoints, + centauriPoints: testingUserData.centauriPoints, ); expect(userAvatarDetails, equals(userAvatarDetailsCopy)); diff --git a/test/viewmodels/dynamic_user_avatar_view_model_integration_test.dart b/test/viewmodels/dynamic_user_avatar_view_model_integration_test.dart new file mode 100644 index 00000000..d84e0434 --- /dev/null +++ b/test/viewmodels/dynamic_user_avatar_view_model_integration_test.dart @@ -0,0 +1,82 @@ +import "package:fake_cloud_firestore/fake_cloud_firestore.dart"; +import "package:flutter_test/flutter_test.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:proxima/services/database/challenge_repository_service.dart"; +import "package:proxima/services/database/firestore_service.dart"; +import "package:proxima/viewmodels/challenge_view_model.dart"; +import "package:proxima/viewmodels/dynamic_user_avatar_view_model.dart"; +import "package:proxima/viewmodels/login_view_model.dart"; + +import "../mocks/data/firestore_challenge.dart"; +import "../mocks/data/firestore_post.dart"; +import "../mocks/data/firestore_user.dart"; +import "../mocks/data/geopoint.dart"; +import "../utils/delay_async_func.dart"; + +void main() { + late FakeFirebaseFirestore fakeFireStore; + late ProviderContainer container; + + setUp(() { + fakeFireStore = FakeFirebaseFirestore(); + }); + + group( + "Dynamic User Avatar check Centauri points ViewModel part integration testing using challenges", + () { + late FirestoreChallengeGenerator challengeGenerator; + late FirestorePostGenerator postGenerator; + + setUp(() async { + container = ProviderContainer( + overrides: [ + loggedInUserIdProvider.overrideWithValue(testingUserFirestoreId), + firestoreProvider.overrideWithValue(fakeFireStore), + ], + ); + challengeGenerator = FirestoreChallengeGenerator(); + postGenerator = FirestorePostGenerator(); + + await setUserFirestore(fakeFireStore, testingUserFirestore); + + final post = postGenerator.generatePostAt( + userPosition1, + ); + await setPostFirestore(post, fakeFireStore); + }); + + test("On challenge completion check centauri points refresh", () async { + // Check the initial number of centauri points + final centauriPointsBeforeChallengeCompletion = await container.read( + dynamicUserAvatarViewModelProvider(testingUserFirestoreId).future, + ); + expect( + centauriPointsBeforeChallengeCompletion.centauriPoints, + testingUserData.centauriPoints, + ); + + // Generate a challenge + final challenge = challengeGenerator.generateChallenge( + false, + const Duration(hours: 2, minutes: 30), + ); + await setChallenge(fakeFireStore, challenge, testingUserFirestoreId); + + await container + .read(challengeViewModelProvider.notifier) + .completeChallenge(challenge.postId); + + await Future.delayed(delayNeededForAsyncFunctionExecution); + + // Check the final number of centauri points + final centauriPointsAfterChallengeCompletion = await container.read( + dynamicUserAvatarViewModelProvider(testingUserFirestoreId).future, + ); + expect( + centauriPointsAfterChallengeCompletion.centauriPoints, + testingUserData.centauriPoints + + ChallengeRepositoryService.soloChallengeReward, + ); + }); + }); +} diff --git a/test/viewmodels/user_avatar_view_model_unit_test.dart b/test/viewmodels/dynamic_user_avatar_view_model_unit_test.dart similarity index 71% rename from test/viewmodels/user_avatar_view_model_unit_test.dart rename to test/viewmodels/dynamic_user_avatar_view_model_unit_test.dart index a8906107..07bd8a27 100644 --- a/test/viewmodels/user_avatar_view_model_unit_test.dart +++ b/test/viewmodels/dynamic_user_avatar_view_model_unit_test.dart @@ -10,7 +10,7 @@ import "package:proxima/viewmodels/login_view_model.dart"; import "../mocks/data/firestore_user.dart"; void main() { - group("User Avatar ViewModel unit testing", () { + group("Dynamic User Avatar ViewModel unit testing", () { late FakeFirebaseFirestore fakeFireStore; late UserRepositoryService userRepo; @@ -31,7 +31,8 @@ void main() { ]; }); - group("User Avatar display name provider unit testing without current user", + group( + "Dynamic User Avatar display name provider unit testing without current user", () { setUp(() async { container = ProviderContainer( @@ -47,7 +48,7 @@ void main() { ); }); }); - group("User Avatar display name provider unit testing", () { + group("Dynamic User Avatar display name provider unit testing", () { late List availableUsers; setUp(() async { @@ -85,6 +86,30 @@ void main() { ); } }); + + group("User Centauri points unit testing", () { + test("Find centauri points given null user id", () async { + final userAvatarDetails = await container.read( + dynamicUserAvatarViewModelProvider(null).future, + ); + expect( + userAvatarDetails.centauriPoints, + availableUsers[0].data.centauriPoints, + ); + }); + + test("Find centauri points given user id in available users", () async { + for (final user in availableUsers) { + final userAvatarDetails = await container.read( + dynamicUserAvatarViewModelProvider(user.uid).future, + ); + expect( + userAvatarDetails.centauriPoints, + user.data.centauriPoints, + ); + } + }); + }); }); }); } diff --git a/test/viewmodels/post_comment_count_view_model_integration_test.dart b/test/viewmodels/post_comment_count_view_model_integration_test.dart new file mode 100644 index 00000000..0678bae4 --- /dev/null +++ b/test/viewmodels/post_comment_count_view_model_integration_test.dart @@ -0,0 +1,134 @@ +import "package:collection/collection.dart"; +import "package:fake_cloud_firestore/fake_cloud_firestore.dart"; +import "package:flutter_test/flutter_test.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:mockito/mockito.dart"; +import "package:proxima/models/database/comment/comment_firestore.dart"; +import "package:proxima/models/database/post/post_firestore.dart"; +import "package:proxima/models/database/post/post_id_firestore.dart"; +import "package:proxima/services/database/comment/comment_repository_service.dart"; +import "package:proxima/services/database/firestore_service.dart"; +import "package:proxima/services/sensors/geolocation_service.dart"; +import "package:proxima/viewmodels/comments_view_model.dart"; +import "package:proxima/viewmodels/login_view_model.dart"; +import "package:proxima/viewmodels/post_comment_count_view_model.dart"; +import "package:proxima/viewmodels/user_comments_view_model.dart"; + +import "../mocks/data/comment_data.dart"; +import "../mocks/data/firestore_post.dart"; +import "../mocks/data/firestore_user.dart"; +import "../mocks/data/geopoint.dart"; +import "../mocks/services/mock_geo_location_service.dart"; +import "../mocks/services/setup_firebase_mocks.dart"; +import "../utils/delay_async_func.dart"; + +void main() { + late MockGeolocationService geoLocationService; + late FakeFirebaseFirestore fakeFireStore; + late ProviderContainer container; + + late CommentRepositoryService commentRepository; + + setUp(() { + setupFirebaseAuthMocks(); + + geoLocationService = MockGeolocationService(); + fakeFireStore = FakeFirebaseFirestore(); + when(geoLocationService.getCurrentPosition()).thenAnswer( + (_) async => userPosition0, + ); + + container = ProviderContainer( + overrides: [ + geolocationServiceProvider.overrideWithValue(geoLocationService), + loggedInUserIdProvider.overrideWithValue(testingUserFirestoreId), + firestoreProvider.overrideWithValue(fakeFireStore), + ], + ); + + commentRepository = container.read(commentRepositoryServiceProvider); + }); + + Future expectCommentCount( + PostIdFirestore postId, + int expectedCount, + ) async { + final actualCount = await container.read( + postCommentCountProvider(postId).future, + ); + expect(actualCount, equals(expectedCount)); + } + + group("Comment count refresh", () { + const int startCommentCount = 5; + late PostFirestore post; + late List comments; + + setUp(() async { + // Create and add a post to the database + final postGenerator = FirestorePostGenerator(); + post = postGenerator.generatePostAt(userPosition0); + await setPostFirestore(post, fakeFireStore); + + // Create comments + final commentDataGenerator = CommentDataGenerator(); + final commentDatas = List.generate( + startCommentCount, + (_) => commentDataGenerator.createMockCommentData( + ownerId: testingUserFirestoreId, + ), + ).toList(); + + // Add the comments to the database + final commentIds = await Future.wait( + commentDatas.map( + (commentData) => commentRepository.addComment(post.id, commentData), + ), + ); + + comments = commentIds + .mapIndexed( + (i, id) => CommentFirestore(id: id, data: commentDatas[i]), + ) + .toList(); + }); + + test("Refresh on comment list refresh", () async { + await expectCommentCount(post.id, startCommentCount); + + // Add a new comment + final newComment = CommentDataGenerator().createMockCommentData(); + await commentRepository.addComment(post.id, newComment); + + // This should not have refreshed for now + await expectCommentCount(post.id, startCommentCount); + + // Refresh the comment list + await container + .read(commentsViewModelProvider(post.id).notifier) + .refresh(); + await Future.delayed(delayNeededForAsyncFunctionExecution); + + // The comment count should now be updated + await expectCommentCount(post.id, startCommentCount + 1); + }); + + test("Refresh on comment deletion in view-model", () async { + await expectCommentCount(post.id, startCommentCount); + + // Delete the post + final comment = comments.first; + final userCommentViewModel = container.read( + userCommentsViewModelProvider.notifier, + ); + await userCommentViewModel.deleteComment( + post.id, + comment.id, + ); + await Future.delayed(delayNeededForAsyncFunctionExecution); + + // The comment count should now be 1 less + await expectCommentCount(post.id, startCommentCount - 1); + }); + }); +} diff --git a/test/viewmodels/posts_feed_view_model_integration_test.dart b/test/viewmodels/posts_feed_view_model_integration_test.dart index f4a90635..6ed0a770 100644 --- a/test/viewmodels/posts_feed_view_model_integration_test.dart +++ b/test/viewmodels/posts_feed_view_model_integration_test.dart @@ -118,6 +118,7 @@ void main() { voteScore: postData.voteScore, ownerDisplayName: owner.data.displayName, ownerUsername: owner.data.username, + ownerUserID: owner.uid, ownerCentauriPoints: owner.data.centauriPoints, commentNumber: postData.commentCount, publicationDate: postData.publicationTime.toDate(), @@ -217,6 +218,7 @@ void main() { voteScore: data.voteScore, ownerDisplayName: owner.data.displayName, ownerUsername: owner.data.username, + ownerUserID: owner.uid, ownerCentauriPoints: owner.data.centauriPoints, commentNumber: data.commentCount, publicationDate: data.publicationTime.toDate(), diff --git a/test/viewmodels/posts_feed_view_model_unit_test.dart b/test/viewmodels/posts_feed_view_model_unit_test.dart index 01b5b1c0..13a35ee9 100644 --- a/test/viewmodels/posts_feed_view_model_unit_test.dart +++ b/test/viewmodels/posts_feed_view_model_unit_test.dart @@ -118,6 +118,7 @@ void main() { commentNumber: post.data.commentCount, ownerDisplayName: owner.data.displayName, ownerUsername: owner.data.username, + ownerUserID: owner.uid, ownerCentauriPoints: owner.data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, @@ -176,6 +177,7 @@ void main() { commentNumber: post.data.commentCount, ownerDisplayName: owner.data.displayName, ownerUsername: owner.data.username, + ownerUserID: owner.uid, ownerCentauriPoints: owner.data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, @@ -242,6 +244,7 @@ void main() { commentNumber: post.data.commentCount, ownerDisplayName: owners[index].data.displayName, ownerUsername: owners[index].data.username, + ownerUserID: owners[index].uid, ownerCentauriPoints: owners[index].data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, @@ -321,6 +324,7 @@ void main() { commentNumber: post.data.commentCount, ownerDisplayName: owner.data.displayName, ownerUsername: owner.data.username, + ownerUserID: owner.uid, ownerCentauriPoints: owner.data.centauriPoints, publicationDate: post.data.publicationTime.toDate(), distance: 0, diff --git a/test/views/pages/home/content/ranking/static_ranking_list_test.dart b/test/views/pages/home/content/ranking/static_ranking_list_test.dart index 2e25c4b9..3a0d2a7e 100644 --- a/test/views/pages/home/content/ranking/static_ranking_list_test.dart +++ b/test/views/pages/home/content/ranking/static_ranking_list_test.dart @@ -1,20 +1,27 @@ import "package:flutter/material.dart"; import "package:flutter_test/flutter_test.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; import "package:proxima/views/components/content/user_profile_pop_up.dart"; import "package:proxima/views/pages/home/content/ranking/components/ranking_card.dart"; import "package:proxima/views/pages/home/content/ranking/components/ranking_list.dart"; import "../../../../../mocks/data/ranking_data.dart"; +import "../../../../../mocks/overrides/override_dynamic_user_avatar_view_model.dart"; void main() { group("Widgets display", () { testWidgets( "Display ranking elements", (tester) async { - final rankingListWidget = MaterialApp( - home: RankingList( - rankingDetails: mockRankingDetailsWithtestUser, - onRefresh: () async {}, + final rankingListWidget = ProviderScope( + overrides: [ + ...mockDynamicUserAvatarViewModelEmptyDisplayNameOverride, + ], + child: MaterialApp( + home: RankingList( + rankingDetails: mockRankingDetailsWithtestUser, + onRefresh: () async {}, + ), ), );