Files
receipity/test/widget_test.dart
T
2026-08-31 16:51:31 +02:00

347 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:receipity/main.dart';
import 'package:receipity/models/line_item.dart';
import 'package:receipity/review_screen.dart';
import 'package:receipity/widgets/shop_heatmap.dart';
import 'test_helpers.dart';
void main() {
ensureTestSqlite();
testWidgets('home screen shows overview and bottom navigation', (
tester,
) async {
await tester.pumpWidget(ReceipityApp(repository: memoryRepository()));
await pumpUntilFound(tester, find.text('Nothing logged yet.'));
expect(find.text('Home'), findsWidgets);
expect(find.text('Products'), findsOneWidget);
expect(find.text('Lists'), findsOneWidget);
expect(find.byTooltip('Add'), findsOneWidget);
expect(find.byIcon(Icons.add), findsWidgets);
expect(find.text('Shop log'), findsOneWidget);
expect(find.byTooltip('Profile'), findsOneWidget);
expect(find.byTooltip('Settings'), findsOneWidget);
});
testWidgets('scan screen shows capture actions', (tester) async {
await tester.pumpWidget(ReceipityApp(repository: memoryRepository()));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.byTooltip('Add'));
await tester.pump();
expect(find.text('Receipity'), findsOneWidget);
expect(find.text('Take photo'), findsOneWidget);
expect(find.text('Pick from gallery'), findsOneWidget);
expect(find.text('Enter manually'), findsOneWidget);
expect(find.text('Use sample receipt'), findsOneWidget);
expect(
find.textContaining('Scan a receipt or enter a trip by hand'),
findsOneWidget,
);
});
testWidgets('empty shop log hides edit and disables search', (tester) async {
await tester.pumpWidget(ReceipityApp(repository: memoryRepository()));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.text('Shop log'));
await pumpUntilFound(
tester,
find.text('Scan a receipt or enter a trip to log it.'),
);
expect(find.byTooltip('Edit'), findsNothing);
expect(find.byTooltip('Search products'), findsOneWidget);
expect(
tester
.widget<IconButton>(
find.ancestor(
of: find.byIcon(Icons.search),
matching: find.byType(IconButton),
),
)
.onPressed,
isNull,
);
});
testWidgets('deleting a trip removes it without leaving the screen', (
tester,
) async {
final repository = memoryRepository();
await tester.runAsync(() async {
await repository.saveShop(
supermarket: 'Jumbo',
shoppedAt: DateTime.now(),
name: 'Weekly shop',
items: [LineItem(id: '1', name: 'Milk', price: 1.5)],
);
});
await tester.pumpWidget(ReceipityApp(repository: repository));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.text('Shop log'));
await pumpUntilFound(tester, find.text('Weekly shop'));
await tester.tap(find.byTooltip('Edit'));
await tester.pump();
await tester.tap(find.byTooltip('Remove'));
await tester.pump();
await tester.tap(find.text('Delete'));
await tester.pump();
await tester.runAsync(
() => Future<void>.delayed(const Duration(milliseconds: 50)),
);
await tester.pump();
expect(find.text('Weekly shop'), findsNothing);
expect(
find.text('Scan a receipt or enter a trip to log it.'),
findsOneWidget,
);
expect(find.byTooltip('Edit'), findsNothing);
});
testWidgets('products edit mode shows delete, star, and wipe all', (
tester,
) async {
final repository = memoryRepository();
await tester.runAsync(() async {
await repository.saveShop(
supermarket: 'Jumbo',
shoppedAt: DateTime.now(),
items: [LineItem(id: '1', name: 'Milk', price: 1.5)],
);
});
await tester.pumpWidget(ReceipityApp(repository: repository));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.text('Products').last);
await pumpUntilFound(tester, find.text('Milk'));
await tester.tap(find.byTooltip('Edit'));
await tester.pump();
expect(find.byTooltip('Delete'), findsOneWidget);
expect(find.byTooltip('Favourite'), findsOneWidget);
expect(find.byTooltip('Delete all products'), findsOneWidget);
await tester.tap(find.byTooltip('Delete all products'));
await tester.pump();
await tester.tap(find.text('Delete'));
await tester.pump();
await tester.runAsync(
() => Future<void>.delayed(const Duration(milliseconds: 50)),
);
await tester.pump();
expect(find.text('Milk'), findsNothing);
expect(find.byTooltip('Edit'), findsNothing);
});
testWidgets('profile screen is a placeholder', (tester) async {
await tester.pumpWidget(ReceipityApp(repository: memoryRepository()));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.byTooltip('Profile'));
await pumpUntilFound(tester, find.text('Guest'));
});
testWidgets('shop heatmap renders the visible month', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ShopHeatmap(
month: DateTime(2026, 8),
counts: const {},
monthTotal: 0,
monthTripCount: 0,
canGoForward: false,
onPreviousMonth: () {},
onNextMonth: () {},
onSelectDay: (_) {},
),
),
),
);
expect(find.text('August 2026'), findsOneWidget);
expect(find.text('No trips this month'), findsOneWidget);
expect(find.text('Less'), findsOneWidget);
expect(find.text('More'), findsOneWidget);
});
testWidgets('enter manually opens a blank trip', (tester) async {
await tester.pumpWidget(ReceipityApp(repository: memoryRepository()));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.byTooltip('Add'));
await tester.pump();
await tester.tap(find.text('Enter manually'));
await pumpUntilFound(tester, find.text('New trip'));
expect(find.text('Add item'), findsOneWidget);
});
testWidgets('lists tab shows shopping lists', (tester) async {
await tester.pumpWidget(ReceipityApp(repository: memoryRepository()));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.text('Lists'));
await tester.pump();
expect(find.text('Shopping lists'), findsOneWidget);
});
testWidgets('review screen lists items and can delete one', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: ReviewScreen(
items: [
LineItem(id: '1', name: 'Milk', price: 1.5),
LineItem(id: '2', name: 'Bread', price: 2),
],
rawText: 'Milk 1.50\nBread 2.00',
repository: memoryRepository(),
),
),
);
expect(find.text('Milk'), findsOneWidget);
expect(find.text('Bread'), findsOneWidget);
expect(find.text('Add item'), findsOneWidget);
expect(find.textContaining('Save trip'), findsOneWidget);
expect(find.text('Supermarket'), findsOneWidget);
expect(find.text('Trip name'), findsOneWidget);
expect(find.text('Add tag'), findsOneWidget);
await tester.drag(find.text('Milk'), const Offset(-500, 0));
await tester.pumpAndSettle();
expect(find.text('Milk'), findsNothing);
expect(find.text('Bread'), findsOneWidget);
});
testWidgets('review screen preselects a supermarket from OCR text', (
tester,
) async {
await tester.pumpWidget(
MaterialApp(
home: ReviewScreen(
items: [LineItem(id: '1', name: 'Milk', price: 1.5)],
rawText: 'Jumbo\nMilk 1.50',
repository: memoryRepository(),
),
),
);
expect(find.text('Jumbo'), findsWidgets);
});
testWidgets('review screen strikes through the pre-discount price', (
tester,
) async {
await tester.pumpWidget(
MaterialApp(
home: ReviewScreen(
items: [
LineItem(id: '1', name: 'Berries', price: 6.85, discount: -1.15),
],
rawText: 'Berries 8.00\nKorting -1.15',
repository: memoryRepository(),
),
),
);
expect(find.text('€ 8.00'), findsOneWidget);
expect(find.text('€ 6.85'), findsOneWidget);
final original = tester.widget<Text>(find.text('€ 8.00'));
expect(original.style?.decoration, TextDecoration.lineThrough);
});
testWidgets('product name is saved when leaving the screen', (tester) async {
final repository = memoryRepository();
await tester.runAsync(() async {
await repository.saveShop(
supermarket: 'Jumbo',
shoppedAt: DateTime.now(),
items: [LineItem(id: '1', name: 'Milk', price: 1.5)],
);
});
await tester.pumpWidget(ReceipityApp(repository: repository));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.text('Products').last);
await pumpUntilFound(tester, find.text('Milk'));
await tester.tap(find.text('Milk'));
await pumpUntilFound(tester, find.text('Name'));
await tester.enterText(find.byType(TextField).first, 'Oat milk');
await tester.tap(find.byTooltip('Back'));
await tester.runAsync(
() => Future<void>.delayed(const Duration(milliseconds: 150)),
);
await tester.pump();
expect(find.byType(TextField), findsNothing);
expect(find.text('Oat milk'), findsOneWidget);
expect(find.text('Milk'), findsNothing);
});
testWidgets('products screen marks case-only duplicates', (tester) async {
final repository = memoryRepository();
await tester.runAsync(() => seedCaseConflictCatalog(repository));
await tester.pumpWidget(ReceipityApp(repository: repository));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.text('Products').last);
await pumpUntilFound(tester, find.text('1 duplicate product'));
expect(find.text('Broccoli'), findsOneWidget);
expect(find.text('BROCCOLI'), findsOneWidget);
expect(find.byTooltip('Resolve name issues'), findsWidgets);
});
testWidgets('products screen marks receipt spelling variants', (
tester,
) async {
final repository = memoryRepository();
await tester.runAsync(() async {
await repository.saveShop(
supermarket: 'Jumbo',
shoppedAt: DateTime(2026, 8, 1),
items: [LineItem(id: '1', name: 'Broccoli', price: 1.5)],
);
await repository.saveShop(
supermarket: 'Jumbo',
shoppedAt: DateTime(2026, 8, 2),
items: [LineItem(id: '2', name: 'BROCCOLI', price: 1.8)],
);
});
await tester.pumpWidget(ReceipityApp(repository: repository));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.text('Products').last);
await pumpUntilFound(tester, find.text('1 product, multiple spellings'));
expect(find.text('Broccoli'), findsOneWidget);
expect(find.byTooltip('Resolve name issues'), findsWidgets);
});
testWidgets('resolve name issues sheet shows merge and leave as is', (
tester,
) async {
final repository = memoryRepository();
await tester.runAsync(() => seedCaseConflictCatalog(repository));
await tester.pumpWidget(ReceipityApp(repository: repository));
await pumpUntilFound(tester, find.text('Overview'));
await tester.tap(find.text('Products').last);
await pumpUntilFound(tester, find.text('1 duplicate product'));
await tester.tap(find.text('1 duplicate product'));
await pumpUntilFound(tester, find.text('Resolve name issues'));
expect(find.text('Resolve name issues'), findsOneWidget);
expect(find.text('Separate products to merge'), findsOneWidget);
expect(find.text('Broccoli'), findsWidgets);
expect(find.text('BROCCOLI'), findsWidgets);
expect(find.text('Merge'), findsOneWidget);
expect(find.text('Leave as is'), findsOneWidget);
});
}