
* Date picker special labeling for currentDate with localization and tests. * Updated CalendarDatePicker semantics test * removed recursive import * changed labeling for current date to be less verbose
1196 lines
45 KiB
Dart
1196 lines
45 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.
|
|
|
|
import 'dart:ui';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'feedback_tester.dart';
|
|
|
|
void main() {
|
|
late DateTime firstDate;
|
|
late DateTime lastDate;
|
|
late DateTime? currentDate;
|
|
late DateTimeRange? initialDateRange;
|
|
late DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar;
|
|
|
|
String? cancelText;
|
|
String? confirmText;
|
|
String? errorInvalidRangeText;
|
|
String? errorFormatText;
|
|
String? errorInvalidText;
|
|
String? fieldStartHintText;
|
|
String? fieldEndHintText;
|
|
String? fieldStartLabelText;
|
|
String? fieldEndLabelText;
|
|
String? helpText;
|
|
String? saveText;
|
|
|
|
setUp(() {
|
|
firstDate = DateTime(2015);
|
|
lastDate = DateTime(2016, DateTime.december, 31);
|
|
currentDate = null;
|
|
initialDateRange = DateTimeRange(
|
|
start: DateTime(2016, DateTime.january, 15),
|
|
end: DateTime(2016, DateTime.january, 25),
|
|
);
|
|
initialEntryMode = DatePickerEntryMode.calendar;
|
|
|
|
cancelText = null;
|
|
confirmText = null;
|
|
errorInvalidRangeText = null;
|
|
errorFormatText = null;
|
|
errorInvalidText = null;
|
|
fieldStartHintText = null;
|
|
fieldEndHintText = null;
|
|
fieldStartLabelText = null;
|
|
fieldEndLabelText = null;
|
|
helpText = null;
|
|
saveText = null;
|
|
});
|
|
|
|
Future<void> preparePicker(
|
|
WidgetTester tester,
|
|
Future<void> Function(Future<DateTimeRange?> date) callback, {
|
|
TextDirection textDirection = TextDirection.ltr,
|
|
bool useMaterial3 = false,
|
|
}) async {
|
|
late BuildContext buttonContext;
|
|
await tester.pumpWidget(MaterialApp(
|
|
theme: ThemeData(useMaterial3: useMaterial3),
|
|
home: Material(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
onPressed: () {
|
|
buttonContext = context;
|
|
},
|
|
child: const Text('Go'),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
));
|
|
|
|
await tester.tap(find.text('Go'));
|
|
expect(buttonContext, isNotNull);
|
|
|
|
final Future<DateTimeRange?> range = showDateRangePicker(
|
|
context: buttonContext,
|
|
initialDateRange: initialDateRange,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
currentDate: currentDate,
|
|
initialEntryMode: initialEntryMode,
|
|
cancelText: cancelText,
|
|
confirmText: confirmText,
|
|
errorInvalidRangeText: errorInvalidRangeText,
|
|
errorFormatText: errorFormatText,
|
|
errorInvalidText: errorInvalidText,
|
|
fieldStartHintText: fieldStartHintText,
|
|
fieldEndHintText: fieldEndHintText,
|
|
fieldStartLabelText: fieldStartLabelText,
|
|
fieldEndLabelText: fieldEndLabelText,
|
|
helpText: helpText,
|
|
saveText: saveText,
|
|
builder: (BuildContext context, Widget? child) {
|
|
return Directionality(
|
|
textDirection: textDirection,
|
|
child: child ?? const SizedBox(),
|
|
);
|
|
},
|
|
);
|
|
|
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
await callback(range);
|
|
}
|
|
|
|
testWidgets('Save and help text is used', (WidgetTester tester) async {
|
|
helpText = 'help';
|
|
saveText = 'make it so';
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
expect(find.text(helpText!), findsOneWidget);
|
|
expect(find.text(saveText!), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('Material3 has sentence case labels', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
expect(find.text('Save'), findsOneWidget);
|
|
expect(find.text('Select range'), findsOneWidget);
|
|
}, useMaterial3: true);
|
|
});
|
|
|
|
testWidgets('Initial date is the default', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.tap(find.text('SAVE'));
|
|
expect(
|
|
await range,
|
|
DateTimeRange(
|
|
start: DateTime(2016, DateTime.january, 15),
|
|
end: DateTime(2016, DateTime.january, 25),
|
|
),
|
|
);
|
|
});
|
|
});
|
|
|
|
testWidgets('Last month header should be visible if last date is selected', (WidgetTester tester) async {
|
|
firstDate = DateTime(2015);
|
|
lastDate = DateTime(2016, DateTime.december, 31);
|
|
initialDateRange = DateTimeRange(
|
|
start: lastDate,
|
|
end: lastDate,
|
|
);
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
// December header should be showing, but no November
|
|
expect(find.text('December 2016'), findsOneWidget);
|
|
expect(find.text('November 2016'), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('First month header should be visible if first date is selected', (WidgetTester tester) async {
|
|
firstDate = DateTime(2015);
|
|
lastDate = DateTime(2016, DateTime.december, 31);
|
|
initialDateRange = DateTimeRange(
|
|
start: firstDate,
|
|
end: firstDate,
|
|
);
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
// January and February headers should be showing, but no March
|
|
expect(find.text('January 2015'), findsOneWidget);
|
|
expect(find.text('February 2015'), findsOneWidget);
|
|
expect(find.text('March 2015'), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('Current month header should be visible if no date is selected', (WidgetTester tester) async {
|
|
firstDate = DateTime(2015);
|
|
lastDate = DateTime(2016, DateTime.december, 31);
|
|
currentDate = DateTime(2016, DateTime.september);
|
|
initialDateRange = null;
|
|
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
// September and October headers should be showing, but no August
|
|
expect(find.text('September 2016'), findsOneWidget);
|
|
expect(find.text('October 2016'), findsOneWidget);
|
|
expect(find.text('August 2016'), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('Can cancel', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.tap(find.byIcon(Icons.close));
|
|
expect(await range, isNull);
|
|
});
|
|
});
|
|
|
|
testWidgets('Can select a range', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.tap(find.text('12').first);
|
|
await tester.tap(find.text('14').first);
|
|
await tester.tap(find.text('SAVE'));
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2016, DateTime.january, 12),
|
|
end: DateTime(2016, DateTime.january, 14),
|
|
));
|
|
});
|
|
});
|
|
|
|
testWidgets('Tapping earlier date resets selected range', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.tap(find.text('12').first);
|
|
await tester.tap(find.text('11').first);
|
|
await tester.tap(find.text('15').first);
|
|
await tester.tap(find.text('SAVE'));
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2016, DateTime.january, 11),
|
|
end: DateTime(2016, DateTime.january, 15),
|
|
));
|
|
});
|
|
});
|
|
|
|
testWidgets('Can select single day range', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.tap(find.text('12').first);
|
|
await tester.tap(find.text('12').first);
|
|
await tester.tap(find.text('SAVE'));
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2016, DateTime.january, 12),
|
|
end: DateTime(2016, DateTime.january, 12),
|
|
));
|
|
});
|
|
});
|
|
|
|
testWidgets('Cannot select a day outside bounds', (WidgetTester tester) async {
|
|
initialDateRange = DateTimeRange(
|
|
start: DateTime(2017, DateTime.january, 13),
|
|
end: DateTime(2017, DateTime.january, 15),
|
|
);
|
|
firstDate = DateTime(2017, DateTime.january, 12);
|
|
lastDate = DateTime(2017, DateTime.january, 16);
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) 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('SAVE'));
|
|
// We should still be on the initial date.
|
|
expect(await range, initialDateRange);
|
|
});
|
|
});
|
|
|
|
testWidgets('Can switch from calendar to input entry mode', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
expect(find.byType(TextField), findsNothing);
|
|
await tester.tap(find.byIcon(Icons.edit));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(TextField), findsNWidgets(2));
|
|
});
|
|
});
|
|
|
|
testWidgets('Can switch from input to calendar entry mode', (WidgetTester tester) async {
|
|
initialEntryMode = DatePickerEntryMode.input;
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
expect(find.byType(TextField), findsNWidgets(2));
|
|
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 preparePicker(tester, (Future<DateTimeRange?> range) 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 preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
expect(find.byType(TextField), findsNWidgets(2));
|
|
expect(find.byIcon(Icons.calendar_today), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('Input only mode should validate date', (WidgetTester tester) async {
|
|
initialEntryMode = DatePickerEntryMode.inputOnly;
|
|
errorInvalidText = 'oops';
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '08/08/2014');
|
|
await tester.enterText(find.byType(TextField).at(1), '08/08/2014');
|
|
expect(find.text(errorInvalidText!), findsNothing);
|
|
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(errorInvalidText!), findsNWidgets(2));
|
|
});
|
|
});
|
|
|
|
testWidgets('Switching to input mode keeps selected date', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.tap(find.text('12').first);
|
|
await tester.tap(find.text('14').first);
|
|
await tester.tap(find.byIcon(Icons.edit));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('OK'));
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2016, DateTime.january, 12),
|
|
end: DateTime(2016, DateTime.january, 14),
|
|
));
|
|
});
|
|
});
|
|
|
|
group('Toggle from input entry mode validates dates', () {
|
|
setUp(() {
|
|
initialEntryMode = DatePickerEntryMode.input;
|
|
});
|
|
|
|
testWidgets('Invalid start date', (WidgetTester tester) async {
|
|
// Invalid start date should have neither a start nor end date selected in
|
|
// calendar mode
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '12/27/1918');
|
|
await tester.enterText(find.byType(TextField).at(1), '12/25/2016');
|
|
await tester.tap(find.byIcon(Icons.calendar_today));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Start Date'), findsOneWidget);
|
|
expect(find.text('End Date'), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('Invalid end date', (WidgetTester tester) async {
|
|
// Invalid end date should only have a start date selected
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '12/24/2016');
|
|
await tester.enterText(find.byType(TextField).at(1), '12/25/2050');
|
|
await tester.tap(find.byIcon(Icons.calendar_today));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Dec 24'), findsOneWidget);
|
|
expect(find.text('End Date'), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('Invalid range', (WidgetTester tester) async {
|
|
// Start date after end date should just use the start date
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
|
|
await tester.enterText(find.byType(TextField).at(1), '12/24/2016');
|
|
await tester.tap(find.byIcon(Icons.calendar_today));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Dec 25'), findsOneWidget);
|
|
expect(find.text('End Date'), findsOneWidget);
|
|
});
|
|
});
|
|
});
|
|
|
|
testWidgets('OK Cancel button layout', (WidgetTester tester) async {
|
|
Widget buildFrame(TextDirection textDirection) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
child: const Text('X'),
|
|
onPressed: () {
|
|
showDateRangePicker(
|
|
context: context,
|
|
firstDate:DateTime(2001),
|
|
lastDate: DateTime(2031, DateTime.december, 31),
|
|
builder: (BuildContext context, Widget? child) {
|
|
return Directionality(
|
|
textDirection: textDirection,
|
|
child: child ?? const SizedBox(),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> showOkCancelDialog(TextDirection textDirection) async {
|
|
await tester.pumpWidget(buildFrame(textDirection));
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byIcon(Icons.edit));
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
Future<void> dismissOkCancelDialog() async {
|
|
await tester.tap(find.text('CANCEL'));
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
await showOkCancelDialog(TextDirection.ltr);
|
|
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 dismissOkCancelDialog();
|
|
|
|
await showOkCancelDialog(TextDirection.rtl);
|
|
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 dismissOkCancelDialog();
|
|
});
|
|
|
|
group('Haptic feedback', () {
|
|
const Duration hapticFeedbackInterval = Duration(milliseconds: 10);
|
|
late FeedbackTester feedback;
|
|
|
|
setUp(() {
|
|
feedback = FeedbackTester();
|
|
initialDateRange = DateTimeRange(
|
|
start: DateTime(2017, DateTime.january, 15),
|
|
end: DateTime(2017, DateTime.january, 17),
|
|
);
|
|
firstDate = DateTime(2017, DateTime.january, 10);
|
|
lastDate = DateTime(2018, DateTime.january, 20);
|
|
});
|
|
|
|
tearDown(() {
|
|
feedback.dispose();
|
|
});
|
|
|
|
testWidgets('Selecting dates vibrates', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.tap(find.text('10').first);
|
|
await tester.pump(hapticFeedbackInterval);
|
|
expect(feedback.hapticCount, 1);
|
|
await tester.tap(find.text('12').first);
|
|
await tester.pump(hapticFeedbackInterval);
|
|
expect(feedback.hapticCount, 2);
|
|
await tester.tap(find.text('14').first);
|
|
await tester.pump(hapticFeedbackInterval);
|
|
expect(feedback.hapticCount, 3);
|
|
});
|
|
});
|
|
|
|
testWidgets('Tapping unselectable date does not vibrate', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.tap(find.text('8').first);
|
|
await tester.pump(hapticFeedbackInterval);
|
|
expect(feedback.hapticCount, 0);
|
|
});
|
|
});
|
|
});
|
|
|
|
group('Keyboard navigation', () {
|
|
testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) 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.space);
|
|
await tester.pumpAndSettle();
|
|
// Should be in the input mode
|
|
expect(find.byType(TextField), findsNWidgets(2));
|
|
});
|
|
});
|
|
|
|
testWidgets('Can navigate date grid with arrow keys', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) 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 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 to select the beginning of the range
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Navigate to Jan 29
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Activate it to select the end of the range
|
|
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 - Jan 29
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2016, DateTime.january, 18),
|
|
end: DateTime(2016, DateTime.january, 29),
|
|
));
|
|
});
|
|
});
|
|
|
|
testWidgets('Navigating with arrow keys scrolls as needed', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
// Jan and Feb headers should be showing, but no March
|
|
expect(find.text('January 2016'), findsOneWidget);
|
|
expect(find.text('February 2016'), findsOneWidget);
|
|
expect(find.text('March 2016'), findsNothing);
|
|
|
|
// 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.arrowLeft);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Activate it to select the beginning of the range
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Navigate to Mar 17
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Jan should have scrolled off, Mar should be visible
|
|
expect(find.text('January 2016'), findsNothing);
|
|
expect(find.text('February 2016'), findsOneWidget);
|
|
expect(find.text('March 2016'), findsOneWidget);
|
|
|
|
// Activate it to select the end of the range
|
|
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 - Mar 17
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2016, DateTime.january, 18),
|
|
end: DateTime(2016, DateTime.march, 17),
|
|
));
|
|
});
|
|
});
|
|
|
|
testWidgets('RTL text direction reverses the horizontal arrow key navigation', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) 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 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 to Jan 21
|
|
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 19 - Mar 21
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2016, DateTime.january, 19),
|
|
end: DateTime(2016, DateTime.january, 21),
|
|
));
|
|
}, textDirection: TextDirection.rtl);
|
|
});
|
|
});
|
|
|
|
group('Input mode', () {
|
|
setUp(() {
|
|
firstDate = DateTime(2015);
|
|
lastDate = DateTime(2017, DateTime.december, 31);
|
|
initialDateRange = DateTimeRange(
|
|
start: DateTime(2017, DateTime.january, 15),
|
|
end: DateTime(2017, DateTime.january, 17),
|
|
);
|
|
initialEntryMode = DatePickerEntryMode.input;
|
|
});
|
|
|
|
testWidgets('Initial entry mode is used', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
expect(find.byType(TextField), findsNWidgets(2));
|
|
});
|
|
});
|
|
|
|
testWidgets('All custom strings are used', (WidgetTester tester) async {
|
|
initialDateRange = null;
|
|
cancelText = 'nope';
|
|
confirmText = 'yep';
|
|
fieldStartHintText = 'hint1';
|
|
fieldEndHintText = 'hint2';
|
|
fieldStartLabelText = 'label1';
|
|
fieldEndLabelText = 'label2';
|
|
helpText = 'help';
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
expect(find.text(cancelText!), findsOneWidget);
|
|
expect(find.text(confirmText!), findsOneWidget);
|
|
expect(find.text(fieldStartHintText!), findsOneWidget);
|
|
expect(find.text(fieldEndHintText!), findsOneWidget);
|
|
expect(find.text(fieldStartLabelText!), findsOneWidget);
|
|
expect(find.text(fieldEndLabelText!), findsOneWidget);
|
|
expect(find.text(helpText!), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('Initial date is the default', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.tap(find.text('OK'));
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2017, DateTime.january, 15),
|
|
end: DateTime(2017, DateTime.january, 17),
|
|
));
|
|
});
|
|
});
|
|
|
|
testWidgets('Can toggle to calendar entry mode', (WidgetTester tester) async {
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
expect(find.byType(TextField), findsNWidgets(2));
|
|
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 {
|
|
initialDateRange = null;
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
|
|
await tester.enterText(find.byType(TextField).at(1), '12/27/2016');
|
|
await tester.tap(find.byIcon(Icons.calendar_today));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('SAVE'));
|
|
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2016, DateTime.december, 25),
|
|
end: DateTime(2016, DateTime.december, 27),
|
|
));
|
|
});
|
|
});
|
|
|
|
testWidgets('Entered text returns range', (WidgetTester tester) async {
|
|
initialDateRange = null;
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '12/25/2016');
|
|
await tester.enterText(find.byType(TextField).at(1), '12/27/2016');
|
|
await tester.tap(find.text('OK'));
|
|
|
|
expect(await range, DateTimeRange(
|
|
start: DateTime(2016, DateTime.december, 25),
|
|
end: DateTime(2016, DateTime.december, 27),
|
|
));
|
|
});
|
|
});
|
|
|
|
testWidgets('Too short entered text shows error', (WidgetTester tester) async {
|
|
initialDateRange = null;
|
|
errorFormatText = 'oops';
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '12/25');
|
|
await tester.enterText(find.byType(TextField).at(1), '12/25');
|
|
expect(find.text(errorFormatText!), findsNothing);
|
|
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(errorFormatText!), findsNWidgets(2));
|
|
});
|
|
});
|
|
|
|
testWidgets('Bad format entered text shows error', (WidgetTester tester) async {
|
|
initialDateRange = null;
|
|
errorFormatText = 'oops';
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '20202014');
|
|
await tester.enterText(find.byType(TextField).at(1), '20212014');
|
|
expect(find.text(errorFormatText!), findsNothing);
|
|
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(errorFormatText!), findsNWidgets(2));
|
|
});
|
|
});
|
|
|
|
testWidgets('Invalid entered text shows error', (WidgetTester tester) async {
|
|
initialDateRange = null;
|
|
errorInvalidText = 'oops';
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '08/08/2014');
|
|
await tester.enterText(find.byType(TextField).at(1), '08/08/2014');
|
|
expect(find.text(errorInvalidText!), findsNothing);
|
|
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(errorInvalidText!), findsNWidgets(2));
|
|
});
|
|
});
|
|
|
|
testWidgets('End before start date shows error', (WidgetTester tester) async {
|
|
initialDateRange = null;
|
|
errorInvalidRangeText = 'oops';
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
|
|
await tester.enterText(find.byType(TextField).at(1), '12/25/2016');
|
|
expect(find.text(errorInvalidRangeText!), findsNothing);
|
|
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(errorInvalidRangeText!), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('Error text only displayed for invalid date', (WidgetTester tester) async {
|
|
initialDateRange = null;
|
|
errorInvalidText = 'oops';
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
|
|
await tester.enterText(find.byType(TextField).at(1), '01/01/2018');
|
|
expect(find.text(errorInvalidText!), findsNothing);
|
|
|
|
await tester.tap(find.text('OK'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(errorInvalidText!), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('End before start date does not get passed to calendar mode', (WidgetTester tester) async {
|
|
initialDateRange = null;
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
await tester.enterText(find.byType(TextField).at(0), '12/27/2016');
|
|
await tester.enterText(find.byType(TextField).at(1), '12/25/2016');
|
|
|
|
await tester.tap(find.byIcon(Icons.calendar_today));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('SAVE'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Save button should be disabled, so dialog should still be up
|
|
// with the first date selected, but no end date
|
|
expect(find.text('Dec 27'), findsOneWidget);
|
|
expect(find.text('End Date'), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('InputDecorationTheme is honored', (WidgetTester tester) async {
|
|
|
|
// Given a custom paint for an input decoration, extract the border and
|
|
// fill color and test them against the expected values.
|
|
void testInputDecorator(CustomPaint decoratorPaint, InputBorder expectedBorder, Color expectedContainerColor) {
|
|
final dynamic/*_InputBorderPainter*/ inputBorderPainter = decoratorPaint.foregroundPainter;
|
|
// ignore: avoid_dynamic_calls
|
|
final dynamic/*_InputBorderTween*/ inputBorderTween = inputBorderPainter.border;
|
|
// ignore: avoid_dynamic_calls
|
|
final Animation<double> animation = inputBorderPainter.borderAnimation as Animation<double>;
|
|
// ignore: avoid_dynamic_calls
|
|
final InputBorder actualBorder = inputBorderTween.evaluate(animation) as InputBorder;
|
|
// ignore: avoid_dynamic_calls
|
|
final Color containerColor = inputBorderPainter.blendedColor as Color;
|
|
|
|
expect(actualBorder, equals(expectedBorder));
|
|
expect(containerColor, equals(expectedContainerColor));
|
|
}
|
|
|
|
late BuildContext buttonContext;
|
|
const InputBorder border = InputBorder.none;
|
|
await tester.pumpWidget(MaterialApp(
|
|
theme: ThemeData.light().copyWith(
|
|
inputDecorationTheme: const InputDecorationTheme(
|
|
border: border,
|
|
),
|
|
),
|
|
home: Material(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return ElevatedButton(
|
|
onPressed: () {
|
|
buttonContext = context;
|
|
},
|
|
child: const Text('Go'),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
));
|
|
|
|
await tester.tap(find.text('Go'));
|
|
expect(buttonContext, isNotNull);
|
|
|
|
showDateRangePicker(
|
|
context: buttonContext,
|
|
initialDateRange: initialDateRange,
|
|
firstDate: firstDate,
|
|
lastDate: lastDate,
|
|
initialEntryMode: DatePickerEntryMode.input,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final Finder borderContainers = find.descendant(
|
|
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_BorderContainer'),
|
|
matching: find.byWidgetPredicate((Widget w) => w is CustomPaint),
|
|
);
|
|
|
|
// Test the start date text field
|
|
testInputDecorator(tester.widget(borderContainers.first), border, Colors.transparent);
|
|
|
|
// Test the end date text field
|
|
testInputDecorator(tester.widget(borderContainers.last), border, Colors.transparent);
|
|
});
|
|
});
|
|
|
|
testWidgets('DatePickerDialog is state restorable', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
restorationScopeId: 'app',
|
|
home: _RestorableDateRangePickerDialogTestWidget(),
|
|
),
|
|
);
|
|
|
|
// The date range picker should be closed.
|
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
|
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
|
|
|
|
// Open the date range picker.
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(DateRangePickerDialog), findsOneWidget);
|
|
|
|
final TestRestorationData restorationData = await tester.getRestorationData();
|
|
await tester.restartAndRestore();
|
|
|
|
// The date range picker should be open after restoring.
|
|
expect(find.byType(DateRangePickerDialog), findsOneWidget);
|
|
|
|
// Close the date range picker.
|
|
await tester.tap(find.byIcon(Icons.close));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The date range picker should be closed, the text value updated to the
|
|
// newly selected date.
|
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
|
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
|
|
|
|
// The date range picker should be open after restoring.
|
|
await tester.restoreFrom(restorationData);
|
|
expect(find.byType(DateRangePickerDialog), findsOneWidget);
|
|
|
|
// // Select a different date and close the date range picker.
|
|
await tester.tap(find.text('12').first);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('14').first);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Restart after the new selection. It should remain selected.
|
|
await tester.restartAndRestore();
|
|
|
|
// Close the date range picker.
|
|
await tester.tap(find.text('SAVE'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The date range picker should be closed, the text value updated to the
|
|
// newly selected date.
|
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
|
expect(find.text('12/1/2021 to 14/1/2021'), findsOneWidget);
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
|
|
|
testWidgets('DateRangePickerDialog state restoration - DatePickerEntryMode', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
restorationScopeId: 'app',
|
|
home: _RestorableDateRangePickerDialogTestWidget(
|
|
datePickerEntryMode: DatePickerEntryMode.calendarOnly,
|
|
),
|
|
),
|
|
);
|
|
|
|
// The date range picker should be closed.
|
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
|
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
|
|
|
|
// Open the date range picker.
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(DateRangePickerDialog), 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 range picker should be open after restoring.
|
|
expect(find.byType(DateRangePickerDialog), 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.tap(find.byIcon(Icons.close));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The date range picker should be closed, the text value should be the same
|
|
// as before.
|
|
expect(find.byType(DateRangePickerDialog), findsNothing);
|
|
expect(find.text('1/1/2021 to 5/1/2021'), findsOneWidget);
|
|
|
|
// The date range picker should be open after restoring.
|
|
await tester.restoreFrom(restorationData);
|
|
expect(find.byType(DateRangePickerDialog), 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
|
|
|
|
group('showDateRangePicker 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'));
|
|
showDateRangePicker(
|
|
context: context,
|
|
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(DateRangePickerDialog)), const Offset(410.0, 0.0));
|
|
expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), 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'));
|
|
showDateRangePicker(
|
|
context: context,
|
|
firstDate: DateTime(2018),
|
|
lastDate: DateTime(2030),
|
|
anchorPoint: const Offset(1000, 0),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// By default it should place the dialog on the right screen
|
|
expect(tester.getTopLeft(find.byType(DateRangePickerDialog)), const Offset(410.0, 0.0));
|
|
expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), 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'));
|
|
showDateRangePicker(
|
|
context: context,
|
|
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(DateRangePickerDialog)), Offset.zero);
|
|
expect(tester.getBottomRight(find.byType(DateRangePickerDialog)), const Offset(390.0, 600.0));
|
|
});
|
|
});
|
|
|
|
group('Semantics', () {
|
|
testWidgets('calendar mode', (WidgetTester tester) async {
|
|
final SemanticsHandle semantics = tester.ensureSemantics();
|
|
currentDate = DateTime(2016, DateTime.january, 30);
|
|
addTearDown(semantics.dispose);
|
|
await preparePicker(tester, (Future<DateTimeRange?> range) async {
|
|
expect(
|
|
tester.getSemantics(find.text('30')),
|
|
matchesSemantics(
|
|
label: '30, Saturday, January 30, 2016, Today',
|
|
hasTapAction: true,
|
|
isFocusable: true,
|
|
),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
class _RestorableDateRangePickerDialogTestWidget extends StatefulWidget {
|
|
const _RestorableDateRangePickerDialogTestWidget({
|
|
this.datePickerEntryMode = DatePickerEntryMode.calendar,
|
|
});
|
|
|
|
final DatePickerEntryMode datePickerEntryMode;
|
|
|
|
@override
|
|
_RestorableDateRangePickerDialogTestWidgetState createState() => _RestorableDateRangePickerDialogTestWidgetState();
|
|
}
|
|
|
|
class _RestorableDateRangePickerDialogTestWidgetState extends State<_RestorableDateRangePickerDialogTestWidget> with RestorationMixin {
|
|
@override
|
|
String? get restorationId => 'scaffold_state';
|
|
|
|
final RestorableDateTimeN _startDate = RestorableDateTimeN(DateTime(2021));
|
|
final RestorableDateTimeN _endDate = RestorableDateTimeN(DateTime(2021, 1, 5));
|
|
late final RestorableRouteFuture<DateTimeRange?> _restorableDateRangePickerRouteFuture = RestorableRouteFuture<DateTimeRange?>(
|
|
onComplete: _selectDateRange,
|
|
onPresent: (NavigatorState navigator, Object? arguments) {
|
|
return navigator.restorablePush(
|
|
_dateRangePickerRoute,
|
|
arguments: <String, dynamic>{
|
|
'datePickerEntryMode': widget.datePickerEntryMode.index,
|
|
},
|
|
);
|
|
},
|
|
);
|
|
|
|
@override
|
|
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
|
registerForRestoration(_startDate, 'start_date');
|
|
registerForRestoration(_endDate, 'end_date');
|
|
registerForRestoration(_restorableDateRangePickerRouteFuture, 'date_picker_route_future');
|
|
}
|
|
|
|
void _selectDateRange(DateTimeRange? newSelectedDate) {
|
|
if (newSelectedDate != null) {
|
|
setState(() {
|
|
_startDate.value = newSelectedDate.start;
|
|
_endDate.value = newSelectedDate.end;
|
|
});
|
|
}
|
|
}
|
|
|
|
static Route<DateTimeRange?> _dateRangePickerRoute(
|
|
BuildContext context,
|
|
Object? arguments,
|
|
) {
|
|
return DialogRoute<DateTimeRange?>(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
final Map<dynamic, dynamic> args = arguments! as Map<dynamic, dynamic>;
|
|
return DateRangePickerDialog(
|
|
restorationId: 'date_picker_dialog',
|
|
initialEntryMode: DatePickerEntryMode.values[args['datePickerEntryMode'] as int],
|
|
firstDate: DateTime(2021),
|
|
currentDate: DateTime(2021, 1, 25),
|
|
lastDate: DateTime(2022),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final DateTime? startDateTime = _startDate.value;
|
|
final DateTime? endDateTime = _endDate.value;
|
|
// Example: "25/7/1994"
|
|
final String startDateTimeString = '${startDateTime?.day}/${startDateTime?.month}/${startDateTime?.year}';
|
|
final String endDateTimeString = '${endDateTime?.day}/${endDateTime?.month}/${endDateTime?.year}';
|
|
return Scaffold(
|
|
body: Center(
|
|
child: Column(
|
|
children: <Widget>[
|
|
OutlinedButton(
|
|
onPressed: () {
|
|
_restorableDateRangePickerRouteFuture.present();
|
|
},
|
|
child: const Text('X'),
|
|
),
|
|
Text('$startDateTimeString to $endDateTimeString'),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|