import 'package:flutter/material.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:miler/data/service_profile.dart'; import 'package:miler/views/Dashboard/pickups/shipment_review.dart'; /// ───────────────────────────────────────────────────────────────────────── /// REVIEW & PAY /// /// ``` /// verify parcel ──▶ shipment desk ──▶ REVIEW & PAY ──▶ order created /// (no review) from · to animated tick /// weight · fare tracking no /// payment /// [ Pay ₹150 ] /// ``` /// /// Two things are pinned here. /// /// **The button names the amount.** A rider taking cash on a doorstep should /// never have to remember what the total was, and the figure he quotes must be /// the figure the screen collects — so both come from one place. /// /// **The success state is earned.** The tick and "your order has been created" /// appear only after the create actually succeeded. A failure keeps the rider /// on the review with the reason, because the alternative is him riding off /// with the customer's money believing an order exists. /// ───────────────────────────────────────────────────────────────────────── void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() { SharedPreferences.setMockInitialValues({}); ServiceProfile.setActive(ServiceProfile.parcel); }); Map capture({double price = 150}) => { 'captured': true, 'from': {'address': '9 Mettupalayam Road', 'pincode': '641002'}, 'to': { 'address': '12 Cross Cut Road', 'pincode': '641012', 'city': 'Coimbatore', }, 'weight': 2.5, 'category': 'General', 'servicetype': 'Normal', 'zone': 'Local', 'price': price, 'currency': 'INR', 'approxkm': 4.2, }; Future pump( WidgetTester tester, { Map? shipment, Future Function(ShipmentPayMethod, double)? onConfirm, String tracking = 'DM-TRK-A1B2-42', }) async { tester.view.physicalSize = const Size(1080, 2400); tester.view.devicePixelRatio = 3.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget( ScreenUtilInit( designSize: const Size(390, 844), builder: (_, _) => MaterialApp( home: ShipmentReviewPage( pickup: const {'orderid': '2001', 'pickupid': 2001}, shipment: shipment ?? capture(), trackingNo: () => tracking, onConfirm: onConfirm ?? (_, _) async => null, ), ), ), ); await tester.pumpAndSettle(); } group('the review', () { testWidgets('shows the shipment and the payment on one screen', ( tester, ) async { await pump(tester); // Route expect(find.text('FROM'), findsOneWidget); expect(find.text('TO'), findsOneWidget); expect(find.text('9 Mettupalayam Road'), findsOneWidget); expect(find.text('12 Cross Cut Road'), findsOneWidget); // Shipment expect(find.text('Shipment'), findsOneWidget); expect(find.text('2.5 kg'), findsOneWidget); expect(find.text('General'), findsOneWidget); expect(find.text('Local'), findsOneWidget); // Payment expect(find.text('Payment'), findsOneWidget); expect(find.text('Cash'), findsOneWidget); expect(find.text('UPI'), findsOneWidget); }); testWidgets('the button says the amount out loud', (tester) async { await pump(tester); expect( find.text('Pay ₹150'), findsOneWidget, reason: 'a rider at a doorstep should not have to remember the total', ); }); testWidgets('the amount on the button matches the amount in the summary', ( tester, ) async { await pump(tester, shipment: capture(price: 275)); expect(find.text('Pay ₹275'), findsOneWidget); expect(find.text('₹275'), findsOneWidget); // the summary line }); testWidgets('a zero-rated shipment asks for no money', (tester) async { await pump(tester, shipment: capture(price: 0)); expect(find.text('Create order'), findsOneWidget); expect(find.textContaining('Pay '), findsNothing); }); testWidgets('the distance is labelled as the estimate it is', ( tester, ) async { await pump(tester); expect(find.text('≈ 4.2 km'), findsOneWidget); expect( find.textContaining('straight line'), findsOneWidget, reason: 'the fare comes from the zone, and saying so stops a rider ' 'telling a customer the wrong basis for a charge', ); }); }); group('paying', () { testWidgets('passes the chosen method and the quoted amount', ( tester, ) async { ShipmentPayMethod? seenMethod; double? seenAmount; await pump( tester, onConfirm: (m, a) async { seenMethod = m; seenAmount = a; return null; }, ); await tester.tap(find.text('UPI')); await tester.pumpAndSettle(); await tester.tap(find.text('Pay ₹150')); await tester.pumpAndSettle(); expect(seenMethod, ShipmentPayMethod.upi); expect(seenAmount, 150); }); testWidgets('success shows the tick, the copy and the tracking number', ( tester, ) async { await pump(tester); await tester.tap(find.text('Pay ₹150')); await tester.pumpAndSettle(); expect(find.text('Your order has been created'), findsOneWidget); expect(find.byIcon(LucideIcons.check), findsOneWidget); expect(find.text('₹150 collected by Cash'), findsOneWidget); expect( find.text('DM-TRK-A1B2-42'), findsOneWidget, reason: 'a customer asking for the tracking number gets it on the ' 'doorstep instead of a phone call to the hub', ); expect(find.text('Done'), findsOneWidget); }); testWidgets('a failure keeps him on the review with the reason', ( tester, ) async { await pump( tester, onConfirm: (_, _) async => 'Payment could not be recorded — no network', ); await tester.tap(find.text('Pay ₹150')); await tester.pumpAndSettle(); expect( find.text('Your order has been created'), findsNothing, reason: 'the order does not exist; claiming it does would send him ' 'away with the customer\'s money and nothing on the hub screen', ); expect( find.textContaining('Payment could not be recorded'), findsOneWidget, ); // And he can try again. expect(find.text('Pay ₹150'), findsOneWidget); }); testWidgets('Done hands back the outcome the pickup flow reads', ( tester, ) async { Map? popped; tester.view.physicalSize = const Size(1080, 2400); tester.view.devicePixelRatio = 3.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget( ScreenUtilInit( designSize: const Size(390, 844), builder: (_, _) => MaterialApp( home: Builder( builder: (context) => ElevatedButton( onPressed: () async { popped = await Navigator.of(context) .push>( MaterialPageRoute( builder: (_) => ShipmentReviewPage( pickup: const {'orderid': '2001'}, shipment: capture(), trackingNo: () => 'DM-TRK-1', onConfirm: (_, _) async => null, ), ), ); }, child: const Text('open'), ), ), ), ), ); await tester.tap(find.text('open')); await tester.pumpAndSettle(); await tester.tap(find.text('Pay ₹150')); await tester.pumpAndSettle(); await tester.tap(find.text('Done')); await tester.pumpAndSettle(); // The shape `map.dart` already branches on, so the hand-off screen, the // compliance stamp and the next-stop list keep working untouched. expect(popped?['outcome'], 'completed'); expect(popped?['amountCollected'], 150); expect(popped?['paymentMethod'], 'Cash'); }); }); group('the flow order', () { test('logistics verifies, then captures, then reviews', () { // The verify page drops its own review exactly where the shipment desk // follows it — otherwise the rider confirms the parcel, describes the // shipment, and confirms it all again. expect(ServiceProfile.parcel.needsVerification, isTrue); expect(ServiceProfile.parcel.capturesShipmentAddresses, isTrue); expect(ServiceProfile.parcel.initiatesShipment, isTrue); }); test('a milk run reaches none of it', () { const p = ServiceProfile.milkMan; expect(p.needsVerification, isFalse); expect(p.capturesShipmentAddresses, isFalse); expect(p.initiatesShipment, isFalse); expect(p.collectsCash, isFalse); }); }); }