| // 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/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| testWidgets('widget moves scopes during restore', (WidgetTester tester) async { |
| await tester.pumpWidget(const RootRestorationScope( |
| restorationId: 'root', |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: TestWidgetWithCounterChild(), |
| ), |
| )); |
| |
| expect(tester.state<TestWidgetWithCounterChildState>(find.byType(TestWidgetWithCounterChild)).restoreChild, true); |
| expect(find.text('Counter: 0'), findsOneWidget); |
| await tester.tap(find.text('Counter: 0')); |
| await tester.pump(); |
| expect(find.text('Counter: 1'), findsOneWidget); |
| |
| final TestRestorationData dataWithChild = await tester.getRestorationData(); |
| |
| tester.state<TestWidgetWithCounterChildState>(find.byType(TestWidgetWithCounterChild)).restoreChild = false; |
| await tester.pump(); |
| expect(tester.state<TestWidgetWithCounterChildState>(find.byType(TestWidgetWithCounterChild)).restoreChild, false); |
| |
| await tester.tap(find.text('Counter: 1')); |
| await tester.pump(); |
| expect(find.text('Counter: 2'), findsOneWidget); |
| |
| final TestRestorationData dataWithoutChild = await tester.getRestorationData(); |
| |
| // Child moves from outside to inside scope. |
| await tester.restoreFrom(dataWithChild); |
| expect(find.text('Counter: 1'), findsOneWidget); |
| |
| await tester.tap(find.text('Counter: 1')); |
| await tester.pump(); |
| expect(find.text('Counter: 2'), findsOneWidget); |
| |
| // Child stays inside scope. |
| await tester.restoreFrom(dataWithChild); |
| expect(find.text('Counter: 1'), findsOneWidget); |
| |
| await tester.tap(find.text('Counter: 1')); |
| await tester.tap(find.text('Counter: 1')); |
| await tester.tap(find.text('Counter: 1')); |
| await tester.tap(find.text('Counter: 1')); |
| await tester.tap(find.text('Counter: 1')); |
| await tester.pump(); |
| expect(find.text('Counter: 6'), findsOneWidget); |
| |
| // Child moves from inside to outside scope. |
| await tester.restoreFrom(dataWithoutChild); |
| expect(find.text('Counter: 6'), findsOneWidget); |
| |
| await tester.tap(find.text('Counter: 6')); |
| await tester.pump(); |
| expect(find.text('Counter: 7'), findsOneWidget); |
| |
| // Child stays outside scope. |
| await tester.restoreFrom(dataWithoutChild); |
| expect(find.text('Counter: 7'), findsOneWidget); |
| |
| expect(tester.state<TestWidgetWithCounterChildState>(find.byType(TestWidgetWithCounterChild)).toggleCount, 0); |
| }); |
| |
| testWidgets('restoration is turned on later', (WidgetTester tester) async { |
| tester.binding.restorationManager.disableRestoration(); |
| await tester.pumpWidget(const RootRestorationScope( |
| restorationId: 'root-child', |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: TestWidget( |
| restorationId: 'foo', |
| ), |
| ), |
| )); |
| |
| final TestWidgetState state = tester.state<TestWidgetState>(find.byType(TestWidget)); |
| expect(find.text('hello'), findsOneWidget); |
| expect(state.buckets.single, isNull); |
| expect(state.flags.single, isTrue); |
| expect(state.bucket, isNull); |
| |
| state.buckets.clear(); |
| state.flags.clear(); |
| |
| await tester.restoreFrom(TestRestorationData.empty); |
| |
| await tester.pumpWidget(const RootRestorationScope( |
| restorationId: 'root-child', |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: TestWidget( |
| restorationId: 'foo', |
| ), |
| ), |
| )); |
| |
| expect(find.text('hello'), findsOneWidget); |
| expect(state.buckets.single, isNull); |
| expect(state.flags.single, isFalse); |
| expect(state.bucket, isNotNull); |
| |
| expect(state.toggleCount, 0); |
| }); |
| } |
| |
| class TestWidgetWithCounterChild extends StatefulWidget { |
| const TestWidgetWithCounterChild({Key? key}) : super(key: key); |
| |
| @override |
| State<TestWidgetWithCounterChild> createState() => TestWidgetWithCounterChildState(); |
| } |
| |
| class TestWidgetWithCounterChildState extends State<TestWidgetWithCounterChild> with RestorationMixin { |
| final RestorableBool childRestorationEnabled = RestorableBool(true); |
| |
| int toggleCount = 0; |
| |
| @override |
| void didToggleBucket(RestorationBucket? oldBucket) { |
| super.didToggleBucket(oldBucket); |
| toggleCount++; |
| } |
| |
| @override |
| void restoreState(RestorationBucket? oldBucket, bool initialRestore) { |
| registerForRestoration(childRestorationEnabled, 'childRestorationEnabled'); |
| } |
| |
| bool get restoreChild => childRestorationEnabled.value; |
| set restoreChild(bool value) { |
| if (value == childRestorationEnabled.value) { |
| return; |
| } |
| setState(() { |
| childRestorationEnabled.value = value; |
| }); |
| } |
| |
| @override |
| void dispose() { |
| childRestorationEnabled.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Counter( |
| restorationId: restoreChild ? 'counter' : null, |
| ); |
| } |
| |
| @override |
| String get restorationId => 'foo'; |
| } |
| |
| class Counter extends StatefulWidget { |
| const Counter({Key? key, this.restorationId}) : super(key: key); |
| |
| final String? restorationId; |
| |
| @override |
| State<Counter> createState() => CounterState(); |
| } |
| |
| class CounterState extends State<Counter> with RestorationMixin { |
| final RestorableInt count = RestorableInt(0); |
| |
| @override |
| String? get restorationId => widget.restorationId; |
| |
| @override |
| void restoreState(RestorationBucket? oldBucket, bool initialRestore) { |
| registerForRestoration(count, 'counter'); |
| } |
| |
| @override |
| void dispose() { |
| count.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return OutlinedButton( |
| onPressed: () { |
| setState(() { |
| count.value++; |
| }); |
| }, |
| child: Text( |
| 'Counter: ${count.value}', |
| ), |
| ); |
| } |
| } |
| |
| class TestWidget extends StatefulWidget { |
| const TestWidget({Key? key, required this.restorationId}) : super(key: key); |
| |
| final String? restorationId; |
| |
| @override |
| State<TestWidget> createState() => TestWidgetState(); |
| } |
| |
| class TestWidgetState extends State<TestWidget> with RestorationMixin { |
| List<RestorationBucket?> buckets = <RestorationBucket?>[]; |
| List<bool> flags = <bool>[]; |
| |
| @override |
| void restoreState(RestorationBucket? oldBucket, bool initialRestore) { |
| buckets.add(oldBucket); |
| flags.add(initialRestore); |
| } |
| |
| int toggleCount = 0; |
| |
| @override |
| void didToggleBucket(RestorationBucket? oldBucket) { |
| super.didToggleBucket(oldBucket); |
| toggleCount++; |
| } |
| |
| @override |
| String? get restorationId => widget.restorationId; |
| |
| @override |
| Widget build(BuildContext context) { |
| return const Text('hello'); |
| } |
| } |