| // 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 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| testWidgets('value is not accessible when not registered', (WidgetTester tester) async { |
| expect(() => RestorableNum<num>(0).value, throwsAssertionError); |
| expect(() => RestorableDouble(1.0).value, throwsAssertionError); |
| expect(() => RestorableInt(1).value, throwsAssertionError); |
| expect(() => RestorableString('hello').value, throwsAssertionError); |
| expect(() => RestorableBool(true).value, throwsAssertionError); |
| expect(() => RestorableNumN<num?>(0).value, throwsAssertionError); |
| expect(() => RestorableDoubleN(1.0).value, throwsAssertionError); |
| expect(() => RestorableIntN(1).value, throwsAssertionError); |
| expect(() => RestorableStringN('hello').value, throwsAssertionError); |
| expect(() => RestorableBoolN(true).value, throwsAssertionError); |
| expect(() => RestorableTextEditingController().value, throwsAssertionError); |
| expect(() => RestorableDateTime(DateTime(2020, 4, 3)).value, throwsAssertionError); |
| expect(() => RestorableDateTimeN(DateTime(2020, 4, 3)).value, throwsAssertionError); |
| expect(() => _TestRestorableValue().value, throwsAssertionError); |
| }); |
| |
| testWidgets('work when not in restoration scope', (WidgetTester tester) async { |
| await tester.pumpWidget(const _RestorableWidget()); |
| |
| expect(find.text('hello world'), findsOneWidget); |
| final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget)); |
| |
| // Initialized to default values. |
| expect(state.numValue.value, 99); |
| expect(state.doubleValue.value, 123.2); |
| expect(state.intValue.value, 42); |
| expect(state.stringValue.value, 'hello world'); |
| expect(state.boolValue.value, false); |
| expect(state.dateTimeValue.value, DateTime(2021, 3, 16)); |
| expect(state.nullableNumValue.value, null); |
| expect(state.nullableDoubleValue.value, null); |
| expect(state.nullableIntValue.value, null); |
| expect(state.nullableStringValue.value, null); |
| expect(state.nullableBoolValue.value, null); |
| expect(state.nullableDateTimeValue.value, null); |
| expect(state.controllerValue.value.text, 'FooBar'); |
| expect(state.objectValue.value, 55); |
| |
| // Modify values. |
| state.setProperties(() { |
| state.numValue.value = 42.2; |
| state.doubleValue.value = 441.3; |
| state.intValue.value = 10; |
| state.stringValue.value = 'guten tag'; |
| state.boolValue.value = true; |
| state.dateTimeValue.value = DateTime(2020, 7, 4); |
| state.nullableNumValue.value = 5.0; |
| state.nullableDoubleValue.value = 2.0; |
| state.nullableIntValue.value = 1; |
| state.nullableStringValue.value = 'hullo'; |
| state.nullableBoolValue.value = false; |
| state.nullableDateTimeValue.value = DateTime(2020, 4, 4); |
| state.controllerValue.value.text = 'blabla'; |
| state.objectValue.value = 53; |
| }); |
| await tester.pump(); |
| |
| expect(state.numValue.value, 42.2); |
| expect(state.doubleValue.value, 441.3); |
| expect(state.intValue.value, 10); |
| expect(state.stringValue.value, 'guten tag'); |
| expect(state.boolValue.value, true); |
| expect(state.dateTimeValue.value, DateTime(2020, 7, 4)); |
| expect(state.nullableNumValue.value, 5.0); |
| expect(state.nullableDoubleValue.value, 2.0); |
| expect(state.nullableIntValue.value, 1); |
| expect(state.nullableStringValue.value, 'hullo'); |
| expect(state.nullableBoolValue.value, false); |
| expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4)); |
| expect(state.controllerValue.value.text, 'blabla'); |
| expect(state.objectValue.value, 53); |
| expect(find.text('guten tag'), findsOneWidget); |
| }); |
| |
| testWidgets('restart and restore', (WidgetTester tester) async { |
| await tester.pumpWidget(const RootRestorationScope( |
| restorationId: 'root-child', |
| child: _RestorableWidget(), |
| )); |
| |
| expect(find.text('hello world'), findsOneWidget); |
| _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget)); |
| |
| // Initialized to default values. |
| expect(state.numValue.value, 99); |
| expect(state.doubleValue.value, 123.2); |
| expect(state.intValue.value, 42); |
| expect(state.stringValue.value, 'hello world'); |
| expect(state.boolValue.value, false); |
| expect(state.dateTimeValue.value, DateTime(2021, 3, 16)); |
| expect(state.nullableNumValue.value, null); |
| expect(state.nullableDoubleValue.value, null); |
| expect(state.nullableIntValue.value, null); |
| expect(state.nullableStringValue.value, null); |
| expect(state.nullableBoolValue.value, null); |
| expect(state.nullableDateTimeValue.value, null); |
| expect(state.controllerValue.value.text, 'FooBar'); |
| expect(state.objectValue.value, 55); |
| |
| // Modify values. |
| state.setProperties(() { |
| state.numValue.value = 42.2; |
| state.doubleValue.value = 441.3; |
| state.intValue.value = 10; |
| state.stringValue.value = 'guten tag'; |
| state.boolValue.value = true; |
| state.dateTimeValue.value = DateTime(2020, 7, 4); |
| state.nullableNumValue.value = 5.0; |
| state.nullableDoubleValue.value = 2.0; |
| state.nullableIntValue.value = 1; |
| state.nullableStringValue.value = 'hullo'; |
| state.nullableBoolValue.value = false; |
| state.nullableDateTimeValue.value = DateTime(2020, 4, 4); |
| state.controllerValue.value.text = 'blabla'; |
| state.objectValue.value = 53; |
| }); |
| await tester.pump(); |
| |
| expect(state.numValue.value, 42.2); |
| expect(state.doubleValue.value, 441.3); |
| expect(state.intValue.value, 10); |
| expect(state.stringValue.value, 'guten tag'); |
| expect(state.boolValue.value, true); |
| expect(state.dateTimeValue.value, DateTime(2020, 7, 4)); |
| expect(state.nullableNumValue.value, 5.0); |
| expect(state.nullableDoubleValue.value, 2.0); |
| expect(state.nullableIntValue.value, 1); |
| expect(state.nullableStringValue.value, 'hullo'); |
| expect(state.nullableBoolValue.value, false); |
| expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4)); |
| expect(state.controllerValue.value.text, 'blabla'); |
| expect(state.objectValue.value, 53); |
| expect(find.text('guten tag'), findsOneWidget); |
| |
| // Restores to previous values. |
| await tester.restartAndRestore(); |
| final _RestorableWidgetState oldState = state; |
| state = tester.state(find.byType(_RestorableWidget)); |
| expect(state, isNot(same(oldState))); |
| |
| expect(state.numValue.value, 42.2); |
| expect(state.doubleValue.value, 441.3); |
| expect(state.intValue.value, 10); |
| expect(state.stringValue.value, 'guten tag'); |
| expect(state.boolValue.value, true); |
| expect(state.dateTimeValue.value, DateTime(2020, 7, 4)); |
| expect(state.nullableNumValue.value, 5.0); |
| expect(state.nullableDoubleValue.value, 2.0); |
| expect(state.nullableIntValue.value, 1); |
| expect(state.nullableStringValue.value, 'hullo'); |
| expect(state.nullableBoolValue.value, false); |
| expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4)); |
| expect(state.controllerValue.value.text, 'blabla'); |
| expect(state.objectValue.value, 53); |
| expect(find.text('guten tag'), findsOneWidget); |
| }); |
| |
| testWidgets('restore to older state', (WidgetTester tester) async { |
| await tester.pumpWidget(const RootRestorationScope( |
| restorationId: 'root-child', |
| child: _RestorableWidget(), |
| )); |
| |
| expect(find.text('hello world'), findsOneWidget); |
| final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget)); |
| |
| // Modify values. |
| state.setProperties(() { |
| state.numValue.value = 42.2; |
| state.doubleValue.value = 441.3; |
| state.intValue.value = 10; |
| state.stringValue.value = 'guten tag'; |
| state.boolValue.value = true; |
| state.dateTimeValue.value = DateTime(2020, 7, 4); |
| state.nullableNumValue.value = 5.0; |
| state.nullableDoubleValue.value = 2.0; |
| state.nullableIntValue.value = 1; |
| state.nullableStringValue.value = 'hullo'; |
| state.nullableBoolValue.value = false; |
| state.nullableDateTimeValue.value = DateTime(2020, 4, 4); |
| state.controllerValue.value.text = 'blabla'; |
| state.objectValue.value = 53; |
| }); |
| await tester.pump(); |
| expect(find.text('guten tag'), findsOneWidget); |
| |
| final TestRestorationData restorationData = await tester.getRestorationData(); |
| |
| // Modify values. |
| state.setProperties(() { |
| state.numValue.value = 20; |
| state.doubleValue.value = 20.0; |
| state.intValue.value = 20; |
| state.stringValue.value = 'ciao'; |
| state.boolValue.value = false; |
| state.dateTimeValue.value = DateTime(2020, 3, 2); |
| state.nullableNumValue.value = 20.0; |
| state.nullableDoubleValue.value = 20.0; |
| state.nullableIntValue.value = 20; |
| state.nullableStringValue.value = 'ni hao'; |
| state.nullableBoolValue.value = null; |
| state.nullableDateTimeValue.value = DateTime(2020, 5, 5); |
| state.controllerValue.value.text = 'blub'; |
| state.objectValue.value = 20; |
| }); |
| await tester.pump(); |
| expect(find.text('ciao'), findsOneWidget); |
| final TextEditingController controller = state.controllerValue.value; |
| |
| // Restore to previous. |
| await tester.restoreFrom(restorationData); |
| expect(state.numValue.value, 42.2); |
| expect(state.doubleValue.value, 441.3); |
| expect(state.intValue.value, 10); |
| expect(state.stringValue.value, 'guten tag'); |
| expect(state.boolValue.value, true); |
| expect(state.dateTimeValue.value, DateTime(2020, 7, 4)); |
| expect(state.nullableNumValue.value, 5.0); |
| expect(state.nullableDoubleValue.value, 2.0); |
| expect(state.nullableIntValue.value, 1); |
| expect(state.nullableStringValue.value, 'hullo'); |
| expect(state.nullableBoolValue.value, false); |
| expect(state.nullableDateTimeValue.value, DateTime(2020, 4, 4)); |
| expect(state.controllerValue.value.text, 'blabla'); |
| expect(state.objectValue.value, 53); |
| expect(find.text('guten tag'), findsOneWidget); |
| expect(state.controllerValue.value, isNot(same(controller))); |
| |
| // Restore to empty data will re-initialize to default values. |
| await tester.restoreFrom(TestRestorationData.empty); |
| expect(state.numValue.value, 99); |
| expect(state.doubleValue.value, 123.2); |
| expect(state.intValue.value, 42); |
| expect(state.stringValue.value, 'hello world'); |
| expect(state.boolValue.value, false); |
| expect(state.dateTimeValue.value, DateTime(2021, 3, 16)); |
| expect(state.nullableNumValue.value, null); |
| expect(state.nullableDoubleValue.value, null); |
| expect(state.nullableIntValue.value, null); |
| expect(state.nullableStringValue.value, null); |
| expect(state.nullableBoolValue.value, null); |
| expect(state.nullableDateTimeValue.value, null); |
| expect(state.controllerValue.value.text, 'FooBar'); |
| expect(state.objectValue.value, 55); |
| expect(find.text('hello world'), findsOneWidget); |
| }); |
| |
| testWidgets('call notifiers when value changes', (WidgetTester tester) async { |
| await tester.pumpWidget(const RootRestorationScope( |
| restorationId: 'root-child', |
| child: _RestorableWidget(), |
| )); |
| |
| expect(find.text('hello world'), findsOneWidget); |
| final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget)); |
| |
| final List<String> notifyLog = <String>[]; |
| |
| state.numValue.addListener(() { |
| notifyLog.add('num'); |
| }); |
| state.doubleValue.addListener(() { |
| notifyLog.add('double'); |
| }); |
| state.intValue.addListener(() { |
| notifyLog.add('int'); |
| }); |
| state.stringValue.addListener(() { |
| notifyLog.add('string'); |
| }); |
| state.boolValue.addListener(() { |
| notifyLog.add('bool'); |
| }); |
| state.dateTimeValue.addListener(() { |
| notifyLog.add('date-time'); |
| }); |
| state.nullableNumValue.addListener(() { |
| notifyLog.add('nullable-num'); |
| }); |
| state.nullableDoubleValue.addListener(() { |
| notifyLog.add('nullable-double'); |
| }); |
| state.nullableIntValue.addListener(() { |
| notifyLog.add('nullable-int'); |
| }); |
| state.nullableStringValue.addListener(() { |
| notifyLog.add('nullable-string'); |
| }); |
| state.nullableBoolValue.addListener(() { |
| notifyLog.add('nullable-bool'); |
| }); |
| state.nullableDateTimeValue.addListener(() { |
| notifyLog.add('nullable-date-time'); |
| }); |
| state.controllerValue.addListener(() { |
| notifyLog.add('controller'); |
| }); |
| state.objectValue.addListener(() { |
| notifyLog.add('object'); |
| }); |
| |
| state.setProperties(() { |
| state.numValue.value = 42.2; |
| }); |
| expect(notifyLog.single, 'num'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.doubleValue.value = 42.2; |
| }); |
| expect(notifyLog.single, 'double'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.intValue.value = 45; |
| }); |
| expect(notifyLog.single, 'int'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.stringValue.value = 'bar'; |
| }); |
| expect(notifyLog.single, 'string'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.boolValue.value = true; |
| }); |
| expect(notifyLog.single, 'bool'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.dateTimeValue.value = DateTime(2020, 7, 4); |
| }); |
| expect(notifyLog.single, 'date-time'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.nullableNumValue.value = 42.2; |
| }); |
| expect(notifyLog.single, 'nullable-num'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.nullableDoubleValue.value = 42.2; |
| }); |
| expect(notifyLog.single, 'nullable-double'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.nullableIntValue.value = 45; |
| }); |
| expect(notifyLog.single, 'nullable-int'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.nullableStringValue.value = 'bar'; |
| }); |
| expect(notifyLog.single, 'nullable-string'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.nullableBoolValue.value = true; |
| }); |
| expect(notifyLog.single, 'nullable-bool'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.nullableDateTimeValue.value = DateTime(2020, 4, 4); |
| }); |
| expect(notifyLog.single, 'nullable-date-time'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.controllerValue.value.text = 'foo'; |
| }); |
| expect(notifyLog.single, 'controller'); |
| notifyLog.clear(); |
| |
| state.setProperties(() { |
| state.objectValue.value = 42; |
| }); |
| expect(notifyLog.single, 'object'); |
| notifyLog.clear(); |
| |
| await tester.pump(); |
| expect(find.text('bar'), findsOneWidget); |
| |
| // Does not notify when set to same value. |
| state.setProperties(() { |
| state.numValue.value = 42.2; |
| state.doubleValue.value = 42.2; |
| state.intValue.value = 45; |
| state.stringValue.value = 'bar'; |
| state.boolValue.value = true; |
| state.dateTimeValue.value = DateTime(2020, 7, 4); |
| state.nullableNumValue.value = 42.2; |
| state.nullableDoubleValue.value = 42.2; |
| state.nullableIntValue.value = 45; |
| state.nullableStringValue.value = 'bar'; |
| state.nullableBoolValue.value = true; |
| state.nullableDateTimeValue.value = DateTime(2020, 4, 4); |
| state.controllerValue.value.text = 'foo'; |
| state.objectValue.value = 42; |
| }); |
| |
| expect(notifyLog, isEmpty); |
| }); |
| |
| testWidgets('RestorableValue calls didUpdateValue', (WidgetTester tester) async { |
| await tester.pumpWidget(const RootRestorationScope( |
| restorationId: 'root-child', |
| child: _RestorableWidget(), |
| )); |
| |
| expect(find.text('hello world'), findsOneWidget); |
| final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget)); |
| |
| expect(state.objectValue.didUpdateValueCallCount, 0); |
| |
| state.setProperties(() { |
| state.objectValue.value = 44; |
| }); |
| expect(state.objectValue.didUpdateValueCallCount, 1); |
| |
| await tester.pump(); |
| |
| state.setProperties(() { |
| state.objectValue.value = 44; |
| }); |
| expect(state.objectValue.didUpdateValueCallCount, 1); |
| }); |
| |
| |
| testWidgets('RestorableN types are properly defined', (WidgetTester tester) async { |
| await tester.pumpWidget(const RootRestorationScope( |
| restorationId: 'root-child', |
| child: _RestorableWidget(), |
| )); |
| |
| expect(find.text('hello world'), findsOneWidget); |
| final _RestorableWidgetState state = tester.state(find.byType(_RestorableWidget)); |
| state.setProperties(() { |
| state.nullableIntValue.value = 24; |
| state.nullableDoubleValue.value = 1.5; |
| }); |
| |
| // The following types of asserts do not work. They pass even when the |
| // type of `value` is a `num` and not an `int` because `num` is a |
| // superclass of `int`. This test is intended to prevent a regression |
| // where RestorableIntN's value is of type `num?`, but it is passed into |
| // a function which requires an `int?` value. This resulted in Dart |
| // compile-time errors. |
| // |
| // expect(state.nullableIntValue.value, isA<int>()); |
| // expect(state.nullableIntValue.value.runtimeType, int); |
| |
| // A function that takes a nullable int value. |
| void takesInt(int? value) {} |
| // The following would result in a Dart compile-time error if `value` is |
| // a `num?` instead of an `int?`. |
| takesInt(state.nullableIntValue.value); |
| |
| // A function that takes a nullable double value. |
| void takesDouble(double? value) {} |
| // The following would result in a Dart compile-time error if `value` is |
| // a `num?` instead of a `double?`. |
| takesDouble(state.nullableDoubleValue.value); |
| }); |
| } |
| |
| class _TestRestorableValue extends RestorableValue<Object?> { |
| @override |
| Object createDefaultValue() { |
| return 55; |
| } |
| |
| int didUpdateValueCallCount = 0; |
| |
| @override |
| void didUpdateValue(Object? oldValue) { |
| didUpdateValueCallCount++; |
| notifyListeners(); |
| } |
| |
| @override |
| Object? fromPrimitives(Object? data) { |
| return data; |
| } |
| |
| @override |
| Object? toPrimitives() { |
| return value; |
| } |
| } |
| |
| class _RestorableWidget extends StatefulWidget { |
| const _RestorableWidget({Key? key}) : super(key: key); |
| |
| @override |
| State<_RestorableWidget> createState() => _RestorableWidgetState(); |
| } |
| |
| class _RestorableWidgetState extends State<_RestorableWidget> with RestorationMixin { |
| final RestorableNum<num> numValue = RestorableNum<num>(99); |
| final RestorableDouble doubleValue = RestorableDouble(123.2); |
| final RestorableInt intValue = RestorableInt(42); |
| final RestorableString stringValue = RestorableString('hello world'); |
| final RestorableBool boolValue = RestorableBool(false); |
| final RestorableDateTime dateTimeValue = RestorableDateTime(DateTime(2021, 3, 16)); |
| final RestorableNumN<num?> nullableNumValue = RestorableNumN<num?>(null); |
| final RestorableDoubleN nullableDoubleValue = RestorableDoubleN(null); |
| final RestorableIntN nullableIntValue = RestorableIntN(null); |
| final RestorableStringN nullableStringValue = RestorableStringN(null); |
| final RestorableBoolN nullableBoolValue = RestorableBoolN(null); |
| final RestorableDateTimeN nullableDateTimeValue = RestorableDateTimeN(null); |
| final RestorableTextEditingController controllerValue = RestorableTextEditingController(text: 'FooBar'); |
| final _TestRestorableValue objectValue = _TestRestorableValue(); |
| |
| @override |
| void restoreState(RestorationBucket? oldBucket, bool initialRestore) { |
| registerForRestoration(numValue, 'num'); |
| registerForRestoration(doubleValue, 'double'); |
| registerForRestoration(intValue, 'int'); |
| registerForRestoration(stringValue, 'string'); |
| registerForRestoration(boolValue, 'bool'); |
| registerForRestoration(dateTimeValue, 'dateTime'); |
| registerForRestoration(nullableNumValue, 'nullableNum'); |
| registerForRestoration(nullableDoubleValue, 'nullableDouble'); |
| registerForRestoration(nullableIntValue, 'nullableInt'); |
| registerForRestoration(nullableStringValue, 'nullableString'); |
| registerForRestoration(nullableBoolValue, 'nullableBool'); |
| registerForRestoration(nullableDateTimeValue, 'nullableDateTime'); |
| registerForRestoration(controllerValue, 'controller'); |
| registerForRestoration(objectValue, 'object'); |
| } |
| |
| void setProperties(VoidCallback callback) { |
| setState(callback); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Text(stringValue.value, textDirection: TextDirection.ltr); |
| } |
| |
| @override |
| String get restorationId => 'widget'; |
| } |