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

191 lines
7.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:miler/views/Dashboard/pickups/stop_verify.dart';
import 'package:miler/views/helpers/constants/Colorconstants.dart';
import 'package:miler/views/helpers/widgets/app_widgets.dart';
/// ── The form is the screen; the boxes were not ──
///
/// Every group on a verification leg used to be a filled, rounded, 16pt-padded
/// panel — the stop summary, the parcel checklist, the recipient picker, the
/// OTP, the photo — four to six of them stacked, each holding white controls.
/// That put a *container* rank and a *control* rank on the page at the same
/// weight, when only one of the two is something the rider touches at a door.
///
/// They are named and ruled off instead. These tests pin the parts of that
/// which a later change could quietly undo.
void main() {
Future<void> pump(
WidgetTester tester,
Map<String, dynamic> stop, {
double width = 390,
}) async {
tester.view.physicalSize = Size(width * 3, 844 * 3);
tester.view.devicePixelRatio = 3.0;
addTearDown(tester.view.reset);
await tester.pumpWidget(
ScreenUtilInit(
designSize: const Size(390, 844),
builder: (_, _) =>
MaterialApp(home: StopVerificationPage(pickup: stop)),
),
);
await tester.pumpAndSettle();
}
Map<String, dynamic> stopOf({
required String type,
int deliveryQty = 0,
int pickupQty = 0,
}) => <String, dynamic>{
'orderid': 'T-1',
'type': type,
'pickupcustomer': 'Priya Sharma',
'pickupaddress': '45, Lake View Apartments, Race Course, Coimbatore',
if (deliveryQty > 0) 'deliveryqty': '$deliveryQty',
if (pickupQty > 0) 'pickupqty': '$pickupQty',
'Quantity': '${pickupQty > 0 ? pickupQty : deliveryQty}',
};
/// The width of a chip, measured on the box that actually draws it.
double chipWidth(WidgetTester tester, String label) => tester
.getSize(
find
.ancestor(
of: find.text(label),
matching: find.byType(AnimatedContainer),
)
.first,
)
.width;
testWidgets('a leg step draws no filled panels', (tester) async {
await pump(tester, stopOf(type: 'combined', deliveryQty: 1, pickupQty: 2));
// `cardSurface` is the panel fill. It is still right for the review step —
// one grey block per leg, on the screen the rider signs — but on a step he
// is filling in, every one of them was a box around work rather than the
// work itself.
final fills = tester
.widgetList<Container>(
find.descendant(
of: find.byType(SingleChildScrollView),
matching: find.byType(Container),
),
)
.map((c) => c.decoration)
.whereType<BoxDecoration>()
.map((d) => d.color);
expect(fills, isNot(contains(ColorConstants.cardSurface)));
});
testWidgets('a section names itself, and stays under the stop', (
tester,
) async {
await pump(tester, stopOf(type: 'delivery', deliveryQty: 1));
// ── This assertion changed with the design, deliberately ──
//
// It used to require a grey letterspaced eyebrow, from when the sections
// were hairline-ruled groups on a white page: with no boundary of their
// own, a loud title would have been the only thing separating them, and
// five loud titles is a page of shouting.
//
// They are panels now. The boundary does the separating, so the title is
// free to be a title — the line the rider reads to know what this step
// asks of him — set in the same ink as the rest of the panel's content
// rather than in the grey reserved for metadata.
final label = tester.widget<Text>(find.text('Who received it?'));
expect(label.style!.color, ColorConstants.slateText);
expect(label.style!.fontWeight, FontWeight.w700);
// What has not changed, and must not: it is still quieter than the stop it
// belongs to. The customer's name is the subject of the screen; a step
// inside it is not allowed to outrank it.
final name = tester.widget<Text>(find.text('Priya Sharma'));
expect(label.style!.fontSize, lessThan(name.style!.fontSize!));
});
testWidgets('the pickers are an even grid, with no half-width orphan', (
tester,
) async {
await pump(tester, stopOf(type: 'delivery', deliveryQty: 1));
// Four recipients: two rows of two, all the same width. Natural-width chips
// packed themselves three-then-one, which reads as a list that ran out
// rather than as a set of four equal choices.
final customer = chipWidth(tester, 'Customer');
for (final other in const ['Family', 'Security', 'Neighbour']) {
expect(chipWidth(tester, other), closeTo(customer, 0.5));
}
});
testWidgets('an odd picker gives its last option the full row', (
tester,
) async {
await pump(tester, stopOf(type: 'pickup', pickupQty: 2));
// Three conditions. The third at half width on a line of its own is an
// orphan; at full width it is a second row.
expect(
chipWidth(tester, 'Damaged'),
greaterThan(chipWidth(tester, 'Sealed & intact') * 1.8),
);
});
testWidgets('a blocked step gets a neutral bar, not a faded coloured one', (
tester,
) async {
await pump(tester, stopOf(type: 'combined', deliveryQty: 1, pickupQty: 2));
// Nothing has been filled in, so the step is blocked.
expect(find.text('1 parcel still to hand over'), findsOneWidget);
// No accent button at all — a saturated pill at 45% still reads as a
// button, so the rider taps it and nothing happens.
expect(find.byType(MilerButton), findsNothing);
expect(find.byIcon(LucideIcons.lock), findsOneWidget);
// And the label stays readable: grey type on a neutral fill, not white
// dropped to 45% over a wash of itself.
// Named for where it goes, not for which step it is: the rider is on the
// hand-over leg, so the button announces the collection that follows it.
final label = tester.widget<Text>(find.text('Continue to collect'));
expect(label.style!.color, ColorConstants.secondaryText);
});
testWidgets('completing the step turns the bar a solid accent', (
tester,
) async {
await pump(tester, stopOf(type: 'delivery', deliveryQty: 1));
await tester.tap(find.text('Parcel 1 of 1'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField).first, '482916');
await tester.pumpAndSettle();
// Photo is the last gate and the camera cannot be reached here, so the bar
// is still blocked — but on the name of what is missing, not the parcels.
expect(find.text('Take a proof photo'), findsOneWidget);
// The parcel row and the OTP now report themselves done, which is the
// section-scoped tick: it speaks for its own step, never for the screen.
expect(find.text('Handed over'), findsOneWidget);
});
testWidgets('the app bar clears the status bar', (tester) async {
await pump(tester, stopOf(type: 'delivery', deliveryQty: 1));
// The title used to sit hard against the clock, which is where a system
// default puts it and nowhere near where the rest of the app puts a
// heading. `toolbarHeight` alone only moves it half as far as it adds.
final title = tester.getRect(find.text('Verify delivery'));
expect(title.top, greaterThan(24.0));
});
}