185 lines
7.8 KiB
Dart
185 lines
7.8 KiB
Dart
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/data/stop_area.dart';
|
|
import 'package:miler/views/Dashboard/home/homepage_banner.dart';
|
|
import 'package:miler/views/Dashboard/pickups/pickups.dart';
|
|
|
|
/// ─────────────────────────────────────────────────────────────────────────
|
|
/// THE WORK QUEUE, NOT A LIST OF CARDS
|
|
///
|
|
/// Deliveries answers one question — *where do I go now, and what do I do
|
|
/// there* — and the whole screen is ranked around it: NOW is the command
|
|
/// object, UP NEXT is a glance, LATER recedes. That ranking has to be legible
|
|
/// **before** the labels are read, or the labels are the only thing carrying
|
|
/// it.
|
|
///
|
|
/// These pin the invariants that ranking depends on, plus the two geometry
|
|
/// rules the bottom of this screen has broken twice.
|
|
/// ─────────────────────────────────────────────────────────────────────────
|
|
void main() {
|
|
_realAddresses();
|
|
tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
|
|
|
|
Map<String, dynamic> drop(String id, String who, {String? address}) => {
|
|
'orderid': id,
|
|
'type': 'delivery',
|
|
'orderstatus': 'outfordelivery',
|
|
'pickupcustomer': who,
|
|
'sourcename': 'DailyGrubs RS Puram Kitchen',
|
|
'kitchenname': 'DailyGrubs RS Puram Kitchen',
|
|
'pickupaddress': '9 Mettupalayam Road, RS Puram, Coimbatore 641002',
|
|
'dropaddress': address ?? '12, SNS Colony, Peelamedu, Coimbatore 641004',
|
|
'pickupcontactno': '9787698259',
|
|
};
|
|
|
|
group('the collapsed queue names a place, not a street', () {
|
|
test('an area, never a truncated address', () {
|
|
// The screen's own anti-pattern, named in the brief: twenty rows of
|
|
// `SEQTEST Gandhipur…` answer nothing a blank line would not have.
|
|
final area = areaOf(drop('1', 'Joe Mathew'));
|
|
expect(area, isNot(contains('641004')));
|
|
expect(area, isNot(contains('12,')));
|
|
expect(area.length, lessThan(24));
|
|
expect(RegExp(r'[0-9]').hasMatch(area), isFalse);
|
|
});
|
|
|
|
test('a delivery names where the bag is GOING', () {
|
|
ServiceProfile.setActive(ServiceProfile.milkMan);
|
|
// Once he is carrying it, the kitchen is supporting information. The
|
|
// destination is the question.
|
|
// `Peelamedu`, not `SNS Colony` — the locality, not the sub-locality.
|
|
// See the live-data group above for why the rule prefers a one-word name.
|
|
expect(areaOf(drop('1', 'Joe Mathew'), preferDrop: true), 'Peelamedu');
|
|
expect(
|
|
areaOf(drop('1', 'Joe Mathew'), preferDrop: false),
|
|
'Mettupalayam Road'.contains('9') ? isNotEmpty : 'RS Puram',
|
|
);
|
|
});
|
|
|
|
test('a stop with nothing usable yields nothing, never a placeholder', () {
|
|
expect(areaOf(const {'orderid': '1'}), '');
|
|
});
|
|
|
|
test('one implementation, shared with Home', () {
|
|
// Two copies of this rule got the same subtle case wrong in the same
|
|
// way: `12 SNS Colony` returned as an area because it is not *entirely*
|
|
// digits. One function is one place to find that.
|
|
expect(
|
|
areaOf({'dropaddress': '4/2 Cross Cut Road, Gandhipuram, 641012'}),
|
|
'Gandhipuram',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('the bottom slot is one slot', () {
|
|
/// `maxHeight` is expressed in `.h`, like every dimension in this app, so
|
|
/// these run inside a pumped `ScreenUtilInit` rather than as pure tests.
|
|
Future<void> ready(WidgetTester tester) async {
|
|
tester.view.physicalSize = const Size(390 * 3, 844 * 3);
|
|
tester.view.devicePixelRatio = 3.0;
|
|
addTearDown(tester.view.reset);
|
|
await tester.pumpWidget(
|
|
ScreenUtilInit(
|
|
designSize: const Size(390, 844),
|
|
builder: (_, _) => const MaterialApp(home: SizedBox.shrink()),
|
|
),
|
|
);
|
|
}
|
|
|
|
testWidgets('the reservation follows whichever surface is showing', (
|
|
tester,
|
|
) async {
|
|
await ready(tester);
|
|
const inset = 34.0;
|
|
|
|
// ── One surface, where there were two ──
|
|
//
|
|
// This case used to be a three-way check, because a **Start delivery**
|
|
// bar shared this corner with the live strip. That bar is gone — the
|
|
// release it made now happens per stop, when the rider slides Start ride
|
|
// on the map, which is the moment he actually sets off. See `startRound`.
|
|
//
|
|
// What the rule protects is unchanged: the floor reserves exactly what is
|
|
// floating over the list, plus the device inset.
|
|
expect(
|
|
bottomReservationFor(inset: inset, live: true),
|
|
inset + ActivePickupBanner.maxHeight,
|
|
);
|
|
});
|
|
|
|
testWidgets('the device inset is always counted', (tester) async {
|
|
await ready(tester);
|
|
// The regression: the live branch was a flat `80.h` that forgot the
|
|
// inset, so on a gesture-navigation phone the last row of the run sat
|
|
// behind the banner *and* behind the navigation.
|
|
for (final live in const [true, false]) {
|
|
final withInset = bottomReservationFor(inset: 34, live: live);
|
|
final without = bottomReservationFor(inset: 0, live: live);
|
|
expect(withInset - without, 34);
|
|
}
|
|
});
|
|
|
|
testWidgets('an idle queue reserves almost nothing', (tester) async {
|
|
await ready(tester);
|
|
expect(
|
|
bottomReservationFor(inset: 0, live: false),
|
|
lessThan(ActivePickupBanner.maxHeight),
|
|
);
|
|
});
|
|
|
|
testWidgets('the floating surface declares a real height', (tester) async {
|
|
await ready(tester);
|
|
// Measured, not guessed. It is the only thing that knows how much of the
|
|
// list it covers.
|
|
expect(ActivePickupBanner.maxHeight, greaterThan(48));
|
|
});
|
|
});
|
|
|
|
// ── The "command surface" group is gone with its control ──
|
|
//
|
|
// It covered the **Start delivery** bar: what it said, how it laid out at six
|
|
// widths and text scales, and that it never double-counted the device inset.
|
|
// The bar was removed at the rider's request — the release it made is made
|
|
// per stop now, by the Start ride slider on the map, at the moment he
|
|
// actually sets off. See `startRound` for the whole argument.
|
|
//
|
|
// Nothing replaced it in that corner, so there is nothing here to test. The
|
|
// reservation rule above still pins the geometry the corner has to keep.
|
|
}
|
|
|
|
/// Real addresses from the live tenant, added after seeing them render on a
|
|
/// device. The rule that only skipped digits printed the landmark —
|
|
/// `Coimbatore Gandhipuram Mofussil Bus Stand` — on every row of the run.
|
|
void _realAddresses() {
|
|
group('the area rule against live data', () {
|
|
const cases = <String, String>{
|
|
'Coimbatore Gandhipuram Mofussil Bus Stand, Sathyamurthi Road, '
|
|
'Gandhipuram, Ward 54, Central Zone, Coimbatore, '
|
|
'Coimbatore North, Coimbatore, Tamil Nadu, 641001, India':
|
|
'Gandhipuram',
|
|
'12, SNS Colony, Peelamedu, Coimbatore 641004': 'Peelamedu',
|
|
'4 Cross, RS Puram, Coimbatore 641002': 'RS Puram',
|
|
'19, Thadagam Road, Venkitapuram, Coimbatore': 'Venkitapuram',
|
|
};
|
|
|
|
cases.forEach((address, expected) {
|
|
test('"${address.split(',').first}…" → $expected', () {
|
|
expect(areaOf({'dropaddress': address}), expected);
|
|
});
|
|
});
|
|
|
|
test('a landmark never wins over the locality behind it', () {
|
|
final area = areaOf(const {
|
|
'dropaddress':
|
|
'Coimbatore Gandhipuram Mofussil Bus Stand, '
|
|
'Sathyamurthi Road, Gandhipuram, Coimbatore',
|
|
});
|
|
expect(area.split(' ').length, 1, reason: 'an area is a name');
|
|
expect(area, isNot(contains('Bus Stand')));
|
|
});
|
|
});
|
|
}
|