diff --git a/ecommerce_app/lib/src/features/products/data/fake_products_repository.dart b/ecommerce_app/lib/src/features/products/data/fake_products_repository.dart index 1ca02658..418f1813 100644 --- a/ecommerce_app/lib/src/features/products/data/fake_products_repository.dart +++ b/ecommerce_app/lib/src/features/products/data/fake_products_repository.dart @@ -99,7 +99,8 @@ final productProvider = final productsListSearchProvider = FutureProvider.autoDispose .family, String>((ref, query) async { final link = ref.keepAlive(); - final timer = Timer(const Duration(seconds: 5), () { + // * keep previous search results in memory for 60 seconds + final timer = Timer(const Duration(seconds: 60), () { link.close(); }); ref.onDispose(() => timer.cancel()); diff --git a/ecommerce_app/test/src/features/authentication/auth_flow_test.dart b/ecommerce_app/test/src/features/authentication/auth_flow_test.dart index ddb60ded..efdb9242 100644 --- a/ecommerce_app/test/src/features/authentication/auth_flow_test.dart +++ b/ecommerce_app/test/src/features/authentication/auth_flow_test.dart @@ -4,18 +4,22 @@ import '../../robot.dart'; void main() { testWidgets('Sign in and sign out flow', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - r.products.expectFindAllProductCards(); - await r.openPopupMenu(); - await r.auth.openEmailPasswordSignInScreen(); - await r.auth.tapFormToggleButton(); - await r.auth.enterAndSubmitEmailAndPassword(); - r.products.expectFindAllProductCards(); - await r.openPopupMenu(); - await r.auth.openAccountScreen(); - await r.auth.tapLogoutButton(); - await r.auth.tapDialogLogoutButton(); - r.products.expectFindAllProductCards(); + // * Note: All tests are wrapped with `runAsync` to prevent this error: + // * A Timer is still pending even after the widget tree was disposed. + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + r.products.expectFindAllProductCards(); + await r.openPopupMenu(); + await r.auth.openEmailPasswordSignInScreen(); + await r.auth.tapFormToggleButton(); + await r.auth.enterAndSubmitEmailAndPassword(); + r.products.expectFindAllProductCards(); + await r.openPopupMenu(); + await r.auth.openAccountScreen(); + await r.auth.tapLogoutButton(); + await r.auth.tapDialogLogoutButton(); + r.products.expectFindAllProductCards(); + }); }); } diff --git a/ecommerce_app/test/src/features/cart/presentation/shopping_cart/shopping_cart_screen_test.dart b/ecommerce_app/test/src/features/cart/presentation/shopping_cart/shopping_cart_screen_test.dart index fa6f28b9..e8eb1824 100644 --- a/ecommerce_app/test/src/features/cart/presentation/shopping_cart/shopping_cart_screen_test.dart +++ b/ecommerce_app/test/src/features/cart/presentation/shopping_cart/shopping_cart_screen_test.dart @@ -3,118 +3,140 @@ import '../../../../robot.dart'; void main() { group('shopping cart', () { + // * Note: All tests are wrapped with `runAsync` to prevent this error: + // * A Timer is still pending even after the widget tree was disposed. testWidgets('Empty shopping cart', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - r.products.expectFindNProductCards(14); // check all products are found - await r.cart.openCart(); - r.cart.expectShoppingCartIsEmpty(); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + r.products.expectFindNProductCards(14); // check all products are found + await r.cart.openCart(); + r.cart.expectShoppingCartIsEmpty(); + }); }); testWidgets('Add product with quantity = 1', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - await r.products.selectProduct(); - await r.cart.addToCart(); - await r.cart.openCart(); - r.cart.expectItemQuantity(quantity: 1, atIndex: 0); - r.cart.expectShoppingCartTotalIs('Total: \$15.00'); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + await r.products.selectProduct(); + await r.cart.addToCart(); + await r.cart.openCart(); + r.cart.expectItemQuantity(quantity: 1, atIndex: 0); + r.cart.expectShoppingCartTotalIs('Total: \$15.00'); + }); }); testWidgets('Add product with quantity = 5', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - await r.products.selectProduct(); - await r.products.setProductQuantity(5); - await r.cart.addToCart(); - await r.cart.openCart(); - r.cart.expectItemQuantity(quantity: 5, atIndex: 0); - r.cart.expectShoppingCartTotalIs('Total: \$75.00'); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + await r.products.selectProduct(); + await r.products.setProductQuantity(5); + await r.cart.addToCart(); + await r.cart.openCart(); + r.cart.expectItemQuantity(quantity: 5, atIndex: 0); + r.cart.expectShoppingCartTotalIs('Total: \$75.00'); + }); }); testWidgets('Add product with quantity = 6', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - await r.products.selectProduct(); - await r.products.setProductQuantity(6); - await r.cart.addToCart(); - await r.cart.openCart(); - r.cart.expectItemQuantity(quantity: 5, atIndex: 0); - r.cart.expectShoppingCartTotalIs('Total: \$75.00'); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + await r.products.selectProduct(); + await r.products.setProductQuantity(6); + await r.cart.addToCart(); + await r.cart.openCart(); + r.cart.expectItemQuantity(quantity: 5, atIndex: 0); + r.cart.expectShoppingCartTotalIs('Total: \$75.00'); + }); }); testWidgets('Add product with quantity = 2, then increment by 2', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - await r.products.selectProduct(); - await r.products.setProductQuantity(2); - await r.cart.addToCart(); - await r.cart.openCart(); - await r.cart.incrementCartItemQuantity(quantity: 2, atIndex: 0); - r.cart.expectItemQuantity(quantity: 4, atIndex: 0); - r.cart.expectShoppingCartTotalIs('Total: \$60.00'); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + await r.products.selectProduct(); + await r.products.setProductQuantity(2); + await r.cart.addToCart(); + await r.cart.openCart(); + await r.cart.incrementCartItemQuantity(quantity: 2, atIndex: 0); + r.cart.expectItemQuantity(quantity: 4, atIndex: 0); + r.cart.expectShoppingCartTotalIs('Total: \$60.00'); + }); }); testWidgets('Add product with quantity = 5, then decrement by 2', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - await r.products.selectProduct(); - await r.products.setProductQuantity(5); - await r.cart.addToCart(); - await r.cart.openCart(); - await r.cart.decrementCartItemQuantity(quantity: 2, atIndex: 0); - r.cart.expectItemQuantity(quantity: 3, atIndex: 0); - r.cart.expectShoppingCartTotalIs('Total: \$45.00'); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + await r.products.selectProduct(); + await r.products.setProductQuantity(5); + await r.cart.addToCart(); + await r.cart.openCart(); + await r.cart.decrementCartItemQuantity(quantity: 2, atIndex: 0); + r.cart.expectItemQuantity(quantity: 3, atIndex: 0); + r.cart.expectShoppingCartTotalIs('Total: \$45.00'); + }); }); testWidgets('Add two products', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - // add first product - await r.products.selectProduct(atIndex: 0); - await r.cart.addToCart(); - await r.goBack(); - // add second product - await r.products.selectProduct(atIndex: 1); - await r.cart.addToCart(); - await r.cart.openCart(); - r.cart.expectFindNCartItems(2); - r.cart.expectShoppingCartTotalIs('Total: \$28.00'); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + // add first product + await r.products.selectProduct(atIndex: 0); + await r.cart.addToCart(); + await r.goBack(); + // add second product + await r.products.selectProduct(atIndex: 1); + await r.cart.addToCart(); + await r.cart.openCart(); + r.cart.expectFindNCartItems(2); + r.cart.expectShoppingCartTotalIs('Total: \$28.00'); + }); }); testWidgets('Add product, then delete it', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - await r.products.selectProduct(); - await r.cart.addToCart(); - await r.cart.openCart(); - await r.cart.deleteCartItem(atIndex: 0); - r.cart.expectShoppingCartIsEmpty(); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + await r.products.selectProduct(); + await r.cart.addToCart(); + await r.cart.openCart(); + await r.cart.deleteCartItem(atIndex: 0); + r.cart.expectShoppingCartIsEmpty(); + }); }); testWidgets('Add product with quantity = 5, goes out of stock', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - await r.products.selectProduct(); - await r.products.setProductQuantity(5); - await r.cart.addToCart(); - r.cart.expectProductIsOutOfStock(); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + await r.products.selectProduct(); + await r.products.setProductQuantity(5); + await r.cart.addToCart(); + r.cart.expectProductIsOutOfStock(); + }); }); testWidgets( 'Add product with quantity = 5, remains out of stock when opened again', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - await r.products.selectProduct(); - await r.products.setProductQuantity(5); - await r.cart.addToCart(); - await r.goBack(); - await r.products.selectProduct(); - r.cart.expectProductIsOutOfStock(); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + await r.products.selectProduct(); + await r.products.setProductQuantity(5); + await r.cart.addToCart(); + await r.goBack(); + await r.products.selectProduct(); + r.cart.expectProductIsOutOfStock(); + }); }); }); } diff --git a/ecommerce_app/test/src/features/checkout/presentation/checkout_screen/checkout_screen_test.dart b/ecommerce_app/test/src/features/checkout/presentation/checkout_screen/checkout_screen_test.dart index 1b4fbd9e..e5b42ed6 100644 --- a/ecommerce_app/test/src/features/checkout/presentation/checkout_screen/checkout_screen_test.dart +++ b/ecommerce_app/test/src/features/checkout/presentation/checkout_screen/checkout_screen_test.dart @@ -3,34 +3,40 @@ import 'package:flutter_test/flutter_test.dart'; import '../../../../robot.dart'; void main() { + // * Note: All tests are wrapped with `runAsync` to prevent this error: + // * A Timer is still pending even after the widget tree was disposed. testWidgets('checkout when not previously signed in', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - // add a product and start checkout - await r.products.selectProduct(); - await r.cart.addToCart(); - await r.cart.openCart(); - await r.checkout.startCheckout(); - // sign in from checkout screen - r.auth.expectEmailAndPasswordFieldsFound(); - await r.auth.enterAndSubmitEmailAndPassword(); - // check that we move to the payment page - r.checkout.expectPayButtonFound(); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + // add a product and start checkout + await r.products.selectProduct(); + await r.cart.addToCart(); + await r.cart.openCart(); + await r.checkout.startCheckout(); + // sign in from checkout screen + r.auth.expectEmailAndPasswordFieldsFound(); + await r.auth.enterAndSubmitEmailAndPassword(); + // check that we move to the payment page + r.checkout.expectPayButtonFound(); + }); }); testWidgets('checkout when previously signed in', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - // create an account first - await r.auth.openEmailPasswordSignInScreen(); - await r.auth.tapFormToggleButton(); - await r.auth.enterAndSubmitEmailAndPassword(); - // then add a product and start checkout - await r.products.selectProduct(); - await r.cart.addToCart(); - await r.cart.openCart(); - await r.checkout.startCheckout(); - // expect that we see the payment page right away - r.checkout.expectPayButtonFound(); + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + // create an account first + await r.auth.openEmailPasswordSignInScreen(); + await r.auth.tapFormToggleButton(); + await r.auth.enterAndSubmitEmailAndPassword(); + // then add a product and start checkout + await r.products.selectProduct(); + await r.cart.addToCart(); + await r.cart.openCart(); + await r.checkout.startCheckout(); + // expect that we see the payment page right away + r.checkout.expectPayButtonFound(); + }); }); } diff --git a/ecommerce_app/test/src/features/purchase_flow_test.dart b/ecommerce_app/test/src/features/purchase_flow_test.dart index b5a85574..e1670456 100644 --- a/ecommerce_app/test/src/features/purchase_flow_test.dart +++ b/ecommerce_app/test/src/features/purchase_flow_test.dart @@ -4,38 +4,42 @@ import '../robot.dart'; void main() { testWidgets('Full purchase flow', (tester) async { - final r = Robot(tester); - await r.pumpMyApp(); - r.products.expectFindAllProductCards(); - // add to cart flows - 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 - // check that cart is now empty - await r.cart.openCart(); - r.cart.expectFindZeroCartItems(); - await r.closePage(); - // reviews flow - 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(); - await r.auth.tapLogoutButton(); - await r.auth.tapDialogLogoutButton(); - r.products.expectFindAllProductCards(); + // * Note: All tests are wrapped with `runAsync` to prevent this error: + // * A Timer is still pending even after the widget tree was disposed. + await tester.runAsync(() async { + final r = Robot(tester); + await r.pumpMyApp(); + r.products.expectFindAllProductCards(); + // add to cart flows + 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 + // check that cart is now empty + await r.cart.openCart(); + r.cart.expectFindZeroCartItems(); + await r.closePage(); + // reviews flow + 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(); + await r.auth.tapLogoutButton(); + await r.auth.tapDialogLogoutButton(); + r.products.expectFindAllProductCards(); + }); }); } 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 index cd442158..81a94b36 100644 --- 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 @@ -21,21 +21,25 @@ void main() { } 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!'); + // * Note: All tests are wrapped with `runAsync` to prevent this error: + // * A Timer is still pending even after the widget tree was disposed. + await tester.runAsync(() 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!'); + }); }); }