diff --git a/ecommerce_app/integration_test/purchase_flow_test.dart b/ecommerce_app/integration_test/purchase_flow_test.dart index 1082a743..dddbc2ae 100644 --- a/ecommerce_app/integration_test/purchase_flow_test.dart +++ b/ecommerce_app/integration_test/purchase_flow_test.dart @@ -28,11 +28,11 @@ void main() { r.cart.expectFindZeroCartItems(); await r.closePage(); // reviews flow - // await r.products.selectProduct(); - // r.reviews.expectFindLeaveReview(); - // await r.reviews.tapLeaveReviewButton(); - // await r.reviews.createAndSubmitReview(); - // r.reviews.expectFindOneReview(); + await r.products.selectProduct(); + r.reviews.expectFindLeaveReview(); + await r.reviews.tapLeaveReviewButton(); + await r.reviews.createAndSubmitReview('Love it!'); + r.reviews.expectFindOneReview(); // sign out await r.openPopupMenu(); await r.auth.openAccountScreen(); diff --git a/ecommerce_app/lib/src/features/reviews/application/reviews_service.dart b/ecommerce_app/lib/src/features/reviews/application/reviews_service.dart index 0fddb359..e5539cc6 100644 --- a/ecommerce_app/lib/src/features/reviews/application/reviews_service.dart +++ b/ecommerce_app/lib/src/features/reviews/application/reviews_service.dart @@ -1,4 +1,5 @@ import 'package:ecommerce_app/src/features/authentication/data/fake_auth_repository.dart'; +import 'package:ecommerce_app/src/features/products/data/fake_products_repository.dart'; import 'package:ecommerce_app/src/features/products/domain/product.dart'; import 'package:ecommerce_app/src/features/reviews/data/fake_reviews_repository.dart'; import 'package:ecommerce_app/src/features/reviews/domain/review.dart'; @@ -25,6 +26,37 @@ class ReviewsService { uid: user.uid, review: review, ); + // * Note: this should be done on the backend + // * At this stage the review is already submitted + // * and we don't need to await for the product rating to also be updated + _updateProductRating(productId); + } + + Future _updateProductRating(ProductID productId) async { + final reviews = + await ref.read(reviewsRepositoryProvider).fetchReviews(productId); + final avgRating = _avgReviewScore(reviews); + final product = ref.read(productsRepositoryProvider).getProduct(productId); + if (product == null) { + throw StateError('Product not found with id: $productId.'.hardcoded); + } + final updated = product.copyWith( + avgRating: avgRating, + numRatings: reviews.length, + ); + await ref.read(productsRepositoryProvider).setProduct(updated); + } + + double _avgReviewScore(List reviews) { + if (reviews.isNotEmpty) { + var total = 0.0; + for (var review in reviews) { + total += review.rating; + } + return total / reviews.length; + } else { + return 0.0; + } } } diff --git a/ecommerce_app/test/src/features/purchase_flow_test.dart b/ecommerce_app/test/src/features/purchase_flow_test.dart index 5abde031..b5a85574 100644 --- a/ecommerce_app/test/src/features/purchase_flow_test.dart +++ b/ecommerce_app/test/src/features/purchase_flow_test.dart @@ -26,11 +26,11 @@ void main() { r.cart.expectFindZeroCartItems(); await r.closePage(); // reviews flow - // await r.products.selectProduct(); - // r.reviews.expectFindLeaveReview(); - // await r.reviews.tapLeaveReviewButton(); - // await r.reviews.createAndSubmitReview(); - // r.reviews.expectFindOneReview(); + await r.products.selectProduct(); + r.reviews.expectFindLeaveReview(); + await r.reviews.tapLeaveReviewButton(); + await r.reviews.createAndSubmitReview('Love it!'); + r.reviews.expectFindOneReview(); // sign out await r.openPopupMenu(); await r.auth.openAccountScreen(); diff --git a/ecommerce_app/test/src/features/reviews/application/reviews_service_test.dart b/ecommerce_app/test/src/features/reviews/application/reviews_service_test.dart index b095fb8b..a0e6dfa1 100644 --- a/ecommerce_app/test/src/features/reviews/application/reviews_service_test.dart +++ b/ecommerce_app/test/src/features/reviews/application/reviews_service_test.dart @@ -1,5 +1,7 @@ +import 'package:ecommerce_app/src/constants/test_products.dart'; import 'package:ecommerce_app/src/features/authentication/data/fake_auth_repository.dart'; import 'package:ecommerce_app/src/features/authentication/domain/app_user.dart'; +import 'package:ecommerce_app/src/features/products/data/fake_products_repository.dart'; import 'package:ecommerce_app/src/features/reviews/application/reviews_service.dart'; import 'package:ecommerce_app/src/features/reviews/data/fake_reviews_repository.dart'; import 'package:ecommerce_app/src/features/reviews/domain/review.dart'; @@ -11,14 +13,17 @@ import '../../../mocks.dart'; void main() { const testUser = AppUser(uid: 'abc', email: 'abc@test.com'); - const testProductId = '1'; + final testProductId = kTestProducts[0].id; final testReview = Review(rating: 5, comment: '', date: DateTime(2022, 7, 31)); late MockAuthRepository authRepository; late MockReviewsRepository reviewsRepository; + late MockProductsRepository productsRepository; + setUp(() { authRepository = MockAuthRepository(); reviewsRepository = MockReviewsRepository(); + productsRepository = MockProductsRepository(); }); ReviewsService makeReviewsService() { @@ -26,6 +31,7 @@ void main() { overrides: [ authRepositoryProvider.overrideWithValue(authRepository), reviewsRepositoryProvider.overrideWithValue(reviewsRepository), + productsRepositoryProvider.overrideWithValue(productsRepository), ], ); addTearDown(container.dispose); @@ -54,6 +60,14 @@ void main() { uid: testUser.uid, review: testReview, )).thenAnswer((_) => Future.value()); + when(() => reviewsRepository.fetchReviews(testProductId)) + .thenAnswer((_) => Future.value([])); + when(() => productsRepository.getProduct(testProductId)).thenReturn( + kTestProducts[0], + ); + when(() => productsRepository.setProduct( + kTestProducts[0], + )).thenAnswer((_) => Future.value()); final reviewsService = makeReviewsService(); // run await reviewsService.submitReview(productId: '1', review: testReview); diff --git a/ecommerce_app/test/src/features/reviews/presentation/leave_review_screen/leave_review_screen_test.dart b/ecommerce_app/test/src/features/reviews/presentation/leave_review_screen/leave_review_screen_test.dart new file mode 100644 index 00000000..cd442158 --- /dev/null +++ b/ecommerce_app/test/src/features/reviews/presentation/leave_review_screen/leave_review_screen_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../robot.dart'; + +void main() { + Future purchaseOneProduct(Robot r) async { + // add to cart + await r.products.selectProduct(); + await r.products.setProductQuantity(3); + await r.cart.addToCart(); + await r.cart.openCart(); + r.cart.expectFindNCartItems(1); + // checkout + await r.checkout.startCheckout(); + await r.auth.enterAndSubmitEmailAndPassword(); + r.cart.expectFindNCartItems(1); + await r.checkout.startPayment(); + // when a payment is complete, user is taken to the orders page + r.orders.expectFindNOrders(1); + await r.closePage(); // close orders page + } + + testWidgets('purchase product, leave review, update it', (tester) async { + final r = Robot(tester); + await r.pumpMyApp(); + await purchaseOneProduct(r); + await r.products.selectProduct(); + // leave review + r.reviews.expectFindLeaveReview(); + await r.reviews.tapLeaveReviewButton(); + await r.reviews.createAndSubmitReview('Love it!'); + r.reviews.expectFindOneReview(); + r.reviews.expectFindText('Love it!'); + // update review + r.reviews.expectFindUpdateReview(); + await r.reviews.tapUpdateReviewButton(); + await r.reviews.updateAndSubmitReview('Great!'); + r.reviews.expectFindOneReview(); + r.reviews.expectFindText('Great!'); + }); +} diff --git a/ecommerce_app/test/src/features/reviews/reviews_robot.dart b/ecommerce_app/test/src/features/reviews/reviews_robot.dart index 75062cf1..436c3771 100644 --- a/ecommerce_app/test/src/features/reviews/reviews_robot.dart +++ b/ecommerce_app/test/src/features/reviews/reviews_robot.dart @@ -12,6 +12,11 @@ class ReviewsRobot { expect(finder, findsOneWidget); } + void expectFindUpdateReview() { + final finder = find.text('Update review'); + expect(finder, findsOneWidget); + } + Future tapLeaveReviewButton() async { final finder = find.text('Leave a review'); expect(finder, findsOneWidget); @@ -19,6 +24,13 @@ class ReviewsRobot { await tester.pumpAndSettle(); } + Future tapUpdateReviewButton() async { + final finder = find.text('Update review'); + expect(finder, findsOneWidget); + await tester.tap(finder); + await tester.pumpAndSettle(); + } + void expectFindOneReview() { // don't skip offstage widgets as the product reviews may appear below // the visible area on screen @@ -26,17 +38,22 @@ class ReviewsRobot { expect(finder, findsOneWidget); } + void expectFindText(String text) { + final finder = find.text(text); + expect(finder, findsOneWidget); + } + // leave a review - Future selectReviewScore() async { + Future enterReviewRating() async { final finder = find.byKey(const Key('stars-4')); expect(finder, findsOneWidget); await tester.tap(finder); } - Future enterReviewComment() async { + Future enterReviewComment(String comment) async { final finder = find.byKey(LeaveReviewForm.reviewCommentKey); expect(finder, findsOneWidget); - await tester.enterText(finder, 'Love it!'); + await tester.enterText(finder, comment); } Future submitReview() async { @@ -46,9 +63,14 @@ class ReviewsRobot { await tester.pumpAndSettle(); } - Future createAndSubmitReview() async { - await selectReviewScore(); - await enterReviewComment(); + Future createAndSubmitReview(String comment) async { + await enterReviewRating(); + await enterReviewComment(comment); + await submitReview(); + } + + Future updateAndSubmitReview(String comment) async { + await enterReviewComment(comment); await submitReview(); } }