| // 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. |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../rendering/mock_canvas.dart'; |
| import 'feedback_tester.dart'; |
| |
| class MockClipboard { |
| dynamic _clipboardData = <String, dynamic>{ |
| 'text': null, |
| }; |
| |
| Future<dynamic> handleMethodCall(MethodCall methodCall) async { |
| switch (methodCall.method) { |
| case 'Clipboard.getData': |
| return _clipboardData; |
| case 'Clipboard.setData': |
| _clipboardData = methodCall.arguments; |
| break; |
| } |
| } |
| } |
| |
| void main() { |
| TestWidgetsFlutterBinding.ensureInitialized(); |
| final MockClipboard mockClipboard = MockClipboard(); |
| |
| late DateTime firstDate; |
| late DateTime lastDate; |
| late DateTime initialDate; |
| late DateTime today; |
| late SelectableDayPredicate? selectableDayPredicate; |
| late DatePickerEntryMode initialEntryMode; |
| late DatePickerMode initialCalendarMode; |
| |
| String? cancelText; |
| String? confirmText; |
| String? errorFormatText; |
| String? errorInvalidText; |
| String? fieldHintText; |
| String? fieldLabelText; |
| String? helpText; |
| |
| 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(() async { |
| firstDate = DateTime(2001, DateTime.january, 1); |
| 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; |
| |
| // Fill the clipboard so that the Paste option is available in the text |
| // selection menu. |
| SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); |
| await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); |
| }); |
| |
| tearDown(() { |
| SystemChannels.platform.setMockMethodCallHandler(null); |
| }); |
| |
| Future<void> prepareDatePicker( |
| WidgetTester tester, |
| Future<void> callback(Future<DateTime> date), |
| { TextDirection textDirection = TextDirection.ltr } |
| ) async { |
| late BuildContext buttonContext; |
| await tester.pumpWidget(MaterialApp( |
| home: 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, |
| 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', () { |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('Can cancel', (WidgetTester tester) async { |
| await prepareDatePicker(tester, (Future<DateTime> date) async { |
| await tester.tap(find.text('CANCEL')); |
| expect(await date, isNull); |
| }); |
| }); |
| |
| testWidgets('Can toggle 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); |
| }); |
| }); |
| |
| testWidgets('Toggle 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)); |
| }); |
| }); |
| |
| testWidgets('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 calender 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); |
| }); |
| }); |
| |
| testWidgets('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 window. |
| expect(tester.getBottomLeft(find.text('OK')).dx, 800 - ltrOkRight); |
| }); |
| |
| testWidgets('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); |
| }); |
| |
| testWidgets('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( |
| 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().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.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| 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); |
| }); |
| |
| testWidgets('OK Cancel button layout', (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(2016, DateTime.january, 15), |
| firstDate:DateTime(2001, DateTime.january, 1), |
| 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.binding.window.clearPhysicalSizeTestValue); |
| tester.binding.window.physicalSizeTestValue = 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(); |
| }); |
| }); |
| |
| group('Calendar mode', () { |
| testWidgets('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))); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('Changing year does not 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(2016, DateTime.january, 15))); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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) |
| ); |
| }); |
| }); |
| }); |
| |
| group('Input mode', () { |
| setUp(() { |
| firstDate = DateTime(2015, DateTime.january, 1); |
| lastDate = DateTime(2017, DateTime.december, 31); |
| initialDate = DateTime(2016, DateTime.january, 15); |
| initialEntryMode = DatePickerEntryMode.input; |
| }); |
| |
| testWidgets('Initial entry mode is used', (WidgetTester tester) async { |
| await prepareDatePicker(tester, (Future<DateTime> date) async { |
| expect(find.byType(TextField), findsOneWidget); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('InputDecorationTheme is honored', (WidgetTester tester) async { |
| late BuildContext buttonContext; |
| const InputBorder border = InputBorder.none; |
| await tester.pumpWidget(MaterialApp( |
| theme: ThemeData.light().copyWith( |
| inputDecorationTheme: const InputDecorationTheme( |
| filled: false, |
| border: border, |
| ), |
| ), |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return ElevatedButton( |
| onPressed: () { |
| buttonContext = context; |
| }, |
| child: const Text('Go'), |
| ); |
| }, |
| ), |
| ), |
| )); |
| |
| await tester.tap(find.text('Go')); |
| expect(buttonContext, isNotNull); |
| |
| showDatePicker( |
| context: buttonContext, |
| initialDate: initialDate, |
| firstDate: firstDate, |
| lastDate: lastDate, |
| currentDate: today, |
| initialEntryMode: DatePickerEntryMode.input, |
| ); |
| |
| await tester.pumpAndSettle(); |
| |
| // Get the border and container color from the painter of the _BorderContainer |
| // (this was cribbed from input_decorator_test.dart). |
| final CustomPaint customPaint = tester.widget(find.descendant( |
| of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_BorderContainer'), |
| matching: find.byWidgetPredicate((Widget w) => w is CustomPaint), |
| )); |
| final dynamic/*_InputBorderPainter*/ inputBorderPainter = customPaint.foregroundPainter; |
| final dynamic/*_InputBorderTween*/ inputBorderTween = inputBorderPainter.border; |
| final Animation<double> animation = inputBorderPainter.borderAnimation as Animation<double>; |
| final InputBorder actualBorder = inputBorderTween.evaluate(animation) as InputBorder; |
| final Color containerColor = inputBorderPainter.blendedColor as Color; |
| |
| // Border should match |
| expect(actualBorder, equals(border)); |
| |
| // It shouldn't be filled, so the color should be transparent |
| expect(containerColor, equals(Colors.transparent)); |
| }); |
| }); |
| |
| group('CalendarDatePicker', () { |
| // Tests for the standalone CalendarDatePicker class |
| testWidgets('Updates to initialDate parameter is reflected in the state', (WidgetTester tester) async { |
| final Key pickerKey = UniqueKey(); |
| final DateTime initialDate = DateTime(2020, 1, 21); |
| final DateTime updatedDate = DateTime(1976, 2, 23); |
| const Color selectedColor = Color(0xff2196f3); // default primary color |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: CalendarDatePicker( |
| key: pickerKey, |
| initialDate: initialDate, |
| firstDate: DateTime(1970, 1, 1), |
| lastDate: DateTime(2099, 31, 12), |
| onDateChanged: (DateTime value) {}, |
| ), |
| ), |
| )); |
| await tester.pumpAndSettle(); |
| |
| // Month should show as January 2020 |
| expect(find.text('January 2020'), findsOneWidget); |
| // Selected date should be painted with a colored circle |
| expect( |
| Material.of(tester.element(find.text('21'))), |
| paints..circle(color: selectedColor, style: PaintingStyle.fill) |
| ); |
| |
| // Change to the updated initialDate |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: CalendarDatePicker( |
| key: pickerKey, |
| initialDate: updatedDate, |
| firstDate: DateTime(1970, 1, 1), |
| lastDate: DateTime(2099, 31, 12), |
| onDateChanged: (DateTime value) {}, |
| ), |
| ), |
| )); |
| // Wait for the page scroll animation to finish |
| await tester.pumpAndSettle(const Duration(milliseconds: 200)); |
| |
| // Month should show as February 1976 |
| expect(find.text('January 2020'), findsNothing); |
| expect(find.text('February 1976'), findsOneWidget); |
| // Selected date should be painted with a colored circle |
| expect( |
| Material.of(tester.element(find.text('23'))), |
| paints..circle(color: selectedColor, style: PaintingStyle.fill) |
| ); |
| |
| }); |
| }); |
| |
| group('Haptic feedback', () { |
| const Duration hapticFeedbackInterval = Duration(milliseconds: 10); |
| late FeedbackTester feedback; |
| |
| setUp(() { |
| feedback = FeedbackTester(); |
| initialDate = DateTime(2017, DateTime.january, 16); |
| firstDate = DateTime(2017, DateTime.january, 10); |
| lastDate = DateTime(2018, DateTime.january, 20); |
| initialCalendarMode = DatePickerMode.day; |
| selectableDayPredicate = (DateTime date) => date.day.isEven; |
| }); |
| |
| tearDown(() { |
| feedback.dispose(); |
| }); |
| |
| testWidgets('Selecting date vibrates', (WidgetTester tester) async { |
| await prepareDatePicker(tester, (Future<DateTime> date) async { |
| await tester.tap(find.text('10')); |
| await tester.pump(hapticFeedbackInterval); |
| expect(feedback.hapticCount, 1); |
| await tester.tap(find.text('12')); |
| await tester.pump(hapticFeedbackInterval); |
| expect(feedback.hapticCount, 2); |
| await tester.tap(find.text('14')); |
| await tester.pump(hapticFeedbackInterval); |
| expect(feedback.hapticCount, 3); |
| }); |
| }); |
| |
| testWidgets('Tapping unselectable date does not vibrate', (WidgetTester tester) async { |
| await prepareDatePicker(tester, (Future<DateTime> date) async { |
| await tester.tap(find.text('11')); |
| await tester.pump(hapticFeedbackInterval); |
| expect(feedback.hapticCount, 0); |
| await tester.tap(find.text('13')); |
| await tester.pump(hapticFeedbackInterval); |
| expect(feedback.hapticCount, 0); |
| await tester.tap(find.text('15')); |
| await tester.pump(hapticFeedbackInterval); |
| expect(feedback.hapticCount, 0); |
| }); |
| }); |
| |
| testWidgets('Changing modes and year vibrates', (WidgetTester tester) async { |
| await prepareDatePicker(tester, (Future<DateTime> date) async { |
| await tester.tap(find.text('January 2017')); |
| await tester.pump(hapticFeedbackInterval); |
| expect(feedback.hapticCount, 1); |
| await tester.tap(find.text('2018')); |
| await tester.pump(hapticFeedbackInterval); |
| expect(feedback.hapticCount, 2); |
| }); |
| }); |
| }); |
| |
| group('Semantics', () { |
| testWidgets('calendar day mode', (WidgetTester tester) async { |
| final SemanticsHandle semantics = tester.ensureSemantics(); |
| addTearDown(semantics.dispose); |
| |
| 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(switchToInputIcon), matchesSemantics( |
| label: 'Switch to input', |
| isButton: true, |
| hasTapAction: true, |
| isEnabled: true, |
| hasEnabledState: true, |
| isFocusable: true, |
| )); |
| |
| // Year mode drop down button |
| expect(tester.getSemantics(find.text('January 2016')), matchesSemantics( |
| label: 'Select year', |
| isButton: true, |
| )); |
| |
| // Prev/Next month buttons |
| expect(tester.getSemantics(previousMonthIcon), matchesSemantics( |
| label: 'Previous month December 2015', |
| isButton: true, |
| hasTapAction: true, |
| isEnabled: true, |
| hasEnabledState: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(nextMonthIcon), matchesSemantics( |
| label: 'Next month February 2016', |
| isButton: true, |
| hasTapAction: true, |
| isEnabled: true, |
| hasEnabledState: true, |
| isFocusable: true, |
| )); |
| |
| // Day grid |
| expect(tester.getSemantics(find.text('1')), matchesSemantics( |
| label: '1, Friday, January 1, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('2')), matchesSemantics( |
| label: '2, Saturday, January 2, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('3')), matchesSemantics( |
| label: '3, Sunday, January 3, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('4')), matchesSemantics( |
| label: '4, Monday, January 4, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('5')), matchesSemantics( |
| label: '5, Tuesday, January 5, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('6')), matchesSemantics( |
| label: '6, Wednesday, January 6, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('7')), matchesSemantics( |
| label: '7, Thursday, January 7, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('8')), matchesSemantics( |
| label: '8, Friday, January 8, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('9')), matchesSemantics( |
| label: '9, Saturday, January 9, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('10')), matchesSemantics( |
| label: '10, Sunday, January 10, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('11')), matchesSemantics( |
| label: '11, Monday, January 11, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('12')), matchesSemantics( |
| label: '12, Tuesday, January 12, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('13')), matchesSemantics( |
| label: '13, Wednesday, January 13, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('14')), matchesSemantics( |
| label: '14, Thursday, January 14, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('15')), matchesSemantics( |
| label: '15, Friday, January 15, 2016', |
| hasTapAction: true, |
| isSelected: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('16')), matchesSemantics( |
| label: '16, Saturday, January 16, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('17')), matchesSemantics( |
| label: '17, Sunday, January 17, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('18')), matchesSemantics( |
| label: '18, Monday, January 18, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('19')), matchesSemantics( |
| label: '19, Tuesday, January 19, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('20')), matchesSemantics( |
| label: '20, Wednesday, January 20, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('21')), matchesSemantics( |
| label: '21, Thursday, January 21, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('22')), matchesSemantics( |
| label: '22, Friday, January 22, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('23')), matchesSemantics( |
| label: '23, Saturday, January 23, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('24')), matchesSemantics( |
| label: '24, Sunday, January 24, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('25')), matchesSemantics( |
| label: '25, Monday, January 25, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('26')), matchesSemantics( |
| label: '26, Tuesday, January 26, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('27')), matchesSemantics( |
| label: '27, Wednesday, January 27, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('28')), matchesSemantics( |
| label: '28, Thursday, January 28, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('29')), matchesSemantics( |
| label: '29, Friday, January 29, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| expect(tester.getSemantics(find.text('30')), matchesSemantics( |
| label: '30, Saturday, January 30, 2016', |
| hasTapAction: true, |
| isFocusable: true, |
| )); |
| |
| // 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, |
| )); |
| }); |
| }); |
| |
| testWidgets('calendar year mode', (WidgetTester tester) async { |
| final SemanticsHandle semantics = tester.ensureSemantics(); |
| addTearDown(semantics.dispose); |
| |
| initialCalendarMode = DatePickerMode.year; |
| 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(switchToInputIcon), matchesSemantics( |
| label: 'Switch to input', |
| isButton: true, |
| hasTapAction: true, |
| isEnabled: true, |
| hasEnabledState: true, |
| isFocusable: true, |
| )); |
| |
| // Year mode drop down button |
| expect(tester.getSemantics(find.text('January 2016')), matchesSemantics( |
| label: 'Select year', |
| isButton: true, |
| )); |
| |
| // Year grid only shows 2010 - 2024 |
| for (int year = 2010; year <= 2024; year++) { |
| expect(tester.getSemantics(find.text('$year')), matchesSemantics( |
| label: '$year', |
| hasTapAction: true, |
| isSelected: year == 2016, |
| isFocusable: true, |
| )); |
| } |
| |
| // 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, |
| )); |
| }); |
| }); |
| |
| testWidgets('input mode', (WidgetTester tester) async { |
| final SemanticsHandle semantics = tester.ensureSemantics(); |
| addTearDown(semantics.dispose); |
| |
| 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( |
| label: 'Switch to calendar', |
| isButton: true, |
| hasTapAction: true, |
| isEnabled: true, |
| hasEnabledState: true, |
| isFocusable: true, |
| )); |
| |
| // Text field |
| expect(tester.getSemantics(find.byType(EditableText)), matchesSemantics( |
| label: 'Enter Date\nmm/dd/yyyy', |
| isTextField: true, |
| isFocused: true, |
| value: '01/15/2016', |
| hasTapAction: true, |
| hasSetSelectionAction: true, |
| hasCopyAction: true, |
| hasCutAction: true, |
| hasPasteAction: true, |
| hasMoveCursorBackwardByCharacterAction: true, |
| hasMoveCursorBackwardByWordAction: true, |
| )); |
| |
| // 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, |
| )); |
| }); |
| }); |
| }); |
| |
| group('Keyboard navigation', () { |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('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)); |
| }); |
| }); |
| |
| testWidgets('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.binding.window.physicalSizeTestValue = size; |
| addTearDown(tester.binding.window.clearPhysicalSizeTestValue); |
| tester.binding.window.devicePixelRatioTestValue = 1.0; |
| addTearDown(tester.binding.window.clearDevicePixelRatioTestValue); |
| await prepareDatePicker(tester, (Future<DateTime> date) async { |
| await tester.tap(find.text('OK')); |
| }); |
| await tester.pumpAndSettle(); |
| } |
| |
| testWidgets('common screen size - portrait', (WidgetTester tester) async { |
| await _showPicker(tester, kCommonScreenSizePortrait); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('common screen size - landscape', (WidgetTester tester) async { |
| await _showPicker(tester, kCommonScreenSizeLandscape); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('common screen size - portrait - textScale 1.3', (WidgetTester tester) async { |
| await _showPicker(tester, kCommonScreenSizePortrait, 1.3); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('common screen size - landscape - textScale 1.3', (WidgetTester tester) async { |
| await _showPicker(tester, kCommonScreenSizeLandscape, 1.3); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('small screen size - portrait', (WidgetTester tester) async { |
| await _showPicker(tester, kSmallScreenSizePortrait); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('small screen size - landscape', (WidgetTester tester) async { |
| await _showPicker(tester, kSmallScreenSizeLandscape); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('small screen size - portrait -textScale 1.3', (WidgetTester tester) async { |
| await _showPicker(tester, kSmallScreenSizePortrait, 1.3); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('small screen size - landscape - textScale 1.3', (WidgetTester tester) async { |
| await _showPicker(tester, kSmallScreenSizeLandscape, 1.3); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| } |
| |
| class _DatePickerObserver extends NavigatorObserver { |
| int datePickerCount = 0; |
| |
| @override |
| void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { |
| if (route.toString().contains('_DialogRoute')) { |
| datePickerCount++; |
| } |
| super.didPush(route, previousRoute); |
| } |
| } |