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>
This commit is contained in:
122
test/skip_leaves_deliveries_test.dart
Normal file
122
test/skip_leaves_deliveries_test.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'dart:convert';
|
||||
|
||||
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/service_profile.dart';
|
||||
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
/// A SKIPPED DELIVERY IS DONE WITH, NOT PARKED
|
||||
///
|
||||
/// ── The regression ──
|
||||
///
|
||||
/// A skip at a customer's door left the tab and came back. The in-memory set
|
||||
/// that holds a just-finished stop off the list is what was doing the work,
|
||||
/// and it dies with the process — so the next cold start put the stop the
|
||||
/// rider had written off an hour earlier back at the top of his queue, while
|
||||
/// its record sat in Activity saying otherwise.
|
||||
///
|
||||
/// It is specifically a *skip* problem. A delivery closes the consignment, so
|
||||
/// the backend stops calling it delivery work within a poll or two and the row
|
||||
/// drops out on its own. A skip is a failed attempt: the server rightly leaves
|
||||
/// the consignment `Out_for_Delivery`, and it will keep coming back for as long
|
||||
/// as the day lasts unless the device remembers what it did.
|
||||
///
|
||||
/// These pin the store rules that hold it off the tab and put it on Activity
|
||||
/// under its own word.
|
||||
/// ─────────────────────────────────────────────────────────────────────────
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUp(() {
|
||||
SharedPreferences.setMockInitialValues({'userid': 38});
|
||||
ServiceProfile.setActive(ServiceProfile.milkMan);
|
||||
});
|
||||
tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
|
||||
|
||||
Map<String, dynamic> stop(String id) => {
|
||||
'orderid': id,
|
||||
'pickupid': int.parse(id),
|
||||
'orderstatus': 'outfordelivery',
|
||||
'pickupcustomer': 'SEQTEST Ukkadam',
|
||||
};
|
||||
|
||||
group('the record', () {
|
||||
test(
|
||||
'a skip is filed as a skip, not flattened into a cancellation',
|
||||
() async {
|
||||
await addCompletedBookings([stop('1001')], terminalStatus: 'skipped');
|
||||
|
||||
final filed = (await getCompletedBookings()).single;
|
||||
expect(stopStatusOf(filed).isSkipped, isTrue);
|
||||
expect(
|
||||
stopStatusOf(filed).isCancelled,
|
||||
isFalse,
|
||||
reason:
|
||||
'a stop the rider walked away from and one the office called '
|
||||
'off are different things, and Activity slices them apart',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('the exact word wins over the cancelled shorthand', () async {
|
||||
// `cancelled: true` is what suppresses the "delivery N of M" toast, so
|
||||
// the skip path passes both — and the precise word has to survive.
|
||||
await addCompletedBookings(
|
||||
[stop('1001')],
|
||||
cancelled: true,
|
||||
terminalStatus: 'skipped',
|
||||
);
|
||||
expect((await getCompletedBookings()).single['orderstatus'], 'skipped');
|
||||
});
|
||||
|
||||
test('a real cancellation still files as cancelled', () async {
|
||||
await addCompletedBookings([stop('1001')], cancelled: true);
|
||||
expect((await getCompletedBookings()).single['orderstatus'], 'cancelled');
|
||||
});
|
||||
|
||||
test('a delivery still files as delivered', () async {
|
||||
await addCompletedBookings([stop('1001')]);
|
||||
expect(
|
||||
(await getCompletedBookings()).single['orderstatus'],
|
||||
'delivered',
|
||||
reason: 'the milk run ends at the customer, not at a hub',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('it stays off the deliveries tab', () {
|
||||
test('the closed set survives the process that wrote it', () async {
|
||||
await addCompletedBookings([stop('1001')], terminalStatus: 'skipped');
|
||||
|
||||
// What a cold start reads back — the same call the Deliveries tab makes
|
||||
// to rebuild its just-finished set.
|
||||
expect(await getCompletedOrderIds(), contains('1001'));
|
||||
});
|
||||
|
||||
test('closing one stop does not close the rest of the load', () async {
|
||||
await addCompletedBookings([stop('1001')], terminalStatus: 'skipped');
|
||||
expect(await getCompletedOrderIds(), isNot(contains('1002')));
|
||||
});
|
||||
|
||||
test('yesterday’s skip is not today’s work', () async {
|
||||
// Pruned on read, like every other record in this store: a skip the
|
||||
// rider can no longer act on must not hold a stop off tomorrow's tab.
|
||||
await addCompletedBookings([stop('1001')], terminalStatus: 'skipped');
|
||||
final aged = (await getCompletedBookings())
|
||||
.map((b) => {...b, 'completedday': '2020-01-01'})
|
||||
.toList();
|
||||
await debugSeedStore('completed_bookings', jsonEncode(aged));
|
||||
|
||||
expect(await getCompletedOrderIds(), isEmpty);
|
||||
});
|
||||
|
||||
test('another rider’s skip is not this one’s', () async {
|
||||
await addCompletedBookings([stop('1001')], terminalStatus: 'skipped');
|
||||
SharedPreferences.setMockInitialValues({'userid': 99});
|
||||
expect(await getCompletedOrderIds(), isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user