Unverified Commit e58ee0fb authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

[State Restoration] Material DateRangePicker, adds some general state restoration tests (#78506)

parent 2977a346
...@@ -385,6 +385,34 @@ class RestorableDateTime extends RestorableValue<DateTime> { ...@@ -385,6 +385,34 @@ class RestorableDateTime extends RestorableValue<DateTime> {
Object? toPrimitives() => value.millisecondsSinceEpoch; Object? toPrimitives() => value.millisecondsSinceEpoch;
} }
/// A [RestorableValue] that knows how to save and restore [DateTime] that is
/// nullable.
///
/// {@macro flutter.widgets.RestorableNum}.
class RestorableDateTimeN extends RestorableValue<DateTime?> {
/// Creates a [RestorableDateTime].
///
/// {@macro flutter.widgets.RestorableNum.constructor}
RestorableDateTimeN(DateTime? defaultValue) : _defaultValue = defaultValue;
final DateTime? _defaultValue;
@override
DateTime? createDefaultValue() => _defaultValue;
@override
void didUpdateValue(DateTime? oldValue) {
assert(debugIsSerializableForRestoration(value?.millisecondsSinceEpoch));
notifyListeners();
}
@override
DateTime? fromPrimitives(Object? data) => data != null ? DateTime.fromMillisecondsSinceEpoch(data as int) : null;
@override
Object? toPrimitives() => value?.millisecondsSinceEpoch;
}
/// A base class for creating a [RestorableProperty] that stores and restores a /// A base class for creating a [RestorableProperty] that stores and restores a
/// [Listenable]. /// [Listenable].
/// ///
......
...@@ -1162,9 +1162,14 @@ void main() { ...@@ -1162,9 +1162,14 @@ void main() {
await tester.restoreFrom(restorationData); await tester.restoreFrom(restorationData);
expect(find.byType(DatePickerDialog), findsOneWidget); expect(find.byType(DatePickerDialog), findsOneWidget);
// Select a different date and close the date picker. // Select a different date.
await tester.tap(find.text('30')); await tester.tap(find.text('30'));
await tester.pumpAndSettle(); 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.tap(find.text('OK'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -1210,8 +1215,8 @@ void main() { ...@@ -1210,8 +1215,8 @@ void main() {
await tester.tapAt(const Offset(10.0, 10.0)); await tester.tapAt(const Offset(10.0, 10.0));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// The date picker should be closed, the text value updated to the // The date picker should be closed, the text value should be the same
// newly selected date. // as before.
expect(find.byType(DatePickerDialog), findsNothing); expect(find.byType(DatePickerDialog), findsNothing);
expect(find.text('25/7/2021'), findsOneWidget); expect(find.text('25/7/2021'), findsOneWidget);
......
...@@ -853,4 +853,198 @@ void main() { ...@@ -853,4 +853,198 @@ void main() {
_testInputDecorator(tester.widget(borderContainers.last), border, Colors.transparent); _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
}
class _RestorableDateRangePickerDialogTestWidget extends StatefulWidget {
const _RestorableDateRangePickerDialogTestWidget({
Key? key,
this.datePickerEntryMode = DatePickerEntryMode.calendar,
}) : super(key: key);
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, 1, 1));
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, 1, 1),
currentDate: DateTime(2021, 1, 25),
lastDate: DateTime(2022, 1, 1),
);
},
);
}
@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'),
],
),
),
);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment