Files
doormile_milderapp/test/consignment_state_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

144 lines
5.5 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:miler/data/consignment_state.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// BOOKING WORDS ARE NOT CONSIGNMENT WORDS
///
/// The delivery half of the lifecycle lives on the consignment, and the app
/// spent a long time inferring it from the booking — whose story ends at
/// `Converted_To_Consignment` and never mentions delivery at all. These pin
/// the vocabulary boundary and the one state `deliver` accepts.
///
/// Root cause this suite exists to prevent, verified against the live API on
/// 2026-08-21: `POST /miler/consignments/:id/deliver` answers
/// `400 consignment is not out for delivery` for **two opposite** situations —
/// "still at the hub" and "already delivered". Consignment 34's own history
/// read `Out_for_Delivery` then `Delivered`; the app showed the rider an error
/// for work he had completed an hour earlier, because the only state it could
/// see was the booking's terminal word.
/// ─────────────────────────────────────────────────────────────────────────
void main() {
group('parsing', () {
test('the consignment vocabulary is understood in every spelling', () {
expect(
consignmentStateFromRaw('Out_for_Delivery'),
ConsignmentState.outForDelivery,
);
expect(
consignmentStateFromRaw('out for delivery'),
ConsignmentState.outForDelivery,
);
expect(
consignmentStateFromRaw('OUTFORDELIVERY'),
ConsignmentState.outForDelivery,
);
expect(consignmentStateFromRaw('Delivered'), ConsignmentState.delivered);
expect(
consignmentStateFromRaw('Inwarded_at_Hub'),
ConsignmentState.inwardedAtHub,
);
});
test('BOOKING words are not consignment states', () {
// The exact confusion that produced the bug: these are booking-side
// words, and none of them may resolve to a deliverable consignment.
for (final booking in const [
'Converted_To_Consignment',
'Picked_Up',
'Pickup_Scheduled',
'Miler_Assigned',
'Pending_Pickup',
]) {
final state = consignmentStateFromRaw(booking);
expect(
state,
ConsignmentState.unknown,
reason:
'"$booking" is a BOOKING status; it must not name a '
'consignment state',
);
expect(state.isDeliverable, isFalse);
}
});
test('an unknown status is never deliverable and never finished', () {
final s = consignmentStateFromRaw('Teleported');
expect(s, ConsignmentState.unknown);
expect(s.isDeliverable, isFalse);
expect(s.isDelivered, isFalse);
expect(s.isClosed, isFalse);
expect(s.awaitsHub, isFalse);
expect(ConsignmentGate.gateFor(s), DeliverGate.unknown);
});
test('empty and null are unknown, not deliverable', () {
expect(consignmentStateFromRaw(null).isDeliverable, isFalse);
expect(consignmentStateFromRaw('').isDeliverable, isFalse);
});
});
group('the gate', () {
test('only Out_for_Delivery may be delivered', () {
for (final s in ConsignmentState.values) {
expect(
s.isDeliverable,
s == ConsignmentState.outForDelivery,
reason:
'${s.name} must ${s == ConsignmentState.outForDelivery ? '' : 'not '}'
'be deliverable',
);
}
expect(
ConsignmentGate.gateFor(ConsignmentState.outForDelivery),
DeliverGate.deliverable,
);
});
test('an already-delivered consignment reconciles, it does not error', () {
expect(
ConsignmentGate.gateFor(ConsignmentState.delivered),
DeliverGate.alreadyDelivered,
reason: 'the work is done server-side; the device is what is behind',
);
});
test('a genuine hub hold stays blocked — the logistics guard', () {
// The guard the brief requires be kept: a consignment inside the hub's
// half of the network is not this rider's to hand over.
for (final s in const [
ConsignmentState.created,
ConsignmentState.inwardedAtHub,
ConsignmentState.tripsheetLoaded,
ConsignmentState.inTransit,
]) {
expect(s.awaitsHub, isTrue, reason: '${s.name} is hub-side');
expect(s.isDeliverable, isFalse);
expect(ConsignmentGate.gateFor(s), DeliverGate.awaitingHub);
}
});
test('closed work is refused without blaming the rider', () {
for (final s in const [
ConsignmentState.cancelled,
ConsignmentState.returnedToSender,
]) {
expect(ConsignmentGate.gateFor(s), DeliverGate.closed);
expect(s.isDeliverable, isFalse);
}
});
test('an unreadable state does not block the rider', () async {
// A failed GET is not evidence of anything. Blocking here would strand a
// rider at a door because a read timed out; the server stays the judge.
expect(
ConsignmentGate.gateFor(ConsignmentState.unknown),
DeliverGate.unknown,
);
// No id is unreadable, and must not throw or hang.
expect(await ConsignmentGate.stateOf(''), ConsignmentState.unknown);
expect(await ConsignmentGate.stateOf('0'), ConsignmentState.unknown);
});
});
}