129 lines
4.8 KiB
Dart
129 lines
4.8 KiB
Dart
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<void> 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<void> 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, 320));
|
|
|
|
expect(find.byType(MealPlannerScreen), findsOneWidget);
|
|
expect(find.text('Today'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
|
|
// The whole page scrolls: drag upward and verify the content moved up.
|
|
final before = tester.getTopLeft(find.text('Today')).dy;
|
|
await tester.drag(find.text('Today'), const Offset(0, -120));
|
|
await tester.pump();
|
|
final after = tester.getTopLeft(find.text('Today')).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);
|
|
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('Today'), findsOneWidget);
|
|
// 1000 dp is above the 900 dp wide breakpoint, so the full seven-column
|
|
// grid renders; empty slots surface the "+ Assign Recipe" affordance.
|
|
expect(find.text('Assign Recipe'), findsWidgets);
|
|
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);
|
|
});
|
|
|
|
testWidgets('slot tap opens the searchable assign sheet from the vault',
|
|
(tester) async {
|
|
await pumpScreen(tester, logicalSize: const Size(1400, 900));
|
|
|
|
// Wide grid: tap the first "+ Assign Recipe" affordance.
|
|
await tester.tap(find.text('Assign Recipe').first);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Search Recipe Vault'), findsOneWidget);
|
|
expect(find.text('Custom entry (no recipe)'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
await unmountAndFlushTimers(tester);
|
|
});
|
|
}
|