mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2899 lines
109 KiB
Dart
2899 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;
|
|
}
|
|
}
|