import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:kooltap/models/product.dart'; import 'package:kooltap/services/product_service.dart'; import 'package:kooltap/viewmodels/inventory_view_model.dart'; import 'package:mocktail/mocktail.dart'; class MockProductService extends Mock implements ProductService {} void main() { late MockProductService mockService; late StreamController> products; late InventoryViewModel viewModel; const beer = Product( id: 'prod-1', name: 'Beer', category: 'Drinks', stockQuantity: 20, lowStockThreshold: 5, priceInCents: 400, ); setUp(() { mockService = MockProductService(); products = StreamController>.broadcast(); when(() => mockService.watchProducts()).thenAnswer((_) => products.stream); viewModel = InventoryViewModel(productService: mockService); }); tearDown(() async { viewModel.dispose(); await products.close(); }); group('InventoryViewModel', () { test('is loading until the first emission', () async { viewModel.ensureLoaded(); expect(viewModel.isLoading, true); products.add([beer]); await pumpEventQueue(); expect(viewModel.isLoading, false); expect(viewModel.products, [beer]); }); test('follows stock changes from the database', () async { viewModel.ensureLoaded(); products.add([beer]); await pumpEventQueue(); products.add([beer.copyWith(stockQuantity: 19)]); await pumpEventQueue(); expect(viewModel.products.single.stockQuantity, 19); }); test('subscribes only once', () async { viewModel.ensureLoaded(); viewModel.ensureLoaded(); verify(() => mockService.watchProducts()).called(1); }); test('notifies listeners on every emission', () async { var notifyCount = 0; viewModel.addListener(() => notifyCount++); viewModel.ensureLoaded(); products ..add([beer]) ..add([]); await pumpEventQueue(); expect(notifyCount, 2); }); test('exposes a load error when the stream fails', () async { viewModel.ensureLoaded(); products.addError(StateError('database closed')); await pumpEventQueue(); expect(viewModel.loadError, isNotNull); }); }); }