Files

352 lines
14 KiB
Dart
Raw Permalink Normal View History

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);
});
}