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 bags · 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 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 pump( WidgetTester tester, List> stops, { Set accepted = const {}, Set selected = const {}, Set 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); expect(find.text('Bag 1'), findsOneWidget); }); // 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('one order is one bag, 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, in the unit the rider carries; the // check that orders and bags agree is the manifest itself, one named // bag per order. `N orders · N bags` was the same number twice and it // cost the header its `to accept` clause on a real route. expect(find.textContaining('3 bags'), findsWidgets); for (final bag in const ['Bag 1', 'Bag 2', 'Bag 3']) { expect(find.text(bag), findsOneWidget); } }); }); 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 bags'), 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); }); } }); }