import 'package:drift/native.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:glp1_companion/core/database/app_database.dart'; import 'package:glp1_companion/features/meal_planner/presentation/screens/meal_planner_screen.dart'; /// Regression tests for the desktop resize overflow (yellow/black hatched /// stripes): the meal planner must reflow into scrollable layouts at short /// window heights instead of overflowing a rigid `Column`. /// /// RenderFlex overflows raise a [FlutterError] inside widget tests, so a /// clean run also proves the stripes are gone. void main() { late AppDatabase database; setUp(() { database = AppDatabase.forTesting(NativeDatabase.memory()); }); tearDown(() => database.close()); Future pumpScreen( WidgetTester tester, { required Size logicalSize, }) async { tester.view.physicalSize = logicalSize; tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget( ProviderScope( overrides: [appDatabaseProvider.overrideWithValue(database)], child: const MaterialApp(home: MealPlannerScreen()), ), ); // Let the initial drift loads (watch stream + microtask refresh) settle. await tester.pump(); await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50)); } /// Unmounts the tree and flushes the zero-duration timer drift schedules /// when its query streams are cancelled during container disposal /// (`StreamQueryStore.markAsClosed`), which would otherwise trip the test /// framework's pending-timers invariant. Future unmountAndFlushTimers(WidgetTester tester) async { await tester.pumpWidget(const SizedBox()); await tester.pump(const Duration(milliseconds: 1)); } testWidgets( 'short compact window collapses into a scrollable page without overflow', (tester) async { await pumpScreen(tester, logicalSize: const Size(480, 400)); expect(find.byType(MealPlannerScreen), findsOneWidget); expect(find.text('Saved Recipes'), findsOneWidget); expect(tester.takeException(), isNull); // The whole page scrolls: drag upward and verify the content moved up. final before = tester.getTopLeft(find.text('Saved Recipes')).dy; await tester.drag(find.text('Saved Recipes'), const Offset(0, -200)); await tester.pump(); final after = tester.getTopLeft(find.text('Saved Recipes')).dy; expect(after, lessThan(before), reason: 'the page should scroll vertically when the window is short'); expect(tester.takeException(), isNull); await unmountAndFlushTimers(tester); }); testWidgets( 'short wide window keeps the 7-day grid with scrollable day columns', (tester) async { await pumpScreen(tester, logicalSize: const Size(1100, 420)); // Wide breakpoint: all seven weekday headers render at once. for (final label in const [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', ]) { expect(find.textContaining(label), findsWidgets); } expect(tester.takeException(), isNull); // The outer page scrolls so the calendar area is reachable when the // window is shorter than the fixed top sections + calendar. final before = tester.getTopLeft(find.text('Saved Recipes')).dy; await tester.drag(find.text('Saved Recipes'), const Offset(0, -200)); await tester.pump(); expect(tester.getTopLeft(find.text('Saved Recipes')).dy, lessThan(before)); expect(tester.takeException(), isNull); await unmountAndFlushTimers(tester); }); testWidgets('tall window uses the pinned layout without overflow', (tester) async { await pumpScreen(tester, logicalSize: const Size(1000, 900)); expect(find.text('Saved Recipes'), findsOneWidget); for (final label in const [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', ]) { expect(find.textContaining(label), findsWidgets); } expect(tester.takeException(), isNull); await unmountAndFlushTimers(tester); }); testWidgets('extremely short window still renders without overflow', (tester) async { await pumpScreen(tester, logicalSize: const Size(1280, 320)); expect(find.byType(MealPlannerScreen), findsOneWidget); expect(tester.takeException(), isNull); await unmountAndFlushTimers(tester); }); }