261 lines
9.7 KiB
Dart
261 lines
9.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:get/get.dart';
|
|
|
|
import 'package:miler/data/service_profile.dart';
|
|
import 'package:miler/views/Dashboard/home/trip.dart';
|
|
import 'package:miler/views/Dashboard/home/trip_card.dart';
|
|
|
|
/// ─────────────────────────────────────────────────────────────────────────
|
|
/// THE KITCHEN IS THE CARD; ITS ORDERS ARE THE CONTENTS
|
|
///
|
|
/// Home's pickup model is one place per card, disclosed to reveal the orders
|
|
/// under it, ticked in batches and advanced with one gesture:
|
|
///
|
|
/// ```
|
|
/// Vidhya Kitchen · 4 orders · 2 to accept ˅
|
|
/// ◉ Joe Gandhipuram Bag 1
|
|
/// ◉ Priya RS Puram Bag 2
|
|
/// ```
|
|
///
|
|
/// ── The regression this file exists for ──
|
|
///
|
|
/// Two functions answered "what is this place called" and read different keys:
|
|
/// `MilkRun.sourceNameOf` took `sourcename` / `kitchenname`, `stopSourceName`
|
|
/// took those **and** the CamelCase `SourceName` / `KitchenName` the payload
|
|
/// sometimes carries. `trip_card.dart` uses both in one expression, so a
|
|
/// booking with only the capitalised key was:
|
|
///
|
|
/// • **not flat** — `stopSourceName` found a counter, so the group became a
|
|
/// foldable place with its orders underneath; and
|
|
/// • **titled "pickup"** — `sourceNameOf` found none, so `navigationLabel`
|
|
/// fell through to its leg description.
|
|
///
|
|
/// On a device that is a group headed by a backend word, which the rider taps
|
|
/// and which opens a detail sheet instead of the list. Fixed at the domain
|
|
/// layer: one reader, four keys, `stopSourceName` delegating to it.
|
|
/// ─────────────────────────────────────────────────────────────────────────
|
|
void main() {
|
|
tearDown(() {
|
|
Get.reset();
|
|
ServiceProfile.setActive(ServiceProfile.parcel);
|
|
});
|
|
|
|
/// An order as the queue hands it over. [kitchenKey] is the variable under
|
|
/// test: every spelling the payload uses must group and name identically.
|
|
Map<String, dynamic> order(
|
|
String id,
|
|
String who,
|
|
String drop, {
|
|
String kitchenKey = 'KitchenName',
|
|
String status = 'new',
|
|
}) => {
|
|
'orderid': id,
|
|
'pickupid': int.parse(id),
|
|
'step': int.parse(id) - 1000,
|
|
kitchenKey: 'Vidhya Kitchen',
|
|
'sourceid': 'K1',
|
|
'pickupcustomer': who,
|
|
'dropaddress': drop,
|
|
'pickupaddress': '9 Kitchen Street, Coimbatore',
|
|
'orderstatus': status,
|
|
'type': 'delivery',
|
|
'pickuplat': 11.01,
|
|
'pickuplon': 76.95,
|
|
};
|
|
|
|
Future<void> pump(
|
|
WidgetTester tester,
|
|
List<Map<String, dynamic>> stops, {
|
|
Set<String> accepted = const {},
|
|
Set<String> selected = const {},
|
|
Set<String> collected = const {},
|
|
double width = 390,
|
|
double scale = 1.0,
|
|
}) async {
|
|
ServiceProfile.setActive(ServiceProfile.milkMan);
|
|
tester.view.physicalSize = Size(width * 3, 1400 * 3);
|
|
tester.view.devicePixelRatio = 3;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
ScreenUtilInit(
|
|
designSize: const Size(390, 844),
|
|
minTextAdapt: true,
|
|
builder: (_, _) => MaterialApp(
|
|
home: MediaQuery(
|
|
data: MediaQueryData(textScaler: TextScaler.linear(scale)),
|
|
child: Scaffold(
|
|
body: SingleChildScrollView(
|
|
child: TripCard(
|
|
trip: Trip.fromStops(id: 'T1', stops: stops),
|
|
acceptedIds: accepted,
|
|
rejectedIds: const {},
|
|
collectedIds: collected,
|
|
selectedIds: selected,
|
|
onToggleSelect: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
group('the place is named from the domain, not the screen', () {
|
|
for (final key in const [
|
|
'sourcename',
|
|
'SourceName',
|
|
'kitchenname',
|
|
'KitchenName',
|
|
]) {
|
|
testWidgets('$key names the card and groups its orders', (tester) async {
|
|
await pump(tester, [
|
|
order('1001', 'Joe', 'Gandhipuram, Coimbatore', kitchenKey: key),
|
|
order('1002', 'Priya', 'RS Puram, Coimbatore', kitchenKey: key),
|
|
]);
|
|
|
|
expect(find.text('Vidhya Kitchen'), findsOneWidget);
|
|
// The leg description must never surface as a place's name.
|
|
expect(find.text('pickup'), findsNothing);
|
|
// Grouped, so the orders are disclosed under the header rather than the
|
|
// header being the stop.
|
|
expect(find.text('Joe'), findsOneWidget);
|
|
expect(find.text('Priya'), findsOneWidget);
|
|
});
|
|
}
|
|
|
|
testWidgets('a payload naming no counter still renders its one stop', (
|
|
tester,
|
|
) async {
|
|
final o = order('1001', 'Joe', 'Gandhipuram, Coimbatore');
|
|
o.remove('KitchenName');
|
|
o.remove('sourceid');
|
|
await pump(tester, [o]);
|
|
|
|
// Nothing to group under, so the header IS the stop — named for the
|
|
// customer, never for the leg.
|
|
expect(find.text('Joe'), findsWidgets);
|
|
expect(find.text('pickup'), findsNothing);
|
|
});
|
|
});
|
|
|
|
group('the row carries what the rider decides on', () {
|
|
testWidgets('the drop area, not the booking reference', (tester) async {
|
|
await pump(tester, [order('1001', 'Joe', 'Gandhipuram, Coimbatore')]);
|
|
|
|
// "Are Joe and Priya the same trip out of the door, or opposite ends of
|
|
// the city?" is the question this list answers. A reference answers the
|
|
// one the office asks, once, when something has gone wrong — it lives on
|
|
// the detail sheet and the Activity record.
|
|
expect(find.text('Gandhipuram'), findsOneWidget);
|
|
expect(find.textContaining('1001'), findsNothing);
|
|
// And no invented shelf reference beside it: this counter printed
|
|
// nothing, so the row carries the person and the area and stops there.
|
|
expect(find.text('Bag 1'), findsNothing);
|
|
});
|
|
|
|
// Updated when `areaOf` was corrected against real Coimbatore addresses
|
|
// on a device: it used to take the first part with no digits in it, which
|
|
// is the *street*. The row's job is to say which neighbourhood a stop is
|
|
// in, so the rule now prefers the shortest digit-free part — the locality
|
|
// — and this expectation follows the rule rather than the other way round.
|
|
testWidgets('a door number is skipped for the area behind it', (
|
|
tester,
|
|
) async {
|
|
await pump(tester, [
|
|
order('1001', 'Joe', '12, Cross Cut Road, Gandhipuram'),
|
|
]);
|
|
expect(find.text('Gandhipuram'), findsOneWidget);
|
|
expect(find.textContaining('12,'), findsNothing);
|
|
});
|
|
|
|
testWidgets('no drop means no line, never a blank one', (tester) async {
|
|
final o = order('1001', 'Joe', '');
|
|
o.remove('dropaddress');
|
|
o.remove('pickupaddress');
|
|
await pump(tester, [o]);
|
|
|
|
expect(find.text('Joe'), findsWidgets);
|
|
expect(find.text(''), findsNothing);
|
|
});
|
|
|
|
testWidgets('the load is stated once, and counted off the orders', (
|
|
tester,
|
|
) async {
|
|
await pump(tester, [
|
|
order('1001', 'Joe', 'Gandhipuram, Coimbatore'),
|
|
order('1002', 'Priya', 'RS Puram, Coimbatore'),
|
|
order('1003', 'Arun', 'Peelamedu, Coimbatore'),
|
|
]);
|
|
// The header states the load once. `N orders · N bags` was the same
|
|
// number twice and it cost the header its `to accept` clause on a real
|
|
// route; the derived per-row numbering that made up the second half is
|
|
// gone entirely.
|
|
expect(find.textContaining('3 orders'), findsWidgets);
|
|
for (final invented in const ['Bag 1', 'Bag 2', 'Bag 3']) {
|
|
expect(find.text(invented), findsNothing);
|
|
}
|
|
// One row per order, named by the person it is for.
|
|
for (final who in const ['Joe', 'Priya', 'Arun']) {
|
|
expect(find.textContaining(who), findsWidgets);
|
|
}
|
|
});
|
|
});
|
|
|
|
group('a partly-accepted kitchen says so', () {
|
|
testWidgets('2 of 4 taken is visible on the header', (tester) async {
|
|
await pump(
|
|
tester,
|
|
[
|
|
order('1001', 'Joe', 'Gandhipuram, Coimbatore'),
|
|
order('1002', 'Priya', 'RS Puram, Coimbatore'),
|
|
order('1003', 'Arun', 'Peelamedu, Coimbatore'),
|
|
order('1004', 'Kumar', 'Saibaba Colony, Coimbatore'),
|
|
],
|
|
accepted: const {'1002', '1004'},
|
|
);
|
|
|
|
// The rider is mid-decision at one counter; the count of what is still
|
|
// undecided is the whole content of that state.
|
|
expect(find.textContaining('4 orders'), findsWidgets);
|
|
expect(find.textContaining('to accept'), findsWidgets);
|
|
});
|
|
});
|
|
|
|
group('it survives a narrow phone and a large font', () {
|
|
for (final (width, scale) in const [
|
|
(390.0, 1.0),
|
|
(375.0, 1.0),
|
|
(320.0, 1.0),
|
|
(390.0, 2.0),
|
|
(375.0, 2.0),
|
|
(320.0, 2.0),
|
|
]) {
|
|
testWidgets('${width.toInt()}px at ${scale}x', (tester) async {
|
|
await pump(
|
|
tester,
|
|
[
|
|
order(
|
|
'1001',
|
|
'Ramachandran Venkataraman',
|
|
'Saibaba Colony, Coimbatore',
|
|
),
|
|
order('1002', 'Priya', 'RS Puram, Coimbatore'),
|
|
],
|
|
accepted: const {'1002'},
|
|
selected: const {'1001'},
|
|
width: width,
|
|
scale: scale,
|
|
);
|
|
// A `RenderFlex` overflow throws, and on a doorstep the stripe bar
|
|
// reads as the app having crashed.
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
});
|
|
}
|