feat: add alot

This commit is contained in:
2026-10-08 04:12:36 +02:00
parent ccf5165417
commit fc607cb7bd
99 changed files with 4981 additions and 3655 deletions
+131 -37
View File
@@ -1,10 +1,10 @@
import 'package:drift/drift.dart' hide isNull, isNotNull;
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kooltab2/database/app_database.dart';
import 'package:kooltab2/models/product.dart';
import 'package:kooltab2/services/bar_tab_service.dart';
import 'package:kooltab2/services/product_service.dart';
import 'package:kooltap/database/app_database.dart';
import 'package:kooltap/models/product.dart';
import 'package:kooltap/services/bar_tab_service.dart';
import 'package:kooltap/services/product_service.dart';
void main() {
late AppDatabase database;
@@ -49,14 +49,21 @@ void main() {
group('DriftBarTabService', () {
group('createTab', () {
test('creates tab with open status', () async {
test('creates an empty tab at version 1', () async {
final tab = await service.createTab(customerName: 'John');
expect(tab.customerName, 'John');
expect(tab.status, 'open');
expect(tab.version, 1);
expect(tab.items, isEmpty);
});
test('rejects a blank customer name', () async {
await expectLater(
service.createTab(customerName: ' '),
throwsA(isA<ArgumentError>()),
);
});
test('creates tab with unique id', () async {
final tab1 = await service.createTab(customerName: 'John');
final tab2 = await service.createTab(customerName: 'Jane');
@@ -71,25 +78,6 @@ void main() {
expect(tabs, isEmpty);
});
test('returns only open tabs', () async {
await service.createTab(customerName: 'Open Tab');
await database
.into(database.barTabs)
.insert(
BarTabsCompanion.insert(
id: 'closed-tab',
customerName: 'Closed Tab',
status: const Value('closed'),
openedAt: DateTime.now(),
),
);
final tabs = await service.getOpenTabs();
expect(tabs.length, 1);
expect(tabs.first.customerName, 'Open Tab');
});
test('tabs sorted by openedAt descending', () async {
final now = DateTime.now();
await database
@@ -98,7 +86,6 @@ void main() {
BarTabsCompanion.insert(
id: 'tab-first',
customerName: 'First',
status: const Value('open'),
openedAt: now.subtract(const Duration(hours: 1)),
),
);
@@ -108,7 +95,6 @@ void main() {
BarTabsCompanion.insert(
id: 'tab-second',
customerName: 'Second',
status: const Value('open'),
openedAt: now,
),
);
@@ -154,10 +140,8 @@ void main() {
await service.addProductToTab(tabId: tab.id, product: product);
await service.addProductToTab(tabId: tab.id, product: product);
final deletedItems = await service.deleteTab(tab.id);
await service.deleteTab(tab.id);
expect(deletedItems.length, 1);
expect(deletedItems.first.quantity, 2);
expect(await service.getTabById(tab.id), isNull);
final updatedProduct = await (database.select(
@@ -165,8 +149,8 @@ void main() {
)..where((row) => row.id.equals(product.id))).getSingle();
expect(updatedProduct.stockQuantity, 100);
final remainingItems = await database.select(database.tabItems).get();
expect(remainingItems, isEmpty);
expect(await database.select(database.tabItems).get(), isEmpty);
expect(await database.select(database.tabItemPurchases).get(), isEmpty);
});
});
@@ -412,16 +396,55 @@ void main() {
expect(updatedTab!.items, isEmpty);
});
test('tab remains open but items cleared', () async {
test(
'keeps the tab under the same name and drops its purchases',
() async {
final tab = await service.createTab(customerName: 'John');
final product = createTestProduct();
await service.addProductToTab(tabId: tab.id, product: product);
await service.closeTab(tab.id);
final updatedTab = await service.getTabById(tab.id);
expect(updatedTab!.customerName, 'John');
expect(updatedTab.items, isEmpty);
expect(
await database.select(database.tabItemPurchases).get(),
isEmpty,
);
},
);
test('throws when the tab changed since the expected version', () async {
final tab = await service.createTab(customerName: 'John');
final product = createTestProduct();
await service.addProductToTab(tabId: tab.id, product: product);
await service.closeTab(tab.id);
await expectLater(
service.closeTab(tab.id, expectedVersion: tab.version),
throwsA(isA<TabVersionConflictException>()),
);
final updatedTab = await service.getTabById(tab.id);
expect(updatedTab!.isOpen, true);
expect(updatedTab.items, isEmpty);
expect((await service.getTabById(tab.id))!.items, hasLength(1));
expect(await service.getClosedTabs(), isEmpty);
});
test('closes when the expected version matches', () async {
final tab = await service.createTab(customerName: 'John');
final product = createTestProduct();
await service.addProductToTab(tabId: tab.id, product: product);
final current = await service.getTabById(tab.id);
await service.closeTab(tab.id, expectedVersion: current!.version);
expect(await service.getClosedTabs(), hasLength(1));
});
test('throws for an unknown tab', () async {
await expectLater(
service.closeTab('missing'),
throwsA(isA<TabNotFoundException>()),
);
});
test('closed tab preserves item prices', () async {
@@ -436,6 +459,77 @@ void main() {
});
});
group('versioning', () {
test('every change bumps the tab version', () async {
final tab = await service.createTab(customerName: 'John');
final product = createTestProduct();
await service.addProductToTab(tabId: tab.id, product: product);
final afterAdd = (await service.getTabById(tab.id))!;
await service.adjustTabItemQuantity(
tabItemId: afterAdd.items.first.id,
delta: 1,
);
final afterAdjust = (await service.getTabById(tab.id))!;
await service.renameTab(tabId: tab.id, customerName: 'Jane');
final afterRename = (await service.getTabById(tab.id))!;
expect(afterAdd.version, tab.version + 1);
expect(afterAdjust.version, afterAdd.version + 1);
expect(afterRename.version, afterAdjust.version + 1);
});
test('a failed add leaves the version untouched', () async {
final tab = await service.createTab(customerName: 'John');
final product = createTestProduct();
await (database.update(database.products)
..where((row) => row.id.equals(product.id)))
.write(const ProductsCompanion(stockQuantity: Value(0)));
await expectLater(
service.addProductToTab(tabId: tab.id, product: product),
throwsA(isA<InsufficientStockException>()),
);
expect((await service.getTabById(tab.id))!.version, tab.version);
});
});
group('watchOpenTabs', () {
test('emits again when an item is added', () async {
final tab = await service.createTab(customerName: 'John');
final emissions = service.watchOpenTabs();
final expectation = expectLater(
emissions.map((tabs) => tabs.single.itemCount),
emitsThrough(1),
);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await expectation;
});
});
group('getDistinctCustomerNames', () {
test('returns each closed customer once, sorted', () async {
for (final name in ['Zoe', 'Adam', 'Zoe']) {
final tab = await service.createTab(customerName: name);
await service.addProductToTab(
tabId: tab.id,
product: createTestProduct(),
);
await service.closeTab(tab.id);
}
expect(await service.getDistinctCustomerNames(), ['Adam', 'Zoe']);
});
});
group('getClosedTabs', () {
test('returns empty when no closed tabs', () async {
final tabs = await service.getClosedTabs();