production
This commit is contained in:
@@ -5,12 +5,31 @@ 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. The illustrations are miniature mock-ups of the real
|
||||
/// Home screen rather than a centred Material icon, which means they are made of
|
||||
/// fixed-width rows — the hub rail on slide 1 is a dozen sized children in a
|
||||
/// single unflexed `Row`. That is exactly the shape that overflows on a narrow
|
||||
/// phone, so every slide is laid out at the narrowest screen the app supports.
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
/// 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;
|
||||
@@ -27,6 +46,18 @@ void main() {
|
||||
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),
|
||||
@@ -39,45 +70,104 @@ void main() {
|
||||
await pump(tester, screen: size);
|
||||
expect(tester.takeException(), isNull);
|
||||
|
||||
// Slide 1 — the slot, its load, and the round trip.
|
||||
expect(find.text('Your route arrives ready'), findsOneWidget);
|
||||
expect(find.text('12:00 PM – 3:00 PM'), findsOneWidget);
|
||||
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, because each
|
||||
// slide hosts its own vertical scroll view and the two share the arena.
|
||||
// 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(find.text('Work the stops in order'), findsOneWidget);
|
||||
expect(find.text('Skipped'), findsOneWidget);
|
||||
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(find.text('Collect, then close the loop'), findsOneWidget);
|
||||
expect(find.text('₹340 cash'), findsOneWidget);
|
||||
expect(shownAsset(tester), slides[2]);
|
||||
|
||||
// Last slide swaps the CTA and drops Skip.
|
||||
// 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 copy pitched a gig marketplace — "pickups near you", accepted from a
|
||||
// feed. This product assigns routes from a hub. Onboarding that teaches the
|
||||
// wrong model is worse than none, so the wrong words are pinned out.
|
||||
testWidgets('does not promise a marketplace the app does not have', (
|
||||
tester,
|
||||
) async {
|
||||
// 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',
|
||||
@@ -85,11 +175,7 @@ void main() {
|
||||
'Earn more, every shift',
|
||||
'Powered by AI',
|
||||
]) {
|
||||
expect(
|
||||
find.text(wrong),
|
||||
findsNothing,
|
||||
reason: '"$wrong" is not this app',
|
||||
);
|
||||
expect(find.text(wrong), findsNothing, reason: '"$wrong" is not this app');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user