blob: 49383e41f872c8a9bf0d653c976d1594c79636a1 [file] [log] [blame]
// 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/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'restoration.dart';
void main() {
test('root bucket values', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket bucket = RestorationBucket.root(manager: manager, rawData: rawData);
expect(bucket.restorationId, 'root');
expect(bucket.debugOwner, manager);
// Bucket contains expected values from rawData.
expect(bucket.read<int>('value1'), 10);
expect(bucket.read<String>('value2'), 'Hello');
expect(bucket.read<String>('value3'), isNull); // Does not exist.
expect(manager.updateScheduled, isFalse);
// Can overwrite existing value.
bucket.write<int>('value1', 22);
expect(manager.updateScheduled, isTrue);
expect(bucket.read<int>('value1'), 22);
manager.doSerialization();
expect((rawData[valuesMapKey] as Map<String, dynamic>)['value1'], 22);
expect(manager.updateScheduled, isFalse);
// Can add a new value.
bucket.write<bool>('value3', true);
expect(manager.updateScheduled, isTrue);
expect(bucket.read<bool>('value3'), true);
manager.doSerialization();
expect((rawData[valuesMapKey] as Map<String, dynamic>)['value3'], true);
expect(manager.updateScheduled, isFalse);
// Can remove existing value.
expect(bucket.remove<int>('value1'), 22);
expect(manager.updateScheduled, isTrue);
expect(bucket.read<int>('value1'), isNull); // Does not exist anymore.
manager.doSerialization();
expect((rawData[valuesMapKey] as Map<String, dynamic>).containsKey('value1'), isFalse);
expect(manager.updateScheduled, isFalse);
// Removing non-existing value is no-op.
expect(bucket.remove<Object>('value4'), isNull);
expect(manager.updateScheduled, isFalse);
// Can store null.
bucket.write<bool?>('value4', null);
expect(manager.updateScheduled, isTrue);
expect(bucket.read<int>('value4'), null);
manager.doSerialization();
expect((rawData[valuesMapKey] as Map<String, dynamic>).containsKey('value4'), isTrue);
expect((rawData[valuesMapKey] as Map<String, dynamic>)['value4'], null);
expect(manager.updateScheduled, isFalse);
});
test('child bucket values', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rootRawData = _createRawDataSet();
final Object debugOwner = Object();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rootRawData);
final RestorationBucket child = RestorationBucket.child(
restorationId: 'child1',
parent: root,
debugOwner: debugOwner,
);
expect(child.restorationId, 'child1');
expect(child.debugOwner, debugOwner);
// Bucket contains expected values from rawData.
expect(child.read<int>('foo'), 22);
expect(child.read<String>('bar'), isNull); // Does not exist.
expect(manager.updateScheduled, isFalse);
// Can overwrite existing value.
child.write<int>('foo', 44);
expect(manager.updateScheduled, isTrue);
expect(child.read<int>('foo'), 44);
manager.doSerialization();
expect((((rootRawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>)[valuesMapKey] as Map<String, dynamic>)['foo'], 44);
expect(manager.updateScheduled, isFalse);
// Can add a new value.
child.write<bool>('value3', true);
expect(manager.updateScheduled, isTrue);
expect(child.read<bool>('value3'), true);
manager.doSerialization();
expect((((rootRawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>)[valuesMapKey] as Map<String, dynamic>)['value3'], true);
expect(manager.updateScheduled, isFalse);
// Can remove existing value.
expect(child.remove<int>('foo'), 44);
expect(manager.updateScheduled, isTrue);
expect(child.read<int>('foo'), isNull); // Does not exist anymore.
manager.doSerialization();
expect(((rootRawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>).containsKey('foo'), isFalse);
expect(manager.updateScheduled, isFalse);
// Removing non-existing value is no-op.
expect(child.remove<Object>('value4'), isNull);
expect(manager.updateScheduled, isFalse);
// Can store null.
child.write<bool?>('value4', null);
expect(manager.updateScheduled, isTrue);
expect(child.read<int>('value4'), null);
manager.doSerialization();
expect((((rootRawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>)[valuesMapKey] as Map<String, dynamic>).containsKey('value4'), isTrue);
expect((((rootRawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>)[valuesMapKey] as Map<String, dynamic>)['value4'], null);
expect(manager.updateScheduled, isFalse);
});
test('claim child with existing data', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket bucket = RestorationBucket.root(manager: manager, rawData: rawData);
final Object debugOwner = Object();
final RestorationBucket child = bucket.claimChild('child1', debugOwner: debugOwner);
expect(manager.updateScheduled, isFalse);
expect(child.restorationId, 'child1');
expect(child.debugOwner, debugOwner);
expect(child.read<int>('foo'), 22);
child.write('bar', 44);
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect((((rawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>)[valuesMapKey] as Map<String, dynamic>)['bar'], 44);
expect(manager.updateScheduled, isFalse);
});
test('claim child with no existing data', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket bucket = RestorationBucket.root(manager: manager, rawData: rawData);
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child2'), isFalse);
final Object debugOwner = Object();
final RestorationBucket child = bucket.claimChild('child2', debugOwner: debugOwner);
expect(manager.updateScheduled, isTrue);
expect(child.restorationId, 'child2');
expect(child.debugOwner, debugOwner);
child.write('foo', 55);
expect(child.read<int>('foo'), 55);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child2'), isTrue);
expect((((rawData[childrenMapKey] as Map<String, dynamic>)['child2'] as Map<String, dynamic>)[valuesMapKey] as Map<Object?, Object?>)['foo'], 55);
});
test('claim child that is already claimed throws if not given up', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket bucket = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child1 = bucket.claimChild('child1', debugOwner: 'FirstClaim');
expect(manager.updateScheduled, isFalse);
expect(child1.restorationId, 'child1');
expect(child1.read<int>('foo'), 22);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
final RestorationBucket child2 = bucket.claimChild('child1', debugOwner: 'SecondClaim');
expect(child2.restorationId, 'child1');
expect(child2.read<int>('foo'), isNull); // Value does not exist in this child.
// child1 is not given up before running finalizers.
expect(
() => manager.doSerialization(),
throwsA(isFlutterError.having(
(FlutterError error) => error.message,
'message',
equals(
'Multiple owners claimed child RestorationBuckets with the same IDs.\n'
'The following IDs were claimed multiple times from the parent RestorationBucket(restorationId: root, owner: MockManager):\n'
' * "child1" was claimed by:\n'
' * SecondClaim\n'
' * FirstClaim (current owner)',
),
)),
);
});
test('claim child that is already claimed does not throw if given up', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket bucket = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child1 = bucket.claimChild('child1', debugOwner: 'FirstClaim');
expect(manager.updateScheduled, isFalse);
expect(child1.restorationId, 'child1');
expect(child1.read<int>('foo'), 22);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
final RestorationBucket child2 = bucket.claimChild('child1', debugOwner: 'SecondClaim');
expect(child2.restorationId, 'child1');
expect(child2.read<int>('foo'), isNull); // Value does not exist in this child.
child2.write<int>('bar', 55);
// give up child1.
child1.dispose();
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect((((rawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>)[valuesMapKey] as Map<Object?, Object?>).containsKey('foo'), isFalse);
expect((((rawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>)[valuesMapKey] as Map<Object?, Object?>)['bar'], 55);
});
test('claiming a claimed child twice and only giving it up once throws', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket bucket = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child1 = bucket.claimChild('child1', debugOwner: 'FirstClaim');
expect(child1.restorationId, 'child1');
final RestorationBucket child2 = bucket.claimChild('child1', debugOwner: 'SecondClaim');
expect(child2.restorationId, 'child1');
child1.dispose();
final RestorationBucket child3 = bucket.claimChild('child1', debugOwner: 'ThirdClaim');
expect(child3.restorationId, 'child1');
expect(manager.updateScheduled, isTrue);
expect(() => manager.doSerialization(), throwsFlutterError);
});
test('unclaiming and then claiming same id gives fresh bucket', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket bucket = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child1 = bucket.claimChild('child1', debugOwner: 'FirstClaim');
expect(manager.updateScheduled, isFalse);
expect(child1.read<int>('foo'), 22);
child1.dispose();
expect(manager.updateScheduled, isTrue);
final RestorationBucket child2 = bucket.claimChild('child1', debugOwner: 'SecondClaim');
expect(child2.read<int>('foo'), isNull);
});
test('cleans up raw data if last value/child is dropped', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
expect(rawData.containsKey(childrenMapKey), isTrue);
final RestorationBucket child = root.claimChild('child1', debugOwner: 'owner');
child.dispose();
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(rawData.containsKey(childrenMapKey), isFalse);
expect(rawData.containsKey(valuesMapKey), isTrue);
expect(root.remove<int>('value1'), 10);
expect(root.remove<String>('value2'), 'Hello');
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(rawData.containsKey(valuesMapKey), isFalse);
});
test('dispose deletes data', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child1 = root.claimChild('child1', debugOwner: 'owner1');
child1.claimChild('child1OfChild1', debugOwner: 'owner1.1');
child1.claimChild('child2OfChild1', debugOwner: 'owner1.2');
final RestorationBucket child2 = root.claimChild('child2', debugOwner: 'owner2');
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isTrue);
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child2'), isTrue);
child1.dispose();
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isFalse);
child2.dispose();
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect(rawData.containsKey(childrenMapKey), isFalse);
});
test('rename is no-op if same id', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child = root.claimChild('child1', debugOwner: 'owner1');
expect(manager.updateScheduled, isFalse);
expect(child.restorationId, 'child1');
child.rename('child1');
expect(manager.updateScheduled, isFalse);
expect(child.restorationId, 'child1');
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isTrue);
});
test('rename to unused id', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child = root.claimChild('child1', debugOwner: 'owner1');
final Object rawChildData = (rawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Object;
expect(rawChildData, isNotNull);
expect(manager.updateScheduled, isFalse);
expect(child.restorationId, 'child1');
child.rename('new-name');
expect(child.restorationId, 'new-name');
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect((rawData[childrenMapKey] as Map<Object?, Object?>).containsKey('child1'), isFalse);
expect((rawData[childrenMapKey] as Map<Object?, Object?>)['new-name'], rawChildData);
});
test('rename to used id throws if id is not given up', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child1 = root.claimChild('child1', debugOwner: 'owner1');
final RestorationBucket child2 = root.claimChild('child2', debugOwner: 'owner1');
manager.doSerialization();
expect(child1.restorationId, 'child1');
expect(child2.restorationId, 'child2');
child2.rename('child1');
expect(child2.restorationId, 'child1');
expect(manager.updateScheduled, isTrue);
expect(() => manager.doSerialization(), throwsFlutterError);
});
test('rename to used id does not throw if id is given up', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child1 = root.claimChild('child1', debugOwner: 'owner1');
final RestorationBucket child2 = root.claimChild('child2', debugOwner: 'owner1');
manager.doSerialization();
final Object rawChild1Data = (rawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Object;
expect(rawChild1Data, isNotNull);
final Object rawChild2Data = (rawData[childrenMapKey] as Map<String, dynamic>)['child2'] as Object;
expect(rawChild2Data, isNotNull);
expect(child1.restorationId, 'child1');
expect(child2.restorationId, 'child2');
child2.rename('child1');
expect(child2.restorationId, 'child1');
expect(child1.restorationId, 'child1');
child1.dispose();
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect((rawData[childrenMapKey] as Map<String, dynamic>)['child1'], rawChild2Data);
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child2'), isFalse);
});
test('renaming a to be added child', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final Object rawChild1Data = (rawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Object;
expect(rawChild1Data, isNotNull);
final RestorationBucket child1 = root.claimChild('child1', debugOwner: 'owner1');
final RestorationBucket child2 = root.claimChild('child1', debugOwner: 'owner1');
child2.rename('foo');
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect(child1.restorationId, 'child1');
expect(child2.restorationId, 'foo');
expect((rawData[childrenMapKey] as Map<String, dynamic>)['child1'], rawChild1Data);
expect((rawData[childrenMapKey] as Map<String, dynamic>)['foo'], isEmpty); // new bucket
});
test('adopt is no-op if same parent', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child1 = root.claimChild('child1', debugOwner: 'owner1');
root.adoptChild(child1);
expect(manager.updateScheduled, isFalse);
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('child1'), isTrue);
});
test('adopt fresh child', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child = RestorationBucket.empty(restorationId: 'fresh-child', debugOwner: 'owner1');
root.adoptChild(child);
expect(manager.updateScheduled, isTrue);
child.write('value', 22);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect((rawData[childrenMapKey] as Map<String, dynamic>).containsKey('fresh-child'), isTrue);
expect((((rawData[childrenMapKey] as Map<String, dynamic>)['fresh-child'] as Map<String, dynamic>)[valuesMapKey] as Map<Object?, Object?>)['value'], 22);
child.write('bar', 'blabla');
expect(manager.updateScheduled, isTrue);
});
test('adopt child that already had a parent', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child = root.claimChild('child1', debugOwner: 'owner1');
final RestorationBucket childOfChild = child.claimChild('childOfChild', debugOwner: 'owner2');
childOfChild.write<String>('foo', 'bar');
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
final Object childOfChildData = (((rawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>)[childrenMapKey] as Map<Object?, Object?>)['childOfChild']!;
expect(childOfChildData, isNotEmpty);
root.adoptChild(childOfChild);
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect(((rawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>).containsKey(childrenMapKey), isFalse); // child1 has no children anymore.
expect((rawData[childrenMapKey] as Map<String, dynamic>)['childOfChild'], childOfChildData);
});
test('adopting child throws if id is already in use and not given up', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child = root.claimChild('child1', debugOwner: 'owner1');
final RestorationBucket childOfChild = child.claimChild('child1', debugOwner: 'owner2');
childOfChild.write<String>('foo', 'bar');
root.adoptChild(childOfChild);
expect(manager.updateScheduled, isTrue);
expect(() => manager.doSerialization(), throwsFlutterError);
});
test('adopting child does not throw if id is already in use and given up', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child = root.claimChild('child1', debugOwner: 'owner1');
final RestorationBucket childOfChild = child.claimChild('child1', debugOwner: 'owner2');
childOfChild.write<String>('foo', 'bar');
final Object childOfChildData = (((rawData[childrenMapKey] as Map<String, dynamic>)['child1'] as Map<String, dynamic>)[childrenMapKey] as Map<Object?, Object?>)['child1']!;
expect(childOfChildData, isNotEmpty);
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
root.adoptChild(childOfChild);
expect(manager.updateScheduled, isTrue);
child.dispose();
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect((rawData[childrenMapKey] as Map<String, dynamic>)['child1'], childOfChildData);
});
test('adopting a to-be-added child under an already in use id', () {
final MockRestorationManager manager = MockRestorationManager();
final Map<String, dynamic> rawData = _createRawDataSet();
final RestorationBucket root = RestorationBucket.root(manager: manager, rawData: rawData);
final RestorationBucket child1 = root.claimChild('child1', debugOwner: 'owner1');
final RestorationBucket child2 = root.claimChild('child2', debugOwner: 'owner1');
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
final RestorationBucket child1OfChild1 = child1.claimChild('child2', debugOwner: 'owner2');
child1OfChild1.write<String>('hello', 'world');
final RestorationBucket child2OfChild1 = child1.claimChild('child2', debugOwner: 'owner2');
child2OfChild1.write<String>('foo', 'bar');
root.adoptChild(child2OfChild1);
child2.dispose();
expect(manager.updateScheduled, isTrue);
manager.doSerialization();
expect(manager.updateScheduled, isFalse);
expect((((rawData[childrenMapKey] as Map<String, dynamic>)['child2'] as Map<String, dynamic>)[valuesMapKey] as Map<Object?, Object?>)['foo'], 'bar');
expect((((((rawData[childrenMapKey] as Map<String, dynamic>)
['child1'] as Map<String, dynamic>)
[childrenMapKey] as Map<Object?, Object?>)
['child2']! as Map<String, dynamic>)
[valuesMapKey] as Map<Object?, Object?>)
['hello'], 'world');
});
test('throws when used after dispose', () {
final RestorationBucket bucket = RestorationBucket.empty(restorationId: 'foo', debugOwner: null);
bucket.dispose();
expect(() => bucket.debugOwner, throwsFlutterError);
expect(() => bucket.restorationId, throwsFlutterError);
expect(() => bucket.read<int>('foo'), throwsFlutterError);
expect(() => bucket.write('foo', 10), throwsFlutterError);
expect(() => bucket.remove<int>('foo'), throwsFlutterError);
expect(() => bucket.contains('foo'), throwsFlutterError);
expect(() => bucket.claimChild('child', debugOwner: null), throwsFlutterError);
final RestorationBucket child = RestorationBucket.empty(restorationId: 'child', debugOwner: null);
expect(() => bucket.adoptChild(child), throwsFlutterError);
expect(() => bucket.rename('bar'), throwsFlutterError);
expect(() => bucket.dispose(), throwsFlutterError);
});
}
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,
},
},
},
};
}