2898 lines
109 KiB
Dart
2898 lines
109 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'dart:ui';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import '../widgets/clipboard_utils.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
late DateTime firstDate;
|
|
late DateTime lastDate;
|
|
late DateTime? initialDate;
|
|
late DateTime today;
|
|
late SelectableDayPredicate? selectableDayPredicate;
|
|
late DatePickerEntryMode initialEntryMode;
|
|
late DatePickerMode initialCalendarMode;
|
|
late DatePickerEntryMode currentMode;
|
|
|
|
String? cancelText;
|
|
String? confirmText;
|
|
String? errorFormatText;
|
|
String? errorInvalidText;
|
|
String? fieldHintText;
|
|
String? fieldLabelText;
|
|
String? helpText;
|
|
TextInputType? keyboardType;
|
|
|
|
final Finder nextMonthIcon = find.byWidgetPredicate(
|
|
(Widget w) => w is IconButton && (w.tooltip?.startsWith('Next month') ?? false),
|
|
);
|
|
final Finder previousMonthIcon = find.byWidgetPredicate(
|
|
(Widget w) => w is IconButton && (w.tooltip?.startsWith('Previous month') ?? false),
|
|
);
|
|
final Finder switchToInputIcon = find.byIcon(Icons.edit);
|
|
final Finder switchToCalendarIcon = find.byIcon(Icons.calendar_today);
|
|
|
|
TextField textField(WidgetTester tester) {
|
|
return tester.widget<TextField>(find.byType(TextField));
|
|
}
|
|
|
|
setUp(() {
|
|
firstDate = DateTime(2001);
|
|
lastDate = DateTime(2031, DateTime.december, 31);
|
|
initialDate = DateTime(2016, DateTime.january, 15);
|
|
today = DateTime(2016, DateTime.january, 3);
|
|
selectableDayPredicate = null;
|
|
initialEntryMode = DatePickerEntryMode.calendar;
|
|
initialCalendarMode = DatePickerMode.day;
|
|
|
|
cancelText = null;
|
|
confirmText = null;
|
|
errorFormatText = null;
|
|
errorInvalidText = null;
|
|
fieldHintText = null;
|
|
fieldLabelText = null;
|
|
helpText = null;
|
|
keyboardType = null;
|
|
currentMode = initialEntryMode;
|
|
});
|
|
|
|
const wideWindowSize = Size(1920.0, 1080.0);
|
|
const narrowWindowSize = Size(1070.0, 1770.0);
|
|
|
|
Future<void> prepareDatePicker(
|
|
WidgetTester tester,
|
|
Future<void> Function(Future<DateTime?> date) callback, {
|
|
TextDirection textDirection = TextDirection.ltr,
|
|
bool useMaterial3 = false,
|
|
ThemeData? theme,
|
|
TextScaler textScaler = TextScaler.noScaling,
|
|
}) async {
|
|
late BuildContext buttonContext;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme ?? ThemeData(useMaterial3: useMaterial3),
|
|
home: MediaQuery(
|
|
data: MediaQueryData(textScaler: textScaler),
|
|
child: Material(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
onPressed: () {
|
|
buttonContext = context;
|
|
},
|
|
child: const Text('Go'),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Go'));
|
|
expect(buttonContext, isNotNull);
|
|
|
|
final Future<DateTime?> date = showDatePicker(
|
|
context: buttonContext,
|
|
initialDate: initialDate,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
currentDate: today,
|
|
selectableDayPredicate: selectableDayPredicate,
|
|
initialDatePickerMode: initialCalendarMode,
|
|
initialEntryMode: initialEntryMode,
|
|
cancelText: cancelText,
|
|
confirmText: confirmText,
|
|
errorFormatText: errorFormatText,
|
|
errorInvalidText: errorInvalidText,
|
|
fieldHintText: fieldHintText,
|
|
fieldLabelText: fieldLabelText,
|
|
helpText: helpText,
|
|
keyboardType: keyboardType,
|
|
onDatePickerModeChange: (DatePickerEntryMode value) {
|
|
currentMode = value;
|
|
},
|
|
builder: (BuildContext context, Widget? child) {
|
|
return Directionality(textDirection: textDirection, child: child ?? const SizedBox());
|
|
},
|
|
);
|
|
|
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
await callback(date);
|
|
}
|
|
|
|
ShapeDecoration? findDayDecoration(WidgetTester tester, String day) {
|
|
return tester
|
|
.widget<Ink>(find.ancestor(of: find.text(day), matching: find.byType(Ink)))
|
|
.decoration
|
|
as ShapeDecoration?;
|
|
}
|
|
|
|
MaterialInkController findDayGridMaterial(WidgetTester tester) {
|
|
// All days are painted on the same Material widget.
|
|
// Use an arbitrary day to find this Material.
|
|
return Material.of(tester.element(find.text('17')));
|
|
}
|
|
|
|
group('showDatePicker Dialog', () {
|
|
testWidgets('Default dialog size', (WidgetTester tester) async {
|
|
Future<void> showPicker(WidgetTester tester, Size size) async {
|
|
tester.view.physicalSize = size;
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, useMaterial3: true);
|
|
}
|
|
|
|
const calendarLandscapeDialogSize = Size(496.0, 346.0);
|
|
const calendarPortraitDialogSizeM3 = Size(360.0, 568.0);
|
|
|
|
// Test landscape layout.
|
|
await showPicker(tester, wideWindowSize);
|
|
|
|
Size dialogContainerSize = tester.getSize(find.byType(AnimatedContainer));
|
|
expect(dialogContainerSize, calendarLandscapeDialogSize);
|
|
|
|
// Close the dialog.
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Test portrait layout.
|
|
await showPicker(tester, narrowWindowSize);
|
|
|
|
dialogContainerSize = tester.getSize(find.byType(AnimatedContainer));
|
|
expect(dialogContainerSize, calendarPortraitDialogSizeM3);
|
|
});
|
|
|
|
testWidgets('Default dialog properties', (WidgetTester tester) async {
|
|
final theme = ThemeData();
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
final Material dialogMaterial = tester.widget<Material>(
|
|
find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first,
|
|
);
|
|
|
|
expect(dialogMaterial.color, theme.colorScheme.surfaceContainerHigh);
|
|
expect(dialogMaterial.shadowColor, Colors.transparent);
|
|
expect(dialogMaterial.surfaceTintColor, Colors.transparent);
|
|
expect(dialogMaterial.elevation, 6.0);
|
|
expect(
|
|
dialogMaterial.shape,
|
|
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0))),
|
|
);
|
|
expect(dialogMaterial.clipBehavior, Clip.antiAlias);
|
|
|
|
final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog));
|
|
expect(dialog.insetPadding, const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0));
|
|
}, useMaterial3: theme.useMaterial3);
|
|
});
|
|
|
|
testWidgets('Material3 uses sentence case labels', (WidgetTester tester) async {
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
expect(find.text('Select date'), findsOneWidget);
|
|
}, useMaterial3: true);
|
|
});
|
|
|
|
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 switch from calendar to input entry mode', (WidgetTester tester) async {
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
expect(find.byType(TextField), findsNothing);
|
|
await tester.tap(find.byIcon(Icons.edit));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(TextField), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('Can switch from input to calendar entry mode', (WidgetTester tester) async {
|
|
initialEntryMode = DatePickerEntryMode.input;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
expect(find.byType(TextField), findsOneWidget);
|
|
await tester.tap(find.byIcon(Icons.calendar_today));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(TextField), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('Can not switch out of calendarOnly mode', (WidgetTester tester) async {
|
|
initialEntryMode = DatePickerEntryMode.calendarOnly;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
expect(find.byType(TextField), findsNothing);
|
|
expect(find.byIcon(Icons.edit), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('Can not switch out of inputOnly mode', (WidgetTester tester) async {
|
|
initialEntryMode = DatePickerEntryMode.inputOnly;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
expect(find.byType(TextField), findsOneWidget);
|
|
expect(find.byIcon(Icons.calendar_today), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('Switching to input mode keeps selected date', (WidgetTester tester) async {
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
await tester.tap(find.text('12'));
|
|
await tester.tap(find.byIcon(Icons.edit));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('OK'));
|
|
expect(await date, DateTime(2016, DateTime.january, 12));
|
|
});
|
|
});
|
|
|
|
testWidgets('Input only mode should validate date', (WidgetTester tester) async {
|
|
initialEntryMode = DatePickerEntryMode.inputOnly;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
// Enter text input mode and type an invalid date to get error.
|
|
await tester.enterText(find.byType(TextField), '1234567');
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Invalid format.'), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
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 calendar mode and then back to input mode
|
|
await tester.tap(find.byIcon(Icons.calendar_today));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byIcon(Icons.edit));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Invalid format.'), findsNothing);
|
|
|
|
// Edit the text, the error should not be showing until ok is tapped
|
|
await tester.enterText(find.byType(TextField), '1234567');
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Invalid format.'), findsNothing);
|
|
});
|
|
});
|
|
|
|
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 view.
|
|
expect(tester.getBottomLeft(find.text('OK')).dx, moreOrLessEquals(800 - ltrOkRight));
|
|
});
|
|
|
|
group('Barrier dismissible', () {
|
|
late _DatePickerObserver rootObserver;
|
|
|
|
setUp(() {
|
|
rootObserver = _DatePickerObserver();
|
|
});
|
|
|
|
testWidgets('Barrier is dismissible with default parameter', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
navigatorObservers: <NavigatorObserver>[rootObserver],
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('X'),
|
|
onPressed: () => showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
builder: (BuildContext context, Widget? child) => const SizedBox(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Open the dialog.
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(rootObserver.datePickerCount, 1);
|
|
|
|
// Tap on the barrier.
|
|
await tester.tapAt(const Offset(10.0, 10.0));
|
|
await tester.pumpAndSettle();
|
|
expect(rootObserver.datePickerCount, 0);
|
|
});
|
|
|
|
testWidgets('Barrier is not dismissible with barrierDismissible is false', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
navigatorObservers: <NavigatorObserver>[rootObserver],
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('X'),
|
|
onPressed: () => showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
barrierDismissible: false,
|
|
builder: (BuildContext context, Widget? child) => const SizedBox(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Open the dialog.
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(rootObserver.datePickerCount, 1);
|
|
|
|
// Tap on the barrier, which shouldn't do anything this time.
|
|
await tester.tapAt(const Offset(10.0, 10.0));
|
|
await tester.pumpAndSettle();
|
|
expect(rootObserver.datePickerCount, 1);
|
|
});
|
|
});
|
|
|
|
testWidgets('Barrier color', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('X'),
|
|
onPressed: () => showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
builder: (BuildContext context, Widget? child) => const SizedBox(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Open the dialog.
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.black54);
|
|
|
|
// Dismiss the dialog.
|
|
await tester.tapAt(const Offset(10.0, 10.0));
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('X'),
|
|
onPressed: () => showDatePicker(
|
|
context: context,
|
|
barrierColor: Colors.pink,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
builder: (BuildContext context, Widget? child) => const SizedBox(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Open the dialog.
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.pink);
|
|
});
|
|
|
|
testWidgets('Barrier Label', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('X'),
|
|
onPressed: () => showDatePicker(
|
|
context: context,
|
|
barrierLabel: 'Custom Label',
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
builder: (BuildContext context, Widget? child) => const SizedBox(),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Open the dialog.
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).semanticsLabel,
|
|
'Custom Label',
|
|
);
|
|
});
|
|
|
|
testWidgets('uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
|
|
final rootObserver = _DatePickerObserver();
|
|
final 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 datePickerDefaultDialogTheme = DialogTheme(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
|
|
elevation: 24,
|
|
);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('X'),
|
|
onPressed: () {
|
|
showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
final Material defaultDialogMaterial = tester.widget<Material>(
|
|
find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first,
|
|
);
|
|
expect(defaultDialogMaterial.shape, datePickerDefaultDialogTheme.shape);
|
|
expect(defaultDialogMaterial.elevation, datePickerDefaultDialogTheme.elevation);
|
|
|
|
// Test that it honors ThemeData.dialogTheme settings
|
|
const customDialogTheme = DialogThemeData(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(40.0))),
|
|
elevation: 50,
|
|
);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData.fallback(useMaterial3: false).copyWith(dialogTheme: customDialogTheme),
|
|
home: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('X'),
|
|
onPressed: () {
|
|
showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pump(); // start theme animation
|
|
await tester.pump(const Duration(seconds: 5)); // end theme animation
|
|
final Material themeDialogMaterial = tester.widget<Material>(
|
|
find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first,
|
|
);
|
|
expect(themeDialogMaterial.shape, customDialogTheme.shape);
|
|
expect(themeDialogMaterial.elevation, customDialogTheme.elevation);
|
|
});
|
|
|
|
testWidgets('OK Cancel button layout', (WidgetTester tester) async {
|
|
Widget buildFrame(TextDirection textDirection) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('X'),
|
|
onPressed: () {
|
|
showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime(2016, DateTime.january, 15),
|
|
firstDate: DateTime(2001),
|
|
lastDate: DateTime(2031, DateTime.december, 31),
|
|
builder: (BuildContext context, Widget? child) {
|
|
return Directionality(
|
|
textDirection: textDirection,
|
|
child: child ?? const SizedBox(),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default landscape layout.
|
|
|
|
await tester.pumpWidget(buildFrame(TextDirection.ltr));
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(tester.getBottomRight(find.text('OK')).dx, 622);
|
|
expect(tester.getBottomLeft(find.text('OK')).dx, 594);
|
|
expect(tester.getBottomRight(find.text('CANCEL')).dx, 560);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.pumpWidget(buildFrame(TextDirection.rtl));
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(tester.getBottomRight(find.text('OK')).dx, 206);
|
|
expect(tester.getBottomLeft(find.text('OK')).dx, 178);
|
|
expect(tester.getBottomRight(find.text('CANCEL')).dx, 324);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Portrait layout.
|
|
|
|
addTearDown(tester.view.reset);
|
|
tester.view.physicalSize = const Size(900, 1200);
|
|
|
|
await tester.pumpWidget(buildFrame(TextDirection.ltr));
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(tester.getBottomRight(find.text('OK')).dx, 258);
|
|
expect(tester.getBottomLeft(find.text('OK')).dx, 230);
|
|
expect(tester.getBottomRight(find.text('CANCEL')).dx, 196);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.pumpWidget(buildFrame(TextDirection.rtl));
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(tester.getBottomRight(find.text('OK')).dx, 70);
|
|
expect(tester.getBottomLeft(find.text('OK')).dx, 42);
|
|
expect(tester.getBottomRight(find.text('CANCEL')).dx, 188);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('honors switchToInputEntryModeIcon', (WidgetTester tester) async {
|
|
Widget buildApp({bool? useMaterial3, Icon? switchToInputEntryModeIcon}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: useMaterial3 ?? false),
|
|
home: Material(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('Click X'),
|
|
onPressed: () {
|
|
showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
switchToInputEntryModeIcon: switchToInputEntryModeIcon,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byIcon(Icons.edit), findsOneWidget);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.pumpWidget(buildApp(useMaterial3: true));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byIcon(Icons.edit_outlined), findsOneWidget);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.pumpWidget(buildApp(switchToInputEntryModeIcon: const Icon(Icons.keyboard)));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byIcon(Icons.keyboard), findsOneWidget);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('honors switchToCalendarEntryModeIcon', (WidgetTester tester) async {
|
|
Widget buildApp({bool? useMaterial3, Icon? switchToCalendarEntryModeIcon}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: useMaterial3 ?? false),
|
|
home: Material(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('Click X'),
|
|
onPressed: () {
|
|
showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
switchToCalendarEntryModeIcon: switchToCalendarEntryModeIcon,
|
|
initialEntryMode: DatePickerEntryMode.input,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byIcon(Icons.calendar_today), findsOneWidget);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.pumpWidget(buildApp(useMaterial3: true));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byIcon(Icons.calendar_today), findsOneWidget);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.pumpWidget(buildApp(switchToCalendarEntryModeIcon: const Icon(Icons.favorite)));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byType(ElevatedButton));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byIcon(Icons.favorite), findsOneWidget);
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
});
|
|
|
|
group('Calendar mode', () {
|
|
testWidgets('Default Calendar mode layout (Landscape)', (WidgetTester tester) async {
|
|
final Finder helpText = find.text('Select date');
|
|
final Finder headerText = find.text('Fri, Jan 15');
|
|
final Finder subHeaderText = find.text('January 2016');
|
|
final Finder cancelButtonText = find.text('Cancel');
|
|
final Finder okButtonText = find.text('OK');
|
|
const insetPadding = EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0);
|
|
|
|
tester.view.physicalSize = wideWindowSize;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: DatePickerDialog(
|
|
initialDate: initialDate,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(helpText, findsOneWidget);
|
|
expect(headerText, findsOneWidget);
|
|
expect(subHeaderText, findsOneWidget);
|
|
expect(cancelButtonText, findsOneWidget);
|
|
expect(okButtonText, findsOneWidget);
|
|
|
|
// Test help text position.
|
|
final Offset dialogTopLeft = tester.getTopLeft(find.byType(AnimatedContainer));
|
|
final Offset helpTextTopLeft = tester.getTopLeft(helpText);
|
|
expect(helpTextTopLeft.dx, dialogTopLeft.dx + (insetPadding.horizontal / 2));
|
|
expect(helpTextTopLeft.dy, dialogTopLeft.dy + 16.0);
|
|
|
|
// Test header text position.
|
|
final Offset headerTextTopLeft = tester.getTopLeft(headerText);
|
|
final Offset helpTextBottomLeft = tester.getBottomLeft(helpText);
|
|
expect(headerTextTopLeft.dx, dialogTopLeft.dx + (insetPadding.horizontal / 2));
|
|
expect(headerTextTopLeft.dy, helpTextBottomLeft.dy + 16.0);
|
|
|
|
// Test switch button position.
|
|
final Finder switchButtonM3 = find.widgetWithIcon(IconButton, Icons.edit_outlined);
|
|
final Offset switchButtonTopLeft = tester.getTopLeft(switchButtonM3);
|
|
final Offset switchButtonBottomLeft = tester.getBottomLeft(switchButtonM3);
|
|
final Offset headerTextBottomLeft = tester.getBottomLeft(headerText);
|
|
final Offset dialogBottomLeft = tester.getBottomLeft(find.byType(AnimatedContainer));
|
|
expect(switchButtonTopLeft.dx, dialogTopLeft.dx + 8.0);
|
|
expect(switchButtonTopLeft.dy, headerTextBottomLeft.dy);
|
|
expect(switchButtonBottomLeft.dx, dialogTopLeft.dx + 8.0);
|
|
expect(switchButtonBottomLeft.dy, dialogBottomLeft.dy - 6.0);
|
|
|
|
// Test vertical divider position.
|
|
final Finder divider = find.byType(VerticalDivider);
|
|
final Offset dividerTopLeft = tester.getTopLeft(divider);
|
|
final Offset headerTextTopRight = tester.getTopRight(headerText);
|
|
expect(dividerTopLeft.dx, headerTextTopRight.dx + 16.0);
|
|
expect(dividerTopLeft.dy, dialogTopLeft.dy);
|
|
|
|
// Test sub header text position.
|
|
final Offset subHeaderTextTopLeft = tester.getTopLeft(subHeaderText);
|
|
final Offset dividerTopRight = tester.getTopRight(divider);
|
|
expect(subHeaderTextTopLeft.dx, dividerTopRight.dx + 24.0);
|
|
expect(subHeaderTextTopLeft.dy, dialogTopLeft.dy + 16.0);
|
|
|
|
// Test sub header icon position.
|
|
final Finder subHeaderIcon = find.byIcon(Icons.arrow_drop_down);
|
|
final Offset subHeaderIconTopLeft = tester.getTopLeft(subHeaderIcon);
|
|
final Offset subHeaderTextTopRight = tester.getTopRight(subHeaderText);
|
|
expect(subHeaderIconTopLeft.dx, subHeaderTextTopRight.dx);
|
|
expect(subHeaderIconTopLeft.dy, dialogTopLeft.dy + 14.0);
|
|
|
|
// Test calendar page view position.
|
|
final Finder calendarPageView = find.byType(PageView);
|
|
final Offset calendarPageViewTopLeft = tester.getTopLeft(calendarPageView);
|
|
final Offset subHeaderTextBottomLeft = tester.getBottomLeft(subHeaderText);
|
|
expect(calendarPageViewTopLeft.dx, dividerTopRight.dx);
|
|
expect(calendarPageViewTopLeft.dy, subHeaderTextBottomLeft.dy + 16.0);
|
|
|
|
// Test month navigation icons position.
|
|
final Finder previousMonthButton = find.widgetWithIcon(IconButton, Icons.chevron_left);
|
|
final Finder nextMonthButton = find.widgetWithIcon(IconButton, Icons.chevron_right);
|
|
final Offset previousMonthButtonTopRight = tester.getTopRight(previousMonthButton);
|
|
final Offset nextMonthButtonTopRight = tester.getTopRight(nextMonthButton);
|
|
final Offset dialogTopRight = tester.getTopRight(find.byType(AnimatedContainer));
|
|
expect(nextMonthButtonTopRight.dx, dialogTopRight.dx - 4.0);
|
|
expect(nextMonthButtonTopRight.dy, dialogTopRight.dy + 2.0);
|
|
expect(previousMonthButtonTopRight.dx, nextMonthButtonTopRight.dx - 48.0);
|
|
|
|
// Test action buttons position.
|
|
final Offset dialogBottomRight = tester.getBottomRight(find.byType(AnimatedContainer));
|
|
final Offset okButtonTopRight = tester.getTopRight(find.widgetWithText(TextButton, 'OK'));
|
|
final Offset cancelButtonTopRight = tester.getTopRight(
|
|
find.widgetWithText(TextButton, 'Cancel'),
|
|
);
|
|
final Offset calendarPageViewBottomRight = tester.getBottomRight(calendarPageView);
|
|
expect(okButtonTopRight.dx, dialogBottomRight.dx - 8);
|
|
expect(okButtonTopRight.dy, calendarPageViewBottomRight.dy + 2);
|
|
final Offset okButtonTopLeft = tester.getTopLeft(find.widgetWithText(TextButton, 'OK'));
|
|
expect(cancelButtonTopRight.dx, okButtonTopLeft.dx - 8);
|
|
});
|
|
|
|
testWidgets('Default Calendar mode layout (Portrait)', (WidgetTester tester) async {
|
|
final Finder helpText = find.text('Select date');
|
|
final Finder headerText = find.text('Fri, Jan 15');
|
|
final Finder subHeaderText = find.text('January 2016');
|
|
final Finder cancelButtonText = find.text('Cancel');
|
|
final Finder okButtonText = find.text('OK');
|
|
|
|
tester.view.physicalSize = narrowWindowSize;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: DatePickerDialog(
|
|
initialDate: initialDate,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(helpText, findsOneWidget);
|
|
expect(headerText, findsOneWidget);
|
|
expect(subHeaderText, findsOneWidget);
|
|
expect(cancelButtonText, findsOneWidget);
|
|
expect(okButtonText, findsOneWidget);
|
|
|
|
// Test help text position.
|
|
final Offset dialogTopLeft = tester.getTopLeft(find.byType(AnimatedContainer));
|
|
final Offset helpTextTopLeft = tester.getTopLeft(helpText);
|
|
expect(helpTextTopLeft.dx, dialogTopLeft.dx + 24.0);
|
|
expect(helpTextTopLeft.dy, dialogTopLeft.dy + 16.0);
|
|
|
|
// Test header text position
|
|
final Offset headerTextTextTopLeft = tester.getTopLeft(headerText);
|
|
final Offset helpTextBottomLeft = tester.getBottomLeft(helpText);
|
|
expect(headerTextTextTopLeft.dx, dialogTopLeft.dx + 24.0);
|
|
expect(headerTextTextTopLeft.dy, helpTextBottomLeft.dy + 28.0);
|
|
|
|
// Test switch button position.
|
|
final Finder switchButtonM3 = find.widgetWithIcon(IconButton, Icons.edit_outlined);
|
|
final Offset switchButtonTopRight = tester.getTopRight(switchButtonM3);
|
|
final Offset dialogTopRight = tester.getTopRight(find.byType(AnimatedContainer));
|
|
expect(switchButtonTopRight.dx, dialogTopRight.dx - 12.0);
|
|
expect(switchButtonTopRight.dy, headerTextTextTopLeft.dy - 4.0);
|
|
|
|
// Test horizontal divider position.
|
|
final Finder divider = find.byType(Divider);
|
|
final Offset dividerTopLeft = tester.getTopLeft(divider);
|
|
final Offset headerTextBottomLeft = tester.getBottomLeft(headerText);
|
|
expect(dividerTopLeft.dx, dialogTopLeft.dx);
|
|
expect(dividerTopLeft.dy, headerTextBottomLeft.dy + 16.0);
|
|
|
|
// Test subHeaderText position.
|
|
final Offset subHeaderTextTopLeft = tester.getTopLeft(subHeaderText);
|
|
final Offset dividerBottomLeft = tester.getBottomLeft(divider);
|
|
expect(subHeaderTextTopLeft.dx, dialogTopLeft.dx + 24.0);
|
|
expect(subHeaderTextTopLeft.dy, dividerBottomLeft.dy + 16.0);
|
|
|
|
// Test sub header icon position.
|
|
final Finder subHeaderIcon = find.byIcon(Icons.arrow_drop_down);
|
|
final Offset subHeaderIconTopLeft = tester.getTopLeft(subHeaderIcon);
|
|
final Offset subHeaderTextTopRight = tester.getTopRight(subHeaderText);
|
|
expect(subHeaderIconTopLeft.dx, subHeaderTextTopRight.dx);
|
|
expect(subHeaderIconTopLeft.dy, dividerBottomLeft.dy + 14.0);
|
|
|
|
// Test month navigation icons position.
|
|
final Finder previousMonthButton = find.widgetWithIcon(IconButton, Icons.chevron_left);
|
|
final Finder nextMonthButton = find.widgetWithIcon(IconButton, Icons.chevron_right);
|
|
final Offset previousMonthButtonTopRight = tester.getTopRight(previousMonthButton);
|
|
final Offset nextMonthButtonTopRight = tester.getTopRight(nextMonthButton);
|
|
expect(nextMonthButtonTopRight.dx, dialogTopRight.dx - 4.0);
|
|
expect(nextMonthButtonTopRight.dy, dividerBottomLeft.dy + 2.0);
|
|
expect(previousMonthButtonTopRight.dx, nextMonthButtonTopRight.dx - 48.0);
|
|
|
|
// Test calendar page view position.
|
|
final Finder calendarPageView = find.byType(PageView);
|
|
final Offset calendarPageViewTopLeft = tester.getTopLeft(calendarPageView);
|
|
final Offset subHeaderTextBottomLeft = tester.getBottomLeft(subHeaderText);
|
|
expect(calendarPageViewTopLeft.dx, dialogTopLeft.dx);
|
|
expect(calendarPageViewTopLeft.dy, subHeaderTextBottomLeft.dy + 16.0);
|
|
|
|
// Test action buttons position.
|
|
final Offset dialogBottomRight = tester.getBottomRight(find.byType(AnimatedContainer));
|
|
final Offset okButtonTopRight = tester.getTopRight(find.widgetWithText(TextButton, 'OK'));
|
|
final Offset cancelButtonTopRight = tester.getTopRight(
|
|
find.widgetWithText(TextButton, 'Cancel'),
|
|
);
|
|
final Offset calendarPageViewBottomRight = tester.getBottomRight(calendarPageView);
|
|
final Offset okButtonTopLeft = tester.getTopLeft(find.widgetWithText(TextButton, 'OK'));
|
|
expect(okButtonTopRight.dx, dialogBottomRight.dx - 8);
|
|
expect(okButtonTopRight.dy, calendarPageViewBottomRight.dy + 2);
|
|
expect(cancelButtonTopRight.dx, okButtonTopLeft.dx - 8);
|
|
});
|
|
|
|
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('Can select a day with no initial date', (WidgetTester tester) async {
|
|
initialDate = null;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
await tester.tap(find.text('12'));
|
|
await tester.tap(find.text('OK'));
|
|
expect(await date, equals(DateTime(2016, DateTime.january, 12)));
|
|
});
|
|
});
|
|
|
|
testWidgets('Can select a month with no initial date', (WidgetTester tester) async {
|
|
initialDate = null;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
await tester.tap(previousMonthIcon);
|
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
await tester.tap(find.text('25'));
|
|
await tester.tap(find.text('OK'));
|
|
expect(await date, DateTime(2015, DateTime.december, 25));
|
|
});
|
|
});
|
|
|
|
testWidgets('Can select a year with no initial date', (WidgetTester tester) async {
|
|
initialDate = null;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
await tester.tap(find.text('January 2016')); // Switch to year mode.
|
|
await tester.pump();
|
|
await tester.tap(find.text('2018'));
|
|
await tester.pump();
|
|
expect(find.text('January 2018'), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
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 change selected date', (WidgetTester tester) async {
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
await tester.tap(find.text('January 2016'));
|
|
await tester.pump();
|
|
await tester.tap(find.text('2018'));
|
|
await tester.pump();
|
|
await tester.tap(find.text('OK'));
|
|
expect(await date, equals(DateTime(2018, DateTime.january, 15)));
|
|
});
|
|
});
|
|
|
|
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 todayColor = Color(0xff2196f3); // default primary color
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
// The current day should be painted with a circle outline
|
|
paints..circle(color: todayColor, style: PaintingStyle.stroke, strokeWidth: 1.0),
|
|
);
|
|
});
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves hovered state', (WidgetTester tester) async {
|
|
final theme = ThemeData();
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
final Offset center = tester.getCenter(find.text('30'));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves focused state', (WidgetTester tester) async {
|
|
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
final theme = ThemeData();
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
// 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 to day 30.
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.10)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves pressed state', (WidgetTester tester) async {
|
|
final theme = ThemeData();
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
final Offset center = tester.getCenter(find.text('30'));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle() // Hovered decoration.
|
|
..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.10)),
|
|
);
|
|
await gesture.up();
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/130586.
|
|
testWidgets('Date picker dayOverlayColor resolves selected and hovered state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData();
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
// Select day 30.
|
|
await tester.tap(find.text('30'));
|
|
await tester.pumpAndSettle();
|
|
final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!;
|
|
expect(day30Decoration.color, theme.colorScheme.primary);
|
|
|
|
final Offset center = tester.getCenter(find.text('30'));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle(color: theme.colorScheme.onPrimary.withOpacity(0.08)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves selected and focused state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
final theme = ThemeData();
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
// Select day 30.
|
|
await tester.tap(find.text('30'));
|
|
await tester.pumpAndSettle();
|
|
final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!;
|
|
expect(day30Decoration.color, theme.colorScheme.primary);
|
|
|
|
// 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();
|
|
|
|
// Day 30 is selected and focused.
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle(color: theme.colorScheme.onPrimary.withOpacity(0.10)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves selected and pressed state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData();
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
// Select day 30.
|
|
await tester.tap(find.text('30'));
|
|
await tester.pumpAndSettle();
|
|
final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!;
|
|
expect(day30Decoration.color, theme.colorScheme.primary);
|
|
|
|
final Offset center = tester.getCenter(find.text('30'));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle() // Hovered decoration.
|
|
..circle(color: theme.colorScheme.onPrimary.withOpacity(0.10)),
|
|
);
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Selecting date does not switch picker to year selection', (
|
|
WidgetTester tester,
|
|
) async {
|
|
initialDate = DateTime(2020, DateTime.may, 10);
|
|
initialCalendarMode = DatePickerMode.year;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
await tester.pump();
|
|
await tester.tap(find.text('2017'));
|
|
await tester.pump();
|
|
expect(find.text('May 2017'), findsOneWidget);
|
|
await tester.tap(find.text('10'));
|
|
await tester.pump();
|
|
expect(find.text('May 2017'), findsOneWidget);
|
|
expect(find.text('2017'), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('Calendar dialog contents are visible - textScaler 0.88, 1.0, 2.0', (
|
|
WidgetTester tester,
|
|
) async {
|
|
addTearDown(tester.view.reset);
|
|
tester.view.physicalSize = const Size(400, 800);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
final scales = <double>[0.88, 1.0, 2.0];
|
|
|
|
for (final scale in scales) {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: MediaQuery(
|
|
data: MediaQueryData(textScaler: TextScaler.linear(scale)),
|
|
child: Material(
|
|
child: DatePickerDialog(
|
|
firstDate: DateTime(2001),
|
|
lastDate: DateTime(2031, DateTime.december, 31),
|
|
initialDate: DateTime(2016, DateTime.january, 15),
|
|
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(Dialog),
|
|
matchesGoldenFile('date_picker.calendar.contents.visible.$scale.png'),
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
group('Input mode', () {
|
|
setUp(() {
|
|
firstDate = DateTime(2015);
|
|
lastDate = DateTime(2017, DateTime.december, 31);
|
|
initialDate = DateTime(2016, DateTime.january, 15);
|
|
initialEntryMode = DatePickerEntryMode.input;
|
|
});
|
|
|
|
testWidgets('Default InputDecoration', (WidgetTester tester) async {
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
final InputDecoration decoration = tester
|
|
.widget<TextField>(find.byType(TextField))
|
|
.decoration!;
|
|
expect(decoration.border, const OutlineInputBorder());
|
|
expect(decoration.filled, false);
|
|
expect(decoration.hintText, 'mm/dd/yyyy');
|
|
expect(decoration.labelText, 'Enter Date');
|
|
expect(decoration.errorText, null);
|
|
}, useMaterial3: true);
|
|
});
|
|
|
|
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('KeyboardType is used', (WidgetTester tester) async {
|
|
keyboardType = TextInputType.text;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
final TextField field = textField(tester);
|
|
expect(field.keyboardType, TextInputType.text);
|
|
});
|
|
});
|
|
|
|
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('Invalid entered text shows error on autovalidate', (WidgetTester tester) async {
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/126397.
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
final TextField field = textField(tester);
|
|
field.controller!.clear();
|
|
|
|
// Enter some text to trigger autovalidate.
|
|
await tester.enterText(find.byType(TextField), 'xyz');
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Invalid format validation error should be shown.
|
|
expect(find.text('Invalid format.'), findsOneWidget);
|
|
|
|
// Clear the text.
|
|
field.controller!.clear();
|
|
|
|
// Enter an invalid date that is too long while autovalidate is still on.
|
|
await tester.enterText(find.byType(TextField), '10/05/2023666777889');
|
|
await tester.pump();
|
|
|
|
// Invalid format validation error should be shown.
|
|
expect(find.text('Invalid format.'), findsOneWidget);
|
|
// Should not throw an exception.
|
|
expect(tester.takeException(), null);
|
|
});
|
|
});
|
|
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/131989.
|
|
testWidgets('Dialog contents do not overflow when resized during orientation change', (
|
|
WidgetTester tester,
|
|
) async {
|
|
addTearDown(tester.view.reset);
|
|
// Initial window size is wide for landscape mode.
|
|
tester.view.physicalSize = wideWindowSize;
|
|
tester.view.devicePixelRatio = 1.0;
|
|
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
// Change window size to narrow for portrait mode.
|
|
tester.view.physicalSize = narrowWindowSize;
|
|
await tester.pump();
|
|
expect(tester.takeException(), null);
|
|
});
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/140311.
|
|
testWidgets('Text field stays visible when orientation is portrait and height is reduced', (
|
|
WidgetTester tester,
|
|
) async {
|
|
addTearDown(tester.view.reset);
|
|
tester.view.physicalSize = const Size(720, 1280);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
initialEntryMode = DatePickerEntryMode.input;
|
|
|
|
// Text field and header are visible by default.
|
|
await prepareDatePicker(tester, useMaterial3: true, (Future<DateTime?> range) async {
|
|
expect(find.byType(TextField), findsOneWidget);
|
|
expect(find.text('Select date'), findsOne);
|
|
});
|
|
|
|
// Simulate the portait mode on a device with a small display when the virtual
|
|
// keyboard is visible.
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 1000);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Text field is visible and header is hidden.
|
|
expect(find.byType(TextField), findsOneWidget);
|
|
expect(find.text('Select date'), findsNothing);
|
|
});
|
|
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/139120.
|
|
testWidgets('Dialog contents are visible - textScaler 0.88, 1.0, 2.0', (
|
|
WidgetTester tester,
|
|
) async {
|
|
addTearDown(tester.view.reset);
|
|
tester.view.physicalSize = const Size(400, 800);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
final scales = <double>[0.88, 1.0, 2.0];
|
|
|
|
for (final scale in scales) {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: MediaQuery(
|
|
data: MediaQueryData(textScaler: TextScaler.linear(scale)),
|
|
child: Material(
|
|
child: DatePickerDialog(
|
|
firstDate: DateTime(2001),
|
|
lastDate: DateTime(2031, DateTime.december, 31),
|
|
initialDate: DateTime(2016, DateTime.january, 15),
|
|
initialEntryMode: DatePickerEntryMode.input,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(Dialog),
|
|
matchesGoldenFile('date_picker.dialog.contents.visible.$scale.png'),
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
group('Semantics', () {
|
|
testWidgets('calendar mode', (WidgetTester tester) async {
|
|
final SemanticsHandle semantics = tester.ensureSemantics();
|
|
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
// Header
|
|
expect(
|
|
tester.getSemantics(find.text('SELECT DATE')),
|
|
matchesSemantics(label: 'SELECT DATE\nFri, Jan 15'),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.text('3')),
|
|
matchesSemantics(
|
|
label: '3, Sunday, January 3, 2016, Today',
|
|
isButton: true,
|
|
hasEnabledState: true,
|
|
hasTapAction: true,
|
|
hasSelectedState: true,
|
|
hasFocusAction: true,
|
|
isFocusable: true,
|
|
isEnabled: true,
|
|
),
|
|
);
|
|
|
|
// Input mode toggle button
|
|
expect(
|
|
tester.getSemantics(switchToInputIcon),
|
|
matchesSemantics(
|
|
tooltip: 'Switch to input',
|
|
isButton: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isEnabled: true,
|
|
hasEnabledState: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
|
|
// The semantics of the CalendarDatePicker are tested in its tests.
|
|
|
|
// Ok/Cancel buttons
|
|
expect(
|
|
tester.getSemantics(find.text('OK')),
|
|
matchesSemantics(
|
|
label: 'OK',
|
|
isButton: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isEnabled: true,
|
|
hasEnabledState: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getSemantics(find.text('CANCEL')),
|
|
matchesSemantics(
|
|
label: 'CANCEL',
|
|
isButton: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isEnabled: true,
|
|
hasEnabledState: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
});
|
|
semantics.dispose();
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/158325.
|
|
testWidgets('Calendar mode respects tap target guidelines in portrait orientation', (
|
|
WidgetTester tester,
|
|
) async {
|
|
addTearDown(tester.view.reset);
|
|
tester.view.physicalSize = const Size(400, 800);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
|
|
await prepareDatePicker(tester, useMaterial3: true, (Future<DateTime?> date) async {
|
|
expect(find.byType(DatePickerDialog), findsOneWidget);
|
|
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
|
|
});
|
|
});
|
|
|
|
testWidgets('input mode', (WidgetTester tester) async {
|
|
// Fill the clipboard so that the Paste option is available in the text
|
|
// selection menu.
|
|
final mockClipboard = MockClipboard();
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
mockClipboard.handleMethodCall,
|
|
);
|
|
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
null,
|
|
),
|
|
);
|
|
|
|
final SemanticsHandle semantics = tester.ensureSemantics();
|
|
|
|
initialEntryMode = DatePickerEntryMode.input;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
// Header
|
|
expect(
|
|
tester.getSemantics(find.text('SELECT DATE')),
|
|
matchesSemantics(label: 'SELECT DATE\nFri, Jan 15'),
|
|
);
|
|
|
|
// Input mode toggle button
|
|
expect(
|
|
tester.getSemantics(switchToCalendarIcon),
|
|
matchesSemantics(
|
|
tooltip: 'Switch to calendar',
|
|
isButton: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isEnabled: true,
|
|
hasEnabledState: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(EditableText)),
|
|
matchesSemantics(
|
|
label: 'Enter Date',
|
|
isEnabled: true,
|
|
hasEnabledState: true,
|
|
isTextField: true,
|
|
isFocusable: true,
|
|
isFocused: true,
|
|
value: '01/15/2016',
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
hasSetTextAction: true,
|
|
hasSetSelectionAction: true,
|
|
hasCopyAction: true,
|
|
hasCutAction: true,
|
|
hasPasteAction: true,
|
|
hasMoveCursorBackwardByCharacterAction: true,
|
|
hasMoveCursorBackwardByWordAction: true,
|
|
validationResult: SemanticsValidationResult.valid,
|
|
),
|
|
);
|
|
|
|
// Ok/Cancel buttons
|
|
expect(
|
|
tester.getSemantics(find.text('OK')),
|
|
matchesSemantics(
|
|
label: 'OK',
|
|
isButton: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isEnabled: true,
|
|
hasEnabledState: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
expect(
|
|
tester.getSemantics(find.text('CANCEL')),
|
|
matchesSemantics(
|
|
label: 'CANCEL',
|
|
isButton: true,
|
|
hasTapAction: true,
|
|
hasFocusAction: true,
|
|
isEnabled: true,
|
|
hasEnabledState: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
});
|
|
semantics.dispose();
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/pull/152705
|
|
testWidgets('datepicker dialog semantics node not focusable', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: DatePickerDialog(
|
|
initialDate: initialDate,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final SemanticsNode node = tester.semantics.find(find.byType(DatePickerDialog));
|
|
final SemanticsData semanticsData = node.getSemanticsData();
|
|
expect(semanticsData.flagsCollection.isFocused, Tristate.none);
|
|
});
|
|
});
|
|
|
|
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 kCommonScreenSizePortrait = Size(1070, 1770);
|
|
const kCommonScreenSizeLandscape = Size(1770, 1070);
|
|
|
|
// Small screen size based on a LG K130
|
|
const kSmallScreenSizePortrait = Size(320, 521);
|
|
const kSmallScreenSizeLandscape = Size(521, 320);
|
|
|
|
Future<void> showPicker(WidgetTester tester, Size size, [double textScaleFactor = 1.0]) async {
|
|
tester.view.physicalSize = size;
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
await tester.tap(find.text('OK'));
|
|
});
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
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);
|
|
});
|
|
});
|
|
|
|
group('showDatePicker avoids overlapping display features', () {
|
|
testWidgets('positioning with anchorPoint', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
builder: (BuildContext context, Widget? child) {
|
|
return MediaQuery(
|
|
// Display has a vertical hinge down the middle
|
|
data: const MediaQueryData(
|
|
size: Size(800, 600),
|
|
displayFeatures: <DisplayFeature>[
|
|
DisplayFeature(
|
|
bounds: Rect.fromLTRB(390, 0, 410, 600),
|
|
type: DisplayFeatureType.hinge,
|
|
state: DisplayFeatureState.unknown,
|
|
),
|
|
],
|
|
),
|
|
child: child!,
|
|
);
|
|
},
|
|
home: const Center(child: Text('Test')),
|
|
),
|
|
);
|
|
|
|
final BuildContext context = tester.element(find.text('Test'));
|
|
showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
anchorPoint: const Offset(1000, 0),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Should take the right side of the screen
|
|
expect(tester.getTopLeft(find.byType(DatePickerDialog)), const Offset(410.0, 0.0));
|
|
expect(tester.getBottomRight(find.byType(DatePickerDialog)), const Offset(800.0, 600.0));
|
|
});
|
|
|
|
testWidgets('positioning with Directionality', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
builder: (BuildContext context, Widget? child) {
|
|
return MediaQuery(
|
|
// Display has a vertical hinge down the middle
|
|
data: const MediaQueryData(
|
|
size: Size(800, 600),
|
|
displayFeatures: <DisplayFeature>[
|
|
DisplayFeature(
|
|
bounds: Rect.fromLTRB(390, 0, 410, 600),
|
|
type: DisplayFeatureType.hinge,
|
|
state: DisplayFeatureState.unknown,
|
|
),
|
|
],
|
|
),
|
|
child: Directionality(textDirection: TextDirection.rtl, child: child!),
|
|
);
|
|
},
|
|
home: const Center(child: Text('Test')),
|
|
),
|
|
);
|
|
|
|
final BuildContext context = tester.element(find.text('Test'));
|
|
showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// By default it should place the dialog on the right screen
|
|
expect(tester.getTopLeft(find.byType(DatePickerDialog)), const Offset(410.0, 0.0));
|
|
expect(tester.getBottomRight(find.byType(DatePickerDialog)), const Offset(800.0, 600.0));
|
|
});
|
|
|
|
testWidgets('positioning with defaults', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
builder: (BuildContext context, Widget? child) {
|
|
return MediaQuery(
|
|
// Display has a vertical hinge down the middle
|
|
data: const MediaQueryData(
|
|
size: Size(800, 600),
|
|
displayFeatures: <DisplayFeature>[
|
|
DisplayFeature(
|
|
bounds: Rect.fromLTRB(390, 0, 410, 600),
|
|
type: DisplayFeatureType.hinge,
|
|
state: DisplayFeatureState.unknown,
|
|
),
|
|
],
|
|
),
|
|
child: child!,
|
|
);
|
|
},
|
|
home: const Center(child: Text('Test')),
|
|
),
|
|
);
|
|
|
|
final BuildContext context = tester.element(find.text('Test'));
|
|
showDatePicker(
|
|
context: context,
|
|
initialDate: DateTime.now(),
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// By default it should place the dialog on the left screen
|
|
expect(tester.getTopLeft(find.byType(DatePickerDialog)), Offset.zero);
|
|
expect(tester.getBottomRight(find.byType(DatePickerDialog)), const Offset(390.0, 600.0));
|
|
});
|
|
});
|
|
|
|
testWidgets('DatePickerDialog is state restorable', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(restorationScopeId: 'app', home: _RestorableDatePickerDialogTestWidget()),
|
|
);
|
|
|
|
// The date picker should be closed.
|
|
expect(find.byType(DatePickerDialog), findsNothing);
|
|
expect(find.text('25/7/2021'), findsOneWidget);
|
|
|
|
// Open the date picker.
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(DatePickerDialog), findsOneWidget);
|
|
|
|
final TestRestorationData restorationData = await tester.getRestorationData();
|
|
await tester.restartAndRestore();
|
|
|
|
// The date picker should be open after restoring.
|
|
expect(find.byType(DatePickerDialog), findsOneWidget);
|
|
|
|
// Tap on the barrier.
|
|
await tester.tapAt(const Offset(10.0, 10.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The date picker should be closed, the text value updated to the
|
|
// newly selected date.
|
|
expect(find.byType(DatePickerDialog), findsNothing);
|
|
expect(find.text('25/7/2021'), findsOneWidget);
|
|
|
|
// The date picker should be open after restoring.
|
|
await tester.restoreFrom(restorationData);
|
|
expect(find.byType(DatePickerDialog), findsOneWidget);
|
|
|
|
// Select a different date.
|
|
await tester.tap(find.text('30'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Restart after the new selection. It should remain selected.
|
|
await tester.restartAndRestore();
|
|
|
|
// Close the date picker.
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The date picker should be closed, the text value updated to the
|
|
// newly selected date.
|
|
expect(find.byType(DatePickerDialog), findsNothing);
|
|
expect(find.text('30/7/2021'), findsOneWidget);
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
|
|
|
testWidgets('DatePickerDialog state restoration - DatePickerEntryMode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
restorationScopeId: 'app',
|
|
home: _RestorableDatePickerDialogTestWidget(
|
|
datePickerEntryMode: DatePickerEntryMode.calendarOnly,
|
|
),
|
|
),
|
|
);
|
|
|
|
// The date picker should be closed.
|
|
expect(find.byType(DatePickerDialog), findsNothing);
|
|
expect(find.text('25/7/2021'), findsOneWidget);
|
|
|
|
// Open the date picker.
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(DatePickerDialog), findsOneWidget);
|
|
|
|
// Only in calendar mode and cannot switch out.
|
|
expect(find.byType(TextField), findsNothing);
|
|
expect(find.byIcon(Icons.edit), findsNothing);
|
|
|
|
final TestRestorationData restorationData = await tester.getRestorationData();
|
|
await tester.restartAndRestore();
|
|
|
|
// The date picker should be open after restoring.
|
|
expect(find.byType(DatePickerDialog), findsOneWidget);
|
|
// Only in calendar mode and cannot switch out.
|
|
expect(find.byType(TextField), findsNothing);
|
|
expect(find.byIcon(Icons.edit), findsNothing);
|
|
|
|
// Tap on the barrier.
|
|
await tester.tapAt(const Offset(10.0, 10.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The date picker should be closed, the text value should be the same
|
|
// as before.
|
|
expect(find.byType(DatePickerDialog), findsNothing);
|
|
expect(find.text('25/7/2021'), findsOneWidget);
|
|
|
|
// The date picker should be open after restoring.
|
|
await tester.restoreFrom(restorationData);
|
|
expect(find.byType(DatePickerDialog), findsOneWidget);
|
|
// Only in calendar mode and cannot switch out.
|
|
expect(find.byType(TextField), findsNothing);
|
|
expect(find.byIcon(Icons.edit), findsNothing);
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
|
|
|
testWidgets('Test Callback on Toggle of DatePicker Mode', (WidgetTester tester) async {
|
|
prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
await tester.tap(find.byIcon(Icons.edit));
|
|
expect(currentMode, DatePickerEntryMode.input);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(TextField), findsOneWidget);
|
|
await tester.tap(find.byIcon(Icons.calendar_today));
|
|
expect(currentMode, DatePickerEntryMode.calendar);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(TextField), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('DatePickerDialog with updated insetPadding', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: DatePickerDialog(
|
|
initialDate: initialDate,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
insetPadding: const EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog));
|
|
expect(dialog.insetPadding, const EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0));
|
|
});
|
|
|
|
group('Landscape input-only date picker headers use headlineSmall', () {
|
|
// Regression test for https://github.com/flutter/flutter/issues/122056
|
|
|
|
// Common screen size roughly based on a Pixel 1
|
|
const kCommonScreenSizePortrait = Size(1070, 1770);
|
|
const kCommonScreenSizeLandscape = Size(1770, 1070);
|
|
|
|
Future<void> showPicker(WidgetTester tester, Size size) async {
|
|
addTearDown(tester.view.reset);
|
|
tester.view.physicalSize = size;
|
|
tester.view.devicePixelRatio = 1.0;
|
|
initialEntryMode = DatePickerEntryMode.input;
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, useMaterial3: true);
|
|
}
|
|
|
|
testWidgets('portrait', (WidgetTester tester) async {
|
|
await showPicker(tester, kCommonScreenSizePortrait);
|
|
expect(tester.widget<Text>(find.text('Fri, Jan 15')).style?.fontSize, 32);
|
|
await tester.tap(find.text('Cancel'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('landscape', (WidgetTester tester) async {
|
|
await showPicker(tester, kCommonScreenSizeLandscape);
|
|
expect(tester.widget<Text>(find.text('Fri, Jan 15')).style?.fontSize, 24);
|
|
await tester.tap(find.text('Cancel'));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
});
|
|
|
|
group('Material 2', () {
|
|
// These tests are only relevant for Material 2. Once Material 2
|
|
// support is deprecated and the APIs are removed, these tests
|
|
// can be deleted.
|
|
|
|
group('showDatePicker Dialog', () {
|
|
testWidgets('Default dialog size', (WidgetTester tester) async {
|
|
Future<void> showPicker(WidgetTester tester, Size size) async {
|
|
tester.view.physicalSize = size;
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {});
|
|
}
|
|
|
|
const wideWindowSize = Size(1920.0, 1080.0);
|
|
const narrowWindowSize = Size(1070.0, 1770.0);
|
|
const calendarLandscapeDialogSize = Size(496.0, 346.0);
|
|
const calendarPortraitDialogSizeM2 = Size(330.0, 518.0);
|
|
|
|
// Test landscape layout.
|
|
await showPicker(tester, wideWindowSize);
|
|
|
|
Size dialogContainerSize = tester.getSize(find.byType(AnimatedContainer));
|
|
expect(dialogContainerSize, calendarLandscapeDialogSize);
|
|
|
|
// Close the dialog.
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Test portrait layout.
|
|
await showPicker(tester, narrowWindowSize);
|
|
|
|
dialogContainerSize = tester.getSize(find.byType(AnimatedContainer));
|
|
expect(dialogContainerSize, calendarPortraitDialogSizeM2);
|
|
});
|
|
|
|
testWidgets('Default dialog properties', (WidgetTester tester) async {
|
|
final theme = ThemeData(useMaterial3: false);
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
final Material dialogMaterial = tester.widget<Material>(
|
|
find.descendant(of: find.byType(Dialog), matching: find.byType(Material)).first,
|
|
);
|
|
|
|
expect(dialogMaterial.color, theme.colorScheme.surface);
|
|
expect(dialogMaterial.shadowColor, theme.shadowColor);
|
|
expect(dialogMaterial.elevation, 24.0);
|
|
expect(
|
|
dialogMaterial.shape,
|
|
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
|
|
);
|
|
expect(dialogMaterial.clipBehavior, Clip.antiAlias);
|
|
|
|
final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog));
|
|
expect(dialog.insetPadding, const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0));
|
|
}, useMaterial3: theme.useMaterial3);
|
|
});
|
|
});
|
|
|
|
group('Input mode', () {
|
|
setUp(() {
|
|
firstDate = DateTime(2015);
|
|
lastDate = DateTime(2017, DateTime.december, 31);
|
|
initialDate = DateTime(2016, DateTime.january, 15);
|
|
initialEntryMode = DatePickerEntryMode.input;
|
|
});
|
|
|
|
testWidgets('Default InputDecoration', (WidgetTester tester) async {
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {
|
|
final InputDecoration decoration = tester
|
|
.widget<TextField>(find.byType(TextField))
|
|
.decoration!;
|
|
expect(decoration.border, const UnderlineInputBorder());
|
|
expect(decoration.filled, false);
|
|
expect(decoration.hintText, 'mm/dd/yyyy');
|
|
expect(decoration.labelText, 'Enter Date');
|
|
expect(decoration.errorText, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves hovered state', (WidgetTester tester) async {
|
|
final theme = ThemeData(useMaterial3: false);
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
final Offset center = tester.getCenter(find.text('30'));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.08)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves focused state', (WidgetTester tester) async {
|
|
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
final theme = ThemeData(useMaterial3: false);
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
// 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 to day 30.
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.12)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves pressed state', (WidgetTester tester) async {
|
|
final theme = ThemeData(useMaterial3: false);
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
final Offset center = tester.getCenter(find.text('30'));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle() // Hovered decoration.
|
|
..circle(color: theme.colorScheme.onSurfaceVariant.withOpacity(0.12)),
|
|
);
|
|
await gesture.up();
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/130586.
|
|
testWidgets('Date picker dayOverlayColor resolves selected and hovered state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData(useMaterial3: false);
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
// Select day 30.
|
|
await tester.tap(find.text('30'));
|
|
await tester.pumpAndSettle();
|
|
final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!;
|
|
expect(day30Decoration.color, theme.colorScheme.primary);
|
|
|
|
final Offset center = tester.getCenter(find.text('30'));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle(color: theme.colorScheme.onPrimary.withOpacity(0.08)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves selected and focused state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
final theme = ThemeData(useMaterial3: false);
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
// Select day 30.
|
|
await tester.tap(find.text('30'));
|
|
await tester.pumpAndSettle();
|
|
final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!;
|
|
expect(day30Decoration.color, theme.colorScheme.primary);
|
|
|
|
// 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();
|
|
|
|
// Day 30 is selected and focused.
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle(color: theme.colorScheme.onPrimary.withOpacity(0.12)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Date picker dayOverlayColor resolves selected and pressed state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData(useMaterial3: false);
|
|
await prepareDatePicker(tester, (Future<DateTime?> date) async {}, theme: theme);
|
|
|
|
// Select day 30.
|
|
await tester.tap(find.text('30'));
|
|
await tester.pumpAndSettle();
|
|
final ShapeDecoration day30Decoration = findDayDecoration(tester, '30')!;
|
|
expect(day30Decoration.color, theme.colorScheme.primary);
|
|
|
|
final Offset center = tester.getCenter(find.text('30'));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
findDayGridMaterial(tester),
|
|
paints
|
|
..circle() // Today decoration.
|
|
..circle() // Selected day decoration.
|
|
..circle() // Hovered decoration.
|
|
..circle(color: theme.colorScheme.onPrimary.withOpacity(0.38)),
|
|
);
|
|
await gesture.up();
|
|
});
|
|
});
|
|
|
|
group('Calendar Delegate', () {
|
|
testWidgets('Defaults to Gregorian calendar system', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: DatePickerDialog(
|
|
initialDate: initialDate,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final DatePickerDialog dialog = tester.widget(find.byType(DatePickerDialog));
|
|
expect(dialog.calendarDelegate, isA<GregorianCalendarDelegate>());
|
|
});
|
|
|
|
testWidgets('Using custom calendar delegate implementation', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: DatePickerDialog(
|
|
initialDate: initialDate,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
calendarDelegate: const TestCalendarDelegate(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final DatePickerDialog dialog = tester.widget(find.byType(DatePickerDialog));
|
|
expect(dialog.calendarDelegate, isA<TestCalendarDelegate>());
|
|
});
|
|
|
|
testWidgets('Displays calendar based on the calendar delegate', (WidgetTester tester) async {
|
|
Text getLastDayText() {
|
|
final Finder dayFinder = find.descendant(of: find.byType(Ink), matching: find.byType(Text));
|
|
return tester.widget(dayFinder.last);
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: DatePickerDialog(
|
|
initialDate: initialDate,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
calendarDelegate: const TestCalendarDelegate(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder nextMonthButton = find.byIcon(Icons.chevron_right);
|
|
|
|
Text lastDayText = getLastDayText();
|
|
expect(find.text('January 2016'), findsOneWidget);
|
|
expect(lastDayText.data, equals('28'));
|
|
|
|
await tester.tap(nextMonthButton);
|
|
await tester.pumpAndSettle();
|
|
|
|
lastDayText = getLastDayText();
|
|
expect(find.text('February 2016'), findsOneWidget);
|
|
expect(lastDayText.data, equals('21'));
|
|
|
|
await tester.tap(nextMonthButton);
|
|
await tester.pumpAndSettle();
|
|
|
|
lastDayText = getLastDayText();
|
|
expect(find.text('March 2016'), findsOneWidget);
|
|
expect(lastDayText.data, equals('28'));
|
|
});
|
|
});
|
|
|
|
testWidgets('DatePickerDialog renders at zero area', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: SizedBox.shrink(
|
|
child: DatePickerDialog(firstDate: firstDate, lastDate: lastDate),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(DatePickerDialog)).isEmpty, isTrue);
|
|
});
|
|
}
|
|
|
|
class _RestorableDatePickerDialogTestWidget extends StatefulWidget {
|
|
const _RestorableDatePickerDialogTestWidget({
|
|
this.datePickerEntryMode = DatePickerEntryMode.calendar,
|
|
});
|
|
|
|
final DatePickerEntryMode datePickerEntryMode;
|
|
|
|
@override
|
|
_RestorableDatePickerDialogTestWidgetState createState() =>
|
|
_RestorableDatePickerDialogTestWidgetState();
|
|
}
|
|
|
|
@pragma('vm:entry-point')
|
|
class _RestorableDatePickerDialogTestWidgetState
|
|
extends State<_RestorableDatePickerDialogTestWidget>
|
|
with RestorationMixin {
|
|
@override
|
|
String? get restorationId => 'scaffold_state';
|
|
|
|
final RestorableDateTime _selectedDate = RestorableDateTime(DateTime(2021, 7, 25));
|
|
late final RestorableRouteFuture<DateTime?> _restorableDatePickerRouteFuture =
|
|
RestorableRouteFuture<DateTime?>(
|
|
onComplete: _selectDate,
|
|
onPresent: (NavigatorState navigator, Object? arguments) {
|
|
return navigator.restorablePush(
|
|
_datePickerRoute,
|
|
arguments: <String, dynamic>{
|
|
'selectedDate': _selectedDate.value.millisecondsSinceEpoch,
|
|
'datePickerEntryMode': widget.datePickerEntryMode.index,
|
|
},
|
|
);
|
|
},
|
|
);
|
|
|
|
@override
|
|
void dispose() {
|
|
_selectedDate.dispose();
|
|
_restorableDatePickerRouteFuture.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
|
registerForRestoration(_selectedDate, 'selected_date');
|
|
registerForRestoration(_restorableDatePickerRouteFuture, 'date_picker_route_future');
|
|
}
|
|
|
|
void _selectDate(DateTime? newSelectedDate) {
|
|
if (newSelectedDate != null) {
|
|
setState(() {
|
|
_selectedDate.value = newSelectedDate;
|
|
});
|
|
}
|
|
}
|
|
|
|
@pragma('vm:entry-point')
|
|
static Route<DateTime> _datePickerRoute(BuildContext context, Object? arguments) {
|
|
return DialogRoute<DateTime>(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
final args = arguments! as Map<dynamic, dynamic>;
|
|
return DatePickerDialog(
|
|
restorationId: 'date_picker_dialog',
|
|
initialEntryMode: DatePickerEntryMode.values[args['datePickerEntryMode'] as int],
|
|
initialDate: DateTime.fromMillisecondsSinceEpoch(args['selectedDate'] as int),
|
|
firstDate: DateTime(2021),
|
|
lastDate: DateTime(2022),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final DateTime selectedDateTime = _selectedDate.value;
|
|
// Example: "25/7/1994"
|
|
final selectedDateTimeString =
|
|
'${selectedDateTime.day}/${selectedDateTime.month}/${selectedDateTime.year}';
|
|
return Scaffold(
|
|
body: Center(
|
|
child: Column(
|
|
children: <Widget>[
|
|
OutlinedButton(
|
|
onPressed: () {
|
|
_restorableDatePickerRouteFuture.present();
|
|
},
|
|
child: const Text('X'),
|
|
),
|
|
Text(selectedDateTimeString),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DatePickerObserver extends NavigatorObserver {
|
|
int datePickerCount = 0;
|
|
|
|
@override
|
|
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
|
|
if (route is DialogRoute) {
|
|
datePickerCount++;
|
|
}
|
|
super.didPush(route, previousRoute);
|
|
}
|
|
|
|
@override
|
|
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
|
|
if (route is DialogRoute) {
|
|
datePickerCount--;
|
|
}
|
|
super.didPop(route, previousRoute);
|
|
}
|
|
}
|
|
|
|
class TestCalendarDelegate extends GregorianCalendarDelegate {
|
|
const TestCalendarDelegate();
|
|
|
|
@override
|
|
int getDaysInMonth(int year, int month) {
|
|
return month.isEven ? 21 : 28;
|
|
}
|
|
|
|
@override
|
|
int firstDayOffset(int year, int month, MaterialLocalizations localizations) {
|
|
return 1;
|
|
}
|
|
}
|