Handle Widget instances being moved as-is to different parts of the tree.
This commit is contained in:
parent
4d84e1b732
commit
6cac226fa6
@ -212,6 +212,15 @@ abstract class Widget {
|
|||||||
/// The parent of this widget in the widget tree.
|
/// The parent of this widget in the widget tree.
|
||||||
Widget get parent => _parent;
|
Widget get parent => _parent;
|
||||||
|
|
||||||
|
// The "generation" of a Widget is the frame in which it was last
|
||||||
|
// synced. We use this to tell if an instance of a Widget has moved
|
||||||
|
// to earlier in the tree so that when we come across where it used
|
||||||
|
// to be, we pretend it was never there. See syncChild().
|
||||||
|
static int _currentGeneration = 1;
|
||||||
|
int _generation = 0;
|
||||||
|
bool get isFromOldGeneration => _generation < _currentGeneration;
|
||||||
|
void _markAsFromCurrentGeneration() { _generation = _currentGeneration; }
|
||||||
|
|
||||||
bool _mounted = false;
|
bool _mounted = false;
|
||||||
bool _wasMounted = false;
|
bool _wasMounted = false;
|
||||||
bool get mounted => _mounted;
|
bool get mounted => _mounted;
|
||||||
@ -305,6 +314,11 @@ abstract class Widget {
|
|||||||
bool retainStatefulNodeIfPossible(Widget newNode) => false;
|
bool retainStatefulNodeIfPossible(Widget newNode) => false;
|
||||||
|
|
||||||
void _sync(Widget old, dynamic slot) {
|
void _sync(Widget old, dynamic slot) {
|
||||||
|
assert(isFromOldGeneration);
|
||||||
|
assert(old == null || old.isFromOldGeneration);
|
||||||
|
_markAsFromCurrentGeneration();
|
||||||
|
if (old != null && old != this)
|
||||||
|
old._markAsFromCurrentGeneration();
|
||||||
if (key is GlobalKey)
|
if (key is GlobalKey)
|
||||||
(key as GlobalKey)._didSync(); // TODO(ianh): Remove the cast once the analyzer is cleverer.
|
(key as GlobalKey)._didSync(); // TODO(ianh): Remove the cast once the analyzer is cleverer.
|
||||||
}
|
}
|
||||||
@ -342,35 +356,63 @@ abstract class Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void remove() {
|
void remove() {
|
||||||
walkChildren((Widget child) => child.remove());
|
walkChildren((Widget child) {
|
||||||
|
if (child._generation <= _generation)
|
||||||
|
child.remove();
|
||||||
|
});
|
||||||
_renderObject = null;
|
_renderObject = null;
|
||||||
setParent(null);
|
setParent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void detachRenderObject();
|
void detachRenderObject();
|
||||||
|
|
||||||
|
Widget _getCandidateSingleChildFrom(Widget oldChild) {
|
||||||
|
Widget candidate = oldChild.singleChild;
|
||||||
|
if (candidate != null && !candidate.isFromOldGeneration)
|
||||||
|
candidate = null;
|
||||||
|
assert(candidate == null || candidate.parent == oldChild);
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the child which should be retained as the child of this node.
|
// Returns the child which should be retained as the child of this node.
|
||||||
Widget syncChild(Widget newNode, Widget oldNode, dynamic slot) {
|
Widget syncChild(Widget newNode, Widget oldNode, dynamic slot) {
|
||||||
|
|
||||||
|
assert(() {
|
||||||
|
'You have probably used a single instance of a Widget in two different places in the widget tree. Widgets can only be used in one place at a time.';
|
||||||
|
return newNode == null || newNode.isFromOldGeneration;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oldNode != null && !oldNode.isFromOldGeneration)
|
||||||
|
oldNode = null;
|
||||||
|
|
||||||
if (newNode == oldNode) {
|
if (newNode == oldNode) {
|
||||||
assert(newNode == null || newNode.mounted);
|
assert(newNode == null || newNode.isFromOldGeneration);
|
||||||
assert(newNode is! RenderObjectWrapper ||
|
assert(newNode is! RenderObjectWrapper ||
|
||||||
(newNode is RenderObjectWrapper && newNode._ancestor != null)); // TODO(ianh): Simplify this once the analyzer is cleverer
|
(newNode is RenderObjectWrapper && newNode._ancestor != null)); // TODO(ianh): Simplify this once the analyzer is cleverer
|
||||||
if (newNode != null)
|
if (newNode != null) {
|
||||||
newNode.setParent(this);
|
newNode.setParent(this);
|
||||||
|
newNode._markAsFromCurrentGeneration();
|
||||||
|
}
|
||||||
return newNode; // Nothing to do. Subtrees must be identical.
|
return newNode; // Nothing to do. Subtrees must be identical.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newNode == null) {
|
if (newNode == null) {
|
||||||
// the child in this slot has gone away
|
// the child in this slot has gone away (we know oldNode != null)
|
||||||
|
assert(oldNode != null);
|
||||||
|
assert(oldNode.isFromOldGeneration);
|
||||||
assert(oldNode.mounted);
|
assert(oldNode.mounted);
|
||||||
oldNode.detachRenderObject();
|
oldNode.detachRenderObject();
|
||||||
oldNode.remove();
|
oldNode.remove();
|
||||||
assert(!oldNode.mounted);
|
assert(!oldNode.mounted);
|
||||||
|
// we don't update the generation of oldNode, because there's
|
||||||
|
// still a chance it could be reused as-is later in the tree.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldNode != null) {
|
if (oldNode != null) {
|
||||||
|
assert(newNode != null);
|
||||||
|
assert(newNode.isFromOldGeneration);
|
||||||
|
assert(oldNode.isFromOldGeneration);
|
||||||
if (!_canSync(newNode, oldNode)) {
|
if (!_canSync(newNode, oldNode)) {
|
||||||
assert(oldNode.mounted);
|
assert(oldNode.mounted);
|
||||||
// We want to handle the case where there is a removal of zero
|
// We want to handle the case where there is a removal of zero
|
||||||
@ -378,8 +420,7 @@ abstract class Widget {
|
|||||||
// ourselves with a Widget that is a descendant of the
|
// ourselves with a Widget that is a descendant of the
|
||||||
// oldNode, skipping the nodes in between. Let's try that.
|
// oldNode, skipping the nodes in between. Let's try that.
|
||||||
Widget deadNode = oldNode;
|
Widget deadNode = oldNode;
|
||||||
Widget candidate = oldNode.singleChild;
|
Widget candidate = _getCandidateSingleChildFrom(oldNode);
|
||||||
assert(candidate == null || candidate.parent == oldNode);
|
|
||||||
oldNode = null;
|
oldNode = null;
|
||||||
while (candidate != null && _shouldReparentDuringSync) {
|
while (candidate != null && _shouldReparentDuringSync) {
|
||||||
if (_canSync(newNode, candidate)) {
|
if (_canSync(newNode, candidate)) {
|
||||||
@ -389,13 +430,15 @@ abstract class Widget {
|
|||||||
oldNode = candidate;
|
oldNode = candidate;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
assert(candidate.singleChild == null || candidate.singleChild.parent == candidate);
|
candidate = _getCandidateSingleChildFrom(candidate);
|
||||||
candidate = candidate.singleChild;
|
|
||||||
}
|
}
|
||||||
deadNode.detachRenderObject();
|
deadNode.detachRenderObject();
|
||||||
deadNode.remove();
|
deadNode.remove();
|
||||||
}
|
}
|
||||||
if (oldNode != null) {
|
if (oldNode != null) {
|
||||||
|
assert(newNode.isFromOldGeneration);
|
||||||
|
assert(oldNode.isFromOldGeneration);
|
||||||
|
assert(_canSync(newNode, oldNode));
|
||||||
if (oldNode.retainStatefulNodeIfPossible(newNode)) {
|
if (oldNode.retainStatefulNodeIfPossible(newNode)) {
|
||||||
assert(oldNode.mounted);
|
assert(oldNode.mounted);
|
||||||
assert(!newNode.mounted);
|
assert(!newNode.mounted);
|
||||||
@ -410,7 +453,6 @@ abstract class Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert(oldNode == null || (oldNode.mounted == false && oldNode.parent == null));
|
assert(oldNode == null || (oldNode.mounted == false && oldNode.parent == null));
|
||||||
assert(!newNode.mounted);
|
|
||||||
newNode.setParent(this);
|
newNode.setParent(this);
|
||||||
newNode._sync(oldNode, slot);
|
newNode._sync(oldNode, slot);
|
||||||
assert(newNode.renderObject is RenderObject);
|
assert(newNode.renderObject is RenderObject);
|
||||||
@ -439,7 +481,13 @@ abstract class Widget {
|
|||||||
nextPrefix = prefix + ' ';
|
nextPrefix = prefix + ' ';
|
||||||
childrenString += lastChild.toString(nextPrefix, _adjustPrefixWithParentCheck(lastChild, nextStartPrefix));
|
childrenString += lastChild.toString(nextPrefix, _adjustPrefixWithParentCheck(lastChild, nextStartPrefix));
|
||||||
}
|
}
|
||||||
return '$startPrefix${toStringName()}\n$childrenString';
|
String suffix = '';
|
||||||
|
if (_generation != _currentGeneration) {
|
||||||
|
int delta = _generation - _currentGeneration;
|
||||||
|
String sign = delta < 0 ? '' : '+';
|
||||||
|
suffix = ' gen$sign$delta';
|
||||||
|
}
|
||||||
|
return '$startPrefix${toStringName()}$suffix\n$childrenString';
|
||||||
}
|
}
|
||||||
String toStringName() {
|
String toStringName() {
|
||||||
if (key == null)
|
if (key == null)
|
||||||
@ -465,7 +513,9 @@ abstract class Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _canSync(Widget a, Widget b) {
|
bool _canSync(Widget a, Widget b) {
|
||||||
return a.runtimeType == b.runtimeType && a.key == b.key;
|
return a.runtimeType == b.runtimeType &&
|
||||||
|
a.key == b.key &&
|
||||||
|
(a._generation == 0 || b._generation == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -786,6 +836,7 @@ abstract class Component extends Widget {
|
|||||||
void _buildIfDirty() {
|
void _buildIfDirty() {
|
||||||
if (!_dirty || !_mounted)
|
if (!_dirty || !_mounted)
|
||||||
return;
|
return;
|
||||||
|
assert(isFromOldGeneration);
|
||||||
assert(renderObject != null);
|
assert(renderObject != null);
|
||||||
_sync(null, _slot);
|
_sync(null, _slot);
|
||||||
}
|
}
|
||||||
@ -900,7 +951,7 @@ void exitLayoutCallbackBuilder(LayoutCallbackBuilderHandle handle) {
|
|||||||
_inLayoutCallbackBuilder -= 1;
|
_inLayoutCallbackBuilder -= 1;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
Widget._notifyMountStatusChanged();
|
_endSyncPhase();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> _debugFrameTimes = <int>[];
|
List<int> _debugFrameTimes = <int>[];
|
||||||
@ -942,8 +993,7 @@ void _buildDirtyComponents() {
|
|||||||
_inBuildDirtyComponents = false;
|
_inBuildDirtyComponents = false;
|
||||||
sky.tracing.end('Component.flushBuild');
|
sky.tracing.end('Component.flushBuild');
|
||||||
}
|
}
|
||||||
|
_endSyncPhase();
|
||||||
Widget._notifyMountStatusChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_shouldLogRenderDuration) {
|
if (_shouldLogRenderDuration) {
|
||||||
@ -956,6 +1006,12 @@ void _buildDirtyComponents() {
|
|||||||
_debugFrameTimes.clear();
|
_debugFrameTimes.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void _endSyncPhase() {
|
||||||
|
Widget._currentGeneration += 1;
|
||||||
|
Widget._notifyMountStatusChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _scheduleComponentForRender(Component component) {
|
void _scheduleComponentForRender(Component component) {
|
||||||
@ -1111,9 +1167,10 @@ abstract class RenderObjectWrapper extends Widget {
|
|||||||
// top of the lists
|
// top of the lists
|
||||||
while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
|
while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
|
||||||
Widget oldChild = oldChildren[childrenTop];
|
Widget oldChild = oldChildren[childrenTop];
|
||||||
|
if (!oldChild.isFromOldGeneration)
|
||||||
|
break;
|
||||||
assert(oldChild.mounted);
|
assert(oldChild.mounted);
|
||||||
Widget newChild = newChildren[childrenTop];
|
Widget newChild = newChildren[childrenTop];
|
||||||
assert(newChild == oldChild || !newChild.mounted);
|
|
||||||
if (!_canSync(oldChild, newChild))
|
if (!_canSync(oldChild, newChild))
|
||||||
break;
|
break;
|
||||||
childrenTop += 1;
|
childrenTop += 1;
|
||||||
@ -1124,9 +1181,10 @@ abstract class RenderObjectWrapper extends Widget {
|
|||||||
// bottom of the lists
|
// bottom of the lists
|
||||||
while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
|
while ((childrenTop <= oldChildrenBottom) && (childrenTop <= newChildrenBottom)) {
|
||||||
Widget oldChild = oldChildren[oldChildrenBottom];
|
Widget oldChild = oldChildren[oldChildrenBottom];
|
||||||
|
if (!oldChild.isFromOldGeneration)
|
||||||
|
break;
|
||||||
assert(oldChild.mounted);
|
assert(oldChild.mounted);
|
||||||
Widget newChild = newChildren[newChildrenBottom];
|
Widget newChild = newChildren[newChildrenBottom];
|
||||||
assert(newChild == oldChild || !newChild.mounted);
|
|
||||||
if (!_canSync(oldChild, newChild))
|
if (!_canSync(oldChild, newChild))
|
||||||
break;
|
break;
|
||||||
newChild = syncChild(newChild, oldChild, nextSibling);
|
newChild = syncChild(newChild, oldChild, nextSibling);
|
||||||
@ -1145,6 +1203,7 @@ abstract class RenderObjectWrapper extends Widget {
|
|||||||
oldKeyedChildren = new Map<Key, Widget>();
|
oldKeyedChildren = new Map<Key, Widget>();
|
||||||
while (childrenTop <= oldChildrenBottom) {
|
while (childrenTop <= oldChildrenBottom) {
|
||||||
Widget oldChild = oldChildren[oldChildrenBottom];
|
Widget oldChild = oldChildren[oldChildrenBottom];
|
||||||
|
if (oldChild.isFromOldGeneration) {
|
||||||
assert(oldChild.mounted);
|
assert(oldChild.mounted);
|
||||||
if (oldChild.key != null) {
|
if (oldChild.key != null) {
|
||||||
oldKeyedChildren[oldChild.key] = oldChild;
|
oldKeyedChildren[oldChild.key] = oldChild;
|
||||||
@ -1154,6 +1213,7 @@ abstract class RenderObjectWrapper extends Widget {
|
|||||||
oldChildrenBottom -= 1;
|
oldChildrenBottom -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// middle of the lists - new list
|
// middle of the lists - new list
|
||||||
while (childrenTop <= newChildrenBottom) {
|
while (childrenTop <= newChildrenBottom) {
|
||||||
@ -1163,7 +1223,7 @@ abstract class RenderObjectWrapper extends Widget {
|
|||||||
Key key = newChild.key;
|
Key key = newChild.key;
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
oldChild = oldKeyedChildren[newChild.key];
|
oldChild = oldKeyedChildren[newChild.key];
|
||||||
if (oldChild != null) {
|
if (oldChild != null && oldChild.isFromOldGeneration) {
|
||||||
if (oldChild.runtimeType != newChild.runtimeType)
|
if (oldChild.runtimeType != newChild.runtimeType)
|
||||||
oldChild = null;
|
oldChild = null;
|
||||||
oldKeyedChildren.remove(key);
|
oldKeyedChildren.remove(key);
|
||||||
@ -1186,8 +1246,9 @@ abstract class RenderObjectWrapper extends Widget {
|
|||||||
childrenTop -= 1;
|
childrenTop -= 1;
|
||||||
Widget oldChild = oldChildren[childrenTop];
|
Widget oldChild = oldChildren[childrenTop];
|
||||||
assert(oldChild.mounted);
|
assert(oldChild.mounted);
|
||||||
|
assert(oldChild.isFromOldGeneration);
|
||||||
Widget newChild = newChildren[childrenTop];
|
Widget newChild = newChildren[childrenTop];
|
||||||
assert(newChild == oldChild || !newChild.mounted);
|
assert(newChild.isFromOldGeneration);
|
||||||
assert(_canSync(oldChild, newChild));
|
assert(_canSync(oldChild, newChild));
|
||||||
newChild = syncChild(newChild, oldChild, nextSibling);
|
newChild = syncChild(newChild, oldChild, nextSibling);
|
||||||
assert(newChild.mounted);
|
assert(newChild.mounted);
|
||||||
@ -1198,6 +1259,7 @@ abstract class RenderObjectWrapper extends Widget {
|
|||||||
|
|
||||||
if (haveOldNodes && !oldKeyedChildren.isEmpty) {
|
if (haveOldNodes && !oldKeyedChildren.isEmpty) {
|
||||||
for (Widget oldChild in oldKeyedChildren.values)
|
for (Widget oldChild in oldKeyedChildren.values)
|
||||||
|
if (oldChild.isFromOldGeneration)
|
||||||
syncChild(null, oldChild, null);
|
syncChild(null, oldChild, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,9 +201,15 @@ class MixedViewport extends RenderObjectWrapper {
|
|||||||
Widget widget = builder(index);
|
Widget widget = builder(index);
|
||||||
assert(widget != null);
|
assert(widget != null);
|
||||||
assert(widget.key != null);
|
assert(widget.key != null);
|
||||||
|
assert(widget.isFromOldGeneration);
|
||||||
_Key key = new _Key.fromWidget(widget);
|
_Key key = new _Key.fromWidget(widget);
|
||||||
Widget oldWidget = _childrenByKey[key];
|
Widget oldWidget = _childrenByKey[key];
|
||||||
assert(oldWidget != null);
|
assert(oldWidget != null);
|
||||||
|
assert(() {
|
||||||
|
'One of the nodes that was in this MixedViewport was placed in another part of the tree, without the MixedViewport\'s token or builder being changed ' +
|
||||||
|
'and without the MixedViewport\'s MixedViewportLayoutState object being told about that any of the children were invalid.';
|
||||||
|
return oldWidget.isFromOldGeneration;
|
||||||
|
});
|
||||||
assert(oldWidget.renderObject.parent == renderObject);
|
assert(oldWidget.renderObject.parent == renderObject);
|
||||||
widget = syncChild(widget, oldWidget, renderObject.childAfter(oldWidget.renderObject));
|
widget = syncChild(widget, oldWidget, renderObject.childAfter(oldWidget.renderObject));
|
||||||
assert(widget != null);
|
assert(widget != null);
|
||||||
@ -225,8 +231,11 @@ class MixedViewport extends RenderObjectWrapper {
|
|||||||
Widget newWidget = builder(index);
|
Widget newWidget = builder(index);
|
||||||
assert(newWidget != null);
|
assert(newWidget != null);
|
||||||
assert(newWidget.key != null);
|
assert(newWidget.key != null);
|
||||||
|
assert(newWidget.isFromOldGeneration);
|
||||||
final _Key key = new _Key.fromWidget(newWidget);
|
final _Key key = new _Key.fromWidget(newWidget);
|
||||||
Widget oldWidget = _childrenByKey[key];
|
Widget oldWidget = _childrenByKey[key];
|
||||||
|
if (oldWidget != null && !oldWidget.isFromOldGeneration)
|
||||||
|
oldWidget = null;
|
||||||
newWidget = syncChild(newWidget, oldWidget, _omit);
|
newWidget = syncChild(newWidget, oldWidget, _omit);
|
||||||
assert(newWidget != null);
|
assert(newWidget != null);
|
||||||
// Update the offsets based on the newWidget's dimensions.
|
// Update the offsets based on the newWidget's dimensions.
|
||||||
@ -254,6 +263,8 @@ class MixedViewport extends RenderObjectWrapper {
|
|||||||
assert(widget.key != null); // items in lists must have keys
|
assert(widget.key != null); // items in lists must have keys
|
||||||
final _Key key = new _Key.fromWidget(widget);
|
final _Key key = new _Key.fromWidget(widget);
|
||||||
Widget oldWidget = _childrenByKey[key];
|
Widget oldWidget = _childrenByKey[key];
|
||||||
|
if (oldWidget != null && !oldWidget.isFromOldGeneration)
|
||||||
|
oldWidget = null;
|
||||||
widget = syncChild(widget, oldWidget, _omit);
|
widget = syncChild(widget, oldWidget, _omit);
|
||||||
if (index >= offsets.length - 1) {
|
if (index >= offsets.length - 1) {
|
||||||
assert(index == offsets.length - 1);
|
assert(index == offsets.length - 1);
|
||||||
@ -286,6 +297,18 @@ class MixedViewport extends RenderObjectWrapper {
|
|||||||
layoutState._notifyListeners();
|
layoutState._notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _unsyncChild(Widget widget) {
|
||||||
|
assert(!widget.isFromOldGeneration);
|
||||||
|
// The following two lines are the equivalent of "syncChild(null,
|
||||||
|
// widget, null)", but actually doing that wouldn't work because
|
||||||
|
// widget is now from the new generation and so syncChild() would
|
||||||
|
// assume that that means someone else has already sync()ed with it
|
||||||
|
// and that it's wanted. But it's not wanted! We want to get rid of
|
||||||
|
// it. So we do it manually.
|
||||||
|
widget.detachRenderObject();
|
||||||
|
widget.remove();
|
||||||
|
}
|
||||||
|
|
||||||
void _doLayout(BoxConstraints constraints) {
|
void _doLayout(BoxConstraints constraints) {
|
||||||
Map<_Key, Widget> newChildren = new Map<_Key, Widget>();
|
Map<_Key, Widget> newChildren = new Map<_Key, Widget>();
|
||||||
Map<int, Widget> builtChildren = new Map<int, Widget>();
|
Map<int, Widget> builtChildren = new Map<int, Widget>();
|
||||||
@ -334,7 +357,7 @@ class MixedViewport extends RenderObjectWrapper {
|
|||||||
builtChildren[index] = widget;
|
builtChildren[index] = widget;
|
||||||
} else {
|
} else {
|
||||||
childrenByKey.remove(widgetKey);
|
childrenByKey.remove(widgetKey);
|
||||||
syncChild(null, widget, null);
|
_unsyncChild(widget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -378,7 +401,7 @@ class MixedViewport extends RenderObjectWrapper {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
childrenByKey.remove(widgetKey);
|
childrenByKey.remove(widgetKey);
|
||||||
syncChild(null, widget, null);
|
_unsyncChild(widget);
|
||||||
startIndex += 1;
|
startIndex += 1;
|
||||||
assert(startIndex == offsets.length - 1);
|
assert(startIndex == offsets.length - 1);
|
||||||
}
|
}
|
||||||
|
@ -43,4 +43,29 @@ void main() {
|
|||||||
expect(container.renderObject.parentData.bottom, isNull);
|
expect(container.renderObject.parentData.bottom, isNull);
|
||||||
expect(container.renderObject.parentData.left, isNull);
|
expect(container.renderObject.parentData.left, isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Can remove parent data', () {
|
||||||
|
WidgetTester tester = new WidgetTester();
|
||||||
|
Container container;
|
||||||
|
|
||||||
|
tester.pumpFrame(() {
|
||||||
|
container = new Container(width: 10.0, height: 10.0);
|
||||||
|
return new Stack([ new Positioned(left: 10.0, child: container) ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.renderObject.parentData.top, isNull);
|
||||||
|
expect(container.renderObject.parentData.right, isNull);
|
||||||
|
expect(container.renderObject.parentData.bottom, isNull);
|
||||||
|
expect(container.renderObject.parentData.left, equals(10.0));
|
||||||
|
|
||||||
|
tester.pumpFrame(() {
|
||||||
|
return new Stack([ container ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.renderObject.parentData.top, isNull);
|
||||||
|
expect(container.renderObject.parentData.right, isNull);
|
||||||
|
expect(container.renderObject.parentData.bottom, isNull);
|
||||||
|
expect(container.renderObject.parentData.left, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,15 @@ import 'package:test/test.dart';
|
|||||||
import 'widget_tester.dart';
|
import 'widget_tester.dart';
|
||||||
|
|
||||||
class TestState extends StatefulComponent {
|
class TestState extends StatefulComponent {
|
||||||
TestState({this.child, this.state});
|
TestState({ this.child, this.persistentState, this.syncedState });
|
||||||
Widget child;
|
Widget child;
|
||||||
int state;
|
int persistentState;
|
||||||
|
int syncedState;
|
||||||
int syncs = 0;
|
int syncs = 0;
|
||||||
void syncConstructorArguments(TestState source) {
|
void syncConstructorArguments(TestState source) {
|
||||||
child = source.child;
|
child = source.child;
|
||||||
// we explicitly do NOT sync the state from the new instance
|
syncedState = source.syncedState;
|
||||||
|
// we explicitly do NOT sync the persistentState from the new instance
|
||||||
// because we're using that to track whether we got recreated
|
// because we're using that to track whether we got recreated
|
||||||
syncs += 1;
|
syncs += 1;
|
||||||
}
|
}
|
||||||
@ -29,7 +31,7 @@ void main() {
|
|||||||
return new Container(
|
return new Container(
|
||||||
child: new Container(
|
child: new Container(
|
||||||
child: new TestState(
|
child: new TestState(
|
||||||
state: 1,
|
persistentState: 1,
|
||||||
child: new Container()
|
child: new Container()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -38,21 +40,21 @@ void main() {
|
|||||||
|
|
||||||
TestState stateWidget = tester.findWidget((widget) => widget is TestState);
|
TestState stateWidget = tester.findWidget((widget) => widget is TestState);
|
||||||
|
|
||||||
expect(stateWidget.state, equals(1));
|
expect(stateWidget.persistentState, equals(1));
|
||||||
expect(stateWidget.syncs, equals(0));
|
expect(stateWidget.syncs, equals(0));
|
||||||
|
|
||||||
tester.pumpFrame(() {
|
tester.pumpFrame(() {
|
||||||
return new Container(
|
return new Container(
|
||||||
child: new Container(
|
child: new Container(
|
||||||
child: new TestState(
|
child: new TestState(
|
||||||
state: 2,
|
persistentState: 2,
|
||||||
child: new Container()
|
child: new Container()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stateWidget.state, equals(1));
|
expect(stateWidget.persistentState, equals(1));
|
||||||
expect(stateWidget.syncs, equals(1));
|
expect(stateWidget.syncs, equals(1));
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -92,4 +94,94 @@ void main() {
|
|||||||
//
|
//
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
test('swap instances around', () {
|
||||||
|
|
||||||
|
WidgetTester tester = new WidgetTester();
|
||||||
|
|
||||||
|
Widget a, b;
|
||||||
|
tester.pumpFrame(() {
|
||||||
|
a = new TestState(persistentState: 0x61, syncedState: 0x41, child: new Text('apple'));
|
||||||
|
b = new TestState(persistentState: 0x62, syncedState: 0x42, child: new Text('banana'));
|
||||||
|
return new Column([]);
|
||||||
|
});
|
||||||
|
GlobalKey keyA = new GlobalKey();
|
||||||
|
GlobalKey keyB = new GlobalKey();
|
||||||
|
|
||||||
|
TestState foundA, foundB;
|
||||||
|
|
||||||
|
tester.pumpFrame(() {
|
||||||
|
return new Column([
|
||||||
|
new Container(
|
||||||
|
key: keyA,
|
||||||
|
child: a
|
||||||
|
),
|
||||||
|
new Container(
|
||||||
|
key: keyB,
|
||||||
|
child: b
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
foundA = (tester.findWidget((widget) => widget.key == keyA) as Container).child as TestState;
|
||||||
|
foundB = (tester.findWidget((widget) => widget.key == keyB) as Container).child as TestState;
|
||||||
|
|
||||||
|
expect(foundA, equals(a));
|
||||||
|
expect(foundA.persistentState, equals(0x61));
|
||||||
|
expect(foundA.syncedState, equals(0x41));
|
||||||
|
expect(foundB, equals(b));
|
||||||
|
expect(foundB.persistentState, equals(0x62));
|
||||||
|
expect(foundB.syncedState, equals(0x42));
|
||||||
|
|
||||||
|
tester.pumpFrame(() {
|
||||||
|
return new Column([
|
||||||
|
new Container(
|
||||||
|
key: keyA,
|
||||||
|
child: a
|
||||||
|
),
|
||||||
|
new Container(
|
||||||
|
key: keyB,
|
||||||
|
child: b
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
foundA = (tester.findWidget((widget) => widget.key == keyA) as Container).child as TestState;
|
||||||
|
foundB = (tester.findWidget((widget) => widget.key == keyB) as Container).child as TestState;
|
||||||
|
|
||||||
|
// same as before
|
||||||
|
expect(foundA, equals(a));
|
||||||
|
expect(foundA.persistentState, equals(0x61));
|
||||||
|
expect(foundA.syncedState, equals(0x41));
|
||||||
|
expect(foundB, equals(b));
|
||||||
|
expect(foundB.persistentState, equals(0x62));
|
||||||
|
expect(foundB.syncedState, equals(0x42));
|
||||||
|
|
||||||
|
// now we swap the nodes over
|
||||||
|
// since they are both "old" nodes, they shouldn't sync with each other even though they look alike
|
||||||
|
|
||||||
|
tester.pumpFrame(() {
|
||||||
|
return new Column([
|
||||||
|
new Container(
|
||||||
|
key: keyA,
|
||||||
|
child: b
|
||||||
|
),
|
||||||
|
new Container(
|
||||||
|
key: keyB,
|
||||||
|
child: a
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
foundA = (tester.findWidget((widget) => widget.key == keyA) as Container).child as TestState;
|
||||||
|
foundB = (tester.findWidget((widget) => widget.key == keyB) as Container).child as TestState;
|
||||||
|
|
||||||
|
expect(foundA, equals(b));
|
||||||
|
expect(foundA.persistentState, equals(0x62));
|
||||||
|
expect(foundA.syncedState, equals(0x42));
|
||||||
|
expect(foundB, equals(a));
|
||||||
|
expect(foundB.persistentState, equals(0x61));
|
||||||
|
expect(foundB.syncedState, equals(0x41));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user