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

179 lines
6.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:miler/views/Dashboard/home/homepage_banner.dart';
import 'package:miler/widget/Bottom_page.dart';
/// The live strip at the foot of Home and Bookings.
///
/// ── The bug being pinned ──
///
/// Both pages pinned it to `bottom: 0`, and the nav bar covered it. The bar
/// *floats* — a `Positioned` pill inside the shell's `Stack` rather than a
/// `bottomNavigationBar` — so the Scaffold reserves no room for it and
/// `SafeArea` cannot see it. Nothing in the type system says "you are about to
/// draw underneath the navigation", which is why it survived on two screens at
/// once, so it is measured here instead.
void main() {
const safeBottom = 34.0; // a home-indicator phone, where it is worst
Future<void> pump(
WidgetTester tester, {
double width = 390,
double textScale = 1.0,
int count = 1,
String address = '19, Thadagam Road, Venkitapuram, Coimbatore 641025',
}) async {
tester.view.physicalSize = Size(width * 3, 844 * 3);
tester.view.devicePixelRatio = 3.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
ScreenUtilInit(
designSize: const Size(390, 844),
builder: (_, _) => MaterialApp(
builder: (context, child) => MediaQuery(
data: MediaQuery.of(context).copyWith(
padding: const EdgeInsets.only(bottom: safeBottom),
textScaler: TextScaler.linear(textScale),
),
child: child!,
),
home: Scaffold(
body: Stack(
children: [
const Positioned.fill(child: ColoredBox(color: Colors.white)),
// Exactly how both pages mount it.
Positioned(
left: 0,
right: 0,
bottom: 0,
child: ActivePickupBanner(
activePickups: [
for (var i = 0; i < count; i++)
{
'orderid': 'ORD${1000 + i}',
'pickupaddress': address,
'orderstatus': 'arrived',
},
],
onTap: (_) {},
),
),
],
),
),
),
),
);
// Not `pumpAndSettle`: the LIVE dot pulses forever by design, so nothing on
// this widget ever settles. Two pumps past the longest transition is enough.
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
}
/// Gap between the bottom of the visible card and the bottom of the screen.
double gapBelowCard(WidgetTester tester) {
final screen =
tester.view.physicalSize.height / tester.view.devicePixelRatio;
return screen - tester.getRect(find.byType(Material).last).bottom;
}
testWidgets('clears the floating nav bar while the bar is showing', (
tester,
) async {
await pump(tester);
final context = tester.element(find.byType(ActivePickupBanner));
final required = BottomPage.bottomInset(context);
expect(
gapBelowCard(tester),
greaterThanOrEqualTo(required),
reason:
'the card must sit above the nav bar, which claims '
'${required.toStringAsFixed(1)}pt at the foot of the page. It was '
'pinned to bottom: 0 and the bar covered it.',
);
});
testWidgets('sits at one height rather than chasing the bar', (tester) async {
// The banner used to follow a `navVisible` notifier down when the bar slid
// off on scroll and back up when it returned. The bar compacts now instead
// of leaving, so there is no state in which that room is free — and a
// banner that re-laid itself out on every change of scroll direction was
// moving for a 20pt gap the rider would never have noticed.
await pump(tester);
final resting = gapBelowCard(tester);
await tester.pump(const Duration(milliseconds: 400));
expect(gapBelowCard(tester), closeTo(resting, 0.5));
final context = tester.element(find.byType(ActivePickupBanner));
expect(
resting,
greaterThanOrEqualTo(BottomPage.bottomInset(context)),
reason:
'it must clear the bar at its resting height, which is the '
'height the bar returns to the moment he scrolls back up',
);
});
testWidgets('lines up with the nav bar rather than running full width', (
tester,
) async {
await pump(tester);
final card = tester.getRect(find.byType(Material).last);
// The nav bar sits on a 16 gutter; the banner stacks directly under it.
expect(card.left, closeTo(16.w, 1));
expect(390 - card.right, closeTo(16.w, 1));
});
group('lays out without overflowing', () {
for (final width in [320.0, 360.0, 390.0, 430.0]) {
for (final scale in [1.0, 1.3, 1.5, 2.0]) {
testWidgets('at ${width.toInt()}pt / ${scale}x', (tester) async {
// A long address plus a plural title is the widest this row ever gets,
// and the title used to sit unflexed beside a fixed-width LIVE chip.
await pump(
tester,
width: width,
textScale: scale,
count: 3,
address:
'19/4B, Thadagam Main Road, Venkitapuram, Coimbatore, '
'Tamil Nadu 641025',
);
expect(
tester.takeException(),
isNull,
reason: 'the banner overflowed at ${width.toInt()}pt / ${scale}x',
);
});
}
}
});
testWidgets('says nothing when there is nothing running', (tester) async {
await pump(tester, count: 0);
expect(find.text('Active Pickup'), findsNothing);
expect(find.text('LIVE'), findsNothing);
});
testWidgets('names the count and the stop status', (tester) async {
await pump(tester, count: 3);
expect(find.text('3 Active Pickups'), findsOneWidget);
expect(
find.textContaining('At the stop'),
findsOneWidget,
reason:
'the rider should see how far into the stop he already is. The '
'wording comes from StopStatusX.label, the one place the app names '
'its statuses — and it is "At the stop" rather than the banner\'s '
'old private "At pickup", which was wrong on a delivery',
);
});
}