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> openTabs; late BarScreenViewModel viewModel; setUp(() { barTabService = MockBarTabService(); openTabs = StreamController>.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()), ); 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); }, ); }