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

232 lines
9.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:miler/data/service_profile.dart';
import 'package:miler/views/Dashboard/home/trip.dart';
import 'package:miler/views/Dashboard/pickups/stop_type.dart';
import 'package:miler/views/helpers/constants/Colorconstants.dart';
import 'package:miler/views/Dashboard/home/trip_card.dart';
import 'package:miler/views/helpers/widgets/app_widgets.dart';
/// P2 — what a stop card says on a service tenant.
///
/// The three facts under test are the three a rider reads before he moves: what
/// kind of work this is, where the order has got to, and which bag out of ten in
/// his box belongs at this door.
void main() {
tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
group('the work-type chip', () {
test('Doormile still names each stop kind separately', () {
ServiceProfile.setActive(ServiceProfile.parcel);
expect(StopKind.pickup.chipLabel, 'PICKUP');
expect(StopKind.delivery.chipLabel, 'DELIVERY');
expect(StopKind.combined.chipLabel, 'P&D');
expect(StopKind.pickup.chipColor, ColorConstants.pickupChip);
});
test('a milk run wears one mark, and it is not the line’s name', () {
// ── This assertion changed with product direction ──
//
// It used to require the word MILK. That is the app's internal name for
// a *service profile* — the thing that decides which capabilities exist
// and whose records belong to whom — and it was being printed on the
// rider's cards and on his Activity record as `Type: MILK`. He did not
// choose it, cannot change it and gains nothing from reading it.
//
// What has NOT changed is the property the mark exists for: on this line
// every stop wears the *same* mark, because the rider handles a kitchen
// crate and a subscriber's doorstep identically and a chip that flipped
// between them would imply a difference in handling that does not exist.
ServiceProfile.setActive(ServiceProfile.meals);
for (final kind in StopKind.values) {
expect(kind.chipLabel, 'DELIVERY', reason: '$kind');
expect(kind.badgeLabel, 'DELIVERY', reason: '$kind');
expect(kind.chipColor, ColorConstants.serviceChip, reason: '$kind');
}
// Sentence case where the type is stated as a label rather than stamped.
expect(StopKind.delivery.titleLabel, 'Delivery');
// And the internal name reaches no screen at all.
for (final kind in StopKind.values) {
expect(kind.chipLabel.toUpperCase(), isNot(contains('MILK')));
expect(kind.titleLabel.toUpperCase(), isNot(contains('MILK')));
}
});
test('the stop keeps its real kind underneath the one label', () {
ServiceProfile.setActive(ServiceProfile.meals);
// P3's collection gate depends on this still being true: a kitchen stop
// is a pickup and a subscriber drop is a delivery, whatever the chip says.
expect(stopKindOf({'type': 'delivery'}), StopKind.delivery);
expect(stopKindOf({'type': 'pickup'}), StopKind.pickup);
});
});
group('the status tag', () {
test('a parcel route names a pending stop as a decision it awaits', () {
ServiceProfile.setActive(ServiceProfile.parcel);
expect(StopState.pending.label, 'Awaiting your decision');
expect(StopState.done.label, 'Completed');
});
test('a meal rider is told the state, not asked for a decision', () {
ServiceProfile.setActive(ServiceProfile.meals);
// He accepted the whole assignment at once — there is no per-stop
// decision left to await.
expect(StopState.pending.label, 'Pending');
expect(StopState.active.label, 'Out for delivery');
expect(StopState.done.label, 'Delivered');
expect(StopState.skipped.label, 'Skipped');
});
});
group('source and bag label', () {
test('read the spellings the backend might send', () {
expect(stopSourceName({'kitchenname': 'Kitchen 1'}), 'Kitchen 1');
expect(stopSourceName({'SourceName': ' Anna Nagar '}), 'Anna Nagar');
expect(stopSourceId({'kitchenid': 'K-77'}), 'K-77');
expect(stopBagLabel({'baglabel': 'DG-1042'}), 'DG-1042');
expect(stopBagLabel({'labelcode': 'DG-9'}), 'DG-9');
});
test(
'absent on a parcel stop rather than empty-stringed onto the card',
() {
expect(stopSourceName({'orderid': '7'}), '');
expect(stopBagLabel({'orderid': '7'}), '');
},
);
});
group('StopSourceLine', () {
Future<void> pump(WidgetTester tester, String source, String bag) async {
await tester.pumpWidget(
ScreenUtilInit(
designSize: const Size(390, 844),
builder: (_, _) => MaterialApp(
home: Scaffold(
body: StopSourceLine(source: source, bagLabel: bag),
),
),
),
);
await tester.pumpAndSettle();
}
testWidgets('takes no space at all on a stop with neither', (tester) async {
await pump(tester, '', '');
// A Doormile card must be exactly what it was — not what it was plus an
// empty row's padding.
final size = tester.getSize(find.byType(StopSourceLine));
expect(size.height, 0);
});
testWidgets('shows both when the stop carries both', (tester) async {
await pump(tester, 'Kitchen 1', 'DG-1042');
expect(find.text('Kitchen 1'), findsOneWidget);
expect(find.text('Bag DG-1042'), findsOneWidget);
});
testWidgets('shows the bag alone when there is no source', (tester) async {
await pump(tester, '', 'DG-1042');
expect(find.text('Bag DG-1042'), findsOneWidget);
expect(
tester.getSize(find.byType(StopSourceLine)).height,
greaterThan(0),
);
});
});
group('the stop row on a service tenant', () {
/// [withSource] mirrors the payload difference between the two tenants: a
/// parcel booking has no kitchen and no bag label to send, which is why a
/// Doormile card is unchanged by P2 — the line is driven by the data, not
/// by who the rider works for.
Map<String, dynamic> stop({bool withSource = true}) => {
'orderid': '1',
'step': 1,
'customername': 'Priya Sundaram',
'pickupaddress': '2B Lakshmi Flats, 4th Main Road, Anna Nagar East',
'status': 'ASSIGNED',
'type': 'delivery',
'quantity': 1,
if (withSource) 'kitchenname': 'Kitchen 1',
if (withSource) 'baglabel': 'DG-1042',
'pickuplat': 13.08,
'pickuplon': 80.21,
};
Future<void> pumpRoute(
WidgetTester tester, {
bool withSource = true,
}) async {
tester.view.physicalSize = const Size(1080, 1800);
tester.view.devicePixelRatio = 3.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final trip = Trip.fromStops(
id: 't1',
stops: [stop(withSource: withSource)],
);
await tester.pumpWidget(
ScreenUtilInit(
designSize: const Size(390, 844),
builder: (_, _) => MaterialApp(
home: Scaffold(
body: SingleChildScrollView(
child: TripCard(trip: trip).buildStops(),
),
),
),
),
);
await tester.pumpAndSettle();
}
// ── The row's marks moved up to the place ──
//
// A card carried a work-type chip and a state chip of its own, and the
// truncation test above was about the two of them sharing one line. Neither
// is on a timeline row: the type is progressive disclosure in the detail
// sheet, and the *state* belongs to the place rather than to each order
// under it — five orders off one counter are all on the same rung, so five
// copies of the same chip said nothing five times.
//
// What survives is the half that was always the contract: the kitchen is
// named once, and the bag the order travels in is on the row.
testWidgets('the kitchen is named once and the bag rides on the row', (
tester,
) async {
ServiceProfile.setActive(ServiceProfile.meals);
await pumpRoute(tester);
// Named once, by the place — not again on every order under it.
expect(find.text('Kitchen 1'), findsOneWidget);
// The label the kitchen printed, straight from [BagManifest]. The `Bag `
// prefix belonged to the card's chip and went with it, which stopped a
// printed `DG-1042` reading as `Bag DG-1042`.
expect(find.text('DG-1042'), findsOneWidget);
// The count the rider checks the shelf against.
expect(find.text('1 bag'), findsOneWidget);
});
testWidgets('a parcel route keeps a bare pending row', (tester) async {
ServiceProfile.setActive(ServiceProfile.parcel);
await pumpRoute(tester, withSource: false);
// The row underneath carries the decision, so a chip would be a caption
// on a control.
expect(find.text('Awaiting your decision'), findsNothing);
// And nothing from the meal run leaks onto a parcel route: no counter to
// group under, so the stop is its own node and no bag arithmetic applies.
expect(find.text('Kitchen 1'), findsNothing);
expect(find.text('MILK'), findsNothing);
expect(find.text('1 order · 1 bag'), findsNothing);
expect(find.text('Priya Sundaram'), findsOneWidget);
});
});
}