import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:doormile_cx/data/dev_doormile_api.dart'; import 'package:doormile_cx/data/doormile_api.dart'; import 'package:doormile_cx/data/models.dart'; import 'package:doormile_cx/state/app_scope.dart'; import 'package:doormile_cx/state/app_state.dart'; import 'package:doormile_cx/ui/screens/booking/send_screen.dart'; /// ───────────────────────────────────────────────────────────────────────── /// SENDING A SECOND PARCEL /// /// One booking carries one destination while the server caps it there, so a /// customer with two parcels for two places books twice. What that used to /// cost them was the whole flow twice — confirm, walk back to Home, re-answer /// the door they had not moved from, the window, everything. /// /// [AppState.startBookingFrom] is what makes the second one short. These pin /// what it carries and, more importantly, what it refuses to. /// ───────────────────────────────────────────────────────────────────────── void main() { late AppState app; late Booking previous; setUp(() async { DoormileApi.overrideInstance(DevDoormileApi()); app = AppState() ..customer = const Customer( id: 'c1', name: 'Joe Oommen', phone: '+91 98765 43210', email: 'joe@example.com', ); await app.refreshOrders(); previous = app.orders.first; }); tearDown(() => DoormileApi.overrideInstance(null)); group('what a repeat carries', () { test('the door, always', () { app.startBookingFrom(previous); expect(app.draftPickup?.title, previous.pickup.title); expect(app.draftPickup?.lat, previous.pickup.lat); expect(app.draftPickup?.lng, previous.pickup.lng); }); test('the destination only when asked', () { // "Send another from here" — same door, new route. app.startBookingFrom(previous); expect( app.draftDestinations.first.destination.districtCode, isNull, reason: 'the customer is choosing somewhere new', ); // "Send another like this" — same door, same route. app.startBookingFrom(previous, keepDestinations: true); expect( app.draftDestinations.first.destination.districtCode, previous.destinations.first.destination.districtCode, ); expect( app.draftDestinations.first.packageCount, previous.destinations.first.packageCount, ); }); test('never the window', () { // ── The one field a repeat must not assume ── // // A slot fills up. Pinning the second booking to the first one's window // would be refused at confirm with nothing the customer could act on — // and it is a genuinely fresh decision anyway: the first parcel going at // 2pm says nothing about when they want the next visit. expect(previous.slotId, isNotEmpty); app.startBookingFrom(previous, keepDestinations: true); expect(app.draftSlotId, isNull); }); test('a copy, not the booking itself', () { app.startBookingFrom(previous, keepDestinations: true); app.draftDestinations.first.packageCount = 9; expect( previous.destinations.first.packageCount, isNot(9), reason: 'editing the draft must not rewrite the order it came from', ); }); }); group('the cap is obeyed', () { test('a repeat never carries more destinations than the server allows', () { final many = Booking( reference: 'DM-999999', pickup: previous.pickup, destinations: [ for (var i = 0; i < 5; i++) DestinationGroup( destination: Destination( stateCode: 'TN', districtCode: 'TN-$i', stateName: 'Tamil Nadu', districtName: 'District $i', ), ), ], slotId: previous.slotId, createdAt: DateTime.now(), ); app.startBookingFrom(many, keepDestinations: true); expect( app.draftDestinations.length, lessThanOrEqualTo(app.limits.maxDestinations), ); }); }); group('the one-destination cap explains itself', () { /// Review with a draft on it, at whatever cap the server is advertising. Future pumpReview(WidgetTester tester, {required int cap}) async { tester.view.physicalSize = const Size(1230, 9000); tester.view.devicePixelRatio = 3.0; addTearDown(tester.view.reset); app.limits = BookingLimits(maxPackages: 20, maxDestinations: cap); app.startBookingFrom(previous, keepDestinations: true); await tester.pumpWidget( AppScope(state: app, child: const MaterialApp(home: SendScreen())), ); for (var i = 0; i < 6; i++) { await tester.pump(const Duration(milliseconds: 120)); } } testWidgets('says so when the server allows one', (tester) async { // ── Why this needs a test at all ── // // The dev backend advertises five, so this line never renders in the // screenshots or anywhere else a person would look. Production // advertises one. A conditional nobody exercises is a conditional that // quietly never fires. await pumpReview(tester, cap: 1); expect(find.textContaining('One destination per pickup'), findsOneWidget); }); testWidgets('stays quiet when it allows more', (tester) async { await pumpReview(tester, cap: 5); expect(find.textContaining('One destination per pickup'), findsNothing); }); }); }