Files
doormile_milderapp/test/stop_verify_review_test.dart
2026-08-11 13:16:33 +05:30

244 lines
8.9 KiB
Dart
Raw Permalink 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 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'package:miler/views/Dashboard/pickups/stop_verify.dart';
/// The review step used to be untestable — "it sits behind a camera gate no
/// widget test can pass" — so the only thing held about it was the pure packing
/// rule in [packReviewFacts]. That left the screen itself unwatched, and it
/// broke: the fact rows were `Row(crossAxisAlignment: stretch)` inside a
/// `SingleChildScrollView`, which asks the row to stretch to an infinite height.
/// Layout threw, the exception propagated up through the leg panel's Column, and
/// the last screen of the flow — the one the rider signs — showed no figures at
/// all.
///
/// The gate is passable: [ImagePickerPlatform.instance] is the seam. Swapping in
/// a fake that returns a real file on disk lets the whole flow run to the review
/// and assert what the rider is actually shown.
class _FakeCamera extends ImagePickerPlatform {
final String path;
_FakeCamera(this.path);
@override
Future<XFile?> getImageFromSource({
required ImageSource source,
ImagePickerOptions options = const ImagePickerOptions(),
}) async => XFile(path);
}
void main() {
setUpAll(() async {
final dir = await Directory.systemTemp.createTemp('miler_review');
final png = File('${dir.path}/proof.png')
// A 1×1 PNG: Image.file needs bytes it can decode, not a real photo.
..writeAsBytesSync(const <int>[
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, //
0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 0, 0, 31, 21, 196, 137,
0, 0, 0, 13, 73, 68, 65, 84, 120, 156, 99, 250, 207, 192, 240,
31, 0, 5, 5, 2, 0, 141, 88, 175, 91, 0, 0, 0, 0, 73, 69,
78, 68, 174, 66, 96, 130,
]);
ImagePickerPlatform.instance = _FakeCamera(png.path);
});
Future<void> pump(
WidgetTester tester,
Map<String, dynamic> stop, {
double width = 390,
double textScale = 1.0,
}) async {
tester.view.physicalSize = Size(width * 3, 844 * 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(
builder: (context, child) => MediaQuery(
data: MediaQuery.of(
context,
).copyWith(textScaler: TextScaler.linear(textScale)),
child: child!,
),
home: StopVerificationPage(pickup: stop),
),
),
);
await tester.pumpAndSettle();
}
/// Taps by visible label, scrolling it into view first — every one of these
/// controls sits below the fold on a phone.
Future<void> tap(WidgetTester tester, String label) async {
final target = find.text(label).first;
await tester.ensureVisible(target);
await tester.pumpAndSettle();
await tester.tap(target);
await tester.pumpAndSettle();
}
Future<void> type(WidgetTester tester, String hint, String value) async {
await tester.enterText(
find.byWidgetPredicate(
(w) => w is TextField && w.decoration?.hintText == hint,
),
value,
);
await tester.pumpAndSettle();
}
Map<String, dynamic> stopOf({
required String type,
int deliveryQty = 0,
int pickupQty = 0,
double cash = 0,
}) => <String, dynamic>{
'orderid': 'T-1',
'type': type,
'pickupcustomer': 'Priya Sharma',
'pickupaddress': '45, Lake View Apartments, Coimbatore',
if (deliveryQty > 0) 'deliveryqty': '$deliveryQty',
if (pickupQty > 0) 'pickupqty': '$pickupQty',
'Quantity': '${pickupQty > 0 ? pickupQty : deliveryQty}',
if (cash > 0) 'collectionamt': '$cash',
};
testWidgets('a pickup stop shows every collected figure on the review', (
tester,
) async {
await pump(tester, stopOf(type: 'pickup', pickupQty: 3, cash: 2450));
await type(tester, 'Total weight in kg', '4.5');
await tap(tester, 'Take photo');
// The pickup code is six boxes over one hidden field, not a hinted one.
await tester.enterText(find.byType(TextField).last, '482156');
await tester.pumpAndSettle();
await tap(tester, 'Review');
// The heading is the screen's own voice, not another section title.
expect(find.text('Check before you confirm'), findsOneWidget);
// Every figure the rider is signing for. Before the layout fix this whole
// block rendered as nothing.
expect(find.text('COLLECTED'), findsOneWidget);
expect(find.text('3 of 3'), findsOneWidget);
expect(find.text('4.5 kg'), findsOneWidget);
expect(find.text('Sealed & intact'), findsOneWidget);
expect(find.text('482156'), findsOneWidget);
// The photo is shown, not asserted as a tick the rider cannot check.
expect(find.byType(Image), findsWidgets);
expect(find.text('Tap to enlarge'), findsOneWidget);
// Money is flagged before the commit, not after it.
expect(
find.text('Collect ₹2450 from the customer on the next screen'),
findsOneWidget,
);
expect(find.text('Confirm pickup'), findsOneWidget);
});
testWidgets('a combined stop reviews both legs separately', (tester) async {
await pump(tester, stopOf(type: 'combined', deliveryQty: 2, pickupQty: 3));
// Hand over.
await tap(tester, 'Parcel 1 of 2');
await tap(tester, 'Parcel 2 of 2');
await tap(tester, 'Security');
await type(tester, "Person's name", 'Ramesh Kumar');
await tester.enterText(find.byType(TextField).last, '482156');
await tester.pumpAndSettle();
await tap(tester, 'Take photo');
await tap(tester, 'Continue to collect');
// Collect, with a damaged seal so the note takes its own wide row.
await tap(tester, 'Damaged');
await type(tester, 'Total weight in kg', '12.75');
await type(
tester,
'e.g. corner of the box is crushed',
'Corner of the second box is crushed',
);
await tap(tester, 'Take photo');
await tap(tester, 'Review');
// Two panels, each with its own facts and its own photo — the pairing is
// the whole reason both shots are taken.
expect(find.text('HANDED OVER'), findsOneWidget);
expect(find.text('2 of 2'), findsOneWidget);
expect(find.text('Ramesh Kumar · Security'), findsOneWidget);
expect(find.text('COLLECTED'), findsOneWidget);
expect(find.text('3 of 3'), findsOneWidget);
expect(find.text('12.75 kg'), findsOneWidget);
expect(find.text('Damaged'), findsOneWidget);
expect(find.text('Corner of the second box is crushed'), findsOneWidget);
expect(find.text('Tap to enlarge'), findsNWidgets(2));
// Both legs are complete, and each says so on itself — on a combined stop
// the page-level verdict does not say which job is short.
expect(find.text('Complete'), findsNWidgets(2));
// No pickup code on a combined stop: the delivery OTP already proved the
// door, so it is never collected and must not be reviewed.
expect(find.text('Pickup code'), findsNothing);
});
testWidgets('tapping a proof photo opens it full frame', (tester) async {
await pump(tester, stopOf(type: 'pickup', pickupQty: 1));
await type(tester, 'Total weight in kg', '4.5');
await tap(tester, 'Take photo');
await tester.enterText(find.byType(TextField).last, '482156');
await tester.pumpAndSettle();
await tap(tester, 'Review');
await tap(tester, 'Tap to enlarge');
expect(find.byType(InteractiveViewer), findsOneWidget);
});
// The review is a grid of fixed-width halves, which is the shape that
// overflows when the system font grows. Measured rather than eyeballed.
for (final scale in const [1.0, 1.3, 1.5, 2.0]) {
testWidgets('the review lays out at ${scale}x on a 360pt phone', (
tester,
) async {
await pump(
tester,
stopOf(type: 'combined', deliveryQty: 2, pickupQty: 3, cash: 2450),
width: 360,
textScale: scale,
);
await tap(tester, 'Parcel 1 of 2');
await tap(tester, 'Parcel 2 of 2');
await tap(tester, 'Security');
await type(tester, "Person's name", 'Ramesh Kumar Subramaniam');
await tester.enterText(find.byType(TextField).last, '482156');
await tester.pumpAndSettle();
await tap(tester, 'Take photo');
await tap(tester, 'Continue to collect');
await tap(tester, 'Damaged');
await type(tester, 'Total weight in kg', '12.75');
await type(
tester,
'e.g. corner of the box is crushed',
'Corner of the second box is crushed on the underside',
);
await tap(tester, 'Take photo');
await tap(tester, 'Review');
expect(find.text('HANDED OVER'), findsOneWidget);
expect(find.text('COLLECTED'), findsOneWidget);
expect(tester.takeException(), isNull);
});
}
}