Files
krow-worker-app/test/screen_layout_test.dart

105 lines
3.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:krow_worker_app/core/theme.dart';
import 'package:krow_worker_app/screens/auth_screens.dart';
import 'package:krow_worker_app/screens/clock_screen.dart';
import 'package:krow_worker_app/screens/pay_screens.dart';
import 'package:krow_worker_app/screens/profile_screen.dart';
import 'package:krow_worker_app/screens/shifts_screen.dart';
import 'package:krow_worker_app/screens/today_screen.dart';
import 'package:krow_worker_app/state/session_controller.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Overflow is reported as a thrown FlutterError, and the stripes only appear
/// in a debug build — so a release web check cannot see it and it reaches a
/// device first. These pump every tab at real phone sizes to catch it here.
void main() {
late SharedPreferences prefs;
setUp(() async {
SharedPreferences.setMockInitialValues({});
prefs = await SharedPreferences.getInstance();
});
Future<void> pumpScreen(
WidgetTester tester,
Widget screen, {
required Size size,
double textScale = 1.0,
}) async {
tester.view.physicalSize = size;
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.reset);
// Captured rather than left to the default handler, so a failure names the
// widget that overflowed instead of just the pixel count.
final errors = <FlutterErrorDetails>[];
final previousHandler = FlutterError.onError;
FlutterError.onError = errors.add;
await tester.pumpWidget(
ProviderScope(
overrides: [prefsProvider.overrideWithValue(prefs)],
child: MaterialApp(
theme: buildKrowTheme(),
home: MediaQuery(
data: MediaQueryData(
size: size,
textScaler: TextScaler.linear(textScale),
// A notch, so the header has to make room for it.
padding: const EdgeInsets.only(top: 44, bottom: 34),
),
child: screen,
),
),
),
);
await tester.pump();
FlutterError.onError = previousHandler;
if (errors.isNotEmpty) {
fail(errors.map((e) => e.toString()).join('\n\n'));
}
// Screens with running timers must be torn down, or the test fails on a
// pending timer rather than on what it is checking.
await tester.pumpWidget(const SizedBox.shrink());
}
final screens = <String, Widget>{
'Today': const TodayScreen(),
'Shifts': const ShiftsScreen(),
'Clock': const ClockScreen(),
'Pay': const PayScreen(),
'Profile': const ProfileScreen(),
// Not a tab, but it renders Today behind a sheet full of copy, so it
// is the most crowded layout in the app.
'Permissions': const PermissionsScreen(),
};
// A small phone, a tall one, and a short-but-wide one.
final sizes = <String, Size>{
'small phone': const Size(360, 640),
'tall phone': const Size(412, 915),
'compact': const Size(320, 568),
};
for (final screen in screens.entries) {
for (final size in sizes.entries) {
testWidgets('${screen.key} lays out on a ${size.key}', (tester) async {
await pumpScreen(tester, screen.value, size: size.value);
});
}
testWidgets('${screen.key} survives large system text', (tester) async {
await pumpScreen(
tester,
screen.value,
size: const Size(360, 640),
textScale: 1.3,
);
});
}
}