import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:miler/Models/stop_status.dart'; import 'package:miler/data/accepted_store.dart'; import 'package:miler/data/service_profile.dart'; import 'package:miler/data/milk_run.dart'; import 'package:miler/data/work_domain.dart'; import 'package:miler/views/Dashboard/pickups/stop_type.dart'; /// ───────────────────────────────────────────────────────────────────────── /// ONE ORDER, ONE SCREEN /// /// ``` /// ASSIGNED ─ accept ─▶ ACCEPTED ─ go ─▶ ARRIVED ─ pick up ─▶ PICKED /// └────────────────── HOME owns all of this ──────────────┘ │ /// pickup-complete │ /// ▼ /// DELIVERIES owns from here on /// ``` /// /// [WorkBoundary] is the single function that decides which side a row is on. /// Deliveries asked it; **Home never did** — it assigned the raw fetch to its /// route list and let each card work itself out. So on a kitchen line, where /// `isWorkComplete` is `delivered || cancelled`, a *picked* stop stayed in /// Home's route while simultaneously sitting in Deliveries' queue: one bag, two /// screens, two different next actions. /// /// These hold the guarantee itself rather than either screen's filter — the /// two lists are provably disjoint and provably exhaustive, so no future filter /// can drift back apart from the other. /// ───────────────────────────────────────────────────────────────────────── void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() => SharedPreferences.setMockInitialValues({})); tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel)); Map stop(String id, String status) => { 'orderid': id, 'pickupid': int.parse(id), 'sourcename': 'Vidhya Kitchen', 'orderstatus': status, }; /// The whole day, in every state a row can reach. List> day() => [ stop('1001', 'assigned'), stop('1002', 'accepted'), stop('1003', 'arrived'), stop('1004', 'picked'), stop('1005', 'outfordelivery'), stop('1006', 'delivered'), stop('1007', 'cancelled'), stop('1008', 'skipped'), ]; group('the two lists never overlap', () { for (final profile in [ServiceProfile.milkMan, ServiceProfile.parcel]) { test('on ${profile.line.name}', () { ServiceProfile.setActive(profile); const accepted = {'1002', '1003'}; const collected = {'1004'}; final home = WorkBoundary.pickupQueue( day(), acceptedIds: accepted, collectedIds: collected, ).map((s) => s['orderid']).toSet(); final deliveries = WorkBoundary.deliveryQueue( day(), acceptedIds: accepted, collectedIds: collected, ).map((s) => s['orderid']).toSet(); expect( home.intersection(deliveries), isEmpty, reason: 'an order on both screens is one bag with two next actions', ); }); } }); group('the boundary is pickup-complete, never acceptance', () { setUp(() => ServiceProfile.setActive(ServiceProfile.milkMan)); test('an accepted booking is Home\'s, not delivery work', () { // The one rule the delivery screens hang off: accepting is a decision // about work still to be done, not evidence anything was collected. // Reading it as a hand-over opened a map, an I'VE ARRIVED and a // confirmation sheet for a pickup the rider had not made. expect( WorkBoundary.domainOf(stop('1002', 'accepted'), acceptedIds: {'1002'}), WorkDomain.pickup, ); }); test('an arrived booking is still Home\'s', () { expect( WorkBoundary.domainOf(stop('1003', 'arrived'), acceptedIds: {'1003'}), WorkDomain.pickup, ); }); test('the collected record moves it, and only after the call landed', () { // `addCollectedOrderIds` is written by Home *after* pickup-complete comes // back successful — never optimistically. expect( WorkBoundary.domainOf(stop('1004', 'accepted')), WorkDomain.pickup, ); expect( WorkBoundary.domainOf(stop('1004', 'accepted'), collectedIds: {'1004'}), WorkDomain.delivery, ); }); test('the backend status moves it on its own', () { for (final s in ['picked', 'outfordelivery']) { expect( WorkBoundary.domainOf(stop('1004', s)), WorkDomain.delivery, reason: '$s is past the counter', ); } }); }); group('closed work belongs to neither', () { test('a delivered milk-run stop leaves both screens', () { ServiceProfile.setActive(ServiceProfile.milkMan); expect( WorkBoundary.domainOf(stop('1006', 'delivered')), WorkDomain.closed, ); }); test('a picked parcel booking is closed; a picked milk run is not', () { // Collecting IS the job on logistics — the hub delivers it. On a round it // is the middle of the morning. ServiceProfile.setActive(ServiceProfile.parcel); expect(WorkBoundary.domainOf(stop('1004', 'picked')), WorkDomain.closed); ServiceProfile.setActive(ServiceProfile.milkMan); expect( WorkBoundary.domainOf(stop('1004', 'picked')), WorkDomain.delivery, ); }); }); _sourceNameContract(); group('the consignment id survives the pivot', () { test('it is filed under the order id the door reads', () async { // `updatePickedStatus` sent no `orderid`, so the mapper filed the // consignment under the *booking* id while `closeDelivery` looked it up // by **order** id. Every collected stop was stored under a key nothing // would ask for, and pressing Delivered answered "this order was never // picked up on the system" for a bag in the rider's hands. await rememberConsignmentId('DM-BK-16DA518D-6381', '55123'); final ids = await getConsignmentIds(); expect(ids['DM-BK-16DA518D-6381'], '55123'); }); test('an empty id is never stored, so nothing can be fabricated', () async { await rememberConsignmentId('DM-1', ''); await rememberConsignmentId('', '55124'); expect(await getConsignmentIds(), isEmpty); }); }); } /// ───────────────────────────────────────────────────────────────────────── /// ONE READER FOR A PLACE'S NAME /// /// `MilkRun.sourceNameOf` read `sourcename` / `kitchenname`; `stopSourceName` /// read those *and* the CamelCase `SourceName` / `KitchenName` the payload /// sometimes carries. A booking with only the capitalised key had a counter /// according to one and none according to the other — and the route card used /// both in one expression, so the group was not flat (there is a counter) but /// was titled "pickup" (there is no counter to name). /// /// On a device that is a foldable place headed by a leg description, holding /// one order named after the same stop — exactly the shape the `flat` flag /// exists to prevent. void _sourceNameContract() { group('a place has one name', () { for (final key in const [ 'sourcename', 'SourceName', 'kitchenname', 'KitchenName', ]) { test('$key is read by both readers', () { final stop = {'orderid': '1', key: 'Vidhya Kitchen'}; expect(MilkRun.sourceNameOf(stop), 'Vidhya Kitchen'); expect(stopSourceName(stop), 'Vidhya Kitchen'); expect(stopSourceName(stop), MilkRun.sourceNameOf(stop)); }); } test('no key means no counter, from both', () { const stop = {'orderid': '1'}; expect(MilkRun.sourceNameOf(stop), isEmpty); expect(stopSourceName(stop), isEmpty); }); }); }