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:shared_preferences/shared_preferences.dart'; import 'package:miler/controllers/rewards_controller.dart'; import 'package:miler/controllers/summary_controller.dart'; import 'package:miler/data/service_profile.dart'; import 'package:miler/views/Dashboard/profile/Profilepage.dart'; import 'package:miler/views/Dashboard/profile/informations/help_center.dart'; import 'package:miler/views/helpers/constants/Colorconstants.dart'; import 'package:miler/views/helpers/constants/design_constants.dart'; import 'package:miler/views/helpers/widgets/app_widgets.dart'; import 'package:miler/views/helpers/widgets/settings_ui.dart'; /// ── The rider's control centre, not a settings screen ── /// /// Account opened on four boxes: an outlined card holding the rider's name, and /// three more side by side holding two figures and a constant, each with a /// saturated brand glyph on it. Under them, twelve rows whose icons were drawn /// in the same near-black as the words they labelled, and at the bottom a /// red-bordered notice with Logout tucked under it in the brand colour, at the /// same weight as the row that picks an alert tone. /// /// It is one page on one surface now: the identity on the paper, the figures on /// a single quiet fill, the icons demoted under the titles they mark, and the /// one row that ends the session set apart and drawn destructive. /// /// `profile_pages_test.dart` holds the bars on every screen this page opens. /// This file holds the page itself. void main() { Future pump( WidgetTester tester, { Map prefs = const {}, ServiceProfile profile = ServiceProfile.parcel, int points = 1240, int pickups = 318, String figuresError = '', double height = 1600, }) async { ServiceProfile.setActive(profile); // 0 keeps the page from firing its two network fetches, so the figures // seeded below are the ones under test. SharedPreferences.setMockInitialValues({'userid': 0, ...prefs}); Get.put(RewardsController()) ..totalPoints.value = points ..isLoading.value = false ..error.value = figuresError; Get.put(SummaryController()) ..total.value = pickups ..isLoading.value = false; tester.view.physicalSize = Size(390 * 3, height * 3); tester.view.devicePixelRatio = 3.0; addTearDown(tester.view.reset); await tester.pumpWidget( ScreenUtilInit( designSize: const Size(390, 844), builder: (_, _) => const MaterialApp(home: ProfilePage()), ), ); await tester.pumpAndSettle(); } tearDown(() { Get.reset(); ServiceProfile.setActive(ServiceProfile.parcel); }); const rider = { 'user_name': 'Joe Mathew', 'user_email': 'joe.mathew@doormile.in', 'contactno': '+91 98765 43210', }; testWidgets('identity leads, and it is not a card', (tester) async { await pump(tester, prefs: rider); expect(find.text('Joe Mathew'), findsOneWidget); expect(find.text('+91 98765 43210'), findsOneWidget); // The header, the three figures and every section used to be a // `SettingsCard`. Nothing on this page is one now — the only fill above the // fold is the strip holding the figures, and it carries no outline. expect(find.byType(SettingsCard), findsNothing); // "View profile" was a second control for the destination the row already // goes to, taking a third line to say so. expect(find.text('View profile'), findsNothing); }); testWidgets('the line of work is read, never named', (tester) async { await pump(tester, prefs: rider); expect(find.text(ServiceProfile.parcel.label), findsOneWidget); expect(find.text('Bookings'), findsOneWidget); expect(find.text('What a new booking sounds like'), findsOneWidget); await tester.pumpWidget(const SizedBox()); Get.reset(); await pump(tester, prefs: rider, profile: ServiceProfile.milkMan); expect(find.text(ServiceProfile.milkMan.label), findsOneWidget); // A milk-run rider does not take bookings, and every stop at a customer's // door is a drop. Both strings follow `ServiceProfile.active`. expect(find.text('Deliveries'), findsOneWidget); expect(find.text('What a new delivery sounds like'), findsOneWidget); expect(find.text('Bookings'), findsNothing); }); testWidgets('a rider who has entered nothing is told nothing', ( tester, ) async { await pump(tester); // These defaulted to `rider@miler.com` and `Not added` and were rendered as // facts — so a new rider's own account page stated an email he had never // given. A field with nothing behind it is a field the page does not draw. expect(find.textContaining('rider@miler.com'), findsNothing); expect(find.text('Not added'), findsNothing); // The row it belonged to is still there, without its second line. expect(find.text('Edit Profile'), findsOneWidget); }); testWidgets('a figure that could not be fetched is a gap, not a zero', ( tester, ) async { await pump(tester, prefs: rider, figuresError: "Couldn't load your points"); expect(find.text('—'), findsOneWidget); expect(find.text('1,240'), findsNothing); // Compact and actionable: one line and a retry, not a screen the whole tab // has to sit in — everything else on this page is read off the phone. expect(find.text('Could not refresh these figures'), findsOneWidget); expect(find.text('Retry'), findsOneWidget); }); testWidgets('ending the session is not a preference', (tester) async { await pump(tester, prefs: rider); final logout = tester.widget(find.text('Logout')); // Brand maroon is navigation and identity; red destroys. Logout was drawn // in the brand colour, which made the row that ends the session the same // colour as the wordmark above it. expect(logout.style!.color, ColorConstants.errorRed); // Nothing is being navigated to, so there is no chevron on it — and it sits // below a rule with a screenful of air above, rather than 18pt under the // emergency banner in the shape of a settings group. final row = find.ancestor( of: find.text('Logout'), matching: find.byType(SettingsRow), ); expect(tester.widget(row).danger, isTrue); expect( tester.getRect(find.text('Logout')).top - tester.getRect(find.byType(EmergencyRow)).bottom, greaterThan(30), ); }); testWidgets('the emergency row is a wash, not an outline', (tester) async { await pump(tester, prefs: rider); final box = tester.widget( find .descendant( of: find.byType(EmergencyRow), matching: find.byType(Container), ) .first, ); final decoration = box.decoration! as BoxDecoration; expect(decoration.border, isNull, reason: 'no red outline'); expect(decoration.color, isNot(ColorConstants.primary)); // The words are the page's own near-black so they read at a glance; the // red is spent on the two marks that carry the meaning. expect( tester.widget(find.text('Emergency help')).style!.color, ColorConstants.slateText, ); expect( find.text('Calls 112 if you are in immediate danger'), findsOneWidget, ); }); testWidgets('Help & Support draws the same emergency row', (tester) async { SharedPreferences.setMockInitialValues({'userid': 0}); tester.view.physicalSize = const Size(390 * 3, 900 * 3); tester.view.devicePixelRatio = 3.0; addTearDown(tester.view.reset); await tester.pumpWidget( ScreenUtilInit( designSize: const Size(390, 844), builder: (_, _) => const MaterialApp(home: HelpCenter()), ), ); await tester.pumpAndSettle(); // It used to be a second, differently-worded copy of the same banner. The // sentence promising a rider what a press will do is not a thing to have // two versions of. expect(find.byType(EmergencyRow), findsOneWidget); expect(find.text('Emergency help'), findsOneWidget); }); testWidgets('every row is a target a thumb can hit', (tester) async { await pump(tester, prefs: rider); for (final title in const [ 'Earnings', 'Edit Profile', 'Saved Address', 'Alert Sound', 'Notifications', 'Help & Support', 'FAQ', 'Terms & Conditions', 'Logout', ]) { final row = find.ancestor( of: find.text(title), matching: find.byType(SettingsRow), ); expect(row, findsOneWidget, reason: '"$title" is a row'); expect( tester.getSize(row).height, greaterThanOrEqualTo(ButtonSizes.minTapTarget), reason: '"$title" is smaller than a fingertip', ); } }); testWidgets('the icons do not outrank the titles they mark', (tester) async { await pump(tester, prefs: rider); // Twelve glyphs in `onSurface` read as loudly as the twelve things they // were labelling. The title leads; the icon locates it. final icon = tester.widget( find .descendant( of: find.ancestor( of: find.text('Saved Address'), matching: find.byType(SettingsRow), ), matching: find.byType(Icon), ) .first, ); expect(icon.color, ColorConstants.secondaryText); expect(icon.color, isNot(ColorConstants.onSurface)); }); testWidgets('a skeleton fills what it is given and stops', (tester) async { // Five placeholder cards are ~820pt tall and the body of a pushed page on // the shortest supported phone is ~768, so the screen whose whole job is to // say "this is coming" greeted the rider with an overflow band. tester.view.physicalSize = const Size(390 * 3, 700 * 3); tester.view.devicePixelRatio = 3.0; addTearDown(tester.view.reset); await tester.pumpWidget( ScreenUtilInit( designSize: const Size(390, 844), builder: (_, _) => const MaterialApp(home: Scaffold(body: SkeletonList(count: 5))), ), ); await tester.pump(const Duration(milliseconds: 100)); expect(tester.takeException(), isNull); }); }