Files
doormile_milderapp/test/pickup_delivery_boundary_test.dart
Thiru-tenext d7348e253f Miler rider app: surface system, visible design language, backend lifecycle
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>
2026-08-22 05:40:35 +05:30

236 lines
8.6 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:miler/data/milk_run.dart';
import 'package:miler/data/service_profile.dart';
import 'package:miler/data/work_domain.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// THE BOUNDARY BETWEEN THE TWO WORK SCREENS
///
/// ```
/// PENDING ─ accept ─▶ ACCEPTED ─ navigate ─▶ ARRIVED ─ pick up ─▶ PICKED
/// └────────────────── HOME owns all of this ───────────────┘ │
/// ▼
/// DELIVERIES owns from here on
/// ```
///
/// The defect these pin: an **accepted** booking was claimed by both screens.
/// It stayed on Home to be collected and it also appeared on Deliveries as live
/// work, with a strip offering to continue it — which opened a map, an I'VE
/// ARRIVED and a confirmation sheet for a collection the rider had not made.
/// Two screens, two filters, one fact, and they disagreed.
///
/// So the invariants are asserted on [WorkBoundary] itself rather than by
/// pumping two widgets and comparing what they drew: it is the single rule both
/// screens now read, and a rule that cannot be re-expressed per screen is the
/// actual fix.
/// ─────────────────────────────────────────────────────────────────────────
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
/// One order in the shape the booking adapter produces: the backend has no
/// per-stop type, so every row calls itself a pickup whatever leg it is on.
Map<String, dynamic> order(
String id, {
required String status,
String kitchen = 'Vidhya Kitchen',
}) => {
'orderid': id,
'pickupid': id,
'type': 'pickup',
'orderstatus': status,
'kitchenname': kitchen,
'pickupcustomer': kitchen,
'dropcustomer': 'Customer $id',
};
group('a kitchen line — the boundary is pickup-complete', () {
setUp(() => ServiceProfile.setActive(ServiceProfile.milkMan));
test('accepted is not picked up, and never leaves Home', () {
// The whole bug in one assertion. Accepting is a decision about work
// still to be done; nothing has changed hands.
final accepted = order('A1', status: 'accepted');
expect(
WorkBoundary.pickupComplete(accepted, collectedIds: const {}),
isFalse,
);
expect(
WorkBoundary.domainOf(
accepted,
acceptedIds: const {'A1'},
collectedIds: const {},
),
WorkDomain.pickup,
reason: 'an acceptance must never be read as a completed pickup',
);
});
test('every rung before pickup-complete belongs to Home', () {
for (final status in ['assigned', 'accepted', 'active', 'arrived']) {
expect(
WorkBoundary.domainOf(
order('A1', status: status),
acceptedIds: const {'A1'},
),
WorkDomain.pickup,
reason: status,
);
}
});
test('the backend saying picked is what moves it', () {
// Both spellings the contract produces: `Picked_Up` and the conversion
// to a consignment.
for (final status in ['picked', 'Picked up']) {
expect(
WorkBoundary.domainOf(order('A1', status: status)),
WorkDomain.delivery,
reason: status,
);
}
});
test('a confirmed pickup keeps it on Deliveries through a stale poll', () {
// The queue is up to one poll behind, and it answers with what it knew
// before the hand-over. Without the collected record the order would
// bounce back to Home for a few seconds, which reads as the confirm
// having failed — so the record wins, and it is written only after the
// backend confirms. See `_advanceStop` on Home.
final stale = order('A1', status: 'accepted');
expect(
WorkBoundary.domainOf(stale, collectedIds: const {'A1'}),
WorkDomain.delivery,
);
});
test('the delivery rungs stay on Deliveries', () {
for (final status in ['out_for_delivery', 'delivery_arrived']) {
expect(
WorkBoundary.domainOf(order('A1', status: status)),
WorkDomain.delivery,
reason: status,
);
}
});
test('delivered and cancelled leave both work screens', () {
expect(
WorkBoundary.domainOf(order('A1', status: 'delivered')),
WorkDomain.closed,
);
expect(
WorkBoundary.domainOf(order('A1', status: 'cancelled')),
WorkDomain.closed,
);
});
test('a declined stop stays on Home — it is not delivery work', () {
// It stays visible there precisely so the rider can change his mind.
expect(
WorkBoundary.domainOf(order('A1', status: 'rejected')),
WorkDomain.pickup,
);
});
test('picked is the middle of the morning, not the end of it', () {
// The line does not end at a hub: he is holding the food and still owes
// every door. Closing it here is what once sent a collected crate to
// Activity as fifteen finished deliveries.
expect(WorkBoundary.isClosed(order('A1', status: 'picked')), isFalse);
});
});
group('one kitchen, partially accepted, partially collected', () {
setUp(() => ServiceProfile.setActive(ServiceProfile.milkMan));
final day = [
order('A1', status: 'picked'), // collected at the counter
order('A2', status: 'accepted'), // taken, still on the counter
order('A3', status: 'assigned'), // not decided yet
order('A4', status: 'delivered'), // done
];
test('the two screens split the day, with nothing shared', () {
final home = WorkBoundary.pickupQueue(day, acceptedIds: const {'A2'});
final deliveries = WorkBoundary.deliveryQueue(
day,
acceptedIds: const {'A2'},
);
expect(home.map(MilkRun.idOf), ['A2', 'A3']);
expect(deliveries.map(MilkRun.idOf), ['A1']);
// The invariant that makes the pill trustworthy: no order can be counted
// by both screens, and none can fall between them.
final overlap = home
.map(MilkRun.idOf)
.toSet()
.intersection(deliveries.map(MilkRun.idOf).toSet());
expect(overlap, isEmpty);
expect(
home.length + deliveries.length,
day.length - 1,
reason: 'A4 closed',
);
});
test("Home's pill counts the other tab's rows, not its own", () {
// The pill's tap goes to Deliveries, so its number has to be that tab's
// — asked of the same rule, from the same day. It used to count accepted
// work, so it read 3 while the tab it opened listed 1.
expect(
WorkBoundary.deliveryQueue(day, acceptedIds: const {'A2'}).length,
1,
);
});
test('accepting the rest moves nothing across', () {
final deliveries = WorkBoundary.deliveryQueue(
day,
acceptedIds: const {'A2', 'A3'},
);
expect(deliveries.map(MilkRun.idOf), ['A1']);
});
});
group('logistics — the boundary is acceptance, and that is unchanged', () {
setUp(() => ServiceProfile.setActive(ServiceProfile.parcel));
test("an accepted booking is the work tab's, as it always was", () {
// There is no counter and nothing to batch on this line: the rider drives
// to one customer and raises the shipment at the door, so the collection
// *is* the job and it is worked from that tab. The rule is the same one,
// read through [ServiceProfile.handoffAt].
expect(
WorkBoundary.domainOf(
order('P1', status: 'accepted'),
acceptedIds: const {'P1'},
),
WorkDomain.delivery,
);
});
test('an undecided booking is still Home\'s', () {
expect(
WorkBoundary.domainOf(order('P1', status: 'assigned')),
WorkDomain.pickup,
);
});
test('collected ends the job on a line that ends at the hub', () {
// Terminal here, unlike on the milk run: the parcel becomes a consignment
// and the hub carries it from there.
expect(WorkBoundary.isClosed(order('P1', status: 'picked')), isTrue);
expect(
WorkBoundary.domainOf(order('P1', status: 'picked')),
WorkDomain.closed,
);
});
});
}