Design system - MilerSurface ladder (canvas → working → raised → floating) with MilerPanel as layer 1; canvas moved to #DEE3EA so white separates at 1.290:1. - Visible vocabulary applied across Home, Deliveries, Activity, Account and the sheets: hero heads (tabular numeral + small caption, clamped at 1.3x), canvas wells for anything that opens, small filled tags for shelf labels, demoted placeholders. Recorded in DESIGN_SYSTEM.md §6. - One icon family: 222 Material glyphs migrated to Lucide; none left outside lib/xpress. - Colour semantics corrected: amber only for what is genuinely owed, brand red reserved for the live stop, disabled primaries go neutral rather than pale. Data and lifecycle - lib/data/lifecycle.dart reads mutations for what they prove; route_order.dart makes admin sequence the single ordering authority; service_day.dart, and stop_area.dart rewritten against live Coimbatore addresses (digit-token stripping, city stoplist, street suffixes, stammer collapse). - countLabel states the load once, in bags. Testing - 1440 tests passing; golden shot harnesses for Home, Deliveries, Activity, sheets and verify, with test/failures/ now gitignored (diff debris). - New pins: home_gutter_test, stop_area_test, plus updated structural bounds. Note: this commit also carries pre-existing working-tree deletions that were present before this work (API_SPEC.md, README.md, demo test fixtures). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
595 lines
22 KiB
Dart
595 lines
22 KiB
Dart
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/api_config.dart';
|
||
import 'package:miler/data/milk_run.dart';
|
||
import 'package:miler/data/service_profile.dart';
|
||
import 'package:miler/views/Dashboard/pickups/stop_type.dart';
|
||
|
||
/// ─────────────────────────────────────────────────────────────────────────
|
||
/// THE MILK RUN, END TO END
|
||
///
|
||
/// ```
|
||
/// 6 orders ─ accept ─┬─ Kitchen 1 ×5 ─ arrived ─ picked ─▶ DELIVERIES
|
||
/// └─ Kitchen 2 ×5 ─ arrived ─ picked ─▶ DELIVERIES
|
||
/// │
|
||
/// arrived ─ delivered ─▶ ACTIVITY
|
||
/// ```
|
||
///
|
||
/// Three units, three different shapes, and the file exists because they were
|
||
/// being conflated:
|
||
///
|
||
/// • **Acceptance is per order** and crosses kitchens freely.
|
||
/// • **Pickup is per kitchen** — one ride, one counter, one handover — so a
|
||
/// selection must never span two of them.
|
||
/// • **Delivery is per order** again, each bag to its own door.
|
||
///
|
||
/// And the lifecycle rule underneath all of it: `picked` is the *middle* of a
|
||
/// milk-run morning. An order collected at a kitchen is a live delivery, not
|
||
/// history — it goes to DELIVERIES, and only `delivered` sends it to Activity.
|
||
/// ─────────────────────────────────────────────────────────────────────────
|
||
void main() {
|
||
TestWidgetsFlutterBinding.ensureInitialized();
|
||
|
||
setUp(() {
|
||
SharedPreferences.setMockInitialValues({});
|
||
ServiceProfile.setActive(ServiceProfile.milkMan);
|
||
});
|
||
tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
|
||
|
||
/// One assigned stop, in the shape the adapter produces.
|
||
Map<String, dynamic> stop({
|
||
required String id,
|
||
required String kitchen,
|
||
String status = 'assigned',
|
||
String? consignmentId,
|
||
}) => {
|
||
'orderid': id,
|
||
'pickupid': int.parse(id.replaceAll(RegExp(r'\D'), '')),
|
||
'orderstatus': status,
|
||
'sourceid': kitchen.hashCode.toString(),
|
||
'sourcename': kitchen,
|
||
'pickupcustomer': 'Customer $id',
|
||
'pickuplat': 11.01,
|
||
'pickuplon': 76.95,
|
||
if (consignmentId != null) 'consignmentid': consignmentId,
|
||
};
|
||
|
||
/// Two kitchens, five orders each — the brief's own example.
|
||
List<Map<String, dynamic>> tenOrders() => [
|
||
for (var i = 1; i <= 5; i++) stop(id: '100$i', kitchen: 'Kitchen 1'),
|
||
for (var i = 6; i <= 10; i++) stop(id: '10$i', kitchen: 'Kitchen 2'),
|
||
];
|
||
|
||
Set<String> idsOf(Iterable<Map<String, dynamic>> stops) =>
|
||
stops.map((s) => s['orderid'] as String).toSet();
|
||
|
||
group('pickup is bounded by the kitchen', () {
|
||
// ── The three units ──
|
||
//
|
||
// Acceptance is per order and crosses kitchens freely. Pickup does not: a
|
||
// selection spanning two counters would post "arrived" for a shop the rider
|
||
// is nowhere near and mark bags collected that are still in another
|
||
// kitchen's warmer. Delivery is per order again.
|
||
|
||
test('orders from one kitchen group together', () {
|
||
final stops = tenOrders();
|
||
final vidhya = MilkRun.sameSourceAs(stops.first, stops);
|
||
expect(vidhya.length, 5);
|
||
expect(vidhya.every((s) => s['sourcename'] == 'Kitchen 1'), isTrue);
|
||
});
|
||
|
||
test('and orders from another kitchen are not swept in', () {
|
||
final stops = tenOrders();
|
||
final vidhya = MilkRun.sameSourceAs(stops.first, stops);
|
||
expect(
|
||
vidhya.any((s) => s['sourcename'] == 'Kitchen 2'),
|
||
isFalse,
|
||
reason: 'Kitchen 2 is a different ride and a different counter',
|
||
);
|
||
expect(MilkRun.sameSource(stops.first, stops.last), isFalse);
|
||
});
|
||
|
||
test('the stable id decides, not the display name', () {
|
||
// Two kitchens can share a name across areas; the id is what the hub
|
||
// controls, so it is what the app compares.
|
||
final a = {'orderid': '1', 'sourceid': 'K1', 'sourcename': 'Vidhya'};
|
||
final b = {'orderid': '2', 'sourceid': 'K2', 'sourcename': 'Vidhya'};
|
||
expect(MilkRun.sameSource(a, b), isFalse);
|
||
});
|
||
|
||
test('a name-only payload still groups', () {
|
||
final a = {'orderid': '1', 'sourcename': 'Vidhya Kitchen'};
|
||
final b = {'orderid': '2', 'sourcename': 'vidhya kitchen'};
|
||
expect(MilkRun.sameSource(a, b), isTrue);
|
||
});
|
||
|
||
test(
|
||
'no source at all is one honest bucket, never a fabricated kitchen',
|
||
() {
|
||
final a = {'orderid': '1'};
|
||
final b = {'orderid': '2'};
|
||
expect(MilkRun.sourceKeyOf(a), 'none');
|
||
expect(MilkRun.sameSource(a, b), isTrue);
|
||
expect(MilkRun.sourceNameOf(a), '');
|
||
},
|
||
);
|
||
|
||
test('a zero pickuplocationid is not an id', () {
|
||
// The adapter emits '0' for an absent location; treating it as a real
|
||
// key would put every unsourced order in one bogus group.
|
||
expect(MilkRun.sourceKeyOf({'pickuplocationid': '0'}), 'none');
|
||
expect(MilkRun.sourceKeyOf({'pickuplocationid': '7'}), 'id:7');
|
||
});
|
||
|
||
test('collected work is NOT held back for the other kitchens', () {
|
||
// The rule §24 turns on: three Kitchen-1 bags collected at 11:40 are
|
||
// three live deliveries, even though Kitchen 2 has not been visited.
|
||
final stops = tenOrders();
|
||
final k1 = idsOf(stops.take(5));
|
||
for (final s in stops.take(5)) {
|
||
expect(
|
||
k1.contains(MilkRun.idOf(s)),
|
||
isTrue,
|
||
reason: 'on the deliveries tab the moment it is in his hands',
|
||
);
|
||
}
|
||
// And the rest are still his to collect.
|
||
final outstanding = MilkRun.outstandingPickups(
|
||
stops,
|
||
acceptedIds: idsOf(stops),
|
||
collectedIds: k1,
|
||
);
|
||
expect(outstanding.length, 5);
|
||
expect(outstanding.every((s) => s['sourcename'] == 'Kitchen 2'), isTrue);
|
||
});
|
||
});
|
||
|
||
group('where Navigate goes', () {
|
||
test('to the kitchen before collection', () {
|
||
final s = stop(id: '1001', kitchen: 'Kitchen 1');
|
||
expect(MilkRun.navigatesToCustomer(s), isFalse);
|
||
final target = MilkRun.navigationTarget(s);
|
||
expect(target?.lat, 11.01);
|
||
expect(MilkRun.navigationLabel(s), 'Kitchen 1');
|
||
});
|
||
|
||
test(
|
||
'a nameless pickup is titled by its neighbourhood, never "pickup"',
|
||
() {
|
||
// The live backend sends no sourcename today, and the literal 'pickup'
|
||
// this used to fall back to became the 24sp headline of the rider's
|
||
// first group — the exact regression the project rule forbids.
|
||
final s = {
|
||
'orderid': '1001',
|
||
'type': 'pickup',
|
||
'pickupaddress': '12/4, RS Puram, Coimbatore, Tamil Nadu 641002',
|
||
};
|
||
expect(
|
||
MilkRun.navigationLabel(s),
|
||
'RS Puram',
|
||
reason:
|
||
'the first non-numeric address component is a place; '
|
||
'"pickup" is a leg description',
|
||
);
|
||
|
||
// A component carrying its own house number is titled by the street.
|
||
expect(
|
||
MilkRun.navigationLabel({
|
||
'orderid': '1002',
|
||
'type': 'pickup',
|
||
'pickupaddress': '124 Gandhipuram Main Road, Coimbatore 641012',
|
||
}),
|
||
'Gandhipuram Main Road',
|
||
);
|
||
|
||
// No address either → a capitalised noun, still never the raw leg word.
|
||
expect(
|
||
MilkRun.navigationLabel({'orderid': '1', 'type': 'pickup'}),
|
||
'Pickup',
|
||
);
|
||
},
|
||
);
|
||
|
||
test('to the customer after collection', () {
|
||
final s = stop(id: '1001', kitchen: 'Kitchen 1')
|
||
..['droplat'] = 11.05
|
||
..['droplon'] = 76.99;
|
||
expect(MilkRun.navigatesToCustomer(s, collectedIds: {'1001'}), isTrue);
|
||
final target = MilkRun.navigationTarget(s, collectedIds: {'1001'});
|
||
expect(target?.lat, 11.05);
|
||
expect(target?.lng, 76.99);
|
||
expect(
|
||
MilkRun.navigationLabel(s, collectedIds: {'1001'}),
|
||
'Customer 1001',
|
||
);
|
||
});
|
||
|
||
test('the backend status alone flips it, without the local set', () {
|
||
final s = stop(id: '1001', kitchen: 'Kitchen 1', status: 'picked')
|
||
..['droplat'] = 11.05
|
||
..['droplon'] = 76.99;
|
||
expect(MilkRun.navigatesToCustomer(s), isTrue);
|
||
expect(MilkRun.navigationTarget(s)?.lat, 11.05);
|
||
});
|
||
|
||
test('null rather than the wrong end when coordinates are missing', () {
|
||
// A Navigate button that quietly opens the other end of the journey is
|
||
// worse than a disabled one: the rider only finds out on arrival.
|
||
final s = stop(id: '1001', kitchen: 'Kitchen 1', status: 'picked');
|
||
expect(MilkRun.navigatesToCustomer(s), isTrue);
|
||
expect(MilkRun.navigationTarget(s), isNull);
|
||
});
|
||
|
||
test('logistics never navigates to a customer', () {
|
||
ServiceProfile.setActive(ServiceProfile.parcel);
|
||
final s = stop(id: '2001', kitchen: '', status: 'picked')
|
||
..['droplat'] = 11.05
|
||
..['droplon'] = 76.99;
|
||
expect(
|
||
MilkRun.navigatesToCustomer(s),
|
||
isFalse,
|
||
reason: 'its collected parcel goes to a hub; somebody else delivers it',
|
||
);
|
||
expect(MilkRun.navigationTarget(s)?.lat, 11.01);
|
||
});
|
||
});
|
||
|
||
group('the ladder a stop climbs', () {
|
||
test('accepted → arrived → picked → out for delivery → delivered', () {
|
||
final s = stop(id: '1001', kitchen: 'Kitchen 1');
|
||
|
||
expect(
|
||
MilkRun.stageOf(s, acceptedIds: const {}, collectedIds: const {}),
|
||
StopStatus.assigned,
|
||
);
|
||
expect(
|
||
MilkRun.stageOf(s, acceptedIds: {'1001'}, collectedIds: const {}),
|
||
StopStatus.accepted,
|
||
);
|
||
|
||
s['orderstatus'] = 'arrived';
|
||
expect(
|
||
MilkRun.stageOf(s, acceptedIds: {'1001'}, collectedIds: const {}),
|
||
StopStatus.arrived,
|
||
);
|
||
|
||
expect(
|
||
MilkRun.stageOf(s, acceptedIds: {'1001'}, collectedIds: {'1001'}),
|
||
StopStatus.picked,
|
||
);
|
||
expect(
|
||
MilkRun.stageOf(
|
||
s,
|
||
acceptedIds: {'1001'},
|
||
collectedIds: {'1001'},
|
||
outForDeliveryIds: {'1001'},
|
||
),
|
||
StopStatus.outForDelivery,
|
||
);
|
||
expect(
|
||
MilkRun.stageOf(
|
||
s,
|
||
acceptedIds: {'1001'},
|
||
collectedIds: {'1001'},
|
||
outForDeliveryIds: {'1001'},
|
||
deliveredIds: {'1001'},
|
||
),
|
||
StopStatus.delivered,
|
||
);
|
||
});
|
||
|
||
test('picked up is never mistaken for delivered', () {
|
||
// The crate is in his hands at 11:50 and the last box goes through a door
|
||
// at 13:20. Reporting the first as the second bills the client for ninety
|
||
// minutes of work that has not happened.
|
||
expect(StopStatus.picked.isDelivered, isFalse);
|
||
expect(StopStatus.picked.isTerminal, isTrue);
|
||
expect(StopStatus.delivered.isDelivered, isTrue);
|
||
expect(StopStatus.outForDelivery.isDeliveryLeg, isTrue);
|
||
expect(StopStatus.picked.isDeliveryLeg, isFalse);
|
||
});
|
||
|
||
test(
|
||
'the pickup arrival and the delivery arrival are different states',
|
||
() {
|
||
expect(stopStatusFromRaw('arrived'), StopStatus.arrived);
|
||
expect(
|
||
stopStatusFromRaw('delivery_arrived'),
|
||
StopStatus.deliveryArrived,
|
||
);
|
||
expect(
|
||
StopStatus.arrived,
|
||
isNot(StopStatus.deliveryArrived),
|
||
reason: 'one is a kitchen counter, the other a customer door',
|
||
);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'the backend spelling of out-for-delivery folds to the same state',
|
||
() {
|
||
for (final raw in [
|
||
'Out_for_Delivery',
|
||
'out for delivery',
|
||
'outfordelivery',
|
||
]) {
|
||
expect(
|
||
stopStatusFromRaw(raw),
|
||
StopStatus.outForDelivery,
|
||
reason: raw,
|
||
);
|
||
}
|
||
},
|
||
);
|
||
|
||
test('each rung offers the button that belongs to it', () {
|
||
expect(MilkRun.nextActionLabel(StopStatus.accepted), 'Arrived');
|
||
expect(MilkRun.nextActionLabel(StopStatus.arrived), 'Picked up');
|
||
expect(MilkRun.nextActionLabel(StopStatus.outForDelivery), 'Arrived');
|
||
expect(MilkRun.nextActionLabel(StopStatus.deliveryArrived), 'Delivered');
|
||
expect(MilkRun.nextActionLabel(StopStatus.delivered), isNull);
|
||
});
|
||
});
|
||
|
||
group('a collected order is on DELIVERIES, never Activity', () {
|
||
// ── The bug this group exists for ──
|
||
//
|
||
// `picked` at a kitchen was being filed as completed work, so the order
|
||
// left the deliveries list and appeared in the rider's history before he
|
||
// had left the counter. Activity's filter and the deliveries list read the
|
||
// same test from opposite sides, so it broke both at once.
|
||
|
||
test('picked is not finished work on a milk run', () {
|
||
ServiceProfile.setActive(ServiceProfile.milkMan);
|
||
expect(StopStatus.picked.isWorkComplete, isFalse);
|
||
expect(StopStatus.delivered.isWorkComplete, isTrue);
|
||
expect(StopStatus.cancelled.isWorkComplete, isTrue);
|
||
});
|
||
|
||
test('picked IS finished work on logistics', () {
|
||
ServiceProfile.setActive(ServiceProfile.parcel);
|
||
expect(
|
||
StopStatus.picked.isWorkComplete,
|
||
isTrue,
|
||
reason: 'collecting the parcel is the job; the hub takes it from there',
|
||
);
|
||
});
|
||
|
||
test('a picked milk-run stop is still live work', () {
|
||
ServiceProfile.setActive(ServiceProfile.milkMan);
|
||
final s = stop(id: '1001', kitchen: 'Kitchen 1', status: 'picked');
|
||
expect(stopStatusOf(s).isWorkComplete, isFalse);
|
||
expect(stopStatusOf(s).isPicked, isTrue);
|
||
});
|
||
});
|
||
|
||
group('the round, on the device', () {
|
||
test('releasing marks the load and finishing clears it', () async {
|
||
expect(await getOutForDeliveryOrderIds(), isEmpty);
|
||
|
||
await addOutForDeliveryOrderIds(['1001', '1002', '1003']);
|
||
expect(await getOutForDeliveryOrderIds(), {'1001', '1002', '1003'});
|
||
|
||
await removeOutForDeliveryOrderIds(['1002']);
|
||
expect(await getOutForDeliveryOrderIds(), {'1001', '1003'});
|
||
});
|
||
|
||
test('a finished day leaves nothing behind for tomorrow', () async {
|
||
await addCollectedOrderIds(['1001']);
|
||
await addOutForDeliveryOrderIds(['1001']);
|
||
await addNotLoadedOrderIds(['1002']);
|
||
|
||
await clearServiceRunState();
|
||
|
||
expect(await getCollectedOrderIds(), isEmpty);
|
||
expect(await getOutForDeliveryOrderIds(), isEmpty);
|
||
expect(await getNotLoadedOrderIds(), isEmpty);
|
||
});
|
||
|
||
test('a milk-run stop is filed as delivered, not picked', () async {
|
||
await addCompletedBookings([
|
||
{'orderid': '1001', 'pickupcustomer': 'A'},
|
||
]);
|
||
final done = await getCompletedBookings();
|
||
expect(done.single['orderstatus'], 'delivered');
|
||
expect(stopStatusOf(done.single), StopStatus.delivered);
|
||
});
|
||
|
||
test('a logistics stop is filed as picked', () async {
|
||
ServiceProfile.setActive(ServiceProfile.parcel);
|
||
await addCompletedBookings([
|
||
{'orderid': '2001', 'pickupcustomer': 'B'},
|
||
]);
|
||
final done = await getCompletedBookings();
|
||
expect(done.single['orderstatus'], 'picked');
|
||
});
|
||
});
|
||
|
||
group('the consignment id survives the trip from the API', () {
|
||
test('the adapter carries source, pincodes and consignment through', () {
|
||
final stop = ApiConfig.pickupFromBooking({
|
||
'bookingid': 42,
|
||
'bookingreference': 'DM-42',
|
||
'status': 'Converted_To_Consignment',
|
||
'pickupaddress': 'Kitchen 1, RS Puram',
|
||
'pickuppincode': '641002',
|
||
'deliveryaddress': '12 Cross Cut Road',
|
||
'deliverypincode': '641012',
|
||
'sourceid': '7',
|
||
'sourcename': 'Kitchen 1',
|
||
'consignmentid': 900,
|
||
});
|
||
|
||
// These four were the ones being dropped: without them every milk-run
|
||
// stop grouped into one nameless pile and no drop could be closed.
|
||
expect(stop['sourceid'], '7');
|
||
expect(stop['sourcename'], 'Kitchen 1');
|
||
expect(stop['consignmentid'], '900');
|
||
expect(stop['pickuppincode'], '641002');
|
||
expect(stop['droppincode'], '641012');
|
||
});
|
||
|
||
test('a booking with no source groups as no source, not as a guess', () {
|
||
final stop = ApiConfig.pickupFromBooking({
|
||
'bookingid': 43,
|
||
'bookingreference': 'DM-43',
|
||
'pickupaddress': '9 Mettupalayam Road',
|
||
});
|
||
expect(stop['sourcename'], '');
|
||
expect(stop['sourceid'], '');
|
||
expect(stop['consignmentid'], '');
|
||
});
|
||
|
||
test('MilkRun reads the consignment the adapter wrote', () {
|
||
expect(MilkRun.consignmentIdOf({'consignmentid': '900'}), '900');
|
||
expect(MilkRun.consignmentIdOf({}), '');
|
||
});
|
||
});
|
||
|
||
// ── The label follows the leg, not the payload ──
|
||
//
|
||
// `ApiConfig.pickupFromBooking` stamps `type: pickup` on every row it builds,
|
||
// because a v1 booking *is* a first-mile pickup and the backend has no
|
||
// per-stop type to send. On a milk run that made a bag already in the rider's
|
||
// box describe itself as a pickup: the delivery card offered **Start Pickup**,
|
||
// the confirmation sheet asked him to **Confirm pickup**, and the write path
|
||
// behind that button took the pickup branch — he was being asked to collect
|
||
// something he was carrying.
|
||
// ── Which screen a rung is worked on ──
|
||
//
|
||
// The defect: after accepting on Home, the Deliveries tab offered a live
|
||
// strip for the stop. Tapping it opened the single-stop map, then I'VE
|
||
// ARRIVED, then a confirmation sheet — a second, contradictory route up a
|
||
// ladder whose rungs are supposed to be one bulk slide on Home, at the
|
||
// counter, for the whole stack of bags at once. It also posted the booking
|
||
// ACTIVE from the tab that is meant to be the rider's load.
|
||
group('worksOnOwnScreen', () {
|
||
Map<String, dynamic> booking(String status) => {
|
||
'orderid': 'B1',
|
||
'type': 'pickup',
|
||
'orderstatus': status,
|
||
'kitchenname': 'Vidhya Kitchen',
|
||
};
|
||
|
||
test('a kitchen collection is worked on Home, not on its own screen', () {
|
||
ServiceProfile.setActive(ServiceProfile.milkMan);
|
||
for (final status in ['assigned', 'accepted', 'arrived']) {
|
||
expect(
|
||
MilkRun.worksOnOwnScreen(booking(status)),
|
||
isFalse,
|
||
reason: '$status is a rung of the bulk ladder on Home',
|
||
);
|
||
}
|
||
});
|
||
|
||
test('the delivery leg is worked on its own screen', () {
|
||
ServiceProfile.setActive(ServiceProfile.milkMan);
|
||
// Both ways a bag is known to be in his hands.
|
||
expect(MilkRun.worksOnOwnScreen(booking('picked')), isTrue);
|
||
expect(
|
||
MilkRun.worksOnOwnScreen(booking('accepted'), collectedIds: {'B1'}),
|
||
isTrue,
|
||
);
|
||
});
|
||
|
||
test('every logistics stop is worked on its own screen', () {
|
||
// Nothing is batched on that line: one customer, one parcel, one
|
||
// shipment raised at the door. The rule must not leak across.
|
||
ServiceProfile.setActive(ServiceProfile.parcel);
|
||
for (final status in ['assigned', 'accepted', 'arrived', 'picked']) {
|
||
expect(
|
||
MilkRun.worksOnOwnScreen(booking(status)),
|
||
isTrue,
|
||
reason: status,
|
||
);
|
||
}
|
||
});
|
||
});
|
||
|
||
group('workingKind', () {
|
||
Map<String, dynamic> booking(String status) => {
|
||
'orderid': 'B1',
|
||
'pickupcustomer': 'Joe',
|
||
// Exactly what the adapter writes.
|
||
'type': 'pickup',
|
||
'orderstatus': status,
|
||
'kitchenname': 'Vidhya Kitchen',
|
||
};
|
||
|
||
test(
|
||
'a collected milk-run order is a delivery, whatever the payload says',
|
||
() {
|
||
ServiceProfile.setActive(ServiceProfile.meals);
|
||
final stop = booking('picked');
|
||
|
||
expect(
|
||
stopKindOf(stop),
|
||
StopKind.pickup,
|
||
reason: 'what the payload says',
|
||
);
|
||
expect(
|
||
MilkRun.workingKind(stop),
|
||
StopKind.delivery,
|
||
reason: 'what the rider is actually doing',
|
||
);
|
||
expect(MilkRun.workingKind(stop).startCtaLabel, 'Start Delivery');
|
||
expect(MilkRun.workingKind(stop).completeLabel, 'Delivered');
|
||
},
|
||
);
|
||
|
||
test('the collected set is what the delivery screen actually has', () {
|
||
// ── The status is not the record on that screen ──
|
||
//
|
||
// A stop on the work tab comes out of the *accepted* store, which
|
||
// normalises every row it holds to `accepted`. What records the hand-over
|
||
// is the collected set — so asking the leg without it reads a bag already
|
||
// in the rider's box as still needing collection, which is exactly how
|
||
// the delivery card came to offer **Start Pickup**.
|
||
ServiceProfile.setActive(ServiceProfile.meals);
|
||
final onTheTab = booking('accepted');
|
||
|
||
expect(MilkRun.workingKind(onTheTab), StopKind.pickup);
|
||
expect(
|
||
MilkRun.workingKind(onTheTab, collectedIds: {'B1'}).startCtaLabel,
|
||
'Start Delivery',
|
||
);
|
||
expect(
|
||
MilkRun.workingKind(onTheTab, collectedIds: {'B1'}).completeLabel,
|
||
'Delivered',
|
||
);
|
||
});
|
||
|
||
test('the local collected set counts, before any status lands', () {
|
||
ServiceProfile.setActive(ServiceProfile.meals);
|
||
final stop = booking('accepted');
|
||
|
||
// Straight after the pickup slide the server may still say `accepted`;
|
||
// the rider is holding the bag either way.
|
||
expect(MilkRun.workingKind(stop), StopKind.pickup);
|
||
expect(
|
||
MilkRun.workingKind(stop, collectedIds: {'B1'}),
|
||
StopKind.delivery,
|
||
);
|
||
});
|
||
|
||
test('before collection it is still a pickup', () {
|
||
ServiceProfile.setActive(ServiceProfile.meals);
|
||
expect(MilkRun.workingKind(booking('accepted')), StopKind.pickup);
|
||
});
|
||
|
||
test('a parcel booking is untouched', () {
|
||
ServiceProfile.setActive(ServiceProfile.parcel);
|
||
// A collected parcel goes to a hub and is delivered by somebody else, so
|
||
// the rider never drives to its customer and the word never changes.
|
||
expect(MilkRun.workingKind(booking('picked')), StopKind.pickup);
|
||
expect(
|
||
MilkRun.workingKind(booking('picked')).startCtaLabel,
|
||
'Start Pickup',
|
||
);
|
||
});
|
||
});
|
||
}
|