// 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 'dart:ui'; import 'package:flutter/rendering.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/gestures.dart' show DragStartBehavior; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; void main() { group('showDatePicker', () { _tests(); }); } void _tests() { DateTime firstDate; DateTime lastDate; DateTime initialDate; SelectableDayPredicate selectableDayPredicate; DatePickerMode initialDatePickerMode; 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)); setUp(() { firstDate = DateTime(2001, DateTime.january, 1); lastDate = DateTime(2031, DateTime.december, 31); initialDate = DateTime(2016, DateTime.january, 15); selectableDayPredicate = null; initialDatePickerMode = null; }); testWidgets('tap-select a day', (WidgetTester tester) async { final Key _datePickerKey = UniqueKey(); DateTime _selectedDate = DateTime(2016, DateTime.july, 26); await tester.pumpWidget( MaterialApp( home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Container( width: 400.0, child: SingleChildScrollView( dragStartBehavior: DragStartBehavior.down, child: Material( child: MonthPicker( dragStartBehavior: DragStartBehavior.down, firstDate: DateTime(0), lastDate: DateTime(9999), key: _datePickerKey, selectedDate: _selectedDate, onChanged: (DateTime value) { setState(() { _selectedDate = value; }); }, ), ), ), ); }, ), ), ); expect(_selectedDate, equals(DateTime(2016, DateTime.july, 26))); await tester.tapAt(const Offset(50.0, 100.0)); await tester.pumpAndSettle(); await tester.pump(const Duration(seconds: 2)); await tester.tap(find.text('1')); await tester.pumpAndSettle(); expect(_selectedDate, equals(DateTime(2016, DateTime.july, 1))); await tester.tap(nextMonthIcon); await tester.pumpAndSettle(); expect(_selectedDate, equals(DateTime(2016, DateTime.july, 1))); await tester.tap(find.text('5')); await tester.pumpAndSettle(); expect(_selectedDate, equals(DateTime(2016, DateTime.august, 5))); await tester.drag(find.byKey(_datePickerKey), const Offset(-400.0, 0.0)); await tester.pumpAndSettle(); expect(_selectedDate, equals(DateTime(2016, DateTime.august, 5))); await tester.tap(find.text('25')); await tester.pumpAndSettle(); expect(_selectedDate, equals(DateTime(2016, DateTime.september, 25))); await tester.drag(find.byKey(_datePickerKey), const Offset(800.0, 0.0)); await tester.pumpAndSettle(); expect(_selectedDate, equals(DateTime(2016, DateTime.september, 25))); await tester.tap(find.text('17')); await tester.pumpAndSettle(); expect(_selectedDate, equals(DateTime(2016, DateTime.august, 17))); }); testWidgets('render picker with intrinsic dimensions', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return IntrinsicWidth( child: IntrinsicHeight( child: Material( child: SingleChildScrollView( child: MonthPicker( firstDate: DateTime(0), lastDate: DateTime(9999), onChanged: (DateTime value) { }, selectedDate: DateTime(2000, DateTime.january, 1), ), ), ), ), ); }, ), ), ); await tester.pump(const Duration(seconds: 5)); }); Future<void> preparePicker(WidgetTester tester, Future<void> callback(Future<DateTime> date)) async { BuildContext buttonContext; await tester.pumpWidget(MaterialApp( home: Material( child: Builder( builder: (BuildContext context) { return RaisedButton( onPressed: () { buttonContext = context; }, child: const Text('Go'), ); }, ), ), )); await tester.tap(find.text('Go')); expect(buttonContext, isNotNull); final Future<DateTime> date = initialDatePickerMode == null // Exercise the argument default for initialDatePickerMode. ? showDatePicker( context: buttonContext, initialDate: initialDate, firstDate: firstDate, lastDate: lastDate, selectableDayPredicate: selectableDayPredicate, ) : showDatePicker( context: buttonContext, initialDate: initialDate, firstDate: firstDate, lastDate: lastDate, selectableDayPredicate: selectableDayPredicate, initialDatePickerMode: initialDatePickerMode, ); await tester.pumpAndSettle(const Duration(seconds: 1)); await callback(date); } testWidgets('Initial date is the default', (WidgetTester tester) async { await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('OK')); expect(await date, equals(DateTime(2016, DateTime.january, 15))); }); }); testWidgets('Can cancel', (WidgetTester tester) async { await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('CANCEL')); expect(await date, isNull); }); }); testWidgets('Can select a day', (WidgetTester tester) async { await preparePicker(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 preparePicker(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, equals(DateTime(2015, DateTime.december, 25))); }); }); testWidgets('Can select a year', (WidgetTester tester) async { await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('2016')); await tester.pump(); await tester.tap(find.text('2018')); await tester.tap(find.text('OK')); expect(await date, equals(DateTime(2018, DateTime.january, 15))); }); }); testWidgets('Can select a year and then a day', (WidgetTester tester) async { await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('2016')); await tester.pump(); await tester.tap(find.text('2017')); await tester.pump(); final MaterialLocalizations localizations = MaterialLocalizations.of( tester.element(find.byType(DayPicker)) ); final String dayLabel = localizations.formatMediumDate(DateTime(2017, DateTime.january, 15)); await tester.tap(find.text(dayLabel)); await tester.pump(); await tester.tap(find.text('19')); await tester.tap(find.text('OK')); expect(await date, equals(DateTime(2017, DateTime.january, 19))); }); }); testWidgets('Current year is initially visible in year picker', (WidgetTester tester) async { initialDate = DateTime(2000); firstDate = DateTime(1900); lastDate = DateTime(2100); await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('2000')); await tester.pump(); expect(find.text('2000'), findsNWidgets(2)); }); }); testWidgets('Cannot select a day outside bounds', (WidgetTester tester) async { initialDate = DateTime(2017, DateTime.january, 15); firstDate = initialDate; lastDate = initialDate; await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('10')); // Earlier than firstDate. Should be ignored. await tester.tap(find.text('20')); // Later than lastDate. Should be ignored. await tester.tap(find.text('OK')); // We should still be on the initial date. expect(await date, equals(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 preparePicker(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 preparePicker(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('Selecting firstDate year respects firstDate', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/17309 initialDate = DateTime(2018, DateTime.may, 4); firstDate = DateTime(2016, DateTime.june, 9); lastDate = DateTime(2019, DateTime.january, 15); await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('2018')); await tester.pumpAndSettle(); await tester.tap(find.text('2016')); await tester.pumpAndSettle(); await tester.tap(find.text('OK')); await tester.pumpAndSettle(); expect(await date, DateTime(2016, DateTime.june, 9)); }); }); testWidgets('Selecting lastDate year respects lastDate', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/17309 initialDate = DateTime(2018, DateTime.may, 4); firstDate = DateTime(2016, DateTime.june, 9); lastDate = DateTime(2019, DateTime.january, 15); await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('2018')); await tester.pumpAndSettle(); await tester.tap(find.text('2019')); await tester.pumpAndSettle(); await tester.tap(find.text('OK')); await tester.pumpAndSettle(); expect(await date, DateTime(2019, DateTime.january, 15)); }); }); 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 preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('10')); // Even, works. await tester.tap(find.text('13')); // Odd, doesn't work. await tester.tap(find.text('17')); // Odd, doesn't work. await tester.tap(find.text('OK')); expect(await date, equals(DateTime(2017, DateTime.january, 10))); }); }); testWidgets('Can select initial date picker mode', (WidgetTester tester) async { initialDate = DateTime(2014, DateTime.january, 15); initialDatePickerMode = DatePickerMode.year; await preparePicker(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.tap(find.text('OK')); expect(await date, equals(DateTime(2018, DateTime.january, 15))); }); }); group('haptic feedback', () { const Duration kHapticFeedbackInterval = Duration(milliseconds: 10); FeedbackTester feedback; setUp(() { feedback = FeedbackTester(); initialDate = DateTime(2017, DateTime.january, 16); firstDate = DateTime(2017, DateTime.january, 10); lastDate = DateTime(2018, DateTime.january, 20); selectableDayPredicate = (DateTime date) => date.day.isEven; }); tearDown(() { feedback?.dispose(); }); testWidgets('tap-select date vibrates', (WidgetTester tester) async { await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('10')); await tester.pump(kHapticFeedbackInterval); expect(feedback.hapticCount, 1); await tester.tap(find.text('12')); await tester.pump(kHapticFeedbackInterval); expect(feedback.hapticCount, 2); await tester.tap(find.text('14')); await tester.pump(kHapticFeedbackInterval); expect(feedback.hapticCount, 3); }); }); testWidgets('tap-select unselectable date does not vibrate', (WidgetTester tester) async { await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('11')); await tester.pump(kHapticFeedbackInterval); expect(feedback.hapticCount, 0); await tester.tap(find.text('13')); await tester.pump(kHapticFeedbackInterval); expect(feedback.hapticCount, 0); await tester.tap(find.text('15')); await tester.pump(kHapticFeedbackInterval); expect(feedback.hapticCount, 0); }); }); testWidgets('mode, year change vibrates', (WidgetTester tester) async { await preparePicker(tester, (Future<DateTime> date) async { await tester.tap(find.text('2017')); await tester.pump(kHapticFeedbackInterval); expect(feedback.hapticCount, 1); await tester.tap(find.text('2018')); await tester.pump(kHapticFeedbackInterval); expect(feedback.hapticCount, 2); }); }); }); test('days in month', () { expect(DayPicker.getDaysInMonth(2017, 10), 31); expect(DayPicker.getDaysInMonth(2017, 6), 30); expect(DayPicker.getDaysInMonth(2017, 2), 28); expect(DayPicker.getDaysInMonth(2016, 2), 29); expect(DayPicker.getDaysInMonth(2000, 2), 29); expect(DayPicker.getDaysInMonth(1900, 2), 28); }); testWidgets('month header tap', (WidgetTester tester) async { selectableDayPredicate = null; await preparePicker(tester, (Future<DateTime> date) async { // Switch into the year selector. await tester.tap(find.text('January 2016')); await tester.pump(); expect(find.text('2020'), isNotNull); await tester.tap(find.text('CANCEL')); expect(await date, isNull); }); }); testWidgets('exports semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await preparePicker(tester, (Future<DateTime> date) async { final TestSemantics expected = TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], children: <TestSemantics>[ TestSemantics( elevation: 24.0, thickness: 0.0, children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isFocusable], actions: <SemanticsAction>[SemanticsAction.tap], label: '2016', textDirection: TextDirection.ltr, ), TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.isSelected, SemanticsFlag.isFocusable, ], actions: <SemanticsAction>[SemanticsAction.tap], label: 'Fri, Jan 15', textDirection: TextDirection.ltr, ), TestSemantics( children: <TestSemantics>[ TestSemantics( id: 55, actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight], children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( id: 11, flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling], children: <TestSemantics>[ // TODO(dnfield): These shouldn't be here. https://github.com/flutter/flutter/issues/34431 TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics(), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '1, Friday, January 1, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '2, Saturday, January 2, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '3, Sunday, January 3, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '4, Monday, January 4, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '5, Tuesday, January 5, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '6, Wednesday, January 6, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '7, Thursday, January 7, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '8, Friday, January 8, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '9, Saturday, January 9, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '10, Sunday, January 10, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '11, Monday, January 11, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '12, Tuesday, January 12, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '13, Wednesday, January 13, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '14, Thursday, January 14, 2016', textDirection: TextDirection.ltr, ), TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isSelected], actions: <SemanticsAction>[SemanticsAction.tap], label: '15, Friday, January 15, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '16, Saturday, January 16, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '17, Sunday, January 17, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '18, Monday, January 18, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '19, Tuesday, January 19, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '20, Wednesday, January 20, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '21, Thursday, January 21, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '22, Friday, January 22, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '23, Saturday, January 23, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '24, Sunday, January 24, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '25, Monday, January 25, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '26, Tuesday, January 26, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '27, Wednesday, January 27, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '28, Thursday, January 28, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '29, Friday, January 29, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '30, Saturday, January 30, 2016', textDirection: TextDirection.ltr, ), TestSemantics( actions: <SemanticsAction>[SemanticsAction.tap], label: '31, Sunday, January 31, 2016', textDirection: TextDirection.ltr, ), ], ), ], ), ], ), ], ), ], ), TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.hasEnabledState, SemanticsFlag.isButton, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, ], actions: <SemanticsAction>[SemanticsAction.tap], label: 'Previous month December 2015', textDirection: TextDirection.ltr, ), TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.hasEnabledState, SemanticsFlag.isButton, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, ], actions: <SemanticsAction>[SemanticsAction.tap], label: 'Next month February 2016', textDirection: TextDirection.ltr, ), TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.hasEnabledState, SemanticsFlag.isButton, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, ], actions: <SemanticsAction>[SemanticsAction.tap], label: 'CANCEL', textDirection: TextDirection.ltr, ), TestSemantics( flags: <SemanticsFlag>[ SemanticsFlag.hasEnabledState, SemanticsFlag.isButton, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, ], actions: <SemanticsAction>[SemanticsAction.tap], label: 'OK', textDirection: TextDirection.ltr, ), ], ), ], ); expect(semantics, hasSemantics( TestSemantics.root(children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[expected], ), ]), ignoreId: true, ignoreTransform: true, ignoreRect: true, )); }); semantics.dispose(); }); testWidgets('chervons animate when scrolling month picker', (WidgetTester tester) async { final Key _datePickerKey = UniqueKey(); DateTime _selectedDate = DateTime(2016, DateTime.july, 26); await tester.pumpWidget( MaterialApp( home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Container( width: 400.0, child: SingleChildScrollView( child: Material( child: MonthPicker( firstDate: DateTime(0), lastDate: DateTime(9999), key: _datePickerKey, selectedDate: _selectedDate, onChanged: (DateTime value) { setState(() { _selectedDate = value; }); }, ), ), ), ); }, ), ), ); final Finder chevronFinder = find.byType(IconButton); final List<RenderAnimatedOpacity> chevronRenderers = chevronFinder .evaluate() .map((Element element) => element.findAncestorRenderObjectOfType<RenderAnimatedOpacity>()) .toList(); // Initial chevron animation state should be dismissed // An AlwaysStoppedAnimation is also found and is ignored for (final RenderAnimatedOpacity renderer in chevronRenderers) { expect(renderer.opacity.value, equals(1.0)); expect(renderer.opacity.status, equals(AnimationStatus.dismissed)); } // Drag and hold the picker to test for the opacity change final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0)); await gesture.moveBy(const Offset(50.0, 100.0)); await tester.pumpAndSettle(); for (final RenderAnimatedOpacity renderer in chevronRenderers) { expect(renderer.opacity.value, equals(0.0)); expect(renderer.opacity.status, equals(AnimationStatus.completed)); } // Release the drag and test for the opacity to return to original value await gesture.up(); await tester.pumpAndSettle(); for (final RenderAnimatedOpacity renderer in chevronRenderers) { expect(renderer.opacity.value, equals(1.0)); expect(renderer.opacity.status, equals(AnimationStatus.dismissed)); } }); testWidgets('builder parameter', (WidgetTester tester) async { Widget buildFrame(TextDirection textDirection) { return MaterialApp( home: Material( child: Center( child: Builder( builder: (BuildContext context) { return RaisedButton( 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, ); }, ); }, ); }, ), ), ), ); } 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); }); 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; tester.binding.window.devicePixelRatioTestValue = 1.0; await tester.pumpWidget( MaterialApp( home: Builder( builder: (BuildContext context) { return RaisedButton( child: const Text('X'), onPressed: () { showDatePicker( context: context, initialDate: initialDate, firstDate: firstDate, lastDate: lastDate, ); }, ); }, ), ), ); await tester.tap(find.text('X')); 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); }); }); testWidgets('uses root navigator by default', (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 RaisedButton( onPressed: () { showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2018), lastDate: DateTime(2030), builder: (BuildContext context, Widget child) { return const SizedBox(); }, ); }, child: const Text('Show Date Picker'), ); }, ); }, ), )); // Open the dialog. await tester.tap(find.byType(RaisedButton)); expect(rootObserver.datePickerCount, 1); expect(nestedObserver.datePickerCount, 0); }); 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 RaisedButton( 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(RaisedButton)); expect(rootObserver.datePickerCount, 0); expect(nestedObserver.datePickerCount, 1); }); } 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); } }