273 lines
10 KiB
Dart
273 lines
10 KiB
Dart
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<String, dynamic> order(
|
|
int id, {
|
|
String? sourceId = 'K1',
|
|
String? sourceName = 'Vidhya Kitchen',
|
|
String address = kitchen,
|
|
String status = 'assigned',
|
|
}) => <String, dynamic>{
|
|
'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<RouteGroup> groupsFor(List<Map<String, dynamic>> 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 COLLECTION is a place, headed by its address', () {
|
|
// ── The parcel line draws places now, like the milk run ──
|
|
//
|
|
// Home used to give logistics a flat node per stop, titled with the
|
|
// customer. It is one shape on both lines now: the header names *where*
|
|
// the work is and the rail under it names *who* — asked for directly,
|
|
// against screenshots of the two screens side by side.
|
|
//
|
|
// Three collections at one address are one place holding three orders —
|
|
// which is the case the old shape got most wrong, drawing the same street
|
|
// three times.
|
|
ServiceProfile.setActive(ServiceProfile.parcel);
|
|
final groups = TripCard(
|
|
trip: Trip.fromStops(
|
|
id: 'T1',
|
|
stops: [
|
|
for (var i = 1; i <= 3; i++) {...order(i), 'type': 'pickup'},
|
|
],
|
|
),
|
|
).routeGroups();
|
|
|
|
expect(groups.length, 1);
|
|
expect(groups.single.flat, isFalse);
|
|
expect(groups.single.manifest.length, 3);
|
|
});
|
|
|
|
test('but its deliveries gather under the hub they were loaded at', () {
|
|
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, 1, reason: 'one shelf, one node');
|
|
expect(groups.single.flat, isFalse);
|
|
expect(groups.single.manifest.length, 3);
|
|
});
|
|
});
|
|
|
|
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 = <String, dynamic>{
|
|
'orderid': 'M-1',
|
|
'sourceid': 'K1',
|
|
'pickupaddress': kitchen,
|
|
};
|
|
final neighbour = <String, dynamic>{
|
|
'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 = <String, dynamic>{
|
|
'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 = <String, dynamic>{
|
|
'orderid': 'M-9',
|
|
'pickuplocationid': 'L7',
|
|
'pickupaddress': kitchen,
|
|
};
|
|
expect(MilkRun.sourceKeyOf(byLocationId), 'id:L7');
|
|
});
|
|
});
|
|
}
|