352 lines
14 KiB
Dart
352 lines
14 KiB
Dart
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);
|
|||
|
|
});
|
|||
|
|
}
|