Commit 8b162b59 authored by Adam Barth's avatar Adam Barth

Add tests for MultiChildRenderWidget

parent 436fb0ee
......@@ -775,7 +775,7 @@ class ImageListenerState extends ComponentState<ImageListener> {
}
}
Widget build() {
Widget build(BuildContext context) {
return new Image(
image: _resolvedImage,
width: config.width,
......@@ -805,7 +805,7 @@ class NetworkImage extends StatelessComponent {
final ImageFit fit;
final ImageRepeat repeat;
Widget build() {
Widget build(BuildContext context) {
return new ImageListener(
image: imageCache.load(src),
width: width,
......@@ -837,7 +837,7 @@ class AssetImage extends StatelessComponent {
final ImageFit fit;
final ImageRepeat repeat;
Widget build() {
Widget build(BuildContext context) {
return new ImageListener(
image: bundle.loadImage(name),
width: width,
......
......@@ -271,11 +271,11 @@ abstract class Element<T extends Widget> implements BuildContext {
/// Calls the argument for each descendant, depth-first pre-order.
void visitDescendants(ElementVisitor visitor) {
void walk(Element element) {
void visit(Element element) {
visitor(element);
element.visitChildren(walk);
element.visitChildren(visit);
}
visitChildren(walk);
visitChildren(visit);
}
Element _updateChild(Element child, Widget newWidget, dynamic slot) {
......@@ -301,17 +301,20 @@ abstract class Element<T extends Widget> implements BuildContext {
// The _updateChild() method returns the new child, if it had to create one,
// or the child that was passed in, if it just had to update the child, or
// null, if it removed the child and did not replace it.
assert(slot != null);
if (newWidget == null) {
if (child != null)
_detachChild(child);
return null;
}
if (child != null) {
assert(child._slot == slot);
if (child._widget == newWidget)
if (child._widget == newWidget) {
if (child._slot != slot)
updateSlotForChild(child, slot);
return child;
}
if (_canUpdate(child._widget, newWidget)) {
if (child._slot != slot)
updateSlotForChild(child, slot);
child.update(newWidget);
assert(child._widget == newWidget);
return child;
......@@ -334,7 +337,6 @@ abstract class Element<T extends Widget> implements BuildContext {
assert(_parent == null);
assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.mounted);
assert(_slot == null);
assert(slot != null);
assert(_depth == null);
_parent = parent;
_slot = slot;
......@@ -347,7 +349,6 @@ abstract class Element<T extends Widget> implements BuildContext {
assert(_debugLifecycleState == _ElementLifecycle.mounted);
assert(_widget != null);
assert(newWidget != null);
assert(_slot != null);
assert(_depth != null);
assert(_canUpdate(_widget, newWidget));
_widget = newWidget;
......@@ -360,14 +361,12 @@ abstract class Element<T extends Widget> implements BuildContext {
assert(_debugLifecycleState == _ElementLifecycle.mounted);
assert(child != null);
assert(child._parent == this);
void move(Element element) {
child._updateSlot(slot);
if (child is! RenderObjectElement)
child.visitChildren(move);
void visit(Element element) {
element._updateSlot(slot);
if (element is! RenderObjectElement)
element.visitChildren(visit);
}
move(child);
visit(child);
}
void _updateSlot(dynamic slot) {
......@@ -375,9 +374,7 @@ abstract class Element<T extends Widget> implements BuildContext {
assert(_widget != null);
assert(_parent != null);
assert(_parent._debugLifecycleState == _ElementLifecycle.mounted);
assert(_slot != null);
assert(slot != null);
assert(_depth == null);
assert(_depth != null);
_slot = slot;
}
......@@ -665,6 +662,13 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
parentData.applyParentData(renderObject);
}
void _updateSlot(dynamic slot) {
assert(_slot != slot);
super._updateSlot(slot);
assert(_slot == slot);
_ancestorRenderObjectElement.moveChildRenderObject(renderObject, _slot);
}
void detachRenderObject() {
if (_ancestorRenderObjectElement != null) {
_ancestorRenderObjectElement.removeChildRenderObject(renderObject);
......@@ -673,7 +677,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
}
void insertChildRenderObject(RenderObject child, dynamic slot);
void moveChildRenderObject(RenderObject child, dynamic slot);
void removeChildRenderObject(RenderObject child);
}
......@@ -685,6 +689,10 @@ class LeafRenderObjectElement<T extends RenderObjectWidget> extends RenderObject
assert(false);
}
void moveChildRenderObject(RenderObject child, dynamic slot) {
assert(false);
}
void removeChildRenderObject(RenderObject child) {
assert(false);
}
......@@ -720,6 +728,10 @@ class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends
assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
}
void moveChildRenderObject(RenderObject child, dynamic slot) {
assert(false);
}
void removeChildRenderObject(RenderObject child) {
final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
assert(renderObject is RenderObjectWithChildMixin);
......@@ -739,13 +751,20 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
void insertChildRenderObject(RenderObject child, Element slot) {
final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
RenderObject nextSibling = slot._descendantRenderObject;
assert(nextSibling == null || nextSibling is RenderObject);
RenderObject nextSibling = slot?._descendantRenderObject;
assert(renderObject is ContainerRenderObjectMixin);
renderObject.add(child, before: nextSibling);
assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
}
void moveChildRenderObject(RenderObject child, dynamic slot) {
final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
RenderObject nextSibling = slot?._descendantRenderObject;
assert(renderObject is ContainerRenderObjectMixin);
renderObject.move(child, before: nextSibling);
assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
}
void removeChildRenderObject(RenderObject child) {
final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
assert(renderObject is ContainerRenderObjectMixin);
......@@ -777,7 +796,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
super.mount(parent, slot);
_children = new List<Element>(_widget.children.length);
Element previousChild;
for (int i = 0; i < _children.length; ++i) {
for (int i = _children.length - 1; i >= 0; --i) {
Element newChild = _widget.children[i].createElement();
newChild.mount(this, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.mounted);
......@@ -874,6 +893,8 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
assert(oldChild._debugLifecycleState == _ElementLifecycle.mounted);
if (oldChild._widget.key != null)
oldKeyedChildren[oldChild._widget.key] = oldChild;
else
_detachChild(oldChild);
oldChildrenBottom -= 1;
}
}
......@@ -926,7 +947,7 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
// clean up any of the remaining middle nodes from the old list
if (haveOldNodes && !oldKeyedChildren.isEmpty) {
for (Element oldChild in oldKeyedChildren.values)
oldChild.unmount();
_detachChild(oldChild);
}
assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
......
import 'package:sky/rendering.dart';
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import 'test_widgets.dart';
import 'widget_tester.dart';
void checkTree(WidgetTester tester, List<BoxDecoration> expectedDecorations) {
MultiChildRenderObjectElement element =
tester.findElement((element) => element is MultiChildRenderObjectElement);
expect(element, isNotNull);
expect(element.renderObject is RenderStack, isTrue);
RenderStack renderObject = element.renderObject;
try {
RenderObject child = renderObject.firstChild;
for (BoxDecoration decoration in expectedDecorations) {
expect(child is RenderDecoratedBox, isTrue);
RenderDecoratedBox decoratedBox = child;
expect(decoratedBox.decoration, equals(decoration));
child = decoratedBox.parentData.nextSibling;
}
expect(child, isNull);
} catch (e) {
print(renderObject.toStringDeep());
rethrow;
}
}
void main() {
test('MultiChildRenderObjectElement control test', () {
WidgetTester tester = new WidgetTester();
tester.pumpFrame(
new Stack([
new DecoratedBox(decoration: kBoxDecorationA),
new DecoratedBox(decoration: kBoxDecorationB),
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationB, kBoxDecorationC]);
tester.pumpFrame(
new Stack([
new DecoratedBox(decoration: kBoxDecorationA),
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationC]);
tester.pumpFrame(
new Stack([
new DecoratedBox(decoration: kBoxDecorationA),
new DecoratedBox(key: new Key('b'), decoration: kBoxDecorationB),
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationB, kBoxDecorationC]);
tester.pumpFrame(
new Stack([
new DecoratedBox(key: new Key('b'), decoration: kBoxDecorationB),
new DecoratedBox(decoration: kBoxDecorationC),
new DecoratedBox(key: new Key('a'), decoration: kBoxDecorationA),
])
);
checkTree(tester, [kBoxDecorationB, kBoxDecorationC, kBoxDecorationA]);
tester.pumpFrame(
new Stack([
new DecoratedBox(key: new Key('a'), decoration: kBoxDecorationA),
new DecoratedBox(decoration: kBoxDecorationC),
new DecoratedBox(key: new Key('b'), decoration: kBoxDecorationB),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationC, kBoxDecorationB]);
tester.pumpFrame(
new Stack([
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationC]);
tester.pumpFrame(
new Stack([])
);
checkTree(tester, []);
});
test('MultiChildRenderObjectElement with stateless components', () {
WidgetTester tester = new WidgetTester();
tester.pumpFrame(
new Stack([
new DecoratedBox(decoration: kBoxDecorationA),
new DecoratedBox(decoration: kBoxDecorationB),
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationB, kBoxDecorationC]);
tester.pumpFrame(
new Stack([
new DecoratedBox(decoration: kBoxDecorationA),
new Container(
child: new DecoratedBox(decoration: kBoxDecorationB)
),
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationB, kBoxDecorationC]);
tester.pumpFrame(
new Stack([
new DecoratedBox(decoration: kBoxDecorationA),
new Container(
child: new Container(
child: new DecoratedBox(decoration: kBoxDecorationB)
)
),
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationB, kBoxDecorationC]);
tester.pumpFrame(
new Stack([
new Container(
child: new Container(
child: new DecoratedBox(decoration: kBoxDecorationB)
)
),
new Container(
child: new DecoratedBox(decoration: kBoxDecorationA)
),
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationB, kBoxDecorationA, kBoxDecorationC]);
tester.pumpFrame(
new Stack([
new Container(
child: new DecoratedBox(decoration: kBoxDecorationB)
),
new Container(
child: new DecoratedBox(decoration: kBoxDecorationA)
),
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationB, kBoxDecorationA, kBoxDecorationC]);
tester.pumpFrame(
new Stack([
new Container(
key: new Key('b'),
child: new DecoratedBox(decoration: kBoxDecorationB)
),
new Container(
key: new Key('a'),
child: new DecoratedBox(decoration: kBoxDecorationA)
),
])
);
checkTree(tester, [kBoxDecorationB, kBoxDecorationA]);
tester.pumpFrame(
new Stack([
new Container(
key: new Key('a'),
child: new DecoratedBox(decoration: kBoxDecorationA)
),
new Container(
key: new Key('b'),
child: new DecoratedBox(decoration: kBoxDecorationB)
),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationB]);
tester.pumpFrame(
new Stack([ ])
);
checkTree(tester, []);
});
test('MultiChildRenderObjectElement with stateful components', () {
WidgetTester tester = new WidgetTester();
tester.pumpFrame(
new Stack([
new DecoratedBox(decoration: kBoxDecorationA),
new DecoratedBox(decoration: kBoxDecorationB),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationB]);
tester.pumpFrame(
new Stack([
new FlipComponent(
left: new DecoratedBox(decoration: kBoxDecorationA),
right: new DecoratedBox(decoration: kBoxDecorationB)
),
new DecoratedBox(decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationA, kBoxDecorationC]);
flipStatefulComponent(tester);
tester.pumpFrameWithoutChange();
checkTree(tester, [kBoxDecorationB, kBoxDecorationC]);
tester.pumpFrame(
new Stack([
new FlipComponent(
left: new DecoratedBox(decoration: kBoxDecorationA),
right: new DecoratedBox(decoration: kBoxDecorationB)
),
])
);
checkTree(tester, [kBoxDecorationB]);
flipStatefulComponent(tester);
tester.pumpFrameWithoutChange();
checkTree(tester, [kBoxDecorationA]);
tester.pumpFrame(
new Stack([
new FlipComponent(
key: new Key('flip'),
left: new DecoratedBox(decoration: kBoxDecorationA),
right: new DecoratedBox(decoration: kBoxDecorationB)
),
])
);
tester.pumpFrame(
new Stack([
new DecoratedBox(key: new Key('c'), decoration: kBoxDecorationC),
new FlipComponent(
key: new Key('flip'),
left: new DecoratedBox(decoration: kBoxDecorationA),
right: new DecoratedBox(decoration: kBoxDecorationB)
),
])
);
checkTree(tester, [kBoxDecorationC, kBoxDecorationA]);
flipStatefulComponent(tester);
tester.pumpFrameWithoutChange();
checkTree(tester, [kBoxDecorationC, kBoxDecorationB]);
tester.pumpFrame(
new Stack([
new FlipComponent(
key: new Key('flip'),
left: new DecoratedBox(decoration: kBoxDecorationA),
right: new DecoratedBox(decoration: kBoxDecorationB)
),
new DecoratedBox(key: new Key('c'), decoration: kBoxDecorationC),
])
);
checkTree(tester, [kBoxDecorationB, kBoxDecorationC]);
});
}
......@@ -3,48 +3,7 @@ import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
class TestComponentConfig extends StatefulComponent {
TestComponentConfig({ this.left, this.right });
final Widget left;
final Widget right;
TestComponentState createState() => new TestComponentState(this);
}
class TestComponentState extends ComponentState<TestComponentConfig> {
TestComponentState(TestComponentConfig config): super(config);
bool _showLeft = true;
void flip() {
setState(() {
_showLeft = !_showLeft;
});
}
Widget build(BuildContext context) {
return _showLeft ? config.left : config.right;
}
}
final BoxDecoration kBoxDecorationA = new BoxDecoration();
final BoxDecoration kBoxDecorationB = new BoxDecoration();
class TestBuildCounter extends StatelessComponent {
static int buildCount = 0;
Widget build(BuildContext context) {
++buildCount;
return new DecoratedBox(decoration: kBoxDecorationA);
}
}
void flipStatefulComponent(WidgetTester tester) {
StatefulComponentElement stateElement =
tester.findElement((element) => element is StatefulComponentElement);
(stateElement.state as TestComponentState).flip();
}
import 'test_widgets.dart';
void main() {
test('Stateful component smoke test', () {
......@@ -60,7 +19,7 @@ void main() {
}
tester.pumpFrame(
new TestComponentConfig(
new FlipComponent(
left: new DecoratedBox(decoration: kBoxDecorationA),
right: new DecoratedBox(decoration: kBoxDecorationB)
)
......@@ -69,7 +28,7 @@ void main() {
checkTree(kBoxDecorationA);
tester.pumpFrame(
new TestComponentConfig(
new FlipComponent(
left: new DecoratedBox(decoration: kBoxDecorationB),
right: new DecoratedBox(decoration: kBoxDecorationA)
)
......@@ -84,7 +43,7 @@ void main() {
checkTree(kBoxDecorationA);
tester.pumpFrame(
new TestComponentConfig(
new FlipComponent(
left: new DecoratedBox(decoration: kBoxDecorationA),
right: new DecoratedBox(decoration: kBoxDecorationB)
)
......@@ -97,7 +56,7 @@ void main() {
test('Don\'t rebuild subcomponents', () {
WidgetTester tester = new WidgetTester();
tester.pumpFrame(
new TestComponentConfig(
new FlipComponent(
left: new TestBuildCounter(),
right: new DecoratedBox(decoration: kBoxDecorationB)
)
......
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
final BoxDecoration kBoxDecorationA = new BoxDecoration(
backgroundColor: const Color(0xFFFF0000)
);
final BoxDecoration kBoxDecorationB = new BoxDecoration(
backgroundColor: const Color(0xFF00FF00)
);
final BoxDecoration kBoxDecorationC = new BoxDecoration(
backgroundColor: const Color(0xFF0000FF)
);
class FlipComponent extends StatefulComponent {
FlipComponent({ Key key, this.left, this.right }) : super(key: key);
final Widget left;
final Widget right;
FlipComponentState createState() => new FlipComponentState(this);
}
class FlipComponentState extends ComponentState<FlipComponent> {
FlipComponentState(FlipComponent config): super(config);
bool _showLeft = true;
void flip() {
setState(() {
_showLeft = !_showLeft;
});
}
Widget build(BuildContext context) {
return _showLeft ? config.left : config.right;
}
}
class TestBuildCounter extends StatelessComponent {
static int buildCount = 0;
Widget build(BuildContext context) {
++buildCount;
return new DecoratedBox(decoration: kBoxDecorationA);
}
}
void flipStatefulComponent(WidgetTester tester) {
StatefulComponentElement stateElement =
tester.findElement((element) => element is StatefulComponentElement);
expect(stateElement, isNotNull);
expect(stateElement.state is FlipComponentState, isTrue);
FlipComponentState state = stateElement.state;
state.flip();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment