// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;

import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  late DateTime firstDate;
  late DateTime lastDate;
  late DateTime? initialDate;
  late DateTime today;
  late SelectableDayPredicate? selectableDayPredicate;
  late DatePickerEntryMode initialEntryMode;
  late DatePickerMode initialCalendarMode;
  late DatePickerEntryMode currentMode;

  String? cancelText;
  String? confirmText;
  String? errorFormatText;
  String? errorInvalidText;
  String? fieldHintText;
  String? fieldLabelText;
  String? helpText;
  TextInputType? keyboardType;

  final Finder nextMonthIcon = find.byWidgetPredicate((Widget w) => w is IconButton && (w.tooltip?.startsWith('Next month') ?? false));
  final Finder previousMonthIcon = find.byWidgetPredicate((Widget w) => w is IconButton && (w.tooltip?.startsWith('Previous month') ?? false));
  final Finder switchToInputIcon = find.byIcon(Icons.edit);
  final Finder switchToCalendarIcon = find.byIcon(Icons.calendar_today);

  TextField textField(WidgetTester tester) {
    return tester.widget<TextField>(find.byType(TextField));
  }

  setUp(() {
    firstDate = DateTime(2001);
    lastDate = DateTime(2031, DateTime.december, 31);
    initialDate = DateTime(2016, DateTime.january, 15);
    today = DateTime(2016, DateTime.january, 3);
    selectableDayPredicate = null;
    initialEntryMode = DatePickerEntryMode.calendar;
    initialCalendarMode = DatePickerMode.day;

    cancelText = null;
    confirmText = null;
    errorFormatText = null;
    errorInvalidText = null;
    fieldHintText = null;
    fieldLabelText = null;
    helpText = null;
    keyboardType = null;
    currentMode = initialEntryMode;
  });

  const Size wideWindowSize = Size(1920.0, 1080.0);
  const Size narrowWindowSize = Size(1070.0, 1770.0);

  Future<void> prepareDatePicker(
    WidgetTester tester,
    Future<void> Function(Future<DateTime?> date) callback, {
    TextDirection textDirection = TextDirection.ltr,
    bool useMaterial3 = false,
    ThemeData? theme,
    TextScaler textScaler = TextScaler.noScaling,
  }) async {
    late BuildContext buttonContext;
    await tester.pumpWidget(MaterialApp(
      theme: theme ?? ThemeData(useMaterial3: useMaterial3),
      home: MediaQuery(
        data: MediaQueryData(textScaler: textScaler),
        child: Material(
          child: Builder(
            builder: (BuildContext context) {
              return ElevatedButton(
                onPressed: () {
                  buttonContext = context;
                },
                child: const Text('Go'),
              );
            },
          ),
        ),
      ),
    ));

    await tester.tap(find.text('Go'));
    expect(buttonContext, isNotNull);

    final Future<DateTime?> date = showDatePicker(
      context: buttonContext,
      initialDate: initialDate,
      firstDate: firstDate,
      lastDate: lastDate,
      currentDate: today,
      selectableDayPredicate: selectableDayPredicate,
      initialDatePickerMode: initialCalendarMode,
      initialEntryMode: initialEntryMode,
      cancelText: cancelText,
      confirmText: confirmText,
      errorFormatText: errorFormatText,
      errorInvalidText: errorInvalidText,
      fieldHintText: fieldHintText,
      fieldLabelText: fieldLabelText,
      helpText: helpText,
      keyboardType: keyboardType,
      onDatePickerModeChange: (DatePickerEntryMode value) {
        currentMode = value;
      },
      builder: (BuildContext context, Widget? child) {
        return Directionality(
          textDirection: textDirection,
          child: child ?? const SizedBox(),
        );
      },
    );

    await tester.pumpAndSettle(const Duration(seconds: 1));
    await callback(date);
  }

  group('showDatePicker Dialog', () {
    testWidgetsWithLeakTracking('Default dialog size', (WidgetTester tester) async {
      Future<void> showPicker(WidgetTester tester, Size size) async {
        tester.view.physicalSize = size;
        tester.view.devicePixelRatio = 1.0;
        addTearDown(tester.view.reset);
        await prepareDatePicker(tester, (Future<DateTime?> date) async {}, useMaterial3: true);
      }
      const Size calendarLandscapeDialogSize = Size(496.0, 346.0);
      const Size calendarPortraitDialogSizeM3 = Size(328.0, 512.0);

      // Test landscape layout.
      await showPicker(tester, wideWindowSize);

      Size dialogContainerSize = tester.getSize(find.byType(AnimatedContainer));
      expect(dialogContainerSize, calendarLandscapeDialogSize);

      // Close the dialog.
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();

      // Test portrait layout.
      await showPicker(tester, narrowWindowSize);

      dialogContainerSize = tester.getSize(find.byType(AnimatedContainer));
      expect(dialogContainerSize, calendarPortraitDialogSizeM3);
    });

    testWidgetsWithLeakTracking('Default dialog properties', (WidgetTester tester) async {
      final ThemeData theme = ThemeData(useMaterial3: true);
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        final Material dialogMaterial = tester.widget<Material>(
          find.descendant(of: find.byType(Dialog),
          matching: find.byType(Material),
        ).first);

        expect(dialogMaterial.color, theme.colorScheme.surface);
        expect(dialogMaterial.shadowColor, Colors.transparent);
        expect(dialogMaterial.surfaceTintColor, theme.colorScheme.surfaceTint);
        expect(dialogMaterial.elevation, 6.0);
        expect(
          dialogMaterial.shape,
          const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0))),
        );
        expect(dialogMaterial.clipBehavior, Clip.antiAlias);

        final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog));
        expect(dialog.insetPadding, const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0));
      }, useMaterial3: theme.useMaterial3);
    });

    testWidgetsWithLeakTracking('Material3 uses sentence case labels', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.text('Select date'), findsOneWidget);
      }, useMaterial3: true);
    });

    testWidgetsWithLeakTracking('Cancel, confirm, and help text is used', (WidgetTester tester) async {
      cancelText = 'nope';
      confirmText = 'yep';
      helpText = 'help';
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.text(cancelText!), findsOneWidget);
        expect(find.text(confirmText!), findsOneWidget);
        expect(find.text(helpText!), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Initial date is the default', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('OK'));
        expect(await date, DateTime(2016, DateTime.january, 15));
      });
    });

    testWidgetsWithLeakTracking('Can cancel', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('CANCEL'));
        expect(await date, isNull);
      });
    });

    testWidgetsWithLeakTracking('Can switch from calendar to input entry mode', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.byType(TextField), findsNothing);
        await tester.tap(find.byIcon(Icons.edit));
        await tester.pumpAndSettle();
        expect(find.byType(TextField), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Can switch from input to calendar entry mode', (WidgetTester tester) async {
      initialEntryMode = DatePickerEntryMode.input;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.byType(TextField), findsOneWidget);
        await tester.tap(find.byIcon(Icons.calendar_today));
        await tester.pumpAndSettle();
        expect(find.byType(TextField), findsNothing);
      });
    });

    testWidgetsWithLeakTracking('Can not switch out of calendarOnly mode', (WidgetTester tester) async {
      initialEntryMode = DatePickerEntryMode.calendarOnly;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.byType(TextField), findsNothing);
        expect(find.byIcon(Icons.edit), findsNothing);
      });
    });

    testWidgetsWithLeakTracking('Can not switch out of inputOnly mode', (WidgetTester tester) async {
      initialEntryMode = DatePickerEntryMode.inputOnly;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.byType(TextField), findsOneWidget);
        expect(find.byIcon(Icons.calendar_today), findsNothing);
      });
    });

    testWidgetsWithLeakTracking('Switching to input mode keeps selected date', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('12'));
        await tester.tap(find.byIcon(Icons.edit));
        await tester.pumpAndSettle();
        await tester.tap(find.text('OK'));
        expect(await date, DateTime(2016, DateTime.january, 12));
      });
    });

    testWidgetsWithLeakTracking('Input only mode should validate date', (WidgetTester tester) async {
      initialEntryMode = DatePickerEntryMode.inputOnly;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        // Enter text input mode and type an invalid date to get error.
        await tester.enterText(find.byType(TextField), '1234567');
        await tester.tap(find.text('OK'));
        await tester.pumpAndSettle();
        expect(find.text('Invalid format.'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Switching to input mode resets input error state', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        // Enter text input mode and type an invalid date to get error.
        await tester.tap(find.byIcon(Icons.edit));
        await tester.pumpAndSettle();
        await tester.enterText(find.byType(TextField), '1234567');
        await tester.tap(find.text('OK'));
        await tester.pumpAndSettle();
        expect(find.text('Invalid format.'), findsOneWidget);

        // Toggle to calendar mode and then back to input mode
        await tester.tap(find.byIcon(Icons.calendar_today));
        await tester.pumpAndSettle();
        await tester.tap(find.byIcon(Icons.edit));
        await tester.pumpAndSettle();
        expect(find.text('Invalid format.'), findsNothing);

        // Edit the text, the error should not be showing until ok is tapped
        await tester.enterText(find.byType(TextField), '1234567');
        await tester.pumpAndSettle();
        expect(find.text('Invalid format.'), findsNothing);
      });
    });

    testWidgetsWithLeakTracking('builder parameter', (WidgetTester tester) async {
      Widget buildFrame(TextDirection textDirection) {
        return MaterialApp(
          home: Material(
            child: Center(
              child: Builder(
                builder: (BuildContext context) {
                  return ElevatedButton(
                    child: const Text('X'),
                    onPressed: () {
                      showDatePicker(
                        context: context,
                        initialDate: DateTime.now(),
                        firstDate: DateTime(2018),
                        lastDate: DateTime(2030),
                        builder: (BuildContext context, Widget? child) {
                          return Directionality(
                            textDirection: textDirection,
                            child: child ?? const SizedBox(),
                          );
                        },
                      );
                    },
                  );
                },
              ),
            ),
          ),
        );
      }

      await tester.pumpWidget(buildFrame(TextDirection.ltr));
      await tester.tap(find.text('X'));
      await tester.pumpAndSettle();
      final double ltrOkRight = tester.getBottomRight(find.text('OK')).dx;

      await tester.tap(find.text('OK')); // Dismiss the dialog.
      await tester.pumpAndSettle();

      await tester.pumpWidget(buildFrame(TextDirection.rtl));
      await tester.tap(find.text('X'));
      await tester.pumpAndSettle();

      // Verify that the time picker is being laid out RTL.
      // We expect the left edge of the 'OK' button in the RTL
      // layout to match the gap between right edge of the 'OK'
      // button and the right edge of the 800 wide view.
      expect(tester.getBottomLeft(find.text('OK')).dx, moreOrLessEquals(800 - ltrOkRight));
    });

    group('Barrier dismissible', () {
      late _DatePickerObserver rootObserver;

      setUp(() {
        rootObserver = _DatePickerObserver();
      });

      testWidgetsWithLeakTracking('Barrier is dismissible with default parameter', (WidgetTester tester) async {
        await tester.pumpWidget(
          MaterialApp(
            navigatorObservers: <NavigatorObserver>[rootObserver],
            home: Material(
              child: Center(
                child: Builder(
                  builder: (BuildContext context) {
                    return ElevatedButton(
                      child: const Text('X'),
                      onPressed: () =>
                          showDatePicker(
                            context: context,
                            initialDate: DateTime.now(),
                            firstDate: DateTime(2018),
                            lastDate: DateTime(2030),
                            builder: (BuildContext context,
                                Widget? child) => const SizedBox(),
                          ),
                    );
                  },
                ),
              ),
            ),
          ),
        );

        // Open the dialog.
        await tester.tap(find.byType(ElevatedButton));
        await tester.pumpAndSettle();
        expect(rootObserver.datePickerCount, 1);

        // Tap on the barrier.
        await tester.tapAt(const Offset(10.0, 10.0));
        await tester.pumpAndSettle();
        expect(rootObserver.datePickerCount, 0);
      });

      testWidgetsWithLeakTracking('Barrier is not dismissible with barrierDismissible is false', (WidgetTester tester) async {
        await tester.pumpWidget(
          MaterialApp(
            navigatorObservers: <NavigatorObserver>[rootObserver],
            home: Material(
              child: Center(
                child: Builder(
                  builder: (BuildContext context) {
                    return ElevatedButton(
                      child: const Text('X'),
                      onPressed: () =>
                          showDatePicker(
                            context: context,
                            initialDate: DateTime.now(),
                            firstDate: DateTime(2018),
                            lastDate: DateTime(2030),
                            barrierDismissible: false,
                            builder: (BuildContext context,
                                Widget? child) => const SizedBox(),
                          ),
                    );
                  },
                ),
              ),
            ),
          ),
        );

        // Open the dialog.
        await tester.tap(find.byType(ElevatedButton));
        await tester.pumpAndSettle();
        expect(rootObserver.datePickerCount, 1);

        // Tap on the barrier, which shouldn't do anything this time.
        await tester.tapAt(const Offset(10.0, 10.0));
        await tester.pumpAndSettle();
        expect(rootObserver.datePickerCount, 1);
      });
    });

    testWidgetsWithLeakTracking('Barrier color', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: Center(
              child: Builder(
                builder: (BuildContext context) {
                  return ElevatedButton(
                    child: const Text('X'),
                    onPressed: () =>
                        showDatePicker(
                          context: context,
                          initialDate: DateTime.now(),
                          firstDate: DateTime(2018),
                          lastDate: DateTime(2030),
                          builder: (BuildContext context,
                              Widget? child) => const SizedBox(),
                        ),
                  );
                },
              ),
            ),
          ),
        ),
      );

      // Open the dialog.
      await tester.tap(find.byType(ElevatedButton));
      await tester.pumpAndSettle();
      expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.black54);

      // Dismiss the dialog.
      await tester.tapAt(const Offset(10.0, 10.0));

      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: Center(
              child: Builder(
                builder: (BuildContext context) {
                  return ElevatedButton(
                    child: const Text('X'),
                    onPressed: () =>
                        showDatePicker(
                          context: context,
                          barrierColor: Colors.pink,
                          initialDate: DateTime.now(),
                          firstDate: DateTime(2018),
                          lastDate: DateTime(2030),
                          builder: (BuildContext context,
                              Widget? child) => const SizedBox(),
                        ),
                  );
                },
              ),
            ),
          ),
        ),
      );

      // Open the dialog.
      await tester.tap(find.byType(ElevatedButton));
      await tester.pumpAndSettle();
      expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.pink);
    });

    testWidgetsWithLeakTracking('Barrier Label', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: Material(
            child: Center(
              child: Builder(
                builder: (BuildContext context) {
                  return ElevatedButton(
                    child: const Text('X'),
                    onPressed: () =>
                        showDatePicker(
                          context: context,
                          barrierLabel: 'Custom Label',
                          initialDate: DateTime.now(),
                          firstDate: DateTime(2018),
                          lastDate: DateTime(2030),
                          builder: (BuildContext context,
                              Widget? child) => const SizedBox(),
                        ),
                  );
                },
              ),
            ),
          ),
        ),
      );

      // Open the dialog.
      await tester.tap(find.byType(ElevatedButton));
      await tester.pumpAndSettle();
      expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).semanticsLabel, 'Custom Label');
    });

    testWidgetsWithLeakTracking('uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
      final _DatePickerObserver rootObserver = _DatePickerObserver();
      final _DatePickerObserver nestedObserver = _DatePickerObserver();

      await tester.pumpWidget(MaterialApp(
        navigatorObservers: <NavigatorObserver>[rootObserver],
        home: Navigator(
          observers: <NavigatorObserver>[nestedObserver],
          onGenerateRoute: (RouteSettings settings) {
            return MaterialPageRoute<dynamic>(
              builder: (BuildContext context) {
                return ElevatedButton(
                  onPressed: () {
                    showDatePicker(
                      context: context,
                      useRootNavigator: false,
                      initialDate: DateTime.now(),
                      firstDate: DateTime(2018),
                      lastDate: DateTime(2030),
                      builder: (BuildContext context, Widget? child) => const SizedBox(),
                    );
                  },
                  child: const Text('Show Date Picker'),
                );
              },
            );
          },
        ),
      ));

      // Open the dialog.
      await tester.tap(find.byType(ElevatedButton));

      expect(rootObserver.datePickerCount, 0);
      expect(nestedObserver.datePickerCount, 1);
    });

    testWidgetsWithLeakTracking('honors DialogTheme for shape and elevation', (WidgetTester tester) async {
      // Test that the defaults work
      const DialogTheme datePickerDefaultDialogTheme = DialogTheme(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(Radius.circular(4.0)),
        ),
        elevation: 24,
      );
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData(useMaterial3: false),
          home: Center(
            child: Builder(
              builder: (BuildContext context) {
                return ElevatedButton(
                  child: const Text('X'),
                  onPressed: () {
                    showDatePicker(
                      context: context,
                      initialDate: DateTime.now(),
                      firstDate: DateTime(2018),
                      lastDate: DateTime(2030),
                    );
                  },
                );
              },
            ),
          ),
        ),
      );
      await tester.tap(find.text('X'));
      await tester.pumpAndSettle();
      final Material defaultDialogMaterial = tester.widget<Material>(find.descendant(
        of: find.byType(Dialog), matching: find.byType(Material)).first);
      expect(defaultDialogMaterial.shape, datePickerDefaultDialogTheme.shape);
      expect(defaultDialogMaterial.elevation, datePickerDefaultDialogTheme.elevation);

      // Test that it honors ThemeData.dialogTheme settings
      const DialogTheme customDialogTheme = DialogTheme(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(Radius.circular(40.0)),
        ),
        elevation: 50,
      );
      await tester.pumpWidget(
        MaterialApp(
          theme: ThemeData.fallback(useMaterial3: false).copyWith(dialogTheme: customDialogTheme),
          home: Center(
            child: Builder(
              builder: (BuildContext context) {
                return ElevatedButton(
                  child: const Text('X'),
                  onPressed: () {
                    showDatePicker(
                      context: context,
                      initialDate: DateTime.now(),
                      firstDate: DateTime(2018),
                      lastDate: DateTime(2030),
                    );
                  },
                );
              },
            ),
          ),
        ),
      );
      await tester.pump(); // start theme animation
      await tester.pump(const Duration(seconds: 5)); // end theme animation
      final Material themeDialogMaterial = tester.widget<Material>(find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first);
      expect(themeDialogMaterial.shape, customDialogTheme.shape);
      expect(themeDialogMaterial.elevation, customDialogTheme.elevation);
    });

    testWidgetsWithLeakTracking('OK Cancel button layout', (WidgetTester tester) async {
       Widget buildFrame(TextDirection textDirection) {
         return MaterialApp(
           theme: ThemeData(useMaterial3: false),
           home: Material(
             child: Center(
               child: Builder(
                 builder: (BuildContext context) {
                   return ElevatedButton(
                     child: const Text('X'),
                     onPressed: () {
                       showDatePicker(
                         context: context,
                         initialDate: DateTime(2016, DateTime.january, 15),
                         firstDate:DateTime(2001),
                         lastDate: DateTime(2031, DateTime.december, 31),
                         builder: (BuildContext context, Widget? child) {
                           return Directionality(
                             textDirection: textDirection,
                             child: child ?? const SizedBox(),
                           );
                         },
                       );
                     },
                   );
                 },
               ),
             ),
           ),
         );
       }

      // Default landscape layout.

      await tester.pumpWidget(buildFrame(TextDirection.ltr));
      await tester.tap(find.text('X'));
      await tester.pumpAndSettle();
      expect(tester.getBottomRight(find.text('OK')).dx, 622);
      expect(tester.getBottomLeft(find.text('OK')).dx, 594);
      expect(tester.getBottomRight(find.text('CANCEL')).dx, 560);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();

      await tester.pumpWidget(buildFrame(TextDirection.rtl));
      await tester.tap(find.text('X'));
      await tester.pumpAndSettle();
      expect(tester.getBottomRight(find.text('OK')).dx, 206);
      expect(tester.getBottomLeft(find.text('OK')).dx, 178);
      expect(tester.getBottomRight(find.text('CANCEL')).dx, 324);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();

      // Portrait layout.

      addTearDown(tester.view.reset);
      tester.view.physicalSize = const Size(900, 1200);

      await tester.pumpWidget(buildFrame(TextDirection.ltr));
      await tester.tap(find.text('X'));
      await tester.pumpAndSettle();
      expect(tester.getBottomRight(find.text('OK')).dx, 258);
      expect(tester.getBottomLeft(find.text('OK')).dx, 230);
      expect(tester.getBottomRight(find.text('CANCEL')).dx, 196);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();

      await tester.pumpWidget(buildFrame(TextDirection.rtl));
      await tester.tap(find.text('X'));
      await tester.pumpAndSettle();
      expect(tester.getBottomRight(find.text('OK')).dx, 70);
      expect(tester.getBottomLeft(find.text('OK')).dx, 42);
      expect(tester.getBottomRight(find.text('CANCEL')).dx, 188);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();
    });

    testWidgetsWithLeakTracking('honors switchToInputEntryModeIcon', (WidgetTester tester) async {
      Widget buildApp({bool? useMaterial3, Icon? switchToInputEntryModeIcon}) {
       return MaterialApp(
          theme: ThemeData(
            useMaterial3: useMaterial3 ?? false,
          ),
          home: Material(
            child: Builder(
              builder: (BuildContext context) {
                return ElevatedButton(
                  child: const Text('Click X'),
                  onPressed: () {
                    showDatePicker(
                      context: context,
                      initialDate: DateTime.now(),
                      firstDate: DateTime(2018),
                      lastDate: DateTime(2030),
                      switchToInputEntryModeIcon: switchToInputEntryModeIcon,
                    );
                  },
                );
              },
            ),
          ),
        );
      }

      await tester.pumpWidget(buildApp());
      await tester.pumpAndSettle();
      await tester.tap(find.byType(ElevatedButton));
      await tester.pumpAndSettle();
      expect(find.byIcon(Icons.edit), findsOneWidget);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();

      await tester.pumpWidget(buildApp(useMaterial3: true));
      await tester.pumpAndSettle();
      await tester.tap(find.byType(ElevatedButton));
      await tester.pumpAndSettle();
      expect(find.byIcon(Icons.edit_outlined), findsOneWidget);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();

      await tester.pumpWidget(
        buildApp(
          switchToInputEntryModeIcon: const Icon(Icons.keyboard),
        ),
      );
      await tester.pumpAndSettle();
      await tester.tap(find.byType(ElevatedButton));
      await tester.pumpAndSettle();
      expect(find.byIcon(Icons.keyboard), findsOneWidget);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();
    });

    testWidgetsWithLeakTracking('honors switchToCalendarEntryModeIcon', (WidgetTester tester) async {
      Widget buildApp({bool? useMaterial3, Icon? switchToCalendarEntryModeIcon}) {
       return MaterialApp(
          theme: ThemeData(
            useMaterial3: useMaterial3 ?? false,
          ),
          home: Material(
            child: Builder(
              builder: (BuildContext context) {
                return ElevatedButton(
                  child: const Text('Click X'),
                  onPressed: () {
                    showDatePicker(
                      context: context,
                      initialDate: DateTime.now(),
                      firstDate: DateTime(2018),
                      lastDate: DateTime(2030),
                      switchToCalendarEntryModeIcon: switchToCalendarEntryModeIcon,
                      initialEntryMode: DatePickerEntryMode.input,
                    );
                  },
                );
              },
            ),
          ),
        );
      }

      await tester.pumpWidget(buildApp());
      await tester.pumpAndSettle();
      await tester.tap(find.byType(ElevatedButton));
      await tester.pumpAndSettle();
      expect(find.byIcon(Icons.calendar_today), findsOneWidget);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();

      await tester.pumpWidget(buildApp(useMaterial3: true));
      await tester.pumpAndSettle();
      await tester.tap(find.byType(ElevatedButton));
      await tester.pumpAndSettle();
      expect(find.byIcon(Icons.calendar_today), findsOneWidget);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();

      await tester.pumpWidget(
        buildApp(
          switchToCalendarEntryModeIcon: const Icon(Icons.favorite),
        ),
      );
      await tester.pumpAndSettle();
      await tester.tap(find.byType(ElevatedButton));
      await tester.pumpAndSettle();
      expect(find.byIcon(Icons.favorite), findsOneWidget);
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();
    });
  });

  group('Calendar mode', () {
    testWidgetsWithLeakTracking('Default Calendar mode layout (Landscape)', (WidgetTester tester) async {
      final Finder helpText = find.text('Select date');
      final Finder headerText = find.text('Fri, Jan 15');
      final Finder subHeaderText = find.text('January 2016');
      final Finder cancelButtonText = find.text('Cancel');
      final Finder okButtonText = find.text('OK');
      const EdgeInsets insetPadding = EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0);

      tester.view.physicalSize = wideWindowSize;
      addTearDown(tester.view.reset);

      await tester.pumpWidget(MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: DatePickerDialog(
            initialDate: initialDate,
            firstDate: firstDate,
            lastDate: lastDate,
          ),
        ),
      ));

      expect(helpText, findsOneWidget);
      expect(headerText, findsOneWidget);
      expect(subHeaderText, findsOneWidget);
      expect(cancelButtonText, findsOneWidget);
      expect(okButtonText, findsOneWidget);

      // Test help text position.
      final Offset dialogTopLeft = tester.getTopLeft(find.byType(AnimatedContainer));
      final Offset helpTextTopLeft = tester.getTopLeft(helpText);
      expect(helpTextTopLeft.dx, dialogTopLeft.dx + (insetPadding.horizontal / 2));
      expect(helpTextTopLeft.dy, dialogTopLeft.dy + 16.0);

      // Test header text position.
      final Offset headerTextTopLeft = tester.getTopLeft(headerText);
      final Offset helpTextBottomLeft = tester.getBottomLeft(helpText);
      expect(headerTextTopLeft.dx, dialogTopLeft.dx + (insetPadding.horizontal / 2));
      expect(headerTextTopLeft.dy, helpTextBottomLeft.dy + 16.0);

      // Test switch button position.
      final Finder switchButtonM3 = find.widgetWithIcon(IconButton, Icons.edit_outlined);
      final Offset switchButtonTopLeft = tester.getTopLeft(switchButtonM3);
      final Offset headerTextBottomLeft = tester.getBottomLeft(headerText);
      expect(switchButtonTopLeft.dx, dialogTopLeft.dx + 4.0);
      expect(switchButtonTopLeft.dy, headerTextBottomLeft.dy);

      // Test vertical divider position.
      final Finder divider = find.byType(VerticalDivider);
      final Offset dividerTopLeft = tester.getTopLeft(divider);
      final Offset headerTextTopRiught = tester.getTopRight(headerText);
      expect(dividerTopLeft.dx, headerTextTopRiught.dx + 16.0);
      expect(dividerTopLeft.dy, dialogTopLeft.dy);

      // Test sub header text position.
      final Offset subHeaderTextTopLeft = tester.getTopLeft(subHeaderText);
      final Offset dividerTopRight = tester.getTopRight(divider);
      expect(subHeaderTextTopLeft.dx, dividerTopRight.dx + 24.0);
      if (!kIsWeb || isCanvasKit) { // https://github.com/flutter/flutter/issues/99933
        expect(subHeaderTextTopLeft.dy,  dialogTopLeft.dy + 16.0);
      }

      // Test sub header icon position.
      final Finder subHeaderIcon = find.byIcon(Icons.arrow_drop_down);
      final Offset subHeaderIconTopLeft = tester.getTopLeft(subHeaderIcon);
      final Offset subHeaderTextTopRight = tester.getTopRight(subHeaderText);
      expect(subHeaderIconTopLeft.dx, subHeaderTextTopRight.dx);
      expect(subHeaderIconTopLeft.dy, dialogTopLeft.dy + 14.0);

      // Test calendar page view position.
      final Finder calendarPageView = find.byType(PageView);
      final Offset calendarPageViewTopLeft = tester.getTopLeft(calendarPageView);
      final Offset subHeaderTextBottomLeft = tester.getBottomLeft(subHeaderText);
      expect(calendarPageViewTopLeft.dx, dividerTopRight.dx);
      if (!kIsWeb || isCanvasKit) { // https://github.com/flutter/flutter/issues/99933
        expect(calendarPageViewTopLeft.dy, subHeaderTextBottomLeft.dy + 16.0);
      }

      // Test month navigation icons position.
      final Finder previousMonthButton = find.widgetWithIcon(IconButton, Icons.chevron_left);
      final Finder nextMonthButton = find.widgetWithIcon(IconButton, Icons.chevron_right);
      final Offset previousMonthButtonTopRight = tester.getTopRight(previousMonthButton);
      final Offset nextMonthButtonTopRight = tester.getTopRight(nextMonthButton);
      final Offset dialogTopRight = tester.getTopRight(find.byType(AnimatedContainer));
      expect(nextMonthButtonTopRight.dx, dialogTopRight.dx - 4.0);
      expect(nextMonthButtonTopRight.dy, dialogTopRight.dy + 2.0);
      expect(previousMonthButtonTopRight.dx, nextMonthButtonTopRight.dx - 48.0);

      // Test action buttons position.
      final Offset dialogBottomRight = tester.getBottomRight(find.byType(AnimatedContainer));
      final Offset okButtonTopRight = tester.getTopRight(find.widgetWithText(TextButton, 'OK'));
      final Offset cancelButtonTopRight = tester.getTopRight(find.widgetWithText(TextButton, 'Cancel'));
      final Offset calendarPageViewBottomRight = tester.getBottomRight(calendarPageView);
      expect(okButtonTopRight.dx, dialogBottomRight.dx - 8);
      expect(okButtonTopRight.dy, calendarPageViewBottomRight.dy + 2);
      final Offset okButtonTopLeft = tester.getTopLeft(find.widgetWithText(TextButton, 'OK'));
      expect(cancelButtonTopRight.dx, okButtonTopLeft.dx - 8);
    });

    testWidgetsWithLeakTracking('Default Calendar mode layout (Portrait)', (WidgetTester tester) async {
      final Finder helpText = find.text('Select date');
      final Finder headerText = find.text('Fri, Jan 15');
      final Finder subHeaderText = find.text('January 2016');
      final Finder cancelButtonText = find.text('Cancel');
      final Finder okButtonText = find.text('OK');

      tester.view.physicalSize = narrowWindowSize;
      addTearDown(tester.view.reset);

      await tester.pumpWidget(MaterialApp(
        theme: ThemeData(useMaterial3: true),
        home: Material(
          child: DatePickerDialog(
            initialDate: initialDate,
            firstDate: firstDate,
            lastDate: lastDate,
          ),
        ),
      ));

      expect(helpText, findsOneWidget);
      expect(headerText, findsOneWidget);
      expect(subHeaderText, findsOneWidget);
      expect(cancelButtonText, findsOneWidget);
      expect(okButtonText, findsOneWidget);

      // Test help text position.
      final Offset dialogTopLeft = tester.getTopLeft(find.byType(AnimatedContainer));
      final Offset helpTextTopLeft = tester.getTopLeft(helpText);
      expect(helpTextTopLeft.dx, dialogTopLeft.dx + 24.0);
      expect(helpTextTopLeft.dy, dialogTopLeft.dy + 16.0);

      // Test header text position
      final Offset headerTextTextTopLeft = tester.getTopLeft(headerText);
      final Offset helpTextBottomLeft = tester.getBottomLeft(helpText);
      expect(headerTextTextTopLeft.dx, dialogTopLeft.dx + 24.0);
      if (!kIsWeb || isCanvasKit) { // https://github.com/flutter/flutter/issues/99933
        expect(headerTextTextTopLeft.dy, helpTextBottomLeft.dy + 28.0);
      }

      // Test switch button position.
      final Finder switchButtonM3 = find.widgetWithIcon(IconButton, Icons.edit_outlined);
      final Offset switchButtonTopRight = tester.getTopRight(switchButtonM3);
      final Offset dialogTopRight = tester.getTopRight(find.byType(AnimatedContainer));
      expect(switchButtonTopRight.dx, dialogTopRight.dx - 12.0);
      expect(switchButtonTopRight.dy, headerTextTextTopLeft.dy - 4.0);

      // Test horizontal divider position.
      final Finder divider = find.byType(Divider);
      final Offset dividerTopLeft = tester.getTopLeft(divider);
      final Offset headerTextBottomLeft = tester.getBottomLeft(headerText);
      expect(dividerTopLeft.dx, dialogTopLeft.dx);
      expect(dividerTopLeft.dy, headerTextBottomLeft.dy + 16.0);

      // Test subHeaderText position.
      final Offset subHeaderTextTopLeft = tester.getTopLeft(subHeaderText);
      final Offset dividerBottomLeft = tester.getBottomLeft(divider);
      expect(subHeaderTextTopLeft.dx, dialogTopLeft.dx + 24.0);
      if (!kIsWeb || isCanvasKit) { // https://github.com/flutter/flutter/issues/99933
        expect(subHeaderTextTopLeft.dy, dividerBottomLeft.dy + 16.0);
      }

      // Test sub header icon position.
      final Finder subHeaderIcon = find.byIcon(Icons.arrow_drop_down);
      final Offset subHeaderIconTopLeft = tester.getTopLeft(subHeaderIcon);
      final Offset subHeaderTextTopRight = tester.getTopRight(subHeaderText);
      expect(subHeaderIconTopLeft.dx, subHeaderTextTopRight.dx);
      expect(subHeaderIconTopLeft.dy, dividerBottomLeft.dy + 14.0);

      // Test month navigation icons position.
      final Finder previousMonthButton = find.widgetWithIcon(IconButton, Icons.chevron_left);
      final Finder nextMonthButton = find.widgetWithIcon(IconButton, Icons.chevron_right);
      final Offset previousMonthButtonTopRight = tester.getTopRight(previousMonthButton);
      final Offset nextMonthButtonTopRight = tester.getTopRight(nextMonthButton);
      expect(nextMonthButtonTopRight.dx, dialogTopRight.dx - 4.0);
      expect(nextMonthButtonTopRight.dy, dividerBottomLeft.dy + 2.0);
      expect(previousMonthButtonTopRight.dx, nextMonthButtonTopRight.dx - 48.0);

      // Test calendar page view position.
      final Finder calendarPageView = find.byType(PageView);
      final Offset calendarPageViewTopLeft = tester.getTopLeft(calendarPageView);
      final Offset subHeaderTextBottomLeft = tester.getBottomLeft(subHeaderText);
      expect(calendarPageViewTopLeft.dx, dialogTopLeft.dx);
      if (!kIsWeb || isCanvasKit) { // https://github.com/flutter/flutter/issues/99933
        expect(calendarPageViewTopLeft.dy, subHeaderTextBottomLeft.dy + 16.0);
      }

      // Test action buttons position.
      final Offset dialogBottomRight = tester.getBottomRight(find.byType(AnimatedContainer));
      final Offset okButtonTopRight = tester.getTopRight(find.widgetWithText(TextButton, 'OK'));
      final Offset cancelButtonTopRight = tester.getTopRight(find.widgetWithText(TextButton, 'Cancel'));
      final Offset calendarPageViewBottomRight = tester.getBottomRight(calendarPageView);
      final Offset okButtonTopLeft = tester.getTopLeft(find.widgetWithText(TextButton, 'OK'));
      expect(okButtonTopRight.dx, dialogBottomRight.dx - 8);
      expect(okButtonTopRight.dy, calendarPageViewBottomRight.dy + 2);
      expect(cancelButtonTopRight.dx, okButtonTopLeft.dx - 8);
    });

    testWidgetsWithLeakTracking('Can select a day', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('12'));
        await tester.tap(find.text('OK'));
        expect(await date, equals(DateTime(2016, DateTime.january, 12)));
      });
    });

    testWidgetsWithLeakTracking('Can select a month', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(previousMonthIcon);
        await tester.pumpAndSettle(const Duration(seconds: 1));
        await tester.tap(find.text('25'));
        await tester.tap(find.text('OK'));
        expect(await date, DateTime(2015, DateTime.december, 25));
      });
    });

    testWidgetsWithLeakTracking('Can select a year', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('January 2016')); // Switch to year mode.
        await tester.pump();
        await tester.tap(find.text('2018'));
        await tester.pump();
        expect(find.text('January 2018'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Can select a day with no initial date', (WidgetTester tester) async {
      initialDate = null;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('12'));
        await tester.tap(find.text('OK'));
        expect(await date, equals(DateTime(2016, DateTime.january, 12)));
      });
    });

    testWidgetsWithLeakTracking('Can select a month with no initial date', (WidgetTester tester) async {
      initialDate = null;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(previousMonthIcon);
        await tester.pumpAndSettle(const Duration(seconds: 1));
        await tester.tap(find.text('25'));
        await tester.tap(find.text('OK'));
        expect(await date, DateTime(2015, DateTime.december, 25));
      });
    });

    testWidgetsWithLeakTracking('Can select a year with no initial date', (WidgetTester tester) async {
      initialDate = null;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('January 2016')); // Switch to year mode.
        await tester.pump();
        await tester.tap(find.text('2018'));
        await tester.pump();
        expect(find.text('January 2018'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Selecting date does not change displayed month', (WidgetTester tester) async {
      initialDate = DateTime(2020, DateTime.march, 15);
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(nextMonthIcon);
        await tester.pumpAndSettle(const Duration(seconds: 1));
        expect(find.text('April 2020'), findsOneWidget);
        await tester.tap(find.text('25'));
        await tester.pumpAndSettle();
        expect(find.text('April 2020'), findsOneWidget);
        // There isn't a 31 in April so there shouldn't be one if it is showing April
        expect(find.text('31'), findsNothing);
      });
    });

    testWidgetsWithLeakTracking('Changing year does change selected date', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('January 2016'));
        await tester.pump();
        await tester.tap(find.text('2018'));
        await tester.pump();
        await tester.tap(find.text('OK'));
        expect(await date, equals(DateTime(2018, DateTime.january, 15)));
      });
    });

    testWidgetsWithLeakTracking('Changing year does not change the month', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(nextMonthIcon);
        await tester.pumpAndSettle();
        await tester.tap(nextMonthIcon);
        await tester.pumpAndSettle();
        await tester.tap(find.text('March 2016'));
        await tester.pumpAndSettle();
        await tester.tap(find.text('2018'));
        await tester.pumpAndSettle();
        expect(find.text('March 2018'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Can select a year and then a day', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('January 2016')); // Switch to year mode.
        await tester.pump();
        await tester.tap(find.text('2017'));
        await tester.pump();
        await tester.tap(find.text('19'));
        await tester.tap(find.text('OK'));
        expect(await date, DateTime(2017, DateTime.january, 19));
      });
    });

    testWidgetsWithLeakTracking('Current year is visible in year picker', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('January 2016')); // Switch to year mode.
        await tester.pump();
        expect(find.text('2016'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Cannot select a day outside bounds', (WidgetTester tester) async {
      initialDate = DateTime(2017, DateTime.january, 15);
      firstDate = initialDate!;
      lastDate = initialDate!;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        // Earlier than firstDate. Should be ignored.
        await tester.tap(find.text('10'));
        // Later than lastDate. Should be ignored.
        await tester.tap(find.text('20'));
        await tester.tap(find.text('OK'));
        // We should still be on the initial date.
        expect(await date, initialDate);
      });
    });

    testWidgetsWithLeakTracking('Cannot select a month past last date', (WidgetTester tester) async {
      initialDate = DateTime(2017, DateTime.january, 15);
      firstDate = initialDate!;
      lastDate = DateTime(2017, DateTime.february, 20);
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(nextMonthIcon);
        await tester.pumpAndSettle(const Duration(seconds: 1));
        // Shouldn't be possible to keep going into March.
        expect(nextMonthIcon, findsNothing);
      });
    });

    testWidgetsWithLeakTracking('Cannot select a month before first date', (WidgetTester tester) async {
      initialDate = DateTime(2017, DateTime.january, 15);
      firstDate = DateTime(2016, DateTime.december, 10);
      lastDate = initialDate!;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(previousMonthIcon);
        await tester.pumpAndSettle(const Duration(seconds: 1));
        // Shouldn't be possible to keep going into November.
        expect(previousMonthIcon, findsNothing);
      });
    });

    testWidgetsWithLeakTracking('Cannot select disabled year', (WidgetTester tester) async {
      initialDate = DateTime(2018, DateTime.july, 4);
      firstDate = DateTime(2018, DateTime.june, 9);
      lastDate = DateTime(2018, DateTime.december, 15);
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('July 2018')); // Switch to year mode.
        await tester.pumpAndSettle();
        await tester.tap(find.text('2016')); // Disabled, doesn't change the year.
        await tester.pumpAndSettle();
        await tester.tap(find.text('OK'));
        await tester.pumpAndSettle();
        expect(await date, DateTime(2018, DateTime.july, 4));
      });
    });

    testWidgetsWithLeakTracking('Selecting firstDate year respects firstDate', (WidgetTester tester) async {
      initialDate = DateTime(2018, DateTime.may, 4);
      firstDate = DateTime(2016, DateTime.june, 9);
      lastDate = DateTime(2019, DateTime.january, 15);
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('May 2018'));
        await tester.pumpAndSettle();
        await tester.tap(find.text('2016'));
        await tester.pumpAndSettle();
        // Month should be clamped to June as the range starts at June 2016
        expect(find.text('June 2016'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Selecting lastDate year respects lastDate', (WidgetTester tester) async {
      initialDate = DateTime(2018, DateTime.may, 4);
      firstDate = DateTime(2016, DateTime.june, 9);
      lastDate = DateTime(2019, DateTime.january, 15);
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('May 2018'));
        await tester.pumpAndSettle();
        await tester.tap(find.text('2019'));
        await tester.pumpAndSettle();
        // Month should be clamped to January as the range ends at January 2019
        expect(find.text('January 2019'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Only predicate days are selectable', (WidgetTester tester) async {
      initialDate = DateTime(2017, DateTime.january, 16);
      firstDate = DateTime(2017, DateTime.january, 10);
      lastDate = DateTime(2017, DateTime.january, 20);
      selectableDayPredicate = (DateTime day) => day.day.isEven;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('13')); // Odd, doesn't work.
        await tester.tap(find.text('10')); // Even, works.
        await tester.tap(find.text('17')); // Odd, doesn't work.
        await tester.tap(find.text('OK'));
        expect(await date, DateTime(2017, DateTime.january, 10));
      });
    });

    testWidgetsWithLeakTracking('Can select initial calendar picker mode', (WidgetTester tester) async {
      initialDate = DateTime(2014, DateTime.january, 15);
      initialCalendarMode = DatePickerMode.year;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.pump();
        // 2018 wouldn't be available if the year picker wasn't showing.
        // The initial current year is 2014.
        await tester.tap(find.text('2018'));
        await tester.pump();
        expect(find.text('January 2018'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('currentDate is highlighted', (WidgetTester tester) async {
      today = DateTime(2016, 1, 2);
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.pump();
        const Color todayColor = Color(0xff2196f3); // default primary color
        expect(
          Material.of(tester.element(find.text('2'))),
          // The current day should be painted with a circle outline
          paints..circle(color: todayColor, style: PaintingStyle.stroke, strokeWidth: 1.0),
        );
      });
    });

    testWidgetsWithLeakTracking('Date picker dayOverlayColor resolves pressed state', (WidgetTester tester) async {
      today = DateTime(2023, 5, 4);
      final ThemeData theme = ThemeData();
      final bool material3 = theme.useMaterial3;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.pump();

        // Hovered.
        final Offset center = tester.getCenter(find.text('30'));
        final TestGesture gesture = await tester.createGesture(
          kind: PointerDeviceKind.mouse,
        );
        await gesture.addPointer();
        await gesture.moveTo(center);
        await tester.pumpAndSettle();
        expect(
          Material.of(tester.element(find.text('30'))),
          paints..circle(color: material3 ? theme.colorScheme.onSurfaceVariant.withOpacity(0.08) : theme.colorScheme.onSurfaceVariant.withOpacity(0.08)),
        );

        // Highlighted (pressed).
        await gesture.down(center);
        await tester.pumpAndSettle();
        expect(
          Material.of(tester.element(find.text('30'))),
          paints..circle()..circle(color: material3 ? theme.colorScheme.onSurfaceVariant.withOpacity(0.12) : theme.colorScheme.onSurfaceVariant.withOpacity(0.12))
        );
        await gesture.up();
        await tester.pumpAndSettle();
      }, theme: theme);
    });

    testWidgetsWithLeakTracking('Selecting date does not switch picker to year selection', (WidgetTester tester) async {
      initialDate = DateTime(2020, DateTime.may, 10);
      initialCalendarMode = DatePickerMode.year;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.pump();
        await tester.tap(find.text('2017'));
        await tester.pump();
        expect(find.text('May 2017'), findsOneWidget);
        await tester.tap(find.text('10'));
        await tester.pump();
        expect(find.text('May 2017'), findsOneWidget);
        expect(find.text('2017'), findsNothing);
      });
    });
  });

  group('Input mode', () {
    setUp(() {
      firstDate = DateTime(2015);
      lastDate = DateTime(2017, DateTime.december, 31);
      initialDate = DateTime(2016, DateTime.january, 15);
      initialEntryMode = DatePickerEntryMode.input;
    });

    testWidgetsWithLeakTracking('Default InputDecoration', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        final InputDecoration decoration = tester.widget<TextField>(
          find.byType(TextField)).decoration!;
        expect(decoration.border, const OutlineInputBorder());
        expect(decoration.filled, false);
        expect(decoration.hintText, 'mm/dd/yyyy');
        expect(decoration.labelText, 'Enter Date');
        expect(decoration.errorText, null);
      }, useMaterial3: true);
    });

    testWidgetsWithLeakTracking('Initial entry mode is used', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.byType(TextField), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Hint, label, and help text is used', (WidgetTester tester) async {
      cancelText = 'nope';
      confirmText = 'yep';
      fieldHintText = 'hint';
      fieldLabelText = 'label';
      helpText = 'help';
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.text(cancelText!), findsOneWidget);
        expect(find.text(confirmText!), findsOneWidget);
        expect(find.text(fieldHintText!), findsOneWidget);
        expect(find.text(fieldLabelText!), findsOneWidget);
        expect(find.text(helpText!), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('KeyboardType is used', (WidgetTester tester) async {
      keyboardType = TextInputType.text;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        final TextField field = textField(tester);
        expect(field.keyboardType, TextInputType.text);
      });
    });

    testWidgetsWithLeakTracking('Initial date is the default', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('OK'));
        expect(await date, DateTime(2016, DateTime.january, 15));
      });
    });

    testWidgetsWithLeakTracking('Can toggle to calendar entry mode', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.byType(TextField), findsOneWidget);
        await tester.tap(find.byIcon(Icons.calendar_today));
        await tester.pumpAndSettle();
        expect(find.byType(TextField), findsNothing);
      });
    });

    testWidgetsWithLeakTracking('Toggle to calendar mode keeps selected date', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        final TextField field = textField(tester);
        field.controller!.clear();

        await tester.enterText(find.byType(TextField), '12/25/2016');
        await tester.tap(find.byIcon(Icons.calendar_today));
        await tester.pumpAndSettle();
        await tester.tap(find.text('OK'));
        expect(await date, DateTime(2016, DateTime.december, 25));
      });
    });

    testWidgetsWithLeakTracking('Entered text returns date', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        final TextField field = textField(tester);
        field.controller!.clear();

        await tester.enterText(find.byType(TextField), '12/25/2016');
        await tester.tap(find.text('OK'));
        expect(await date, DateTime(2016, DateTime.december, 25));
      });
    });

    testWidgetsWithLeakTracking('Too short entered text shows error', (WidgetTester tester) async {
      errorFormatText = 'oops';
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        final TextField field = textField(tester);
        field.controller!.clear();

        await tester.pumpAndSettle();
        await tester.enterText(find.byType(TextField), '1225');
        expect(find.text(errorFormatText!), findsNothing);

        await tester.tap(find.text('OK'));
        await tester.pumpAndSettle();
        expect(find.text(errorFormatText!), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Bad format entered text shows error', (WidgetTester tester) async {
      errorFormatText = 'oops';
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        final TextField field = textField(tester);
        field.controller!.clear();

        await tester.pumpAndSettle();
        await tester.enterText(find.byType(TextField), '20 days, 3 months, 2003');
        expect(find.text('20 days, 3 months, 2003'), findsOneWidget);
        expect(find.text(errorFormatText!), findsNothing);

        await tester.tap(find.text('OK'));
        await tester.pumpAndSettle();
        expect(find.text(errorFormatText!), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Invalid entered text shows error', (WidgetTester tester) async {
      errorInvalidText = 'oops';
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        final TextField field = textField(tester);
        field.controller!.clear();

        await tester.pumpAndSettle();
        await tester.enterText(find.byType(TextField), '08/10/1969');
        expect(find.text(errorInvalidText!), findsNothing);

        await tester.tap(find.text('OK'));
        await tester.pumpAndSettle();
        expect(find.text(errorInvalidText!), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Invalid entered text shows error on autovalidate', (WidgetTester tester) async {
      // This is a regression test for https://github.com/flutter/flutter/issues/126397.
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        final TextField field = textField(tester);
        field.controller!.clear();

        // Enter some text to trigger autovalidate.
        await tester.enterText(find.byType(TextField), 'xyz');
        await tester.tap(find.text('OK'));
        await tester.pumpAndSettle();

        // Invalid format validation error should be shown.
        expect(find.text('Invalid format.'), findsOneWidget);

        // Clear the text.
        field.controller!.clear();

        // Enter an invalid date that is too long while autovalidate is still on.
        await tester.enterText(find.byType(TextField), '10/05/2023666777889');
        await tester.pump();

        // Invalid format validation error should be shown.
        expect(find.text('Invalid format.'), findsOneWidget);
        // Should not throw an exception.
        expect(tester.takeException(), null);
      });
    });

    // This is a regression test for https://github.com/flutter/flutter/issues/131989.
    testWidgetsWithLeakTracking('Dialog contents do not overflow when resized during orientation change',
      (WidgetTester tester) async {
        addTearDown(tester.view.reset);
        // Initial window size is wide for landscape mode.
        tester.view.physicalSize = wideWindowSize;
        tester.view.devicePixelRatio = 1.0;

        await prepareDatePicker(tester, (Future<DateTime?> date) async {
          // Change window size to narrow for portrait mode.
          tester.view.physicalSize = narrowWindowSize;
          await tester.pump();
          expect(tester.takeException(), null);
        });
    });

    // This is a regression test for https://github.com/flutter/flutter/issues/139120.
    testWidgetsWithLeakTracking('Dialog contents are visible - textScaler 0.88, 1.0, 2.0',
      (WidgetTester tester) async {
        addTearDown(tester.view.reset);
        tester.view.physicalSize = const Size(400, 800);
        tester.view.devicePixelRatio = 1.0;
        final List<double> scales = <double>[0.88, 1.0, 2.0];

        for (final double scale in scales) {
          await tester.pumpWidget(
            MaterialApp(
              home: MediaQuery(
                data: MediaQueryData(textScaler: TextScaler.linear(scale)),
                child: Material(
                  child: DatePickerDialog(
                    firstDate: DateTime(2001),
                    lastDate: DateTime(2031, DateTime.december, 31),
                    initialDate: DateTime(2016, DateTime.january, 15),
                    initialEntryMode: DatePickerEntryMode.input,
                  ),
                ),
              ),
            ),
          );
          await tester.pumpAndSettle();

          await expectLater(find.byType(Dialog), matchesGoldenFile('date_picker.dialog.contents.visible.$scale.png'));
        }
    });
  });

  group('Semantics', () {
    testWidgetsWithLeakTracking('calendar mode', (WidgetTester tester) async {
      final SemanticsHandle semantics = tester.ensureSemantics();

      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        // Header
        expect(tester.getSemantics(find.text('SELECT DATE')), matchesSemantics(
          label: 'SELECT DATE\nFri, Jan 15',
        ));

        expect(tester.getSemantics(find.text('3')), matchesSemantics(
          label: '3, Sunday, January 3, 2016, Today',
          isButton: true,
          hasTapAction: true,
          isFocusable: true,
        ));

        // Input mode toggle button
        expect(tester.getSemantics(switchToInputIcon), matchesSemantics(
          tooltip: 'Switch to input',
          isButton: true,
          hasTapAction: true,
          isEnabled: true,
          hasEnabledState: true,
          isFocusable: true,
        ));

        // The semantics of the CalendarDatePicker are tested in its tests.

        // Ok/Cancel buttons
        expect(tester.getSemantics(find.text('OK')), matchesSemantics(
          label: 'OK',
          isButton: true,
          hasTapAction: true,
          isEnabled: true,
          hasEnabledState: true,
          isFocusable: true,
        ));
        expect(tester.getSemantics(find.text('CANCEL')), matchesSemantics(
          label: 'CANCEL',
          isButton: true,
          hasTapAction: true,
          isEnabled: true,
          hasEnabledState: true,
          isFocusable: true,
        ));
      });
      semantics.dispose();
    });

    testWidgetsWithLeakTracking('input mode', (WidgetTester tester) async {
      final SemanticsHandle semantics = tester.ensureSemantics();

      initialEntryMode = DatePickerEntryMode.input;
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        // Header
        expect(tester.getSemantics(find.text('SELECT DATE')), matchesSemantics(
          label: 'SELECT DATE\nFri, Jan 15',
        ));

        // Input mode toggle button
        expect(tester.getSemantics(switchToCalendarIcon), matchesSemantics(
          tooltip: 'Switch to calendar',
          isButton: true,
          hasTapAction: true,
          isEnabled: true,
          hasEnabledState: true,
          isFocusable: true,
        ));

        // The semantics of the InputDatePickerFormField are tested in its tests.

        // Ok/Cancel buttons
        expect(tester.getSemantics(find.text('OK')), matchesSemantics(
          label: 'OK',
          isButton: true,
          hasTapAction: true,
          isEnabled: true,
          hasEnabledState: true,
          isFocusable: true,
        ));
        expect(tester.getSemantics(find.text('CANCEL')), matchesSemantics(
          label: 'CANCEL',
          isButton: true,
          hasTapAction: true,
          isEnabled: true,
          hasEnabledState: true,
          isFocusable: true,
        ));
      });
      semantics.dispose();
    });
  });

  group('Keyboard navigation', () {
    testWidgetsWithLeakTracking('Can toggle to calendar entry mode', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.byType(TextField), findsNothing);
        // Navigate to the entry toggle button and activate it
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();
        // Should be in the input mode
        expect(find.byType(TextField), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Can toggle to year mode', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.text('2016'), findsNothing);
        // Navigate to the year selector and activate it
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();
        // The years should be visible
        expect(find.text('2016'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Can navigate next/previous months', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        expect(find.text('January 2016'), findsOneWidget);
        // Navigate to the previous month button and activate it twice
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();
        // Should be showing Nov 2015
        expect(find.text('November 2015'), findsOneWidget);

        // Navigate to the next month button and activate it four times
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();
        // Should be on Mar 2016
        expect(find.text('March 2016'), findsOneWidget);
      });
    });

    testWidgetsWithLeakTracking('Can navigate date grid with arrow keys', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        // Navigate to the grid
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);

        // Navigate from Jan 15 to Jan 18 with arrow keys
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
        await tester.pumpAndSettle();

        // Activate it
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();

        // Navigate out of the grid and to the OK button
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);

        // Activate OK
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();

        // Should have selected Jan 18
        expect(await date, DateTime(2016, DateTime.january, 18));
      });
    });

    testWidgetsWithLeakTracking('Navigating with arrow keys scrolls months', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        // Navigate to the grid
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.pumpAndSettle();

        // Navigate from Jan 15 to Dec 31 with arrow keys
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
        await tester.pumpAndSettle();

        // Should have scrolled to Dec 2015
        expect(find.text('December 2015'), findsOneWidget);

        // Navigate from Dec 31 to Nov 26 with arrow keys
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
        await tester.pumpAndSettle();

        // Should have scrolled to Nov 2015
        expect(find.text('November 2015'), findsOneWidget);

        // Activate it
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();

        // Navigate out of the grid and to the OK button
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.pumpAndSettle();

        // Activate OK
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();

        // Should have selected Jan 18
        expect(await date, DateTime(2015, DateTime.november, 26));
      });
    });

    testWidgetsWithLeakTracking('RTL text direction reverses the horizontal arrow key navigation', (WidgetTester tester) async {
      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        // Navigate to the grid
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.pumpAndSettle();

        // Navigate from Jan 15 to 19 with arrow keys
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
        await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
        await tester.pumpAndSettle();

        // Activate it
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();

        // Navigate out of the grid and to the OK button
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.sendKeyEvent(LogicalKeyboardKey.tab);
        await tester.pumpAndSettle();

        // Activate OK
        await tester.sendKeyEvent(LogicalKeyboardKey.space);
        await tester.pumpAndSettle();

        // Should have selected Jan 18
        expect(await date, DateTime(2016, DateTime.january, 19));
      }, textDirection: TextDirection.rtl);
    });
  });

  group('Screen configurations', () {
    // Test various combinations of screen sizes, orientations and text scales
    // to ensure the layout doesn't overflow and cause an exception to be thrown.

    // Regression tests for https://github.com/flutter/flutter/issues/21383
    // Regression tests for https://github.com/flutter/flutter/issues/19744
    // Regression tests for https://github.com/flutter/flutter/issues/17745

    // Common screen size roughly based on a Pixel 1
    const Size kCommonScreenSizePortrait = Size(1070, 1770);
    const Size kCommonScreenSizeLandscape = Size(1770, 1070);

    // Small screen size based on a LG K130
    const Size kSmallScreenSizePortrait = Size(320, 521);
    const Size kSmallScreenSizeLandscape = Size(521, 320);

    Future<void> showPicker(WidgetTester tester, Size size, [double textScaleFactor = 1.0]) async {
      tester.view.physicalSize = size;
      tester.view.devicePixelRatio = 1.0;
      addTearDown(tester.view.reset);

      await prepareDatePicker(tester, (Future<DateTime?> date) async {
        await tester.tap(find.text('OK'));
      });
      await tester.pumpAndSettle();
    }

    testWidgetsWithLeakTracking('common screen size - portrait', (WidgetTester tester) async {
      await showPicker(tester, kCommonScreenSizePortrait);
      expect(tester.takeException(), isNull);
    });

    testWidgetsWithLeakTracking('common screen size - landscape', (WidgetTester tester) async {
      await showPicker(tester, kCommonScreenSizeLandscape);
      expect(tester.takeException(), isNull);
    });

    testWidgetsWithLeakTracking('common screen size - portrait - textScale 1.3', (WidgetTester tester) async {
      await showPicker(tester, kCommonScreenSizePortrait, 1.3);
      expect(tester.takeException(), isNull);
    });

    testWidgetsWithLeakTracking('common screen size - landscape - textScale 1.3', (WidgetTester tester) async {
      await showPicker(tester, kCommonScreenSizeLandscape, 1.3);
      expect(tester.takeException(), isNull);
    });

    testWidgetsWithLeakTracking('small screen size - portrait', (WidgetTester tester) async {
      await showPicker(tester, kSmallScreenSizePortrait);
      expect(tester.takeException(), isNull);
    });

    testWidgetsWithLeakTracking('small screen size - landscape', (WidgetTester tester) async {
      await showPicker(tester, kSmallScreenSizeLandscape);
      expect(tester.takeException(), isNull);
    });

    testWidgetsWithLeakTracking('small screen size - portrait -textScale 1.3', (WidgetTester tester) async {
      await showPicker(tester, kSmallScreenSizePortrait, 1.3);
      expect(tester.takeException(), isNull);
    });

    testWidgetsWithLeakTracking('small screen size - landscape - textScale 1.3', (WidgetTester tester) async {
      await showPicker(tester, kSmallScreenSizeLandscape, 1.3);
      expect(tester.takeException(), isNull);
    });
  });

  group('showDatePicker avoids overlapping display features', () {
    testWidgetsWithLeakTracking('positioning with anchorPoint', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          builder: (BuildContext context, Widget? child) {
            return MediaQuery(
              // Display has a vertical hinge down the middle
              data: const MediaQueryData(
                size: Size(800, 600),
                displayFeatures: <DisplayFeature>[
                  DisplayFeature(
                    bounds: Rect.fromLTRB(390, 0, 410, 600),
                    type: DisplayFeatureType.hinge,
                    state: DisplayFeatureState.unknown,
                  ),
                ],
              ),
              child: child!,
            );
          },
          home: const Center(child: Text('Test')),
        ),
      );

      final BuildContext context = tester.element(find.text('Test'));
      showDatePicker(
        context: context,
        initialDate: DateTime.now(),
        firstDate: DateTime(2018),
        lastDate: DateTime(2030),
        anchorPoint: const Offset(1000, 0),
      );
      await tester.pumpAndSettle();

      // Should take the right side of the screen
      expect(tester.getTopLeft(find.byType(DatePickerDialog)), const Offset(410.0, 0.0));
      expect(tester.getBottomRight(find.byType(DatePickerDialog)), const Offset(800.0, 600.0));
    });

    testWidgetsWithLeakTracking('positioning with Directionality', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          builder: (BuildContext context, Widget? child) {
            return MediaQuery(
              // Display has a vertical hinge down the middle
              data: const MediaQueryData(
                size: Size(800, 600),
                displayFeatures: <DisplayFeature>[
                  DisplayFeature(
                    bounds: Rect.fromLTRB(390, 0, 410, 600),
                    type: DisplayFeatureType.hinge,
                    state: DisplayFeatureState.unknown,
                  ),
                ],
              ),
              child: Directionality(
                textDirection: TextDirection.rtl,
                child: child!,
              ),
            );
          },
          home: const Center(child: Text('Test')),
        ),
      );

      final BuildContext context = tester.element(find.text('Test'));
      showDatePicker(
        context: context,
        initialDate: DateTime.now(),
        firstDate: DateTime(2018),
        lastDate: DateTime(2030),
      );
      await tester.pumpAndSettle();

      // By default it should place the dialog on the right screen
      expect(tester.getTopLeft(find.byType(DatePickerDialog)), const Offset(410.0, 0.0));
      expect(tester.getBottomRight(find.byType(DatePickerDialog)), const Offset(800.0, 600.0));
    });

    testWidgetsWithLeakTracking('positioning with defaults', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          builder: (BuildContext context, Widget? child) {
            return MediaQuery(
              // Display has a vertical hinge down the middle
              data: const MediaQueryData(
                size: Size(800, 600),
                displayFeatures: <DisplayFeature>[
                  DisplayFeature(
                    bounds: Rect.fromLTRB(390, 0, 410, 600),
                    type: DisplayFeatureType.hinge,
                    state: DisplayFeatureState.unknown,
                  ),
                ],
              ),
              child: child!,
            );
          },
          home: const Center(child: Text('Test')),
        ),
      );

      final BuildContext context = tester.element(find.text('Test'));
      showDatePicker(
        context: context,
        initialDate: DateTime.now(),
        firstDate: DateTime(2018),
        lastDate: DateTime(2030),
      );
      await tester.pumpAndSettle();

      // By default it should place the dialog on the left screen
      expect(tester.getTopLeft(find.byType(DatePickerDialog)), Offset.zero);
      expect(tester.getBottomRight(find.byType(DatePickerDialog)), const Offset(390.0, 600.0));
    });
  });

  testWidgetsWithLeakTracking('DatePickerDialog is state restorable', (WidgetTester tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        restorationScopeId: 'app',
        home: _RestorableDatePickerDialogTestWidget(),
      ),
    );

    // The date picker should be closed.
    expect(find.byType(DatePickerDialog), findsNothing);
    expect(find.text('25/7/2021'), findsOneWidget);

    // Open the date picker.
    await tester.tap(find.text('X'));
    await tester.pumpAndSettle();
    expect(find.byType(DatePickerDialog), findsOneWidget);

    final TestRestorationData restorationData = await tester.getRestorationData();
    await tester.restartAndRestore();

    // The date picker should be open after restoring.
    expect(find.byType(DatePickerDialog), findsOneWidget);

    // Tap on the barrier.
    await tester.tapAt(const Offset(10.0, 10.0));
    await tester.pumpAndSettle();

    // The date picker should be closed, the text value updated to the
    // newly selected date.
    expect(find.byType(DatePickerDialog), findsNothing);
    expect(find.text('25/7/2021'), findsOneWidget);

    // The date picker should be open after restoring.
    await tester.restoreFrom(restorationData);
    expect(find.byType(DatePickerDialog), findsOneWidget);

    // Select a different date.
    await tester.tap(find.text('30'));
    await tester.pumpAndSettle();

    // Restart after the new selection. It should remain selected.
    await tester.restartAndRestore();

    // Close the date picker.
    await tester.tap(find.text('OK'));
    await tester.pumpAndSettle();

    // The date picker should be closed, the text value updated to the
    // newly selected date.
    expect(find.byType(DatePickerDialog), findsNothing);
    expect(find.text('30/7/2021'), findsOneWidget);
  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615

  testWidgetsWithLeakTracking('DatePickerDialog state restoration - DatePickerEntryMode', (WidgetTester tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        restorationScopeId: 'app',
        home: _RestorableDatePickerDialogTestWidget(
          datePickerEntryMode: DatePickerEntryMode.calendarOnly,
        ),
      ),
    );

    // The date picker should be closed.
    expect(find.byType(DatePickerDialog), findsNothing);
    expect(find.text('25/7/2021'), findsOneWidget);

    // Open the date picker.
    await tester.tap(find.text('X'));
    await tester.pumpAndSettle();
    expect(find.byType(DatePickerDialog), findsOneWidget);

    // Only in calendar mode and cannot switch out.
    expect(find.byType(TextField), findsNothing);
    expect(find.byIcon(Icons.edit), findsNothing);

    final TestRestorationData restorationData = await tester.getRestorationData();
    await tester.restartAndRestore();

    // The date picker should be open after restoring.
    expect(find.byType(DatePickerDialog), findsOneWidget);
    // Only in calendar mode and cannot switch out.
    expect(find.byType(TextField), findsNothing);
    expect(find.byIcon(Icons.edit), findsNothing);

    // Tap on the barrier.
    await tester.tapAt(const Offset(10.0, 10.0));
    await tester.pumpAndSettle();

    // The date picker should be closed, the text value should be the same
    // as before.
    expect(find.byType(DatePickerDialog), findsNothing);
    expect(find.text('25/7/2021'), findsOneWidget);

    // The date picker should be open after restoring.
    await tester.restoreFrom(restorationData);
    expect(find.byType(DatePickerDialog), findsOneWidget);
    // Only in calendar mode and cannot switch out.
    expect(find.byType(TextField), findsNothing);
    expect(find.byIcon(Icons.edit), findsNothing);
  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615

  testWidgetsWithLeakTracking('Test Callback on Toggle of DatePicker Mode', (WidgetTester tester) async {
    prepareDatePicker(tester, (Future<DateTime?> date) async {
      await tester.tap(find.byIcon(Icons.edit));
      expect(currentMode, DatePickerEntryMode.input);
      await tester.pumpAndSettle();
      expect(find.byType(TextField), findsOneWidget);
      await tester.tap(find.byIcon(Icons.calendar_today));
      expect(currentMode, DatePickerEntryMode.calendar);
      await tester.pumpAndSettle();
      expect(find.byType(TextField), findsNothing);
    });
  });

  group('Landscape input-only date picker headers use headlineSmall', () {
    // Regression test for https://github.com/flutter/flutter/issues/122056

    // Common screen size roughly based on a Pixel 1
    const Size kCommonScreenSizePortrait = Size(1070, 1770);
    const Size kCommonScreenSizeLandscape = Size(1770, 1070);

    Future<void> showPicker(WidgetTester tester, Size size) async {
      addTearDown(tester.view.reset);
      tester.view.physicalSize = size;
      tester.view.devicePixelRatio = 1.0;
      initialEntryMode = DatePickerEntryMode.input;
      await prepareDatePicker(tester, (Future<DateTime?> date) async { }, useMaterial3: true);
    }

    testWidgetsWithLeakTracking('portrait', (WidgetTester tester) async {
      await showPicker(tester, kCommonScreenSizePortrait);
      expect(tester.widget<Text>(find.text('Fri, Jan 15')).style?.fontSize, 32);
      await tester.tap(find.text('Cancel'));
      await tester.pumpAndSettle();
    });

    testWidgetsWithLeakTracking('landscape', (WidgetTester tester) async {
      await showPicker(tester, kCommonScreenSizeLandscape);
      expect(tester.widget<Text>(find.text('Fri, Jan 15')).style?.fontSize, 24);
      await tester.tap(find.text('Cancel'));
      await tester.pumpAndSettle();
    });
  });

  group('Material 2', () {
    // These tests are only relevant for Material 2. Once Material 2
    // support is deprecated and the APIs are removed, these tests
    // can be deleted.

    group('showDatePicker Dialog', () {
      testWidgetsWithLeakTracking('Default dialog size', (WidgetTester tester) async {
        Future<void> showPicker(WidgetTester tester, Size size) async {
          tester.view.physicalSize = size;
          tester.view.devicePixelRatio = 1.0;
          addTearDown(tester.view.reset);
          await prepareDatePicker(tester, (Future<DateTime?> date) async {});
        }
        const Size wideWindowSize = Size(1920.0, 1080.0);
        const Size narrowWindowSize = Size(1070.0, 1770.0);
        const Size calendarLandscapeDialogSize = Size(496.0, 346.0);
        const Size calendarPortraitDialogSizeM2 = Size(330.0, 518.0);

        // Test landscape layout.
        await showPicker(tester, wideWindowSize);

        Size dialogContainerSize = tester.getSize(find.byType(AnimatedContainer));
        expect(dialogContainerSize, calendarLandscapeDialogSize);

        // Close the dialog.
        await tester.tap(find.text('OK'));
        await tester.pumpAndSettle();

        // Test portrait layout.
        await showPicker(tester, narrowWindowSize);

        dialogContainerSize = tester.getSize(find.byType(AnimatedContainer));
        expect(dialogContainerSize, calendarPortraitDialogSizeM2);
      });

      testWidgetsWithLeakTracking('Default dialog properties', (WidgetTester tester) async {
        final ThemeData theme = ThemeData(useMaterial3: false);
        await prepareDatePicker(tester, (Future<DateTime?> date) async {
          final Material dialogMaterial = tester.widget<Material>(
            find.descendant(of: find.byType(Dialog),
            matching: find.byType(Material),
          ).first);

          expect(dialogMaterial.color, theme.colorScheme.surface);
          expect(dialogMaterial.shadowColor, theme.shadowColor);
          expect(dialogMaterial.elevation, 24.0);
          expect(
            dialogMaterial.shape,
            const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
          );
          expect(dialogMaterial.clipBehavior, Clip.antiAlias);


          final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog));
          expect(dialog.insetPadding, const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0));
        }, useMaterial3: theme.useMaterial3);
      });
    });

    group('Input mode', () {
      setUp(() {
        firstDate = DateTime(2015);
        lastDate = DateTime(2017, DateTime.december, 31);
        initialDate = DateTime(2016, DateTime.january, 15);
        initialEntryMode = DatePickerEntryMode.input;
      });

      testWidgetsWithLeakTracking('Default InputDecoration', (WidgetTester tester) async {
        await prepareDatePicker(tester, (Future<DateTime?> date) async {
          final InputDecoration decoration = tester.widget<TextField>(
            find.byType(TextField)).decoration!;
          expect(decoration.border, const UnderlineInputBorder());
          expect(decoration.filled, false);
          expect(decoration.hintText, 'mm/dd/yyyy');
          expect(decoration.labelText, 'Enter Date');
          expect(decoration.errorText, null);
        });
      });
    });
  });
}

class _RestorableDatePickerDialogTestWidget extends StatefulWidget {
  const _RestorableDatePickerDialogTestWidget({
    this.datePickerEntryMode = DatePickerEntryMode.calendar,
  });

  final DatePickerEntryMode datePickerEntryMode;

  @override
  _RestorableDatePickerDialogTestWidgetState createState() => _RestorableDatePickerDialogTestWidgetState();
}

class _RestorableDatePickerDialogTestWidgetState extends State<_RestorableDatePickerDialogTestWidget> with RestorationMixin {
  @override
  String? get restorationId => 'scaffold_state';

  final RestorableDateTime _selectedDate = RestorableDateTime(DateTime(2021, 7, 25));
  late final RestorableRouteFuture<DateTime?> _restorableDatePickerRouteFuture = RestorableRouteFuture<DateTime?>(
    onComplete: _selectDate,
    onPresent: (NavigatorState navigator, Object? arguments) {
      return navigator.restorablePush(
        _datePickerRoute,
        arguments: <String, dynamic>{
          'selectedDate': _selectedDate.value.millisecondsSinceEpoch,
          'datePickerEntryMode': widget.datePickerEntryMode.index,
        },
      );
    },
  );

  @override
  void dispose() {
    _selectedDate.dispose();
    _restorableDatePickerRouteFuture.dispose();
    super.dispose();
  }

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_selectedDate, 'selected_date');
    registerForRestoration(_restorableDatePickerRouteFuture, 'date_picker_route_future');
  }

  void _selectDate(DateTime? newSelectedDate) {
    if (newSelectedDate != null) {
      setState(() { _selectedDate.value = newSelectedDate; });
    }
  }

  @pragma('vm:entry-point')
  static Route<DateTime> _datePickerRoute(
    BuildContext context,
    Object? arguments,
  ) {
    return DialogRoute<DateTime>(
      context: context,
      builder: (BuildContext context) {
        final Map<dynamic, dynamic> args = arguments! as Map<dynamic, dynamic>;
        return DatePickerDialog(
          restorationId: 'date_picker_dialog',
          initialEntryMode: DatePickerEntryMode.values[args['datePickerEntryMode'] as int],
          initialDate: DateTime.fromMillisecondsSinceEpoch(args['selectedDate'] as int),
          firstDate: DateTime(2021),
          lastDate: DateTime(2022),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    final DateTime selectedDateTime = _selectedDate.value;
    // Example: "25/7/1994"
    final String selectedDateTimeString = '${selectedDateTime.day}/${selectedDateTime.month}/${selectedDateTime.year}';
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            OutlinedButton(
              onPressed: () {
                _restorableDatePickerRouteFuture.present();
              },
              child: const Text('X'),
            ),
            Text(selectedDateTimeString),
          ],
        ),
      ),
    );
  }
}

class _DatePickerObserver extends NavigatorObserver {
  int datePickerCount = 0;

  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    if (route is DialogRoute) {
      datePickerCount++;
    }
    super.didPush(route, previousRoute);
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    if (route is DialogRoute) {
      datePickerCount--;
    }
    super.didPop(route, previousRoute);
  }
}
