import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'package:miler/data/milk_run.dart'; import 'package:miler/data/service_profile.dart'; import 'package:miler/views/Dashboard/home/route_timeline.dart'; import 'package:miler/views/Dashboard/home/trip.dart'; import 'package:miler/views/Dashboard/home/trip_card.dart'; /// ───────────────────────────────────────────────────────────────────────── /// EIGHT ORDERS OFF ONE COUNTER MAKE ONE DROPDOWN OF EIGHT /// /// ── What the rider saw ── /// /// The hub assigned eight orders from one kitchen. Home drew a dropdown holding /// **seven**, and the eighth sat above it as a bare header titled with the /// customer's name — reading as a second place he had to ride to, off the same /// shelf as the other seven. /// /// ── Two causes, one level apart ── /// /// **The grouping key.** `routeGroups` built its own — `stopSourceId`, falling /// back to `stopSourceName` — while [MilkRun.sourceKeyOf] is the function that /// exists to answer *which counter is this*, and is what the bulk collect keys /// on. They disagreed: `sourceKeyOf` reads `pickuplocationid` and the card's /// own expression did not. A row identified only by its location id was one /// place according to the collect and another according to the card offering /// it. And a row carrying no source identity at all got its own bucket, with /// nothing consulted about *where it is collected from* — which is on every /// row, as the pickup address. /// /// **The `flat` flag.** A lone stop was drawn as a bare header — the header /// *is* the stop — whenever the payload carried no source name. A missing name /// is a gap in the data, not evidence that there is no counter, so the one /// order that lost its kitchen name was demoted from a place to a row. /// /// Both now answer from the same fact: a place is identified by its id, then /// its name, then **where it is**. /// ───────────────────────────────────────────────────────────────────────── void main() { tearDown(() { Get.reset(); ServiceProfile.setActive(ServiceProfile.parcel); }); const kitchen = '9 Mettupalayam Road, RS Puram, Coimbatore 641002'; /// One order off the kitchen. [sourceId] and [sourceName] are omitted on the /// row that lost them, which is the case the whole file is about. Map order( int id, { String? sourceId = 'K1', String? sourceName = 'Vidhya Kitchen', String address = kitchen, String status = 'assigned', }) => { 'orderid': 'M-$id', 'pickupid': id, 'step': id, if (sourceId != null) 'sourceid': sourceId, if (sourceName != null) 'kitchenname': sourceName, 'pickupcustomer': 'Customer $id', 'pickupaddress': address, 'dropaddress': 'Door $id, Peelamedu, Coimbatore', 'orderstatus': status, 'type': 'delivery', 'pickuplat': 11.01, 'pickuplon': 76.95, }; List groupsFor(List> stops) { ServiceProfile.setActive(ServiceProfile.milkMan); return TripCard(trip: Trip.fromStops(id: 'T1', stops: stops)).routeGroups(); } group('the reported case: eight assigned, seven in the list', () { test('the odd row out joins its siblings', () { // Seven carry the kitchen's id; the eighth lost both id and name and has // only the address it shares with them. final stops = [ for (var i = 1; i <= 7; i++) order(i), order(8, sourceId: null, sourceName: null), ]; final groups = groupsFor(stops); expect(groups.length, 1, reason: 'one counter, one dropdown'); expect(groups.single.stops.length, 8); expect(groups.single.flat, isFalse); }); test('the count the rider reads says eight', () { final stops = [ for (var i = 1; i <= 7; i++) order(i), order(8, sourceId: null, sourceName: null), ]; final group = groupsFor(stops).single; // The load line and the list under it are the same eight. expect(group.countLabel, '8 orders'); expect(group.manifest.length, 8); expect(group.pendingCount, 8); }); test('a row from a genuinely different place still splits', () { // The fix must not merge two counters. Grouping by address is the weakest // true statement available, not a licence to collapse the route. final stops = [ for (var i = 1; i <= 7; i++) order(i), order( 8, sourceId: null, sourceName: null, address: '12 SNS Colony, Peelamedu, Coimbatore 641004', ), ]; expect(groupsFor(stops).length, 2); }); }); group('the heading names the place, never an order', () { test('a counter with no name is headed by its address', () { final stops = [ for (var i = 1; i <= 3; i++) order(i, sourceId: null, sourceName: null), ]; final group = groupsFor(stops).single; // It used to fall through to `MilkRun.navigationLabel`, which answers // with the customer — so the heading read as one more order. expect(group.name, isNot(contains('Customer'))); expect(group.name, contains('Mettupalayam')); }); test('a named counter still leads with its name, address beneath', () { final group = groupsFor([for (var i = 1; i <= 3; i++) order(i)]).single; expect(group.name, 'Vidhya Kitchen'); expect(group.location, contains('Mettupalayam')); }); test('the address is not printed twice when it is the heading', () { final stops = [ for (var i = 1; i <= 3; i++) order(i, sourceId: null, sourceName: null), ]; final group = groupsFor(stops).single; expect(group.name, isNotEmpty); expect( group.location, isEmpty, reason: 'the heading already says where it is', ); }); }); group('a lone stop is a place when it has one', () { test('one order off an unnamed counter still opens as a list', () { // The same demotion, one level down: a single order whose row lost its // kitchen name was drawn as a bare customer header rather than as a place // holding one order. final group = groupsFor([ order(1, sourceId: null, sourceName: null), ]).single; expect(group.flat, isFalse, reason: 'it has an address to be a place by'); expect(group.name, contains('Mettupalayam')); expect(group.manifest.length, 1); }); test('with nothing at all to name a place by, it is the stop', () { // Unchanged, and deliberately: no counter, no name, no address means // there is no place above the order worth drawing, so the header is the // stop and opens its detail sheet. final group = groupsFor([ order(1, sourceId: null, sourceName: null, address: ''), ]).single; expect(group.flat, isTrue); }); test('a parcel route is always flat, whatever the address says', () { // Every stop is its own group there — the rider really does ride to each // customer for one consignment, and a header plus a single child would // print the same name twice. ServiceProfile.setActive(ServiceProfile.parcel); final groups = TripCard( trip: Trip.fromStops( id: 'T1', stops: [for (var i = 1; i <= 3; i++) order(i)], ), ).routeGroups(); expect(groups.length, 3); expect(groups.every((g) => g.flat), isTrue); }); }); group('adopting a counter, and the limits on it', () { test('an identified stop never consults the day', () { // Adoption only ever fills a gap. A stop carrying its own id keeps it, // so two counters that happen to share a street cannot merge and a row // cannot be pulled off the id the hub gave it. final mine = { 'orderid': 'M-1', 'sourceid': 'K1', 'pickupaddress': kitchen, }; final neighbour = { 'orderid': 'M-2', 'sourceid': 'K2', 'pickupaddress': kitchen, }; expect(MilkRun.sourceKeyOf(mine, within: [mine, neighbour]), 'id:K1'); expect(MilkRun.sourceKeyOf(neighbour, within: [mine, neighbour]), 'id:K2'); }); test('with no day to consult it falls back to the address', () { final orphan = { 'orderid': 'M-3', 'pickupaddress': kitchen, }; expect(MilkRun.sourceKeyOf(orphan), startsWith('at:')); }); test('the bulk collect gathers the adopted order too', () { // The collect must not leave behind the order the card has just learned // to group in — that is the two-readers failure in a new place. final day = [ for (var i = 1; i <= 7; i++) order(i), order(8, sourceId: null, sourceName: null), ]; expect(MilkRun.sameSourceAs(day.first, day).length, 8); expect(MilkRun.sameSourceAs(day.last, day).length, 8); }); }); group('one reader for which counter this is', () { test('the card groups on the same key the bulk collect does', () { // The two disagreed, so a kitchen could be split on screen while the // collect still treated it as one operation. final byLocationId = { 'orderid': 'M-9', 'pickuplocationid': 'L7', 'pickupaddress': kitchen, }; expect(MilkRun.sourceKeyOf(byLocationId), 'id:L7'); }); }); }