diff --git a/packages/flutter/lib/src/fn3/binding.dart b/packages/flutter/lib/src/fn3/binding.dart index 58e8168a19..f832aec0cc 100644 --- a/packages/flutter/lib/src/fn3/binding.dart +++ b/packages/flutter/lib/src/fn3/binding.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:sky' as sky; import 'package:sky/animation.dart'; import 'package:sky/rendering.dart'; import 'package:sky/src/fn3/framework.dart'; @@ -156,8 +155,12 @@ class RenderObjectToWidgetElement extends RenderObjectEl renderObject.child = child; } + void moveChildRenderObject(RenderObject child, dynamic slot) { + assert(false); + } + void removeChildRenderObject(RenderObject child) { assert(renderObject.child == child); renderObject.child = null; } -} \ No newline at end of file +} diff --git a/packages/flutter/lib/src/fn3/framework.dart b/packages/flutter/lib/src/fn3/framework.dart index 62198503ed..aeea2f1e09 100644 --- a/packages/flutter/lib/src/fn3/framework.dart +++ b/packages/flutter/lib/src/fn3/framework.dart @@ -361,6 +361,8 @@ abstract class ParentDataWidget extends ProxyWidget { ParentDataWidget({ Key key, Widget child }) : super(key: key, child: child); + ParentDataElement createElement() => new ParentDataElement(this); + /// Subclasses should override this function to ensure that they are placed /// inside widgets that expect them. /// @@ -600,6 +602,11 @@ abstract class Element implements BuildContext { typedef Widget WidgetBuilder(BuildContext context); typedef void BuildScheduler(BuildableElement element); +class ErrorWidget extends LeafRenderObjectWidget { + RenderBox createRenderObject() => new RenderErrorBox(); + void updateRenderObject(RenderObject renderObject, RenderObjectWidget oldWidget) { } +} + /// Base class for the instantiation of StatelessComponent and StatefulComponent /// widgets. abstract class BuildableElement extends Element { @@ -637,8 +644,17 @@ abstract class BuildableElement extends Element { assert(built != null); } catch (e, stack) { _debugReportException('building $this', e, stack); + built = new ErrorWidget(); + } + + try { + _child = updateChild(_child, built, _slot); + assert(_child != null); + } catch (e, stack) { + _debugReportException('building $this', e, stack); + built = new ErrorWidget(); + _child = updateChild(null, built, _slot); } - _child = updateChild(_child, built, _slot); } static BuildScheduler scheduleBuildFor; @@ -721,6 +737,23 @@ class StatefulComponentElement extends BuildableElement { class ParentDataElement extends StatelessComponentElement { ParentDataElement(ParentDataWidget widget) : super(widget); + void mount(Element parent, dynamic slot) { + assert(() { + Element ancestor = parent; + while (ancestor is! RenderObjectElement) { + assert(ancestor != null); + assert(() { + 'You cannot nest parent data widgets inside one another.'; + return ancestor is! ParentDataElement; + }); + ancestor = ancestor._parent; + } + _widget.debugValidateAncestor(ancestor._widget); + return true; + }); + super.mount(parent, slot); + } + void update(ParentDataWidget newWidget) { ParentDataWidget oldWidget = widget; super.update(newWidget); @@ -813,10 +846,6 @@ abstract class RenderObjectElement extends Element } void updateParentData(ParentDataWidget parentData) { - assert(() { - parentData.debugValidateAncestor(_ancestorRenderObjectElement.widget); - return true; - }); parentData.applyParentData(renderObject); } @@ -1125,12 +1154,14 @@ typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTra /// [debugDumpApp()]. WidgetsExceptionHandler debugWidgetsExceptionHandler; void _debugReportException(String context, dynamic exception, StackTrace stack) { - print('------------------------------------------------------------------------'); - 'Exception caught while $context'.split('\n').forEach(print); - print('$exception'); - print('Stack trace:'); - '$stack'.split('\n').forEach(print); - if (debugWidgetsExceptionHandler != null) + if (debugWidgetsExceptionHandler != null) { debugWidgetsExceptionHandler(context, exception, stack); - print('------------------------------------------------------------------------'); + } else { + print('------------------------------------------------------------------------'); + 'Exception caught while $context'.split('\n').forEach(print); + print('$exception'); + print('Stack trace:'); + '$stack'.split('\n').forEach(print); + print('------------------------------------------------------------------------'); + } } diff --git a/packages/unit/test/fn3/parent_data_test.dart b/packages/unit/test/fn3/parent_data_test.dart new file mode 100644 index 0000000000..9ee52c3013 --- /dev/null +++ b/packages/unit/test/fn3/parent_data_test.dart @@ -0,0 +1,287 @@ +import 'package:sky/rendering.dart'; +import 'package:sky/src/fn3.dart'; +import 'package:test/test.dart'; + +import 'test_widgets.dart'; +import 'widget_tester.dart'; + +class TestParentData { + TestParentData({ this.top, this.right, this.bottom, this.left }); + + final double top; + final double right; + final double bottom; + final double left; +} + +void checkTree(WidgetTester tester, List expectedParentData) { + 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 (TestParentData expected in expectedParentData) { + expect(child is RenderDecoratedBox, isTrue); + RenderDecoratedBox decoratedBox = child; + expect(decoratedBox.parentData is StackParentData, isTrue); + StackParentData parentData = decoratedBox.parentData; + expect(parentData.top, equals(expected.top)); + expect(parentData.right, equals(expected.right)); + expect(parentData.bottom, equals(expected.bottom)); + expect(parentData.left, equals(expected.left)); + child = decoratedBox.parentData.nextSibling; + } + expect(child, isNull); + } catch (e) { + print(renderObject.toStringDeep()); + rethrow; + } +} + +final TestParentData kNonPositioned = new TestParentData(); + +void main() { + dynamic cachedException; + + setUp(() { + assert(cachedException == null); + debugWidgetsExceptionHandler = (String context, dynamic exception, StackTrace stack) { + cachedException = exception; + }; + }); + + tearDown(() { + cachedException = null; + debugWidgetsExceptionHandler = null; + }); + + test('ParentDataWidget control test', () { + WidgetTester tester = new WidgetTester(); + + tester.pumpFrame( + new Stack([ + new DecoratedBox(decoration: kBoxDecorationA), + new Positioned( + top: 10.0, + left: 10.0, + child: new DecoratedBox(decoration: kBoxDecorationB) + ), + new DecoratedBox(decoration: kBoxDecorationC), + ]) + ); + + checkTree(tester, [ + kNonPositioned, + new TestParentData(top: 10.0, left: 10.0), + kNonPositioned, + ]); + + tester.pumpFrame( + new Stack([ + new Positioned( + bottom: 5.0, + right: 7.0, + child: new DecoratedBox(decoration: kBoxDecorationA) + ), + new Positioned( + top: 10.0, + left: 10.0, + child: new DecoratedBox(decoration: kBoxDecorationB) + ), + new DecoratedBox(decoration: kBoxDecorationC), + ]) + ); + + checkTree(tester, [ + new TestParentData(bottom: 5.0, right: 7.0), + new TestParentData(top: 10.0, left: 10.0), + kNonPositioned, + ]); + + DecoratedBox kDecoratedBoxA = new DecoratedBox(decoration: kBoxDecorationA); + DecoratedBox kDecoratedBoxB = new DecoratedBox(decoration: kBoxDecorationB); + DecoratedBox kDecoratedBoxC = new DecoratedBox(decoration: kBoxDecorationC); + + tester.pumpFrame( + new Stack([ + new Positioned( + bottom: 5.0, + right: 7.0, + child: kDecoratedBoxA + ), + new Positioned( + top: 10.0, + left: 10.0, + child: kDecoratedBoxB + ), + kDecoratedBoxC, + ]) + ); + + checkTree(tester, [ + new TestParentData(bottom: 5.0, right: 7.0), + new TestParentData(top: 10.0, left: 10.0), + kNonPositioned, + ]); + + tester.pumpFrame( + new Stack([ + new Positioned( + bottom: 6.0, + right: 8.0, + child: kDecoratedBoxA + ), + new Positioned( + left: 10.0, + right: 10.0, + child: kDecoratedBoxB + ), + kDecoratedBoxC, + ]) + ); + + checkTree(tester, [ + new TestParentData(bottom: 6.0, right: 8.0), + new TestParentData(left: 10.0, right: 10.0), + kNonPositioned, + ]); + + tester.pumpFrame( + new Stack([ + kDecoratedBoxA, + new Positioned( + left: 11.0, + right: 12.0, + child: new Container(child: kDecoratedBoxB) + ), + kDecoratedBoxC, + ]) + ); + + checkTree(tester, [ + kNonPositioned, + new TestParentData(left: 11.0, right: 12.0), + kNonPositioned, + ]); + + tester.pumpFrame( + new Stack([ + kDecoratedBoxA, + new Positioned( + right: 10.0, + child: new Container(child: kDecoratedBoxB) + ), + new Container( + child: new Positioned( + top: 8.0, + child: kDecoratedBoxC + ) + ) + ]) + ); + + checkTree(tester, [ + kNonPositioned, + new TestParentData(right: 10.0), + new TestParentData(top: 8.0), + ]); + + tester.pumpFrame( + new Stack([ + new Positioned( + right: 10.0, + child: new FlipComponent(left: kDecoratedBoxA, right: kDecoratedBoxB) + ), + ]) + ); + + checkTree(tester, [ + new TestParentData(right: 10.0), + ]); + + flipStatefulComponent(tester); + tester.pumpFrameWithoutChange(); + + checkTree(tester, [ + new TestParentData(right: 10.0), + ]); + + tester.pumpFrame( + new Stack([ + new Positioned( + top: 7.0, + child: new FlipComponent(left: kDecoratedBoxA, right: kDecoratedBoxB) + ), + ]) + ); + + checkTree(tester, [ + new TestParentData(top: 7.0), + ]); + + flipStatefulComponent(tester); + tester.pumpFrameWithoutChange(); + + checkTree(tester, [ + new TestParentData(top: 7.0), + ]); + + tester.pumpFrame( + new Stack([]) + ); + + checkTree(tester, []); + }); + + test('ParentDataWidget conflicting data', () { + WidgetTester tester = new WidgetTester(); + + expect(cachedException, isNull); + + tester.pumpFrame( + new Stack([ + new Positioned( + top: 5.0, + bottom: 8.0, + child: new Positioned( + top: 6.0, + left: 7.0, + child: new DecoratedBox(decoration: kBoxDecorationB) + ) + ) + ]) + ); + + expect(cachedException, isNotNull); + cachedException = null; + + tester.pumpFrame(new Stack([])); + + checkTree(tester, []); + expect(cachedException, isNull); + + tester.pumpFrame( + new Container( + child: new Flex([ + new Positioned( + top: 6.0, + left: 7.0, + child: new DecoratedBox(decoration: kBoxDecorationB) + ) + ]) + ) + ); + + expect(cachedException, isNotNull); + cachedException = null; + + tester.pumpFrame( + new Stack([]) + ); + + checkTree(tester, []); + }); + +}