Files
doormile_milderapp/test/intro_screen_test.dart
2026-08-28 11:13:15 +05:30

182 lines
7.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:miler/views/helpers/constants/app_theme.dart';
import 'package:miler/views/introscreens/introscreen.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// THE FIRST-RUN SLIDES
///
/// These used to be miniature mock-ups of Home built out of fixed-width rows —
/// the hub rail on slide 1 was a dozen sized children in a single unflexed
/// `Row` — and the tests existed because that is exactly the shape which
/// overflows on a narrow phone.
///
/// The slides are finished posters now: headline, sub-copy, product shot and
/// the MILER lockup are all baked into the artwork, and the screen draws no
/// text of its own. A `BoxFit.cover` image cannot overflow, so the width sweep
/// is no longer about the illustrations — it is about the controls laid over
/// them, which are the only Flutter-built objects left on the screen.
///
/// What is worth pinning instead is the part a wrong asset path would break
/// silently: three specific images, in the order the rider is meant to see
/// them, with the CTA changing on the last one.
/// ─────────────────────────────────────────────────────────────────────────
void main() {
const slides = <String>[
'assets/images/onboard_1.png',
'assets/images/onboard_2.png',
'assets/images/onboard_3.png',
];
Future<void> pump(WidgetTester tester, {required Size screen}) async {
tester.view.physicalSize = Size(screen.width * 3, screen.height * 3);
tester.view.devicePixelRatio = 3.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
ScreenUtilInit(
designSize: const Size(390, 844),
builder: (_, _) =>
MaterialApp(theme: AppTheme.light, home: const Introscreen()),
),
);
await tester.pumpAndSettle();
}
/// The asset the slide currently on screen is drawing.
String? shownAsset(WidgetTester tester) {
for (final image in tester.widgetList<Image>(find.byType(Image))) {
final provider = image.image;
if (provider is AssetImage &&
provider.assetName.startsWith('assets/images/onboard_')) {
return provider.assetName;
}
}
return null;
}
for (final size in const [
Size(320, 640), // smallest Android still in the fleet
Size(360, 740),
Size(390, 844),
Size(430, 932),
]) {
testWidgets('all three slides lay out at ${size.width.toInt()}pt', (
tester,
) async {
await pump(tester, screen: size);
expect(tester.takeException(), isNull);
expect(shownAsset(tester), slides[0]);
expect(find.text('Next'), findsOneWidget);
expect(find.text('Skip'), findsOneWidget);
// A near-full-width drag, so the page always snaps to the next one. A
// velocity fling does not reliably cross the threshold here.
final swipe = Offset(-size.width * 0.9, 0);
await tester.drag(find.byType(PageView), swipe);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
expect(shownAsset(tester), slides[1]);
expect(find.text('Next'), findsOneWidget);
await tester.drag(find.byType(PageView), swipe);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
expect(shownAsset(tester), slides[2]);
// Last slide swaps the CTA and drops Skip — an invisible-but-present
// control is a trap for screen readers, so it is unmounted rather than
// faded to `opacity: 0`.
expect(find.text('Get started'), findsOneWidget);
expect(find.text('Next'), findsNothing);
expect(find.text('Skip'), findsNothing);
});
}
testWidgets('the three posters are in the order they were given', (
tester,
) async {
await pump(tester, screen: const Size(390, 844));
// `PageView.builder` only builds the page on screen, so the order has to be
// walked rather than read out of the tree in one go. Which is the honest
// test anyway: what matters is the sequence the rider is shown, and 1-3-2
// would pass any assertion made against a set.
final seen = <String?>[shownAsset(tester)];
for (var i = 1; i < slides.length; i++) {
await tester.drag(find.byType(PageView), const Offset(-351, 0));
await tester.pumpAndSettle();
seen.add(shownAsset(tester));
}
expect(seen, slides);
});
testWidgets('the artwork runs edge to edge', (tester) async {
await pump(tester, screen: const Size(390, 844));
final image = tester.widget<Image>(
find
.byWidgetPredicate(
(w) =>
w is Image &&
w.image is AssetImage &&
(w.image as AssetImage).assetName == slides[0],
)
.first,
);
// `cover` is what makes a 940×1672 poster fill a phone of any aspect
// without letterboxing. `contain` would put bars down the sides, which on
// artwork that ends in a red wave reads as a rendering fault.
expect(image.fit, BoxFit.cover);
});
testWidgets('the screen adds no captions of its own', (tester) async {
await pump(tester, screen: const Size(390, 844));
// Every slide's headline and sub-copy are baked into the image. A Flutter
// caption under the picture would say the same thing twice, in a second
// typeface, at a second size — so the only text on the screen is the two
// controls.
final texts = tester
.widgetList<Text>(find.byType(Text))
.map((t) => t.data)
.whereType<String>()
.toSet();
expect(texts, <String>{'Skip', 'Next'});
});
testWidgets('Skip is offered on the first slide', (tester) async {
await pump(tester, screen: const Size(390, 844));
expect(find.text('Skip'), findsOneWidget);
});
// The old code-drawn copy pitched a gig marketplace — "pickups near you",
// accepted from a feed. This product assigns routes from a hub, and
// onboarding that teaches the wrong model is worse than none.
//
// NOTE: this can only speak for text the *app* draws. The posters carry their
// own headlines as pixels, and `onboard_1.png` does print "Powered by AI" —
// a claim the previous slides deliberately dropped because nothing in the app
// backs it. That is a copy decision about the artwork, not something a widget
// test can hold.
testWidgets('the app draws no marketplace copy of its own', (tester) async {
await pump(tester, screen: const Size(390, 844));
for (final wrong in [
'Find pickups near you',
'Navigate the smart way',
'Earn more, every shift',
'Powered by AI',
]) {
expect(find.text(wrong), findsNothing, reason: '"$wrong" is not this app');
}
});
}