import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:doormile_cx/data/dev_doormile_api.dart'; import 'package:doormile_cx/data/doormile_api.dart'; import 'package:doormile_cx/data/models.dart'; import 'package:doormile_cx/state/app_scope.dart'; import 'package:doormile_cx/state/app_state.dart'; import 'package:doormile_cx/ui/screens/account_screen.dart'; import 'package:doormile_cx/ui/screens/settings/about_screen.dart'; import 'package:doormile_cx/ui/screens/settings/notifications_screen.dart'; import 'package:doormile_cx/ui/screens/settings/payment_screen.dart'; import 'package:doormile_cx/ui/screens/settings/support_screen.dart'; import 'package:doormile_cx/ui/widgets/inputs.dart'; /// ───────────────────────────────────────────────────────────────────────── /// EVERY ROW ON ACCOUNT OPENS SOMETHING /// /// Two of these rows had no `onTap` at all and three answered with a toast. /// All five drew a chevron, which is the affordance for "this opens a page" — /// so the screen was making a promise in five places and keeping it in one. /// /// A tap that silently does nothing is the worst version of this, because the /// customer cannot tell it from a page that is slow: they press it again. /// /// The check is deliberately shallow and broad. It does not care what is on /// each page; it cares that the tap goes somewhere, for every row, which is /// the thing that was wrong and the thing that quietly comes back when a row /// is added in a hurry. /// ───────────────────────────────────────────────────────────────────────── void main() { late AppState app; setUp(() async { DoormileApi.overrideInstance(DevDoormileApi()); app = AppState() // Straight onto the state rather than through the sign-in screens: this // is about what Account's rows do, and walking the OTP flow to find out // would make every one of these tests fail for reasons in another file. ..customer = const Customer( id: 'c1', name: 'Joe Oommen', phone: '+91 98765 43210', email: 'joe@example.com', ); await app.refreshOrders(); }); tearDown(() => DoormileApi.overrideInstance(null)); Future pumpAccount(WidgetTester tester) async { // A tall surface so the whole list is laid out. The default 800x600 window // leaves the About group below the fold, and `tap` on an off-screen widget // warns and misses rather than failing — which reads as "the row does not // work" and is the same symptom this file exists to catch. tester.view.physicalSize = const Size(1230, 9000); tester.view.devicePixelRatio = 3.0; addTearDown(tester.view.reset); await tester.pumpWidget( AppScope( state: app, child: const MaterialApp(home: AccountScreen()), ), ); await tester.pumpAndSettle(); } /// Taps a row by its label and lets whatever it opens settle. Future tapRow(WidgetTester tester, String label) async { final row = find.ancestor( of: find.text(label), matching: find.byType(DmRow), ); expect(row, findsOneWidget, reason: 'no row labelled "$label"'); await tester.tap(row); await tester.pumpAndSettle(); } group('the rows that led nowhere', () { testWidgets('Notifications opens its page', (tester) async { await pumpAccount(tester); await tapRow(tester, 'Notifications'); expect(find.byType(NotificationsScreen), findsOneWidget); }); testWidgets('Payment methods opens its page', (tester) async { await pumpAccount(tester); await tapRow(tester, 'Payment methods'); expect(find.byType(PaymentScreen), findsOneWidget); }); testWidgets('Help and support opens its page', (tester) async { await pumpAccount(tester); await tapRow(tester, 'Help and support'); expect(find.byType(SupportScreen), findsOneWidget); }); testWidgets('both policy rows open About', (tester) async { await pumpAccount(tester); await tapRow(tester, 'Terms and policies'); expect(find.byType(AboutScreen), findsOneWidget); // Under its own name, not under the other row's. expect(find.text('Terms and policies'), findsWidgets); // Not `pageBack()`: that looks for a Material or Cupertino back button // and this app's bar carries its own `DmBackButton`. tester.state(find.byType(Navigator).first).pop(); await tester.pumpAndSettle(); await tapRow(tester, 'About Doormile'); expect(find.byType(AboutScreen), findsOneWidget); }); }); group('no row draws a chevron it cannot honour', () { testWidgets('every chevron row is tappable', (tester) async { await pumpAccount(tester); final rows = tester.widgetList(find.byType(DmRow)).toList(); expect(rows, isNotEmpty); final broken = [ for (final row in rows) if (row.showChevron && row.onTap == null) row.label, ]; expect( broken, isEmpty, reason: 'these rows point at a page and swallow the tap: $broken', ); }); }); group('the pages say only what is true', () { testWidgets('Notifications does not promise push', (tester) async { await pumpAccount(tester); await tapRow(tester, 'Notifications'); // No push SDK is wired in — `AppState.registerPushToken` has nothing to // hand over — so the screen states that rather than offering a switch. expect(find.textContaining('not switched on yet'), findsOneWidget); expect(find.byType(Switch), findsNothing); }); testWidgets('Payment offers nothing to add or save', (tester) async { await pumpAccount(tester); await tapRow(tester, 'Payment methods'); expect(find.textContaining('Nothing is charged'), findsOneWidget); expect(find.textContaining('Add card'), findsNothing); }); testWidgets('Support shows no contact until one is configured', ( tester, ) async { await pumpAccount(tester); await tapRow(tester, 'Help and support'); // AppConfig.supportPhone/Email are empty unless passed at build time. // A support line that rings nowhere is worse than none, so the block is // absent rather than invented. expect(find.text('Talk to us'), findsNothing); expect(find.text('Copy details'), findsOneWidget); }); }); }