import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:miler/views/Dashboard/activity/activity_page.dart'; import 'package:miler/views/Dashboard/profile/Profilepage.dart'; import 'package:miler/views/helpers/constants/Colorconstants.dart'; import 'package:miler/views/helpers/widgets/miler_app_bar.dart'; import 'package:miler/data/accepted_store.dart'; /// ── A day's work is a list, not a stack of cards ── /// /// Every finished stop was a filled, rounded card with 12pt of air under it, /// and inside each one a *second*, white panel holding the three figures. So the /// page came down the screen as grey slabs with white slabs punched into them — /// the card-inside-card shape, repeated once per stop — and on top of that every /// row opened with a 40pt green disc that was identical on all of them. /// /// Finished stops are all the same kind of thing, arriving in one order, read by /// scanning down. That is a list, and a list is separated by rules. void main() { String day(DateTime t) => '${t.year}-${t.month.toString().padLeft(2, '0')}-' '${t.day.toString().padLeft(2, '0')}'; Map finished( String id, String name, String address, { required String type, double cash = 0, int minsAgo = 30, }) { final now = DateTime.now(); final at = now.subtract(Duration(minutes: minsAgo)); return { 'orderid': id, 'type': type, 'pickupcustomer': name, 'pickupaddress': address, 'orderstatus': 'picked', 'completedday': day(now), 'completedat': at.toIso8601String(), 'startedat': at.subtract(const Duration(minutes: 14)).toIso8601String(), if (cash > 0) 'collectionamt': '$cash', 'proof': {if (cash > 0) 'amountcollected': cash}, }; } Future pumpActivity(WidgetTester tester) async { SharedPreferences.setMockInitialValues({'userId': 1}); await debugSeedStore( 'completed_bookings', jsonEncode([ finished( 'DM240817', 'Sundar Textiles', '78, Cross Cut Road, Gandhipuram, Coimbatore', type: 'combined', cash: 2450, minsAgo: 22, ), finished( 'DM240816', 'Green Leaf Grocers', '29, Kovaipudur Main Road, Coimbatore', type: 'delivery', minsAgo: 68, ), ]), ); 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: ActivityPage()), ), ); await tester.pumpAndSettle(const Duration(seconds: 2)); } testWidgets('a finished stop is not a card', (tester) async { await pumpActivity(tester); expect(find.text('Sundar Textiles'), findsOneWidget); // `cardSurface` was the row fill and `pureSurface` the fill of the figure // panel nested inside it. Neither may come back: on a white page a filled // row is a box round a list item, and a filled panel inside a filled row is // the shape this page was rebuilt to remove. final fills = tester .widgetList(find.byType(Container)) .map((c) => c.decoration) .whereType() .map((d) => d.color) .toList(); expect(fills, isNot(contains(ColorConstants.cardSurface))); }); // ── A history row says who, when and how much, and nothing else ── // // Every finished stop used to carry a two-line address and a verdict // sentence — `Pickup & Delivery · On time · +2.1 km over · ₹2,450` — so a // rider looking for one drop read four to six lines per row to find it. // // The type and the compliance figures did not disappear; they are fields in // the row's own detail panel, which is where a rider goes when he wants them // for ONE stop rather than for all forty. testWidgets('the collapsed row carries no verdict sentence', (tester) async { await pumpActivity(tester); // The subject of the row. expect(find.text('Sundar Textiles'), findsOneWidget); // `On time` on every row separates nothing. Only exceptions get words now. expect(find.text('On time'), findsNothing); expect(find.text('Time not measured'), findsNothing); // `titleLabel`, the sentence-case type word, was the verdict line's lead // and is gone with it. `badgeLabel` is a field value in the detail panel, // which `AnimatedCrossFade` builds whether or not the row is open. expect(find.text('Pickup & Delivery'), findsNothing); expect(find.text('P&D'), findsNothing); // The type is the record's business, one tap away — see // `test/delivery_details_test.dart`. expect(find.textContaining('PICKUP & DELIVERY'), findsNothing); // ── The reference left the row ── // // It used to head the card, on the argument that an office reads it down // a phone at a rider. That is true and it is still the wrong place for it: // the office asks about *one* stop, and the rider finds that stop by the // customer's name — which he then taps. Printing a database identifier on // every row of a shift log makes him scan past twelve of them to reach the // one name he is looking for. It lives on the record page. expect(find.text('#DM240817'), findsNothing); }); testWidgets('the four tabs agree on what colour the paper is', ( tester, ) async { // Account was on `surface` (#FCF9F8, a warm off-white) while Home and // Activity were on `pureSurface`. Bookings hides its background under // cards; Account is bare rows, so the whole tab read a shade dirtier. // // The paper moved: the scaffold's own background is the **brand** now, so // it shows through the two rounded top corners of [MilerSheet], and the // sheet paints the page. Both halves are contracts — a tab whose ground is // white draws no corners at all, and a tab whose sheet is off-white is the // original bug one layer down — so both are asserted. T? firstOf() { final found = []; void visit(Element e) { final w = e.widget; if (w is T) found.add(w); e.visitChildren(visit); } WidgetsBinding.instance.rootElement!.visitChildren(visit); return found.isEmpty ? null : found.first; } SharedPreferences.setMockInitialValues({'userId': 1}); tester.view.physicalSize = const Size(390 * 3, 844 * 3); tester.view.devicePixelRatio = 3.0; addTearDown(tester.view.reset); for (final page in [const ActivityPage(), const ProfilePage()]) { await tester.pumpWidget( ScreenUtilInit( designSize: const Size(390, 844), builder: (_, _) => MaterialApp(home: page), ), ); await tester.pump(); expect( firstOf()?.backgroundColor, ColorConstants.primary, reason: 'the ground behind the sheet corners must be the brand', ); final sheet = firstOf(); expect(sheet, isNotNull, reason: 'every tab sits in a MilerSheet'); // What the tabs must agree on is the *ladder*, not one flat paper: // a sheet is either the working white (rows drawn straight on it — // Activity) or the canvas with white panels standing on it (Home, // Account). What stays banned is what this test was written against — // the warm off-white that made one tab read a shade dirtier than the // rest. expect( sheet!.color ?? ColorConstants.pureSurface, anyOf(ColorConstants.pureSurface, ColorConstants.daylightSurface), reason: 'a sheet is a rung of the surface ladder, never an off-white', ); } }); // ── A row offers one gesture, and it is the whole row ── // // The card carried a footer band: a hairline, ~22pt of gaps, the booking // reference, and a red bold `Details ›` — which had **no `onTap`**. The // GlassCard around it was the gesture and always had been, so the loudest // element on a finished stop was a link that could not be pressed, pointing // at the tap the rider was already making. It cost ~41pt on every row of a // page that exists to be scanned. // // The reference stayed — the office recites it down a phone at him — and // moved onto the right edge of the measurements line, which was empty. testWidgets('a finished row has no second way in', (tester) async { await pumpActivity(tester); expect( find.text('Details'), findsNothing, reason: 'a control that cannot be pressed is worse than no control', ); // The chevron RETURNED, deliberately, after the Home disclosure pass // taught the difference: the sin was a bold red "Details ›" styled as a // second button; a quiet border-grey chevron on the name line is the // opposite thing — the passive "this goes somewhere" signal every // Account row wears, marking the gesture the whole card owns. expect( find.byIcon(LucideIcons.chevronRight), findsWidgets, reason: 'a tappable row without any affordance is a secret', ); // The reference is on the record page now, not the row — see above. expect(find.text('#DM240817'), findsNothing); }); // ── `the reference shares the measurements line` is gone ── // // It pinned the reference and the duration onto one storey, which was the // right fix while the reference was on the row at all. It is not on the row // any more, so the assertion has no subject — and a test kept alive by // rewriting it to check something it was never about is worse than one // deleted with its reasoning recorded. }