diff --git a/ecommerce_app/lib/src/features/cart/domain/cart.dart b/ecommerce_app/lib/src/features/cart/domain/cart.dart index a1999bf9..1fa65068 100644 --- a/ecommerce_app/lib/src/features/cart/domain/cart.dart +++ b/ecommerce_app/lib/src/features/cart/domain/cart.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; + import 'package:ecommerce_app/src/features/cart/domain/item.dart'; import 'package:ecommerce_app/src/features/products/domain/product.dart'; @@ -30,6 +32,16 @@ class Cart { @override String toString() => 'Cart(items: $items)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is Cart && mapEquals(other.items, items); + } + + @override + int get hashCode => items.hashCode; } extension CartItems on Cart { diff --git a/ecommerce_app/test/src/features/cart/application/cart_service_test.dart b/ecommerce_app/test/src/features/cart/application/cart_service_test.dart index e90f112b..eee0b058 100644 --- a/ecommerce_app/test/src/features/cart/application/cart_service_test.dart +++ b/ecommerce_app/test/src/features/cart/application/cart_service_test.dart @@ -1,13 +1,22 @@ 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/cart/application/cart_service.dart'; import 'package:ecommerce_app/src/features/cart/data/local/local_cart_repository.dart'; import 'package:ecommerce_app/src/features/cart/data/remote/remote_cart_repository.dart'; +import 'package:ecommerce_app/src/features/cart/domain/cart.dart'; +import 'package:ecommerce_app/src/features/cart/domain/item.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; import '../../../mocks.dart'; void main() { + setUpAll(() { + registerFallbackValue(const Cart()); + }); + const testUser = AppUser(uid: 'abc'); + late MockAuthRepository authRepository; late MockRemoteCartRepository remoteCartRepository; late MockLocalCartRepository localCartRepository; @@ -16,6 +25,7 @@ void main() { remoteCartRepository = MockRemoteCartRepository(); localCartRepository = MockLocalCartRepository(); }); + CartService makeCartService() { final container = ProviderContainer( overrides: [ @@ -30,11 +40,150 @@ void main() { group('setItem', () { test('null user, writes item to local cart', () async { + // setup + const expectedCart = Cart({'123': 1}); + when(() => authRepository.currentUser).thenReturn(null); + when(localCartRepository.fetchCart).thenAnswer( + (_) => Future.value(const Cart()), + ); + when(() => localCartRepository.setCart(expectedCart)).thenAnswer( + (_) => Future.value(), + ); final cartService = makeCartService(); + // run + await cartService.setItem( + const Item(productId: '123', quantity: 1), + ); + // verify + verify( + () => localCartRepository.setCart(expectedCart), + ).called(1); + verifyNever( + () => remoteCartRepository.setCart(any(), any()), + ); }); test('non-null user, writes item to remote cart', () async { - // TODO: Implement + // setup + const expectedCart = Cart({'123': 1}); + when(() => authRepository.currentUser).thenReturn(testUser); + when(() => remoteCartRepository.fetchCart(testUser.uid)).thenAnswer( + (_) => Future.value(const Cart()), + ); + when(() => remoteCartRepository.setCart(testUser.uid, expectedCart)) + .thenAnswer( + (_) => Future.value(), + ); + final cartService = makeCartService(); + // run + await cartService.setItem( + const Item(productId: '123', quantity: 1), + ); + // verify + verify( + () => remoteCartRepository.setCart(testUser.uid, expectedCart), + ).called(1); + verifyNever( + () => localCartRepository.setCart(any()), + ); + }); + }); + + group('addItem', () { + test('null user, adds item to local cart', () async { + // setup + const initialCart = Cart({'123': 2}); + const expectedCart = Cart({'123': 3}); + when(() => authRepository.currentUser).thenReturn(null); // null user + when(localCartRepository.fetchCart).thenAnswer( + (_) => Future.value(initialCart), // empty cart + ); + when(() => localCartRepository.setCart(expectedCart)).thenAnswer( + (_) => Future.value(), + ); + final cartService = makeCartService(); + // run + await cartService.addItem(const Item(productId: '123', quantity: 1)); + // verify + verify( + () => localCartRepository.setCart(expectedCart), + ).called(1); + verifyNever( + () => remoteCartRepository.setCart(any(), any()), + ); + }); + + test('non-null user, adds item to remote cart', () async { + // setup + const initialCart = Cart({'123': 2}); + const expectedCart = Cart({'123': 3}); + when(() => authRepository.currentUser).thenReturn(testUser); // null user + when(() => remoteCartRepository.fetchCart(testUser.uid)).thenAnswer( + (_) => Future.value(initialCart), // empty cart + ); + when(() => remoteCartRepository.setCart(testUser.uid, expectedCart)) + .thenAnswer( + (_) => Future.value(), + ); + final cartService = makeCartService(); + // run + await cartService.addItem(const Item(productId: '123', quantity: 1)); + // verify + verifyNever( + () => localCartRepository.setCart(any()), + ); + verify( + () => remoteCartRepository.setCart(testUser.uid, expectedCart), + ).called(1); + }); + }); + + group('removeItem', () { + test('null user, removes item from local cart', () async { + // setup + const initialCart = Cart({'123': 3, '456': 1}); + const expectedCart = Cart({'456': 1}); + when(() => authRepository.currentUser).thenReturn(null); // null user + when(localCartRepository.fetchCart).thenAnswer( + (_) => Future.value(initialCart), // empty cart + ); + when(() => localCartRepository.setCart(expectedCart)).thenAnswer( + (_) => Future.value(), + ); + final cartService = makeCartService(); + // run + await cartService.removeItemById('123'); + // verify + verify( + () => localCartRepository.setCart(expectedCart), + ).called(1); + verifyNever( + () => remoteCartRepository.setCart(any(), any()), + ); + }); + + test('non-null user, removes item from remote cart', () async { + // setup + const initialCart = Cart({'123': 3, '456': 1}); + const expectedCart = Cart({'456': 1}); + when(() => authRepository.currentUser).thenReturn(testUser); // null user + when(() => remoteCartRepository.fetchCart(testUser.uid)).thenAnswer( + (_) => Future.value(initialCart), // empty cart + ); + when(() => remoteCartRepository.setCart(testUser.uid, expectedCart)) + .thenAnswer( + (_) => Future.value(), + ); + final cartService = makeCartService(); + // run + await cartService.removeItemById('123'); + // verify + verifyNever( + () => localCartRepository.setCart(any()), + ); + verify( + () => remoteCartRepository.setCart(testUser.uid, expectedCart), + ).called(1); }); }); }