Files
kooltab/test/bar_screen_view_model_test.dart
T
2026-10-08 04:12:36 +02:00

135 lines
3.5 KiB
Dart

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:kooltap/models/bar_tab.dart';
import 'package:kooltap/models/tab_item.dart';
import 'package:kooltap/services/bar_tab_service.dart';
import 'package:kooltap/services/product_service.dart';
import 'package:kooltap/viewmodels/bar_screen_view_model.dart';
import 'package:mocktail/mocktail.dart';
class MockBarTabService extends Mock implements BarTabService {}
BarTab _tab(String id) {
return BarTab(
id: id,
customerName: id,
openedAt: DateTime(2026),
items: const [],
);
}
void main() {
late MockBarTabService barTabService;
late StreamController<List<BarTab>> openTabs;
late BarScreenViewModel viewModel;
setUp(() {
barTabService = MockBarTabService();
openTabs = StreamController<List<BarTab>>.broadcast();
when(
() => barTabService.watchOpenTabs(),
).thenAnswer((_) => openTabs.stream);
viewModel = BarScreenViewModel(barTabService: barTabService);
});
tearDown(() async {
viewModel.dispose();
await openTabs.close();
});
test('shows tabs from the stream and selects the first one', () async {
viewModel.ensureLoaded();
expect(viewModel.isLoading, true);
openTabs.add([_tab('a'), _tab('b')]);
await pumpEventQueue();
expect(viewModel.isLoading, false);
expect(viewModel.tabs.map((tab) => tab.id), ['a', 'b']);
expect(viewModel.selectedTabId, 'a');
});
test('keeps the selection while that tab still exists', () async {
viewModel.ensureLoaded();
openTabs.add([_tab('a'), _tab('b')]);
await pumpEventQueue();
viewModel.selectTab('b');
openTabs.add([_tab('c'), _tab('b')]);
await pumpEventQueue();
expect(viewModel.selectedTabId, 'b');
});
test(
'falls back to the first tab when the selected one disappears',
() async {
viewModel.ensureLoaded();
openTabs.add([_tab('a'), _tab('b')]);
await pumpEventQueue();
viewModel.selectTab('b');
openTabs.add([_tab('a')]);
await pumpEventQueue();
expect(viewModel.selectedTabId, 'a');
},
);
test('exposes a load error when the stream fails', () async {
viewModel.ensureLoaded();
openTabs.addError(StateError('database closed'));
await pumpEventQueue();
expect(viewModel.loadError, isNotNull);
expect(viewModel.isLoading, false);
});
test('propagates an insufficient-stock error from the transaction', () async {
const item = TabItem(
id: 'item-1',
tabId: 'tab-1',
productId: 'prod-1',
productName: 'Chips',
quantity: 20,
unitPriceInCents: 150,
);
when(
() => barTabService.adjustTabItemQuantity(tabItemId: 'item-1', delta: 1),
).thenAnswer((_) async => throw const InsufficientStockException());
await expectLater(
viewModel.changeItemQuantity(item, 1),
throwsA(isA<InsufficientStockException>()),
);
expect(viewModel.loadError, isNull);
});
test('selects a newly created tab', () async {
when(
() => barTabService.createTab(customerName: 'New'),
).thenAnswer((_) async => _tab('new'));
await viewModel.createTab('New');
expect(viewModel.selectedTabId, 'new');
});
test(
'rethrows failed actions without turning them into a load error',
() async {
when(
() => barTabService.deleteTab('tab-1'),
).thenAnswer((_) async => throw StateError('boom'));
await expectLater(viewModel.deleteTab('tab-1'), throwsStateError);
expect(viewModel.loadError, isNull);
},
);
}