import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:miler/data/accepted_store.dart'; import 'package:miler/data/order_manifest.dart'; import 'package:miler/data/service_profile.dart'; /// ───────────────────────────────────────────────────────────────────────── /// THE DELIVERY QUEUE, AND THE LABEL THAT SURVIVES IT /// /// Two promises are made at a kitchen counter and both have to hold all the way /// to a doorstep: /// /// 1. **Five orders picked is five deliveries** — no order goes missing in the /// hand-off, and nothing appears that was not collected. /// 2. **A label recorded at the counter is the label at the door** — even /// after the first order is delivered and the list underneath it shrinks, /// and even after a second counter loads on top of it. /// /// The second is the subtle one, and it is why the label is *stored* at pickup /// rather than recomputed from whatever is on screen. Anything derived from a /// position silently renames every remaining order the moment one leaves the /// list, which sends the rider looking for something already in somebody's /// hallway. /// /// ── The derived numbering is gone, and these tests changed with it ── /// /// They used to assert `Bag 1 … Bag 5`, manufactured from each order's position /// when the payload printed nothing — the app numbering its own list and /// presenting the result as a physical fact. Only a label a counter actually /// printed is stored now, so the fixtures carry real codes and a counter that /// prints nothing stores nothing. The store's contract is unchanged and is /// still what is pinned here. /// ───────────────────────────────────────────────────────────────────────── void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() => SharedPreferences.setMockInitialValues({})); tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel)); /// [printed] is what the counter stuck on the order. Omitted, the counter /// printed nothing — which is the common route, and stores nothing. Map order(String id, String customer, {String? printed}) => { 'orderid': id, 'pickupcustomer': customer, 'kitchenname': 'Vidhya Kitchen', 'kitchenid': 'K1', 'quantity': 3, if (printed != null) 'baglabel': printed, }; final five = [ order('1', 'Joe', printed: 'DG-1001'), order('2', 'Arun', printed: 'DG-1002'), order('3', 'Priya', printed: 'DG-1003'), order('4', 'Kumar', printed: 'DG-1004'), order('5', 'Ravi', printed: 'DG-1005'), ]; group('the hand-off', () { test( 'five picked orders become five deliveries, each with its label', () async { ServiceProfile.setActive(ServiceProfile.meals); // What the pickup confirmation writes when the slide lands. final manifest = OrderManifest.forGroup(five); await addCollectedOrderIds([for (final l in manifest) l.orderId]); await saveOrderLabels({for (final l in manifest) l.orderId: l.label}); final carrying = await getCollectedOrderIds(); final labels = await getOrderLabels(); expect(carrying.length, 5, reason: 'five picked is five to deliver'); expect(labels.length, 5, reason: 'and five labels, one each'); expect(labels['1'], 'DG-1001'); expect(labels['5'], 'DG-1005'); // No aggregate anywhere: the count of deliveries and the count of // labels are the same set, not two numbers that can drift. expect(labels.keys.toSet(), carrying); }, ); test('an order the counter could not supply never becomes a delivery', () async { ServiceProfile.setActive(ServiceProfile.meals); // Four handed over, one short — the shortfall path the pickup sheet owns. final loaded = five.where((o) => o['orderid'] != '3').toList(); await addCollectedOrderIds([ for (final o in loaded) o['orderid'] as String, ]); await saveOrderLabels({ for (final l in OrderManifest.forGroup(loaded)) l.orderId: l.label, }); await addNotLoadedOrderIds(['3']); expect(await getCollectedOrderIds(), {'1', '2', '4', '5'}); expect(await getNotLoadedOrderIds(), {'3'}); // A short pick is its own record. It is not a skip: nobody was visited // and the customer did nothing. expect(await getSkippedOrderIds(), isEmpty); // And it carries no label, because it was never handed over. expect((await getOrderLabels()).containsKey('3'), isFalse); }); }); group('label identity outlives the queue', () { test('delivering the first does not disturb the rest', () async { ServiceProfile.setActive(ServiceProfile.meals); await saveOrderLabels({ for (final l in OrderManifest.forGroup(five)) l.orderId: l.label, }); // Joe is delivered and leaves the carrying set. await addCollectedOrderIds([ for (final o in five) o['orderid'] as String, ]); await removeCollectedOrderIds(['1']); final labels = await getOrderLabels(); expect(await getCollectedOrderIds(), {'2', '3', '4', '5'}); // Arun keeps the code the counter stuck on his order. Nothing is read // off a position, so a list that shrinks under him changes nothing. expect(labels['2'], 'DG-1002'); expect(labels['3'], 'DG-1003'); expect(labels['5'], 'DG-1005'); }); test('a second kitchen adds to the map rather than replacing it', () async { ServiceProfile.setActive(ServiceProfile.meals); await saveOrderLabels({ for (final l in OrderManifest.forGroup(five.take(2).toList())) l.orderId: l.label, }); // A different counter, printing its own codes. final second = [ order('9', 'Meena', printed: 'AM-2001'), order('10', 'Sai', printed: 'AM-2002'), ]; await saveOrderLabels({ for (final l in OrderManifest.forGroup(second)) l.orderId: l.label, }); final labels = await getOrderLabels(); expect(labels.length, 4, reason: 'the first load must survive the second'); expect(labels['1'], 'DG-1001'); expect(labels['9'], 'AM-2001'); expect(labels['10'], 'AM-2002'); }); test('a skipped delivery keeps its label so it can be resumed', () async { ServiceProfile.setActive(ServiceProfile.meals); await saveOrderLabels({ for (final l in OrderManifest.forGroup(five)) l.orderId: l.label, }); await addSkippedBooking(five[2], reason: 'Customer unavailable'); expect(await getSkippedOrderIds(), contains('3')); // And the reason survives with it — the hub needs to know why, and // "Failed" is not a reason. final skipped = await getSkippedBookings(); expect( skipped.firstWhere((b) => b['orderid'] == '3')['skipreason'], 'Customer unavailable', ); // He is still carrying Priya's lunch — the bag identity is exactly what // he needs when he comes back to it. expect((await getOrderLabels())['3'], 'DG-1003'); }); test("a day's run is wiped so bags never leak into tomorrow", () async { await saveOrderLabels({'1': 'Bag 1'}); await clearServiceRunState(); expect(await getOrderLabels(), isEmpty); }); }); test('a demo bag never outlives the build that wrote it', () async { // The mock day's ids are `MOCK-…` so the launch purge can find them. The // bag map is keyed by order id, so it is swept by the same rule — a stored // pairing is trusted later *because* it is stored, and a stale one is worse // than none. await saveOrderLabels({'MOCK-M-1001': 'Bag 1', '77': 'Bag 2'}); await purgeDemoRecords(); final bags = await getOrderLabels(); expect(bags.containsKey('MOCK-M-1001'), isFalse); expect(bags['77'], 'Bag 2', reason: 'real work is untouched'); }); }