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>
85 lines
3.6 KiB
Dart
85 lines
3.6 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:miler/data/service_profile.dart';
|
|
import 'package:miler/views/Dashboard/activity/activity_format.dart';
|
|
|
|
/// ─────────────────────────────────────────────────────────────────────────
|
|
/// READING A DOORMILE TIMESTAMP
|
|
///
|
|
/// Doormile timestamps are IST wall-clock in Postgres `timestamp without time
|
|
/// zone` columns. Some responses come back with a trailing `Z` anyway — a Go +
|
|
/// pgx footgun where a naive DB timestamp loads into `time.Time` under the UTC
|
|
/// location and marshals with a zone marker it never had. The console strips it
|
|
/// the same way; see `src/utils/doormileTimestamp.js`.
|
|
///
|
|
/// `DateTime.tryParse` believes the `Z`, and that broke the tracking rail in the
|
|
/// one way it cannot survive: on a real phone an event stamped `11:43Z` sorted
|
|
/// *below* two 11:57 events, because as an absolute instant it is 17:13 IST —
|
|
/// while its own rung still printed `11:43`, so nothing on screen explained the
|
|
/// order.
|
|
/// ─────────────────────────────────────────────────────────────────────────
|
|
void main() {
|
|
group('parseStamp', () {
|
|
test('a false Z is stripped, not believed', () {
|
|
final withZ = parseStamp('2026-08-20T11:43:00.000Z')!;
|
|
final naive = parseStamp('2026-08-20T11:43:00.000')!;
|
|
|
|
// Same wall-clock, and therefore the same instant on this device.
|
|
expect(withZ, naive);
|
|
expect(withZ.hour, 11);
|
|
expect(withZ.minute, 43);
|
|
expect(clockOf(withZ), '11:43 AM');
|
|
});
|
|
|
|
test('an offset marker is stripped too', () {
|
|
expect(
|
|
parseStamp('2026-08-20T11:43:00+05:30'),
|
|
parseStamp('2026-08-20T11:43:00'),
|
|
);
|
|
});
|
|
|
|
test('it sorts by the clock it prints', () {
|
|
// The exact shape of the device bug: a backend stamp and two local ones.
|
|
final placed = parseStamp('2026-08-20T11:43:00.000Z')!;
|
|
final setOff = parseStamp('2026-08-20T11:57:00.000')!;
|
|
expect(placed.isBefore(setOff), isTrue);
|
|
});
|
|
|
|
test('empty and unparseable are null, never a fabricated moment', () {
|
|
expect(parseStamp(null), isNull);
|
|
expect(parseStamp(''), isNull);
|
|
expect(parseStamp(' '), isNull);
|
|
expect(parseStamp('not a date'), isNull);
|
|
});
|
|
});
|
|
|
|
group('happenedAt', () {
|
|
test('reads through parseStamp, so the list and the record agree', () {
|
|
final row = {'completedat': '2026-08-20T11:43:00.000Z'};
|
|
expect(happenedAt(row), parseStamp('2026-08-20T11:43:00')!);
|
|
});
|
|
|
|
test('a record with no usable stamp sinks rather than jumping the day', () {
|
|
expect(happenedAt(const {}).millisecondsSinceEpoch, 0);
|
|
});
|
|
});
|
|
|
|
group('routeLineOf', () {
|
|
test('reads as speech, and carries no arrow character', () {
|
|
// Poppins has no U+2192, so the app drew the most-read line on the record
|
|
// as `Vidhya Kitchen ▯ Joe Mathew`. The arrow is a Material glyph on
|
|
// screen now; this string is for screen readers and logs, where the word
|
|
// is what a listener needs anyway.
|
|
const stop = {
|
|
'pickupcustomer': 'Joe Mathew',
|
|
'sourcename': 'Vidhya Kitchen',
|
|
};
|
|
ServiceProfile.setActive(ServiceProfile.milkMan);
|
|
addTearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
|
|
|
|
expect(routeLineOf(stop), 'Vidhya Kitchen to Joe Mathew');
|
|
expect(routeLineOf(stop), isNot(contains('→')));
|
|
});
|
|
});
|
|
}
|