Files
CookIT/test/views/week_plan_test.dart
2026-10-28 22:55:09 +01:00

353 lines
14 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:cookit/src/planning/day.dart';
import 'package:cookit/src/planning/models/meal.dart';
import 'package:cookit/src/recipes/views/widgets/recipe_card.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../support/fixtures.dart';
import '../support/test_app.dart';
final today = dayOf(DateTime.now());
/// The grid cell of [meal] on [day].
Finder cell(DateTime day, Meal meal) => find.byKey(ValueKey('${dayKey(day)}-${meal.id}'));
Finder inCell(DateTime day, Meal meal, Finder finder) => find.descendant(of: cell(day, meal), matching: finder);
Finder inSheet(Finder finder) => find.descendant(of: find.byType(BottomSheet), matching: finder);
Future<void> openWeekPlan(WidgetTester tester, TestApp app) async {
await tester.tap(find.byTooltip('Wochenplan'));
await app.settle(tester);
}
Future<(Meal, Meal)> meals(WidgetTester tester, TestApp app) async {
final all = (await tester.runAsync(app.plan.allMeals))!;
return (all.firstWhere((m) => m.name == 'Mittag'), all.firstWhere((m) => m.name == 'Abend'));
}
/// Long-presses [from] and drops it on [to].
Future<void> drag(WidgetTester tester, TestApp app, Finder from, Finder to) async {
final start = tester.getCenter(from);
final gesture = await tester.startGesture(start);
await tester.pump(kLongPressTimeout + const Duration(milliseconds: 100));
// While lifted, the entry's place shows an empty outline, so [from] finds nothing.
await gesture.moveTo(start + const Offset(0, 20));
await tester.pump();
await gesture.moveTo(tester.getCenter(to));
await tester.pump();
await gesture.up();
await app.settle(tester);
}
void main() {
testWidgets('"Einplanen" puts a Recipe on today\'s Abend, shown in the grid and the banners', (tester) async {
final app = await TestApp.create(tester);
await tester.runAsync(() => app.recipes.saveRecipe(recipe()));
final (_, abend) = await meals(tester, app);
await tester.pumpWidget(app.build());
await app.settle(tester);
await tester.tap(find.descendant(of: find.byType(RecipeCard), matching: find.text('Focaccia')));
await app.settle(tester);
await tester.tap(find.byTooltip('Einplanen'));
await tester.pumpAndSettle();
await tester.tap(inSheet(find.text('Einplanen')));
await app.settle(tester);
expect(find.text('Eingeplant: Heute · Abend'), findsOneWidget);
await tester.tap(find.byTooltip('Zurück'));
await app.settle(tester);
expect(find.text('HEUTE GEPLANT'), findsOneWidget);
expect(find.text('Abend · 50 Min.'), findsOneWidget);
await tester.tap(find.byTooltip('Lieber eine Idee'));
await tester.pumpAndSettle();
expect(find.text('IDEE FÜR HEUTE'), findsOneWidget);
await openWeekPlan(tester, app);
expect(find.text('Diese Woche'), findsOneWidget);
expect(inCell(today, abend, find.text('Focaccia')), findsOneWidget);
expect(find.textContaining('HEUTE ·'), findsOneWidget);
expect(find.text('Kochen'), findsOneWidget);
await app.dispose(tester);
});
testWidgets('a note is added with "+", removed from its cell and brought back', (tester) async {
final app = await TestApp.create(tester);
final (mittag, _) = await meals(tester, app);
await tester.pumpWidget(app.build());
await app.settle(tester);
await openWeekPlan(tester, app);
await tester.tap(cell(today, mittag));
await tester.pumpAndSettle();
await tester.enterText(inSheet(find.byType(TextField)), 'Essen gehen');
await tester.pumpAndSettle();
await tester.tap(find.text('Als Notiz: „Essen gehen“'));
await app.settle(tester);
expect(inCell(today, mittag, find.text('Essen gehen')), findsOneWidget);
await tester.tap(cell(today, mittag));
await tester.pumpAndSettle();
await tester.tap(inSheet(find.text('Entfernen')));
await app.settle(tester);
expect(inCell(today, mittag, find.text('Essen gehen')), findsNothing);
expect(find.text('„Essen gehen“ entfernt'), findsOneWidget);
await tester.tap(find.text('Rückgängig'));
await app.settle(tester);
expect(inCell(today, mittag, find.text('Essen gehen')), findsOneWidget);
await app.dispose(tester);
});
testWidgets('dragging an entry onto another cell adds it there, and each entry drags on its own', (tester) async {
final app = await TestApp.create(tester);
final (mittag, abend) = await meals(tester, app);
await tester.runAsync(() async {
await app.recipes.saveRecipe(recipe());
await app.plan.addEntry(today, abend.id, recipeId: 'r1');
await app.plan.addEntry(today, mittag.id, note: 'Salat');
});
await tester.pumpWidget(app.build());
await app.settle(tester);
await openWeekPlan(tester, app);
await drag(tester, app, cell(today, abend), cell(today, mittag));
final entries = (await tester.runAsync(app.plan.allEntries))!;
expect(entries.map((e) => (e.mealId, e.recipeId ?? e.note)), [(mittag.id, 'Salat'), (mittag.id, 'r1')]);
// Both show in the cell, one slim line each.
expect(inCell(today, mittag, find.text('Salat')), findsOneWidget);
expect(inCell(today, mittag, find.text('Focaccia')), findsOneWidget);
// The navigation bar is back once the finger is up.
expect(find.text('Hier ablegen zum Entfernen'), findsNothing);
// The second one can be dragged on its own.
await drag(tester, app, inCell(today, mittag, find.text('Focaccia')), cell(today, abend));
final after = (await tester.runAsync(app.plan.allEntries))!;
expect(
after.map((e) => (e.mealId, e.recipeId ?? e.note)),
unorderedEquals([(mittag.id, 'Salat'), (abend.id, 'r1')]),
);
await app.dispose(tester);
});
testWidgets('dropping an entry on "Nächste Woche" moves it seven days on', (tester) async {
final app = await TestApp.create(tester);
final (_, abend) = await meals(tester, app);
await tester.runAsync(() async {
await app.recipes.saveRecipe(recipe());
await app.plan.addEntry(today, abend.id, recipeId: 'r1');
});
await tester.pumpWidget(app.build());
await app.settle(tester);
await openWeekPlan(tester, app);
final gesture = await tester.startGesture(tester.getCenter(cell(today, abend)));
await tester.pump(kLongPressTimeout + const Duration(milliseconds: 100));
await gesture.moveBy(const Offset(0, 20));
await tester.pump();
expect(find.text('Hier ablegen zum Entfernen'), findsOneWidget);
await gesture.moveTo(tester.getCenter(find.text('Nächste Woche')));
await tester.pump();
await gesture.up();
await app.settle(tester);
expect((await tester.runAsync(app.plan.allEntries))!.single.day, addDays(today, 7));
expect(find.text('Nächste Woche'), findsOneWidget); // the grid followed it
expect(inCell(addDays(today, 7), abend, find.text('Focaccia')), findsOneWidget);
await app.dispose(tester);
});
testWidgets('dropping an entry on the remove zone removes it, with undo', (tester) async {
final app = await TestApp.create(tester);
final (_, abend) = await meals(tester, app);
await tester.runAsync(() async {
await app.recipes.saveRecipe(recipe());
await app.plan.addEntry(today, abend.id, recipeId: 'r1');
});
await tester.pumpWidget(app.build());
await app.settle(tester);
await openWeekPlan(tester, app);
final gesture = await tester.startGesture(tester.getCenter(cell(today, abend)));
await tester.pump(kLongPressTimeout + const Duration(milliseconds: 100));
await gesture.moveBy(const Offset(0, 20));
await tester.pump();
await gesture.moveTo(tester.getCenter(find.text('Hier ablegen zum Entfernen')));
await tester.pump();
await gesture.up();
await app.settle(tester);
expect(await tester.runAsync(app.plan.allEntries), isEmpty);
await tester.tap(find.text('Rückgängig'));
await app.settle(tester);
expect((await tester.runAsync(app.plan.allEntries))!.single.recipeId, 'r1');
await app.dispose(tester);
});
testWidgets('"Woche füllen" gives every Abend from today on a different Hauptgericht', (tester) async {
final app = await TestApp.create(tester);
final courses = (await tester.runAsync(app.courses.allCourses))!;
final main = courses.firstWhere((c) => c.name == 'Hauptgericht');
final dessert = courses.firstWhere((c) => c.name == 'Dessert');
await tester.runAsync(() async {
for (var i = 0; i < 7; i++) {
await app.recipes.saveRecipe(recipe(id: 'm$i', name: 'Gericht $i', courseId: main.id));
}
await app.recipes.saveRecipe(recipe(id: 'd', name: 'Pudding', courseId: dessert.id));
});
await tester.pumpWidget(app.build());
await app.settle(tester);
await openWeekPlan(tester, app);
await tester.tap(find.byTooltip('Woche füllen'));
await tester.pumpAndSettle();
await tester.tap(find.text('Füllen'));
await app.settle(tester);
final daysLeft = addDays(weekStartOf(today, DateTime.monday), 6).difference(today).inDays + 1;
final entries = (await tester.runAsync(app.plan.allEntries))!;
final (_, abend) = await meals(tester, app);
expect(entries, hasLength(daysLeft));
expect(entries.map((e) => e.recipeId).toSet(), hasLength(daysLeft));
expect(entries.every((e) => e.mealId == abend.id && e.recipeId!.startsWith('m')), isTrue);
expect(find.text(daysLeft == 1 ? '1 Tag gefüllt.' : '$daysLeft Tage gefüllt.'), findsOneWidget);
// Cells show the Course's icon.
expect(inCell(today, abend, find.text('🍝')), findsOneWidget);
await app.dispose(tester);
});
testWidgets('"Zutaten der Woche teilen" adds up what is planned', (tester) async {
final shared = <Map<Object?, Object?>>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
const MethodChannel('dev.fluttercommunity.plus/share'),
(call) async {
shared.add(call.arguments as Map<Object?, Object?>);
return 'dev.fluttercommunity.plus/share/unavailable';
},
);
final app = await TestApp.create(tester);
await tester.runAsync(() async {
await app.recipes.saveRecipe(recipe());
for (final meal in await app.plan.allMeals()) {
await app.plan.addEntry(today, meal.id, recipeId: 'r1');
}
});
await tester.pumpWidget(app.build());
await app.settle(tester);
await openWeekPlan(tester, app);
await tester.tap(find.byTooltip('Teilen'));
await tester.pumpAndSettle();
await tester.tap(find.text('Zutaten der Woche teilen'));
await tester.pumpAndSettle();
await tester.tap(inSheet(find.text('Teilen')));
await tester.pumpAndSettle();
expect(shared.single['text'], '600 g Mehl\n4–6 EL Olivenöl (gutes)\nRosmarin');
await app.dispose(tester);
});
testWidgets('a Meal added in the settings becomes a column of the grid', (tester) async {
final app = await TestApp.create(tester);
await tester.pumpWidget(app.build());
await app.settle(tester);
await tester.tap(find.byTooltip('Einstellungen'));
await tester.pumpAndSettle();
await tester.tap(find.text('Mahlzeiten bearbeiten'));
await app.settle(tester);
await tester.tap(find.text('Mahlzeit hinzufügen'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextFormField), 'Frühstück');
await tester.tap(find.text('Speichern'));
await app.settle(tester);
await tester.tap(find.byTooltip('Zurück'));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Zurück'));
await app.settle(tester);
await openWeekPlan(tester, app);
expect(find.text('Frühstück'), findsOneWidget);
final fruehstueck = (await tester.runAsync(app.plan.allMeals))!.last;
expect(cell(today, fruehstueck), findsOneWidget);
await app.dispose(tester);
});
testWidgets('the add sheet filters Recipes by Course', (tester) async {
final app = await TestApp.create(tester);
final courses = (await tester.runAsync(app.courses.allCourses))!;
final dessert = courses.firstWhere((c) => c.name == 'Dessert');
final main = courses.firstWhere((c) => c.name == 'Hauptgericht');
final (mittag, _) = await meals(tester, app);
await tester.runAsync(() async {
await app.recipes.saveRecipe(recipe(id: 'a', name: 'Tiramisu', courseId: dessert.id));
await app.recipes.saveRecipe(recipe(id: 'b', name: 'Rinderrouladen', courseId: main.id));
});
await tester.pumpWidget(app.build());
await app.settle(tester);
await openWeekPlan(tester, app);
await tester.tap(cell(today, mittag));
await tester.pumpAndSettle();
expect(inSheet(find.text('Tiramisu')), findsOneWidget);
expect(inSheet(find.text('Rinderrouladen')), findsOneWidget);
// The Course chips scroll sideways; Dessert is far to the right.
await tester.dragUntilVisible(
inSheet(find.text(dessert.label)),
inSheet(find.byType(ListView)).first,
const Offset(-200, 0),
);
await tester.ensureVisible(inSheet(find.text(dessert.label)));
await tester.pumpAndSettle();
await tester.tap(inSheet(find.text(dessert.label)));
await tester.pumpAndSettle();
expect(inSheet(find.text('Tiramisu')), findsOneWidget);
expect(inSheet(find.text('Rinderrouladen')), findsNothing);
await tester.tap(inSheet(find.text('Tiramisu')));
await app.settle(tester);
expect(inCell(today, mittag, find.text('Tiramisu')), findsOneWidget);
await app.dispose(tester);
});
testWidgets('a note can be edited from its cell', (tester) async {
final app = await TestApp.create(tester);
final (mittag, _) = await meals(tester, app);
await tester.runAsync(() => app.plan.addEntry(today, mittag.id, note: 'Essen gehen'));
await tester.pumpWidget(app.build());
await app.settle(tester);
await openWeekPlan(tester, app);
await tester.tap(cell(today, mittag));
await tester.pumpAndSettle();
await tester.tap(inSheet(find.text('Bearbeiten')));
await tester.pumpAndSettle();
await tester.enterText(
find.descendant(of: find.byType(AlertDialog), matching: find.byType(TextField)),
'Pizza bestellen',
);
await tester.tap(find.text('Speichern'));
await app.settle(tester);
expect(inCell(today, mittag, find.text('Pizza bestellen')), findsOneWidget);
expect((await tester.runAsync(app.plan.allEntries))!.single.note, 'Pizza bestellen');
await app.dispose(tester);
});
}