import 'package:flutter_test/flutter_test.dart'; import 'package:miler/data/task_profile.dart'; /// One rider, mixed work, and every row deciding its own workflow. /// /// These tests exist because the app used to answer "what kind of work is /// this?" by looking at the **rider** — his tenant, resolved once at login into /// a single app-wide service line. That answer is the same for every row on the /// screen, so a customer pickup sitting beside a kitchen collection either took /// the kitchen's rules or disappeared. The rider is one person with a mixed /// queue; the queue decides. /// /// Every input below is what `GET /miler/bookings` actually sends. Map _row({ String? nextAction, String? pickupSourceType, String? stopType, }) => { if (nextAction != null) 'next_action': nextAction, if (pickupSourceType != null) 'pickup_source_type': pickupSourceType, if (stopType != null) 'stoptype': stopType, }; void main() { group('a row is classified from the server\'s own words', () { test('a customer door pickup is a customer pickup', () { final t = TaskProfile.of( _row(nextAction: 'pickup', pickupSourceType: 'customer'), ); expect(t.kind, WorkKind.customerPickup); expect(t.capturesShipmentDetails, isTrue); expect(t.groupsBySource, isFalse); }); test('a kitchen load is a source pickup, and groups', () { final t = TaskProfile.of( _row(nextAction: 'pickup', pickupSourceType: 'kitchen'), ); expect(t.kind, WorkKind.sourcePickup); expect(t.groupsBySource, isTrue); expect(t.capturesShipmentDetails, isFalse); }); test('a base collection is a source pickup too', () { expect( TaskProfile.of(_row(nextAction: 'pickup', pickupSourceType: 'hub')).kind, WorkKind.sourcePickup, ); }); test('carrying it to a receiver is a delivery', () { expect( TaskProfile.of(_row(nextAction: 'deliver')).kind, WorkKind.customerDelivery, ); expect( TaskProfile.of(_row(nextAction: 'start_delivery')).kind, WorkKind.customerDelivery, ); }); test('carrying it to a base is a handover', () { final t = TaskProfile.of(_row(nextAction: 'inward_at_hub')); expect(t.kind, WorkKind.hubHandover); expect(t.endsAtHub, isTrue); expect(t.deliversToCustomer, isFalse); }); test('terminal rows are done', () { expect(TaskProfile.of(_row(nextAction: 'none')).kind, WorkKind.done); expect( TaskProfile.of(_row(nextAction: 'handed_to_hub')).kind, WorkKind.done, ); }); test('collected with no stated leg is custody-without-destination', () { // NextLeg.unknown's case: it is in his bag and nobody has said where it // goes. Not a delivery (no destination to offer) and not done (he is // still holding it). final t = TaskProfile.of(_row(stopType: 'delivery')); expect(t.kind, WorkKind.unknown); expect(t.deliversToCustomer, isFalse); expect(t.endsAtHub, isFalse); }); test('an unheard-of source takes the safe shape, never a guess', () { final t = TaskProfile.of( _row(nextAction: 'pickup', pickupSourceType: 'something_new'), ); expect(t.kind, WorkKind.unknown); expect( t.capturesShipmentDetails, isFalse, reason: 'the door-capture flow is a claim this row has not earned', ); expect( t.groupsBySource, isFalse, reason: 'nor is a heading it can be filed under', ); }); test('a row with no type fields at all does not group', () { // Every fixture written before pickup_source_type shipped, and every // booking still in flight from then. This defaulted to sourcePickup once // and split a one-base logistics day into a group per customer -- // hub_pickup_group_test.dart is the test that caught it. final legacy = { 'orderid': '9', 'pickupid': '9', 'pickupaddress': 'RS Puram, Coimbatore', 'type': 'pickup', }; final t = TaskProfile.of(legacy); expect(t.kind, WorkKind.unknown); expect(t.groupsBySource, isFalse); expect(t.capturesShipmentDetails, isFalse); }); test('the leg outranks where it was collected from', () { // Picked up at a customer's door, now in the bag on its way to a base. // It is a handover, not a pickup — it stopped being a pickup the moment // it was collected, and only the leg knows that. final t = TaskProfile.of( _row(nextAction: 'inward_at_hub', pickupSourceType: 'customer'), ); expect(t.kind, WorkKind.hubHandover); }); }); group('surfaces', () { test('an unaccepted customer pickup waits on Home', () { final t = TaskProfile.of( _row(nextAction: 'pickup', pickupSourceType: 'customer'), ); expect(t.surfaceWhen(accepted: false), WorkSurface.home); }); test('accepting a customer pickup moves it to Bookings', () { final t = TaskProfile.of( _row(nextAction: 'pickup', pickupSourceType: 'customer'), ); expect(t.surfaceWhen(accepted: true), WorkSurface.bookings); }); test('a kitchen load stays on Home even once accepted', () { // Accepting a manifest puts nothing in his hands. It leaves Home when he // is actually carrying it, not when he agrees to fetch it. final t = TaskProfile.of( _row(nextAction: 'pickup', pickupSourceType: 'kitchen'), ); expect(t.surfaceWhen(accepted: true), WorkSurface.home); }); test('anything in custody is on Deliveries', () { for (final action in ['deliver', 'start_delivery', 'inward_at_hub']) { expect( TaskProfile.of(_row(nextAction: action)).surfaceWhen(accepted: true), WorkSurface.deliveries, reason: action, ); } expect( TaskProfile.of(_row(stopType: 'delivery')).surfaceWhen(accepted: true), WorkSurface.deliveries, ); }); test('finished work is on Activity', () { expect( TaskProfile.of(_row(nextAction: 'none')).surfaceWhen(accepted: true), WorkSurface.activity, ); }); }); group('the mixed shift', () { // The case the architecture exists for: one rider around RS Puram holding // ten meal parcels to collect at a Gandhipuram kitchen and five customer // pickups at five different doors, all at once. final queue = >[ for (var i = 0; i < 10; i++) _row(nextAction: 'pickup', pickupSourceType: 'kitchen'), for (var i = 0; i < 5; i++) _row(nextAction: 'pickup', pickupSourceType: 'customer'), ]; test('all fifteen survive classification', () { final kinds = queue.map((r) => TaskProfile.of(r).kind).toList(); expect(kinds.length, 15); expect(kinds.where((k) => k == WorkKind.sourcePickup).length, 10); expect(kinds.where((k) => k == WorkKind.customerPickup).length, 5); expect( kinds.contains(WorkKind.unknown), isFalse, reason: 'nothing falls through to the safe shape when the server spoke', ); }); test('accepting the five does not move the ten', () { final surfaces = queue .map((r) => TaskProfile.of(r).surfaceWhen(accepted: true)) .toList(); expect(surfaces.where((s) => s == WorkSurface.home).length, 10); expect(surfaces.where((s) => s == WorkSurface.bookings).length, 5); }); test('collected meal parcels coexist with a CX handover on Deliveries', () { final afterCollection = >[ for (var i = 0; i < 10; i++) _row(nextAction: 'deliver'), // The CX pickup, now completed, has become a base handover. _row(nextAction: 'inward_at_hub', pickupSourceType: 'customer'), ]; final surfaces = afterCollection .map((r) => TaskProfile.of(r).surfaceWhen(accepted: true)) .toSet(); expect(surfaces, {WorkSurface.deliveries}); }); }); group('a trip is described by what is actually in it', () { Map row({String? a, String? src}) => { if (a != null) 'next_action': a, if (src != null) 'pickup_source_type': src, }; test('a mixed trip both groups and ends at a base', () { final stops = [ row(a: 'pickup', src: 'kitchen'), row(a: 'pickup', src: 'customer'), row(a: 'inward_at_hub'), ]; expect(TripShape.groupsBySource(stops), isTrue); expect(TripShape.endsAtHub(stops), isTrue); expect(TripShape.hasCustomerPickup(stops), isTrue); }); test('one parcel bound for a base ends the whole day at that base', () { // `any`, not `every`. Telling a rider "END · HOME" while he is still // carrying something the network needs is the failure worth avoiding. final stops = [row(a: 'deliver'), row(a: 'inward_at_hub')]; expect(TripShape.endsAtHub(stops), isTrue); }); test('a pure meal round neither groups to a base nor returns to one', () { final stops = [row(a: 'deliver'), row(a: 'deliver')]; expect(TripShape.endsAtHub(stops), isFalse); expect(TripShape.hasCustomerPickup(stops), isFalse); }); }); }