Initial commit: CookIT recipe collection and week planner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
7d7a0bc60b
173 files changed
+22803
No files matched your search
@@ -0,0 +1,55 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cookit/src/recipes/models/recipe.dart';
|
||||
import 'package:cookit/src/recipes/photo_store.dart';
|
||||
|
||||
final testTime = DateTime.utc(2026, 10, 20, 12);
|
||||
|
||||
Recipe recipe({
|
||||
String id = 'r1',
|
||||
String name = 'Focaccia',
|
||||
String? courseId,
|
||||
List<String> tags = const [],
|
||||
List<IngredientGroup>? ingredientGroups,
|
||||
List<StepSection>? stepSections,
|
||||
List<String> photos = const [],
|
||||
bool favourite = false,
|
||||
List<String> reviewReasons = const [],
|
||||
}) => Recipe(
|
||||
id: id,
|
||||
name: name,
|
||||
courseId: courseId,
|
||||
tags: tags,
|
||||
servings: 4,
|
||||
prepMinutes: 30,
|
||||
cookMinutes: 20,
|
||||
calories: const Calories(775, CaloriesBasis.perServing),
|
||||
ingredientGroups:
|
||||
ingredientGroups ??
|
||||
const [
|
||||
IngredientGroup(
|
||||
lines: [
|
||||
IngredientLine(amount: 300, unitKey: 'gram', name: 'Mehl'),
|
||||
IngredientLine(amount: 2, amountMax: 3, unitKey: 'tablespoon', name: 'Olivenöl', remark: 'gutes'),
|
||||
],
|
||||
),
|
||||
IngredientGroup(
|
||||
name: 'Topping',
|
||||
lines: [IngredientLine(name: 'Rosmarin')],
|
||||
),
|
||||
],
|
||||
stepSections:
|
||||
stepSections ??
|
||||
const [
|
||||
StepSection(title: 'Teig', steps: ['Kneten.', 'Gehen lassen.']),
|
||||
],
|
||||
photos: photos,
|
||||
sources: const ['https://example.org/focaccia'],
|
||||
notes: '22x22 cm Form',
|
||||
favourite: favourite,
|
||||
reviewReasons: reviewReasons,
|
||||
createdAt: testTime,
|
||||
updatedAt: testTime,
|
||||
);
|
||||
|
||||
Future<PhotoStore> tempPhotoStore() async => PhotoStore(await Directory.systemTemp.createTemp('cookit_photos_'));
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cookit/src/app.dart';
|
||||
import 'package:cookit/src/bundle/bundle_service.dart';
|
||||
import 'package:cookit/src/database/database.dart';
|
||||
import 'package:cookit/src/planning/plan_repository.dart';
|
||||
import 'package:cookit/src/planning/plan_share_service.dart';
|
||||
import 'package:cookit/src/planning/week_plan_provider.dart';
|
||||
import 'package:cookit/src/recipes/course_repository.dart';
|
||||
import 'package:cookit/src/recipes/photo_store.dart';
|
||||
import 'package:cookit/src/recipes/recipe_list_provider.dart';
|
||||
import 'package:cookit/src/recipes/recipe_repository.dart';
|
||||
import 'package:cookit/src/recipes/unit_catalog.dart';
|
||||
import 'package:cookit/src/recipes/unit_repository.dart';
|
||||
import 'package:cookit/src/settings/settings_controller.dart';
|
||||
import 'package:cookit/src/sharing/recipe_share_service.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'fixtures.dart';
|
||||
import 'test_database.dart';
|
||||
|
||||
/// The real app wired to an in-memory database and a temporary photo folder.
|
||||
class TestApp {
|
||||
TestApp._(this.db, this.photos)
|
||||
: recipes = RecipeRepository(db, photos),
|
||||
courses = CourseRepository(db),
|
||||
units = UnitRepository(db),
|
||||
plan = PlanRepository(db),
|
||||
bundles = BundleService(RecipeRepository(db, photos), CourseRepository(db), UnitRepository(db), photos);
|
||||
|
||||
final AppDatabase db;
|
||||
final PhotoStore photos;
|
||||
final RecipeRepository recipes;
|
||||
final CourseRepository courses;
|
||||
final UnitRepository units;
|
||||
final PlanRepository plan;
|
||||
final BundleService bundles;
|
||||
|
||||
static Future<TestApp> create(WidgetTester tester) async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
// A tall phone (400 x 2000 logical px) so long forms are built without scrolling.
|
||||
tester.view.physicalSize = const Size(1200, 6000);
|
||||
tester.view.devicePixelRatio = 3;
|
||||
addTearDown(tester.view.reset);
|
||||
final photos = (await tester.runAsync(tempPhotoStore))!;
|
||||
return TestApp._(testDatabase(), photos);
|
||||
}
|
||||
|
||||
Widget build() => MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => SettingsController(SettingsService())),
|
||||
Provider.value(value: photos),
|
||||
Provider.value(value: recipes),
|
||||
Provider.value(value: courses),
|
||||
Provider.value(value: units),
|
||||
Provider.value(value: plan),
|
||||
ChangeNotifierProvider(create: (_) => UnitCatalog(units)),
|
||||
Provider.value(value: bundles),
|
||||
Provider(create: (_) => RecipeShareService(recipes, courses, units, photos, bundles)),
|
||||
Provider(create: (_) => PlanShareService(recipes, courses, units, photos, plan, bundles)),
|
||||
ChangeNotifierProvider(create: (_) => RecipeListProvider(recipes, courses)),
|
||||
ChangeNotifierProvider(create: (_) => WeekPlanProvider(plan)),
|
||||
],
|
||||
child: const CookItApp(),
|
||||
);
|
||||
|
||||
/// Lets database work and the resulting stream updates finish, then redraws.
|
||||
Future<void> settle(WidgetTester tester) async {
|
||||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 50)));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> dispose(WidgetTester tester) async {
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
await tester.runAsync(() async {
|
||||
await db.close();
|
||||
try {
|
||||
await photos.directory.delete(recursive: true);
|
||||
} on FileSystemException {
|
||||
// Windows keeps a just-decoded image file open for a moment; the temp folder can stay.
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cookit/src/database/database.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:sqlite3/open.dart';
|
||||
|
||||
/// An in-memory database for tests, or one in [file] to test migrations. On
|
||||
/// Windows hosts the sqlite3 library comes from COOKIT_SQLITE_DLL if set,
|
||||
/// otherwise from the system's winsqlite3.dll.
|
||||
AppDatabase testDatabase({File? file}) {
|
||||
if (Platform.isWindows) {
|
||||
open.overrideFor(OperatingSystem.windows, () {
|
||||
final custom = Platform.environment['COOKIT_SQLITE_DLL'];
|
||||
return DynamicLibrary.open(custom ?? 'winsqlite3.dll');
|
||||
});
|
||||
}
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
final executor = file == null ? NativeDatabase.memory() : NativeDatabase(file);
|
||||
// Synchronous stream teardown keeps widget tests from hanging on pending timers.
|
||||
return AppDatabase(DatabaseConnection(executor, closeStreamsSynchronously: true));
|
||||
}
|
||||
Reference in new issue
Block a user