feat: New Open Tab dialog with search and optional customer email. Drinks are now grouped per order. Payments trigger cha-ching and money rain.
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:kooltap/models/bar_tab.dart';
|
||||
import 'package:kooltap/models/payment_method.dart';
|
||||
import 'package:kooltap/models/tab_item.dart';
|
||||
import 'package:kooltap/services/bar_tab_service.dart';
|
||||
import 'package:kooltap/viewmodels/bar_screen_view_model.dart';
|
||||
@@ -9,12 +10,22 @@ import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockBarTabService extends Mock implements BarTabService {}
|
||||
|
||||
BarTab _tab(String id) {
|
||||
BarTab _tab(String id, {int drinks = 0}) {
|
||||
return BarTab(
|
||||
id: id,
|
||||
customerName: id,
|
||||
openedAt: DateTime(2026),
|
||||
items: const [],
|
||||
items: [
|
||||
if (drinks > 0)
|
||||
TabItem(
|
||||
id: '$id-item',
|
||||
tabId: id,
|
||||
productId: 'prod-1',
|
||||
productName: 'Beer',
|
||||
quantity: drinks,
|
||||
unitPriceInCents: 400,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,7 +49,7 @@ void main() {
|
||||
await openTabs.close();
|
||||
});
|
||||
|
||||
test('shows tabs from the stream and selects the first one', () async {
|
||||
test('starts with no open tabs and nothing selected', () async {
|
||||
viewModel.ensureLoaded();
|
||||
expect(viewModel.isLoading, true);
|
||||
|
||||
@@ -46,8 +57,62 @@ void main() {
|
||||
await pumpEventQueue();
|
||||
|
||||
expect(viewModel.isLoading, false);
|
||||
expect(viewModel.tabs.map((tab) => tab.id), ['a', 'b']);
|
||||
expect(viewModel.selectedTabId, 'a');
|
||||
expect(viewModel.tabs, isEmpty);
|
||||
expect(viewModel.closedTabs.map((tab) => tab.id), ['a', 'b']);
|
||||
expect(viewModel.selectedTabId, isNull);
|
||||
});
|
||||
|
||||
test('tabs with drinks on them are open', () async {
|
||||
viewModel.ensureLoaded();
|
||||
openTabs.add([_tab('a', drinks: 2), _tab('b')]);
|
||||
await pumpEventQueue();
|
||||
|
||||
expect(viewModel.tabs.map((tab) => tab.id), ['a']);
|
||||
expect(viewModel.closedTabs.map((tab) => tab.id), ['b']);
|
||||
});
|
||||
|
||||
test('opening a tab shows and selects it', () async {
|
||||
viewModel.ensureLoaded();
|
||||
openTabs.add([_tab('a'), _tab('b')]);
|
||||
await pumpEventQueue();
|
||||
|
||||
viewModel.openTab('b');
|
||||
viewModel.deselectTab();
|
||||
|
||||
expect(viewModel.tabs.map((tab) => tab.id), ['b']);
|
||||
expect(viewModel.closedTabs.map((tab) => tab.id), ['a']);
|
||||
});
|
||||
|
||||
test('a tab settled elsewhere goes back to the closed tabs', () async {
|
||||
viewModel.ensureLoaded();
|
||||
openTabs.add([_tab('a', drinks: 2)]);
|
||||
await pumpEventQueue();
|
||||
viewModel.openTab('a');
|
||||
viewModel.deselectTab();
|
||||
|
||||
openTabs.add([_tab('a')]);
|
||||
await pumpEventQueue();
|
||||
|
||||
expect(viewModel.tabs, isEmpty);
|
||||
expect(viewModel.closedTabs.map((tab) => tab.id), ['a']);
|
||||
});
|
||||
|
||||
test('closing the selected tab moves it back and deselects', () async {
|
||||
when(
|
||||
() => barTabService.closeTab(
|
||||
'a',
|
||||
paymentMethod: PaymentMethod.cash,
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
viewModel.ensureLoaded();
|
||||
openTabs.add([_tab('a'), _tab('b')]);
|
||||
await pumpEventQueue();
|
||||
viewModel.openTab('a');
|
||||
|
||||
await viewModel.closeSelectedTab();
|
||||
|
||||
expect(viewModel.selectedTabId, isNull);
|
||||
expect(viewModel.tabs, isEmpty);
|
||||
});
|
||||
|
||||
test('keeps the selection while that tab still exists', () async {
|
||||
@@ -62,20 +127,17 @@ void main() {
|
||||
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');
|
||||
test('selects nothing when the selected tab disappears', () async {
|
||||
viewModel.ensureLoaded();
|
||||
openTabs.add([_tab('a'), _tab('b')]);
|
||||
await pumpEventQueue();
|
||||
viewModel.selectTab('b');
|
||||
|
||||
openTabs.add([_tab('a')]);
|
||||
await pumpEventQueue();
|
||||
openTabs.add([_tab('a')]);
|
||||
await pumpEventQueue();
|
||||
|
||||
expect(viewModel.selectedTabId, 'a');
|
||||
},
|
||||
);
|
||||
expect(viewModel.selectedTabId, isNull);
|
||||
});
|
||||
|
||||
test('exposes a load error when the stream fails', () async {
|
||||
viewModel.ensureLoaded();
|
||||
@@ -97,11 +159,15 @@ void main() {
|
||||
);
|
||||
|
||||
when(
|
||||
() => barTabService.adjustTabItemQuantity(tabItemId: 'item-1', delta: 1),
|
||||
() => barTabService.adjustTabItemQuantity(
|
||||
tabItemId: 'item-1',
|
||||
delta: 1,
|
||||
order: any(named: 'order'),
|
||||
),
|
||||
).thenAnswer((_) async => throw const TabNotFoundException('tab-1'));
|
||||
|
||||
await expectLater(
|
||||
viewModel.changeItemQuantity(item, 1),
|
||||
viewModel.changeItemQuantity(item, 1, null),
|
||||
throwsA(isA<TabNotFoundException>()),
|
||||
);
|
||||
|
||||
@@ -110,7 +176,7 @@ void main() {
|
||||
|
||||
test('selects a newly created tab', () async {
|
||||
when(
|
||||
() => barTabService.createTab(customerName: 'New'),
|
||||
() => barTabService.createTab(customerName: 'New', email: null),
|
||||
).thenAnswer((_) async => _tab('new'));
|
||||
|
||||
await viewModel.createTab('New');
|
||||
|
||||
Reference in New Issue
Block a user