// 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'; import 'restoration.dart'; void main() { group('UnmanagedRestorationScope', () { testWidgets('makes bucket available to descendants', (WidgetTester tester) async { final RestorationBucket bucket1 = RestorationBucket.empty( restorationId: 'foo', debugOwner: 'owner', ); await tester.pumpWidget( UnmanagedRestorationScope( bucket: bucket1, child: const BucketSpy(), ), ); final BucketSpyState state = tester.state(find.byType(BucketSpy)); expect(state.bucket, bucket1); // Notifies when bucket changes. final RestorationBucket bucket2 = RestorationBucket.empty( restorationId: 'foo2', debugOwner: 'owner', ); await tester.pumpWidget( UnmanagedRestorationScope( bucket: bucket2, child: const BucketSpy(), ), ); expect(state.bucket, bucket2); }); testWidgets('null bucket disables restoration', (WidgetTester tester) async { await tester.pumpWidget( const UnmanagedRestorationScope( bucket: null, child: BucketSpy(), ), ); final BucketSpyState state = tester.state(find.byType(BucketSpy)); expect(state.bucket, isNull); }); }); group('RestorationScope', () { testWidgets('makes bucket available to descendants', (WidgetTester tester) async { const String id = 'hello world 1234'; final MockRestorationManager manager = MockRestorationManager(); final Map<String, dynamic> rawData = <String, dynamic>{}; final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData); expect(rawData, isEmpty); await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: const RestorationScope( restorationId: id, child: BucketSpy(), ), ), ); manager.doSerialization(); final BucketSpyState state = tester.state(find.byType(BucketSpy)); expect(state.bucket!.restorationId, id); expect((rawData[childrenMapKey] as Map<Object?, Object?>).containsKey(id), isTrue); }); testWidgets('bucket for descendants contains data claimed from parent', (WidgetTester tester) async { final MockRestorationManager manager = MockRestorationManager(); final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet()); await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: const RestorationScope( restorationId: 'child1', child: BucketSpy(), ), ), ); manager.doSerialization(); final BucketSpyState state = tester.state(find.byType(BucketSpy)); expect(state.bucket!.restorationId, 'child1'); expect(state.bucket!.read<int>('foo'), 22); }); testWidgets('renames existing bucket when new ID is provided', (WidgetTester tester) async { final MockRestorationManager manager = MockRestorationManager(); final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: _createRawDataSet()); await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: const RestorationScope( restorationId: 'child1', child: BucketSpy(), ), ), ); manager.doSerialization(); // Claimed existing bucket with data. final BucketSpyState state = tester.state(find.byType(BucketSpy)); expect(state.bucket!.restorationId, 'child1'); expect(state.bucket!.read<int>('foo'), 22); final RestorationBucket bucket = state.bucket!; // Rename the existing bucket. await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: const RestorationScope( restorationId: 'something else', child: BucketSpy(), ), ), ); manager.doSerialization(); expect(state.bucket!.restorationId, 'something else'); expect(state.bucket!.read<int>('foo'), 22); expect(state.bucket, same(bucket)); }); testWidgets('Disposing a scope removes its data', (WidgetTester tester) async { final MockRestorationManager manager = MockRestorationManager(); final Map<String, dynamic> rawData = _createRawDataSet(); final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData); expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isTrue); await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: const RestorationScope( restorationId: 'child1', child: BucketSpy(), ), ), ); manager.doSerialization(); expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isTrue); await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: Container(), ), ); manager.doSerialization(); expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isFalse); }); testWidgets('no bucket for descendants when id is null', (WidgetTester tester) async { final MockRestorationManager manager = MockRestorationManager(); final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: <String, dynamic>{}); await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: const RestorationScope( restorationId: null, child: BucketSpy(), ), ), ); final BucketSpyState state = tester.state(find.byType(BucketSpy)); expect(state.bucket, isNull); // Change id to non-null. await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: const RestorationScope( restorationId: 'foo', child: BucketSpy(), ), ), ); manager.doSerialization(); expect(state.bucket, isNotNull); expect(state.bucket!.restorationId, 'foo'); // Change id back to null. await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: const RestorationScope( restorationId: null, child: BucketSpy(), ), ), ); manager.doSerialization(); expect(state.bucket, isNull); }); testWidgets('no bucket for descendants when scope is null', (WidgetTester tester) async { final Key scopeKey = GlobalKey(); await tester.pumpWidget( RestorationScope( key: scopeKey, restorationId: 'foo', child: const BucketSpy(), ), ); final BucketSpyState state = tester.state(find.byType(BucketSpy)); expect(state.bucket, isNull); // Move it under a valid scope. final MockRestorationManager manager = MockRestorationManager(); final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: <String, dynamic>{}); await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: RestorationScope( key: scopeKey, restorationId: 'foo', child: const BucketSpy(), ), ), ); manager.doSerialization(); expect(state.bucket, isNotNull); expect(state.bucket!.restorationId, 'foo'); // Move out of scope again. await tester.pumpWidget( RestorationScope( key: scopeKey, restorationId: 'foo', child: const BucketSpy(), ), ); manager.doSerialization(); expect(state.bucket, isNull); }); testWidgets('no bucket for descendants when scope and id are null', (WidgetTester tester) async { await tester.pumpWidget( const RestorationScope( restorationId: null, child: BucketSpy(), ), ); final BucketSpyState state = tester.state(find.byType(BucketSpy)); expect(state.bucket, isNull); }); testWidgets('moving scope moves its data', (WidgetTester tester) async { final MockRestorationManager manager = MockRestorationManager(); final Map<String, dynamic> rawData = <String, dynamic>{}; final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData); final Key scopeKey = GlobalKey(); await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: Row( textDirection: TextDirection.ltr, children: <Widget>[ RestorationScope( restorationId: 'fixed', child: RestorationScope( key: scopeKey, restorationId: 'moving-child', child: const BucketSpy(), ), ), ], ), ), ); manager.doSerialization(); final BucketSpyState state = tester.state(find.byType(BucketSpy)); expect(state.bucket!.restorationId, 'moving-child'); expect((((rawData[childrenMapKey] as Map<Object?, Object?>)['fixed']! as Map<String, dynamic>)[childrenMapKey] as Map<Object?, Object?>).containsKey('moving-child'), isTrue); final RestorationBucket bucket = state.bucket!; state.bucket!.write('value', 11); manager.doSerialization(); // Move scope. await tester.pumpWidget( UnmanagedRestorationScope( bucket: root, child: Row( textDirection: TextDirection.ltr, children: <Widget>[ RestorationScope( restorationId: 'fixed', child: Container(), ), RestorationScope( key: scopeKey, restorationId: 'moving-child', child: const BucketSpy(), ), ], ), ), ); manager.doSerialization(); expect(state.bucket!.restorationId, 'moving-child'); expect(state.bucket, same(bucket)); expect(state.bucket!.read<int>('value'), 11); expect((rawData[childrenMapKey] as Map<Object?, Object?>)['fixed'], isEmpty); expect((rawData[childrenMapKey] as Map<Object?, Object?>).containsKey('moving-child'), isTrue); }); }); } Map<String, dynamic> _createRawDataSet() { return <String, dynamic>{ valuesMapKey: <String, dynamic>{ 'value1' : 10, 'value2' : 'Hello', }, childrenMapKey: <String, dynamic>{ 'child1' : <String, dynamic>{ valuesMapKey : <String, dynamic>{ 'foo': 22, }, }, 'child2' : <String, dynamic>{ valuesMapKey : <String, dynamic>{ 'bar': 33, }, }, }, }; }