feat: add alot
This commit is contained in:
@@ -1,157 +1,89 @@
|
||||
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';
|
||||
import 'package:kooltab2/models/product.dart';
|
||||
import 'package:kooltab2/services/product_service.dart';
|
||||
import 'package:kooltab2/viewmodels/inventory_view_model.dart';
|
||||
|
||||
class MockProductService extends Mock implements ProductService {}
|
||||
|
||||
void main() {
|
||||
late InventoryViewModel viewModel;
|
||||
late MockProductService mockService;
|
||||
late StreamController<List<Product>> 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<List<Product>>.broadcast();
|
||||
when(() => mockService.watchProducts()).thenAnswer((_) => products.stream);
|
||||
|
||||
viewModel = InventoryViewModel(productService: mockService);
|
||||
});
|
||||
|
||||
setUpAll(() {
|
||||
registerFallbackValue(const Product(
|
||||
id: 'fallback',
|
||||
name: 'Fallback',
|
||||
category: 'Test',
|
||||
stockQuantity: 0,
|
||||
lowStockThreshold: 0,
|
||||
priceInCents: 0,
|
||||
));
|
||||
tearDown(() async {
|
||||
viewModel.dispose();
|
||||
await products.close();
|
||||
});
|
||||
|
||||
final testProducts = [
|
||||
const Product(
|
||||
id: 'prod-1',
|
||||
name: 'Beer',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 20,
|
||||
lowStockThreshold: 5,
|
||||
priceInCents: 400,
|
||||
),
|
||||
const Product(
|
||||
id: 'prod-2',
|
||||
name: 'Wine',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 3,
|
||||
lowStockThreshold: 5,
|
||||
priceInCents: 600,
|
||||
),
|
||||
];
|
||||
|
||||
group('InventoryViewModel', () {
|
||||
group('load', () {
|
||||
test('loads products from service', () async {
|
||||
when(() => mockService.getProducts())
|
||||
.thenAnswer((_) async => testProducts);
|
||||
test('is loading until the first emission', () async {
|
||||
viewModel.ensureLoaded();
|
||||
expect(viewModel.isLoading, true);
|
||||
|
||||
await viewModel.load();
|
||||
products.add([beer]);
|
||||
await pumpEventQueue();
|
||||
|
||||
expect(viewModel.products, testProducts);
|
||||
verify(() => mockService.getProducts()).called(1);
|
||||
});
|
||||
|
||||
test('notifies listeners after load', () async {
|
||||
var notifyCount = 0;
|
||||
viewModel.addListener(() => notifyCount++);
|
||||
when(() => mockService.getProducts())
|
||||
.thenAnswer((_) async => testProducts);
|
||||
|
||||
await viewModel.load();
|
||||
|
||||
expect(notifyCount, 1);
|
||||
});
|
||||
expect(viewModel.isLoading, false);
|
||||
expect(viewModel.products, [beer]);
|
||||
});
|
||||
|
||||
group('decreaseStock', () {
|
||||
test('decreases stock via service', () async {
|
||||
when(() => mockService.decreaseStock('prod-1', 3))
|
||||
.thenAnswer((_) async {});
|
||||
test('follows stock changes from the database', () async {
|
||||
viewModel.ensureLoaded();
|
||||
products.add([beer]);
|
||||
await pumpEventQueue();
|
||||
|
||||
await viewModel.decreaseStock('prod-1', 3);
|
||||
products.add([beer.copyWith(stockQuantity: 19)]);
|
||||
await pumpEventQueue();
|
||||
|
||||
verify(() => mockService.decreaseStock('prod-1', 3)).called(1);
|
||||
});
|
||||
|
||||
test('updates local product cache', () async {
|
||||
when(() => mockService.getProducts())
|
||||
.thenAnswer((_) async => testProducts);
|
||||
when(() => mockService.decreaseStock('prod-1', 5))
|
||||
.thenAnswer((_) async {});
|
||||
|
||||
await viewModel.load();
|
||||
await viewModel.decreaseStock('prod-1', 5);
|
||||
|
||||
final product = viewModel.products.firstWhere((p) => p.id == 'prod-1');
|
||||
expect(product.stockQuantity, 15);
|
||||
});
|
||||
|
||||
test('notifies listeners after decrease', () async {
|
||||
var notifyCount = 0;
|
||||
viewModel.addListener(() => notifyCount++);
|
||||
when(() => mockService.decreaseStock('prod-1', 1))
|
||||
.thenAnswer((_) async {});
|
||||
|
||||
await viewModel.decreaseStock('prod-1', 1);
|
||||
|
||||
expect(notifyCount, 1);
|
||||
});
|
||||
expect(viewModel.products.single.stockQuantity, 19);
|
||||
});
|
||||
|
||||
group('increaseStock', () {
|
||||
test('increases stock via service', () async {
|
||||
when(() => mockService.increaseStock('prod-1', 5))
|
||||
.thenAnswer((_) async {});
|
||||
test('subscribes only once', () async {
|
||||
viewModel.ensureLoaded();
|
||||
viewModel.ensureLoaded();
|
||||
|
||||
await viewModel.increaseStock('prod-1', 5);
|
||||
|
||||
verify(() => mockService.increaseStock('prod-1', 5)).called(1);
|
||||
});
|
||||
|
||||
test('updates local product cache', () async {
|
||||
when(() => mockService.getProducts())
|
||||
.thenAnswer((_) async => testProducts);
|
||||
when(() => mockService.increaseStock('prod-2', 2))
|
||||
.thenAnswer((_) async {});
|
||||
|
||||
await viewModel.load();
|
||||
await viewModel.increaseStock('prod-2', 2);
|
||||
|
||||
final product = viewModel.products.firstWhere((p) => p.id == 'prod-2');
|
||||
expect(product.stockQuantity, 5);
|
||||
});
|
||||
|
||||
test('notifies listeners after increase', () async {
|
||||
var notifyCount = 0;
|
||||
viewModel.addListener(() => notifyCount++);
|
||||
when(() => mockService.increaseStock('prod-1', 1))
|
||||
.thenAnswer((_) async {});
|
||||
|
||||
await viewModel.increaseStock('prod-1', 1);
|
||||
|
||||
expect(notifyCount, 1);
|
||||
});
|
||||
verify(() => mockService.watchProducts()).called(1);
|
||||
});
|
||||
|
||||
group('edge cases', () {
|
||||
test('handles decrease for product not in local cache', () async {
|
||||
when(() => mockService.getProducts())
|
||||
.thenAnswer((_) async => testProducts);
|
||||
when(() => mockService.decreaseStock('unknown', 1))
|
||||
.thenAnswer((_) async {});
|
||||
test('notifies listeners on every emission', () async {
|
||||
var notifyCount = 0;
|
||||
viewModel.addListener(() => notifyCount++);
|
||||
viewModel.ensureLoaded();
|
||||
|
||||
await viewModel.load();
|
||||
await viewModel.decreaseStock('unknown', 1);
|
||||
products
|
||||
..add([beer])
|
||||
..add([]);
|
||||
await pumpEventQueue();
|
||||
|
||||
verify(() => mockService.decreaseStock('unknown', 1)).called(1);
|
||||
expect(viewModel.products.length, 2);
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user