| // 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/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../widgets/clipboard_utils.dart'; |
| |
| class TestMaterialLocalizations extends DefaultMaterialLocalizations { |
| @override |
| String formatCompactDate(DateTime date) { |
| return '${date.month}/${date.day}/${date.year}'; |
| } |
| } |
| |
| class TestMaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { |
| @override |
| bool isSupported(Locale locale) => true; |
| |
| @override |
| Future<MaterialLocalizations> load(Locale locale) { |
| return SynchronousFuture<MaterialLocalizations>(TestMaterialLocalizations()); |
| } |
| |
| @override |
| bool shouldReload(TestMaterialLocalizationsDelegate old) => false; |
| } |
| |
| void main() { |
| TestWidgetsFlutterBinding.ensureInitialized(); |
| final MockClipboard mockClipboard = MockClipboard(); |
| |
| Widget inputDatePickerField({ |
| Key? key, |
| DateTime? initialDate, |
| DateTime? firstDate, |
| DateTime? lastDate, |
| ValueChanged<DateTime>? onDateSubmitted, |
| ValueChanged<DateTime>? onDateSaved, |
| SelectableDayPredicate? selectableDayPredicate, |
| String? errorFormatText, |
| String? errorInvalidText, |
| String? fieldHintText, |
| String? fieldLabelText, |
| bool autofocus = false, |
| Key? formKey, |
| ThemeData? theme, |
| Iterable<LocalizationsDelegate<dynamic>>? localizationsDelegates, |
| }) { |
| return MaterialApp( |
| theme: theme ?? ThemeData.from(colorScheme: const ColorScheme.light()), |
| localizationsDelegates: localizationsDelegates, |
| home: Material( |
| child: Form( |
| key: formKey, |
| child: InputDatePickerFormField( |
| key: key, |
| initialDate: initialDate ?? DateTime(2016, DateTime.january, 15), |
| firstDate: firstDate ?? DateTime(2001), |
| lastDate: lastDate ?? DateTime(2031, DateTime.december, 31), |
| onDateSubmitted: onDateSubmitted, |
| onDateSaved: onDateSaved, |
| selectableDayPredicate: selectableDayPredicate, |
| errorFormatText: errorFormatText, |
| errorInvalidText: errorInvalidText, |
| fieldHintText: fieldHintText, |
| fieldLabelText: fieldLabelText, |
| autofocus: autofocus, |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| TextField textField(WidgetTester tester) { |
| return tester.widget<TextField>(find.byType(TextField)); |
| } |
| |
| TextEditingController textFieldController(WidgetTester tester) { |
| return textField(tester).controller!; |
| } |
| |
| double textOpacity(WidgetTester tester, String textValue) { |
| final FadeTransition opacityWidget = tester.widget<FadeTransition>( |
| find.ancestor( |
| of: find.text(textValue), |
| matching: find.byType(FadeTransition), |
| ).first, |
| ); |
| return opacityWidget.opacity.value; |
| } |
| |
| group('InputDatePickerFormField', () { |
| |
| testWidgets('Initial date is the default', (WidgetTester tester) async { |
| final GlobalKey<FormState> formKey = GlobalKey<FormState>(); |
| final DateTime initialDate = DateTime(2016, DateTime.february, 21); |
| DateTime? inputDate; |
| await tester.pumpWidget(inputDatePickerField( |
| initialDate: initialDate, |
| onDateSaved: (DateTime date) => inputDate = date, |
| formKey: formKey, |
| )); |
| expect(textFieldController(tester).value.text, equals('02/21/2016')); |
| formKey.currentState!.save(); |
| expect(inputDate, equals(initialDate)); |
| }); |
| |
| testWidgets('Changing initial date is reflected in text value', (WidgetTester tester) async { |
| final DateTime initialDate = DateTime(2016, DateTime.february, 21); |
| final DateTime updatedInitialDate = DateTime(2016, DateTime.february, 23); |
| await tester.pumpWidget(inputDatePickerField( |
| initialDate: initialDate, |
| )); |
| expect(textFieldController(tester).value.text, equals('02/21/2016')); |
| |
| await tester.pumpWidget(inputDatePickerField( |
| initialDate: updatedInitialDate, |
| )); |
| await tester.pumpAndSettle(); |
| expect(textFieldController(tester).value.text, equals('02/23/2016')); |
| }); |
| |
| testWidgets('Valid date entry', (WidgetTester tester) async { |
| final GlobalKey<FormState> formKey = GlobalKey<FormState>(); |
| DateTime? inputDate; |
| await tester.pumpWidget(inputDatePickerField( |
| onDateSaved: (DateTime date) => inputDate = date, |
| formKey: formKey, |
| )); |
| |
| textFieldController(tester).text = '02/21/2016'; |
| formKey.currentState!.save(); |
| expect(inputDate, equals(DateTime(2016, DateTime.february, 21))); |
| }); |
| |
| testWidgets('Invalid text entry shows errorFormat text', (WidgetTester tester) async { |
| final GlobalKey<FormState> formKey = GlobalKey<FormState>(); |
| DateTime? inputDate; |
| await tester.pumpWidget(inputDatePickerField( |
| onDateSaved: (DateTime date) => inputDate = date, |
| formKey: formKey, |
| )); |
| // Default errorFormat text |
| expect(find.text('Invalid format.'), findsNothing); |
| await tester.enterText(find.byType(TextField), 'foobar'); |
| expect(formKey.currentState!.validate(), isFalse); |
| await tester.pumpAndSettle(); |
| expect(inputDate, isNull); |
| expect(find.text('Invalid format.'), findsOneWidget); |
| |
| // Change to a custom errorFormat text |
| await tester.pumpWidget(inputDatePickerField( |
| onDateSaved: (DateTime date) => inputDate = date, |
| errorFormatText: 'That is not a date.', |
| formKey: formKey, |
| )); |
| expect(formKey.currentState!.validate(), isFalse); |
| await tester.pumpAndSettle(); |
| expect(find.text('Invalid format.'), findsNothing); |
| expect(find.text('That is not a date.'), findsOneWidget); |
| }); |
| |
| testWidgets('Valid text entry, but date outside first or last date shows bounds shows errorInvalid text', (WidgetTester tester) async { |
| final GlobalKey<FormState> formKey = GlobalKey<FormState>(); |
| DateTime? inputDate; |
| await tester.pumpWidget(inputDatePickerField( |
| firstDate: DateTime(1966, DateTime.february, 21), |
| lastDate: DateTime(2040, DateTime.february, 23), |
| onDateSaved: (DateTime date) => inputDate = date, |
| formKey: formKey, |
| )); |
| // Default errorInvalid text |
| expect(find.text('Out of range.'), findsNothing); |
| // Before first date |
| await tester.enterText(find.byType(TextField), '02/21/1950'); |
| expect(formKey.currentState!.validate(), isFalse); |
| await tester.pumpAndSettle(); |
| expect(inputDate, isNull); |
| expect(find.text('Out of range.'), findsOneWidget); |
| // After last date |
| await tester.enterText(find.byType(TextField), '02/23/2050'); |
| expect(formKey.currentState!.validate(), isFalse); |
| await tester.pumpAndSettle(); |
| expect(inputDate, isNull); |
| expect(find.text('Out of range.'), findsOneWidget); |
| |
| await tester.pumpWidget(inputDatePickerField( |
| onDateSaved: (DateTime date) => inputDate = date, |
| errorInvalidText: 'Not in given range.', |
| formKey: formKey, |
| )); |
| expect(formKey.currentState!.validate(), isFalse); |
| await tester.pumpAndSettle(); |
| expect(find.text('Out of range.'), findsNothing); |
| expect(find.text('Not in given range.'), findsOneWidget); |
| }); |
| |
| testWidgets('selectableDatePredicate will be used to show errorInvalid if date is not selectable', (WidgetTester tester) async { |
| final GlobalKey<FormState> formKey = GlobalKey<FormState>(); |
| DateTime? inputDate; |
| await tester.pumpWidget(inputDatePickerField( |
| initialDate: DateTime(2016, DateTime.january, 16), |
| onDateSaved: (DateTime date) => inputDate = date, |
| selectableDayPredicate: (DateTime date) => date.day.isEven, |
| formKey: formKey, |
| )); |
| // Default errorInvalid text |
| expect(find.text('Out of range.'), findsNothing); |
| // Odd day shouldn't be valid |
| await tester.enterText(find.byType(TextField), '02/21/1966'); |
| expect(formKey.currentState!.validate(), isFalse); |
| await tester.pumpAndSettle(); |
| expect(inputDate, isNull); |
| expect(find.text('Out of range.'), findsOneWidget); |
| // Even day is valid |
| await tester.enterText(find.byType(TextField), '02/24/2030'); |
| expect(formKey.currentState!.validate(), isTrue); |
| formKey.currentState!.save(); |
| await tester.pumpAndSettle(); |
| expect(inputDate, equals(DateTime(2030, DateTime.february, 24))); |
| expect(find.text('Out of range.'), findsNothing); |
| }); |
| |
| testWidgets('Empty field shows hint text when focused', (WidgetTester tester) async { |
| await tester.pumpWidget(inputDatePickerField()); |
| // Focus on it |
| await tester.tap(find.byType(TextField)); |
| await tester.pumpAndSettle(); |
| |
| // Hint text should be invisible |
| expect(textOpacity(tester, 'mm/dd/yyyy'), equals(0.0)); |
| textFieldController(tester).clear(); |
| await tester.pumpAndSettle(); |
| // Hint text should be visible |
| expect(textOpacity(tester, 'mm/dd/yyyy'), equals(1.0)); |
| |
| // Change to a different hint text |
| await tester.pumpWidget(inputDatePickerField(fieldHintText: 'Enter some date')); |
| await tester.pumpAndSettle(); |
| expect(find.text('mm/dd/yyyy'), findsNothing); |
| expect(textOpacity(tester, 'Enter some date'), equals(1.0)); |
| await tester.enterText(find.byType(TextField), 'foobar'); |
| await tester.pumpAndSettle(); |
| expect(textOpacity(tester, 'Enter some date'), equals(0.0)); |
| }); |
| |
| testWidgets('Label text', (WidgetTester tester) async { |
| await tester.pumpWidget(inputDatePickerField()); |
| // Default label |
| expect(find.text('Enter Date'), findsOneWidget); |
| |
| await tester.pumpWidget(inputDatePickerField( |
| fieldLabelText: 'Give me a date!', |
| )); |
| expect(find.text('Enter Date'), findsNothing); |
| expect(find.text('Give me a date!'), findsOneWidget); |
| }); |
| |
| testWidgets('Semantics', (WidgetTester tester) async { |
| final SemanticsHandle semantics = tester.ensureSemantics(); |
| addTearDown(semantics.dispose); |
| |
| // Fill the clipboard so that the Paste option is available in the text |
| // selection menu. |
| tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); |
| await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); |
| addTearDown(() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null)); |
| |
| await tester.pumpWidget(inputDatePickerField(autofocus: true)); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getSemantics(find.byType(EditableText)), matchesSemantics( |
| label: 'Enter Date\nmm/dd/yyyy', |
| isTextField: true, |
| isFocused: true, |
| value: '01/15/2016', |
| hasTapAction: true, |
| hasSetTextAction: true, |
| hasSetSelectionAction: true, |
| hasCopyAction: true, |
| hasCutAction: true, |
| hasPasteAction: true, |
| hasMoveCursorBackwardByCharacterAction: true, |
| hasMoveCursorBackwardByWordAction: true, |
| )); |
| }); |
| |
| testWidgets('InputDecorationTheme is honored', (WidgetTester tester) async { |
| const InputBorder border = InputBorder.none; |
| await tester.pumpWidget(inputDatePickerField( |
| theme: ThemeData.from(colorScheme: const ColorScheme.light()).copyWith( |
| inputDecorationTheme: const InputDecorationTheme( |
| border: border, |
| ), |
| ), |
| )); |
| 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; |
| // ignore: avoid_dynamic_calls |
| final dynamic/*_InputBorderTween*/ inputBorderTween = inputBorderPainter.border; |
| // ignore: avoid_dynamic_calls |
| final Animation<double> animation = inputBorderPainter.borderAnimation as Animation<double>; |
| // ignore: avoid_dynamic_calls |
| final InputBorder actualBorder = inputBorderTween.evaluate(animation) as InputBorder; |
| // ignore: avoid_dynamic_calls |
| 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)); |
| }); |
| |
| testWidgets('Date text localization', (WidgetTester tester) async { |
| final Iterable<LocalizationsDelegate<dynamic>> delegates = <LocalizationsDelegate<dynamic>>[ |
| TestMaterialLocalizationsDelegate(), |
| DefaultWidgetsLocalizations.delegate, |
| ]; |
| await tester.pumpWidget( |
| inputDatePickerField( |
| localizationsDelegates: delegates, |
| ) |
| ); |
| await tester.enterText(find.byType(TextField), '01/01/2022'); |
| await tester.pumpAndSettle(); |
| |
| // Verify that the widget can be updated to a new value after the |
| // entered text was transformed by the localization formatter. |
| await tester.pumpWidget( |
| inputDatePickerField( |
| initialDate: DateTime(2017), |
| localizationsDelegates: delegates, |
| ) |
| ); |
| }); |
| }); |
| } |