// 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 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { FlutterError.presentError = (FlutterErrorDetails details) { // Make tests fail on FlutterErrors. throw details.exception; }; test('onNeedVisualUpdate takes precedence over manifold', () { final TestPipelineManifold manifold = TestPipelineManifold(); int rootOnNeedVisualUpdateCallCount = 0; final TestRenderObject rootRenderObject = TestRenderObject(); final PipelineOwner root = PipelineOwner( onNeedVisualUpdate: () { rootOnNeedVisualUpdateCallCount += 1; }, ); root.rootNode = rootRenderObject; rootRenderObject.scheduleInitialLayout(); int child1OnNeedVisualUpdateCallCount = 0; final TestRenderObject child1RenderObject = TestRenderObject(); final PipelineOwner child1 = PipelineOwner( onNeedVisualUpdate: () { child1OnNeedVisualUpdateCallCount += 1; }, ); child1.rootNode = child1RenderObject; child1RenderObject.scheduleInitialLayout(); final TestRenderObject child2RenderObject = TestRenderObject(); final PipelineOwner child2 = PipelineOwner(); child2.rootNode = child2RenderObject; child2RenderObject.scheduleInitialLayout(); root.adoptChild(child1); root.adoptChild(child2); root.attach(manifold); root.flushLayout(); manifold.requestVisualUpdateCount = 0; rootRenderObject.markNeedsLayout(); expect(manifold.requestVisualUpdateCount, 0); expect(rootOnNeedVisualUpdateCallCount, 1); expect(child1OnNeedVisualUpdateCallCount, 0); child1RenderObject.markNeedsLayout(); expect(manifold.requestVisualUpdateCount, 0); expect(rootOnNeedVisualUpdateCallCount, 1); expect(child1OnNeedVisualUpdateCallCount, 1); child2RenderObject.markNeedsLayout(); expect(manifold.requestVisualUpdateCount, 1); expect(rootOnNeedVisualUpdateCallCount, 1); expect(child1OnNeedVisualUpdateCallCount, 1); }); test("parent's render objects are laid out before child's render objects", () { final TestPipelineManifold manifold = TestPipelineManifold(); final List<String> log = <String>[]; final TestRenderObject rootRenderObject = TestRenderObject( onLayout: () { log.add('layout parent'); }, ); final PipelineOwner root = PipelineOwner(); root.rootNode = rootRenderObject; rootRenderObject.scheduleInitialLayout(); final TestRenderObject childRenderObject = TestRenderObject( onLayout: () { log.add('layout child'); }, ); final PipelineOwner child = PipelineOwner(); child.rootNode = childRenderObject; childRenderObject.scheduleInitialLayout(); root.adoptChild(child); root.attach(manifold); expect(log, isEmpty); root.flushLayout(); expect(log, <String>['layout parent', 'layout child']); }); test("child cannot dirty parent's render object during flushLayout", () { final TestPipelineManifold manifold = TestPipelineManifold(); final TestRenderObject rootRenderObject = TestRenderObject(); final PipelineOwner root = PipelineOwner(); root.rootNode = rootRenderObject; rootRenderObject.scheduleInitialLayout(); bool childLayoutExecuted = false; final TestRenderObject childRenderObject = TestRenderObject( onLayout: () { childLayoutExecuted = true; expect(() => rootRenderObject.markNeedsLayout(), throwsFlutterError); }, ); final PipelineOwner child = PipelineOwner(); child.rootNode = childRenderObject; childRenderObject.scheduleInitialLayout(); root.adoptChild(child); root.attach(manifold); root.flushLayout(); expect(childLayoutExecuted, isTrue); }); test('updates compositing bits on children', () { final TestPipelineManifold manifold = TestPipelineManifold(); final TestRenderObject rootRenderObject = TestRenderObject(); final PipelineOwner root = PipelineOwner(); root.rootNode = rootRenderObject; rootRenderObject.markNeedsCompositingBitsUpdate(); final TestRenderObject childRenderObject = TestRenderObject(); final PipelineOwner child = PipelineOwner(); child.rootNode = childRenderObject; childRenderObject.markNeedsCompositingBitsUpdate(); root.adoptChild(child); root.attach(manifold); expect(() => rootRenderObject.needsCompositing, throwsAssertionError); expect(() => childRenderObject.needsCompositing, throwsAssertionError); root.flushCompositingBits(); expect(rootRenderObject.needsCompositing, isTrue); expect(childRenderObject.needsCompositing, isTrue); }); test("parent's render objects are painted before child's render objects", () { final TestPipelineManifold manifold = TestPipelineManifold(); final List<String> log = <String>[]; final TestRenderObject rootRenderObject = TestRenderObject( onPaint: () { log.add('paint parent'); }, ); final PipelineOwner root = PipelineOwner(); root.rootNode = rootRenderObject; final OffsetLayer rootLayer = OffsetLayer(); rootLayer.attach(rootRenderObject); rootRenderObject.scheduleInitialLayout(); rootRenderObject.scheduleInitialPaint(rootLayer); final TestRenderObject childRenderObject = TestRenderObject( onPaint: () { log.add('paint child'); }, ); final PipelineOwner child = PipelineOwner(); child.rootNode = childRenderObject; final OffsetLayer childLayer = OffsetLayer(); childLayer.attach(childRenderObject); childRenderObject.scheduleInitialLayout(); childRenderObject.scheduleInitialPaint(childLayer); root.adoptChild(child); root.attach(manifold); root.flushLayout(); // Can't paint with invalid layout. expect(log, isEmpty); root.flushPaint(); expect(log, <String>['paint parent', 'paint child']); }); test("child paint cannot dirty parent's render object", () { final TestPipelineManifold manifold = TestPipelineManifold(); final TestRenderObject rootRenderObject = TestRenderObject(); final PipelineOwner root = PipelineOwner(); root.rootNode = rootRenderObject; final OffsetLayer rootLayer = OffsetLayer(); rootLayer.attach(rootRenderObject); rootRenderObject.scheduleInitialLayout(); rootRenderObject.scheduleInitialPaint(rootLayer); bool childPaintExecuted = false; final TestRenderObject childRenderObject = TestRenderObject( onPaint: () { childPaintExecuted = true; expect(() => rootRenderObject.markNeedsPaint(), throwsAssertionError); }, ); final PipelineOwner child = PipelineOwner(); child.rootNode = childRenderObject; final OffsetLayer childLayer = OffsetLayer(); childLayer.attach(childRenderObject); childRenderObject.scheduleInitialLayout(); childRenderObject.scheduleInitialPaint(childLayer); root.adoptChild(child); root.attach(manifold); root.flushLayout(); // Can't paint with invalid layout. root.flushPaint(); expect(childPaintExecuted, isTrue); }); test("parent's render objects do semantics before child's render objects", () { final TestPipelineManifold manifold = TestPipelineManifold() ..semanticsEnabled = true; final List<String> log = <String>[]; final TestRenderObject rootRenderObject = TestRenderObject( onSemantics: () { log.add('semantics parent'); }, ); final PipelineOwner root = PipelineOwner( onSemanticsOwnerCreated: () { rootRenderObject.scheduleInitialSemantics(); }, onSemanticsUpdate: (SemanticsUpdate update) { }, ); root.rootNode = rootRenderObject; final TestRenderObject childRenderObject = TestRenderObject( onSemantics: () { log.add('semantics child'); }, ); final PipelineOwner child = PipelineOwner( onSemanticsOwnerCreated: () { childRenderObject.scheduleInitialSemantics(); }, onSemanticsUpdate: (SemanticsUpdate update) { }, ); child.rootNode = childRenderObject; root.adoptChild(child); root.attach(manifold); log.clear(); rootRenderObject.markNeedsSemanticsUpdate(); childRenderObject.markNeedsSemanticsUpdate(); root.flushSemantics(); expect(log, <String>['semantics parent', 'semantics child']); }); test("child cannot mark parent's render object dirty during flushSemantics", () { final TestPipelineManifold manifold = TestPipelineManifold() ..semanticsEnabled = true; final TestRenderObject rootRenderObject = TestRenderObject(); final PipelineOwner root = PipelineOwner( onSemanticsOwnerCreated: () { rootRenderObject.scheduleInitialSemantics(); }, onSemanticsUpdate: (SemanticsUpdate update) { }, ); root.rootNode = rootRenderObject; bool childSemanticsCalled = false; final TestRenderObject childRenderObject = TestRenderObject( onSemantics: () { childSemanticsCalled = true; rootRenderObject.markNeedsSemanticsUpdate(); }, ); final PipelineOwner child = PipelineOwner( onSemanticsOwnerCreated: () { childRenderObject.scheduleInitialSemantics(); }, onSemanticsUpdate: (SemanticsUpdate update) { }, ); child.rootNode = childRenderObject; root.adoptChild(child); root.attach(manifold); rootRenderObject.markNeedsSemanticsUpdate(); childRenderObject.markNeedsSemanticsUpdate(); root.flushSemantics(); expect(childSemanticsCalled, isTrue); }); test('when manifold enables semantics all PipelineOwners in tree create SemanticsOwner', () { final TestPipelineManifold manifold = TestPipelineManifold(); int rootOnSemanticsOwnerCreatedCount = 0; int rootOnSemanticsOwnerDisposed = 0; final PipelineOwner root = PipelineOwner( onSemanticsOwnerCreated: () { rootOnSemanticsOwnerCreatedCount++; }, onSemanticsUpdate: (SemanticsUpdate update) { }, onSemanticsOwnerDisposed: () { rootOnSemanticsOwnerDisposed++; }, ); int childOnSemanticsOwnerCreatedCount = 0; int childOnSemanticsOwnerDisposed = 0; final PipelineOwner child = PipelineOwner( onSemanticsOwnerCreated: () { childOnSemanticsOwnerCreatedCount++; }, onSemanticsUpdate: (SemanticsUpdate update) { }, onSemanticsOwnerDisposed: () { childOnSemanticsOwnerDisposed++; }, ); root.adoptChild(child); root.attach(manifold); expect(rootOnSemanticsOwnerCreatedCount, 0); expect(childOnSemanticsOwnerCreatedCount, 0); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNull); manifold.semanticsEnabled = true; expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); manifold.semanticsEnabled = false; expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 1); expect(childOnSemanticsOwnerDisposed, 1); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNull); }); test('when manifold enables semantics all PipelineOwners in tree that did not have a SemanticsOwner create one', () { final TestPipelineManifold manifold = TestPipelineManifold(); int rootOnSemanticsOwnerCreatedCount = 0; int rootOnSemanticsOwnerDisposed = 0; final PipelineOwner root = PipelineOwner( onSemanticsOwnerCreated: () { rootOnSemanticsOwnerCreatedCount++; }, onSemanticsUpdate: (SemanticsUpdate update) { }, onSemanticsOwnerDisposed: () { rootOnSemanticsOwnerDisposed++; }, ); int childOnSemanticsOwnerCreatedCount = 0; int childOnSemanticsOwnerDisposed = 0; final PipelineOwner child = PipelineOwner( onSemanticsOwnerCreated: () { childOnSemanticsOwnerCreatedCount++; }, onSemanticsUpdate: (SemanticsUpdate update) { }, onSemanticsOwnerDisposed: () { childOnSemanticsOwnerDisposed++; }, ); root.adoptChild(child); root.attach(manifold); final SemanticsHandle childSemantics = child.ensureSemantics(); expect(rootOnSemanticsOwnerCreatedCount, 0); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNotNull); manifold.semanticsEnabled = true; expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); manifold.semanticsEnabled = false; expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 1); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNotNull); childSemantics.dispose(); expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 1); expect(childOnSemanticsOwnerDisposed, 1); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNull); }); test('PipelineOwner can dispose local handle even when manifold forces semantics to on', () { final TestPipelineManifold manifold = TestPipelineManifold(); int rootOnSemanticsOwnerCreatedCount = 0; int rootOnSemanticsOwnerDisposed = 0; final PipelineOwner root = PipelineOwner( onSemanticsOwnerCreated: () { rootOnSemanticsOwnerCreatedCount++; }, onSemanticsUpdate: (SemanticsUpdate update) { }, onSemanticsOwnerDisposed: () { rootOnSemanticsOwnerDisposed++; }, ); int childOnSemanticsOwnerCreatedCount = 0; int childOnSemanticsOwnerDisposed = 0; final PipelineOwner child = PipelineOwner( onSemanticsOwnerCreated: () { childOnSemanticsOwnerCreatedCount++; }, onSemanticsUpdate: (SemanticsUpdate update) { }, onSemanticsOwnerDisposed: () { childOnSemanticsOwnerDisposed++; }, ); root.adoptChild(child); root.attach(manifold); final SemanticsHandle childSemantics = child.ensureSemantics(); expect(rootOnSemanticsOwnerCreatedCount, 0); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNotNull); manifold.semanticsEnabled = true; expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); childSemantics.dispose(); expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); manifold.semanticsEnabled = false; expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 1); expect(childOnSemanticsOwnerDisposed, 1); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNull); }); test('can hold on to local handle when manifold turns off semantics', () { final TestPipelineManifold manifold = TestPipelineManifold(); int rootOnSemanticsOwnerCreatedCount = 0; int rootOnSemanticsOwnerDisposed = 0; final PipelineOwner root = PipelineOwner( onSemanticsOwnerCreated: () { rootOnSemanticsOwnerCreatedCount++; }, onSemanticsUpdate: (SemanticsUpdate update) { }, onSemanticsOwnerDisposed: () { rootOnSemanticsOwnerDisposed++; }, ); int childOnSemanticsOwnerCreatedCount = 0; int childOnSemanticsOwnerDisposed = 0; final PipelineOwner child = PipelineOwner( onSemanticsOwnerCreated: () { childOnSemanticsOwnerCreatedCount++; }, onSemanticsUpdate: (SemanticsUpdate update) { }, onSemanticsOwnerDisposed: () { childOnSemanticsOwnerDisposed++; }, ); root.adoptChild(child); root.attach(manifold); expect(rootOnSemanticsOwnerCreatedCount, 0); expect(childOnSemanticsOwnerCreatedCount, 0); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNull); manifold.semanticsEnabled = true; expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); final SemanticsHandle childSemantics = child.ensureSemantics(); expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 0); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); manifold.semanticsEnabled = false; expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 1); expect(childOnSemanticsOwnerDisposed, 0); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNotNull); childSemantics.dispose(); expect(rootOnSemanticsOwnerCreatedCount, 1); expect(childOnSemanticsOwnerCreatedCount, 1); expect(rootOnSemanticsOwnerDisposed, 1); expect(childOnSemanticsOwnerDisposed, 1); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNull); }); test('cannot attach when already attached', () { final TestPipelineManifold manifold = TestPipelineManifold(); final PipelineOwner owner = PipelineOwner(); owner.attach(manifold); expect(() => owner.attach(manifold), throwsAssertionError); }); test('attach update semanticsOwner', () { final TestPipelineManifold manifold = TestPipelineManifold() ..semanticsEnabled = true; final PipelineOwner owner = PipelineOwner( onSemanticsUpdate: (_) { }, ); expect(owner.semanticsOwner, isNull); owner.attach(manifold); expect(owner.semanticsOwner, isNotNull); }); test('attach does not request visual update if nothing is dirty', () { final TestPipelineManifold manifold = TestPipelineManifold(); final TestRenderObject renderObject = TestRenderObject(); final PipelineOwner owner = PipelineOwner(); owner.rootNode = renderObject; expect(manifold.requestVisualUpdateCount, 0); owner.attach(manifold); expect(manifold.requestVisualUpdateCount, 0); }); test('cannot detach when not attached', () { final PipelineOwner owner = PipelineOwner(); expect(() => owner.detach(), throwsAssertionError); }); test('cannot adopt twice', () { final PipelineOwner root = PipelineOwner(); final PipelineOwner child = PipelineOwner(); root.adoptChild(child); expect(() => root.adoptChild(child), throwsAssertionError); }); test('cannot adopt child of other parent', () { final PipelineOwner root = PipelineOwner(); final PipelineOwner child = PipelineOwner(); final PipelineOwner otherRoot = PipelineOwner(); root.adoptChild(child); expect(() => otherRoot.adoptChild(child), throwsAssertionError); }); test('adopting creates semantics owner if necessary', () { final TestPipelineManifold manifold = TestPipelineManifold(); final PipelineOwner root = PipelineOwner( onSemanticsUpdate: (_) { }, ); final PipelineOwner child = PipelineOwner( onSemanticsUpdate: (_) { }, ); final PipelineOwner childOfChild = PipelineOwner( onSemanticsUpdate: (_) { }, ); root.attach(manifold); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNull); expect(childOfChild.semanticsOwner, isNull); root.adoptChild(child); expect(root.semanticsOwner, isNull); expect(child.semanticsOwner, isNull); expect(childOfChild.semanticsOwner, isNull); manifold.semanticsEnabled = true; expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); expect(childOfChild.semanticsOwner, isNull); child.adoptChild(childOfChild); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); expect(childOfChild.semanticsOwner, isNotNull); }); test('cannot drop unattached child', () { final PipelineOwner root = PipelineOwner(); final PipelineOwner child = PipelineOwner(); expect(() => root.dropChild(child), throwsAssertionError); }); test('cannot drop child attached to other parent', () { final PipelineOwner root = PipelineOwner(); final PipelineOwner child = PipelineOwner(); final PipelineOwner otherRoot = PipelineOwner(); otherRoot.adoptChild(child); expect(() => root.dropChild(child), throwsAssertionError); }); test('dropping destroys semantics owner if necessary', () { final TestPipelineManifold manifold = TestPipelineManifold() ..semanticsEnabled = true; final PipelineOwner root = PipelineOwner( onSemanticsUpdate: (_) { }, ); final PipelineOwner child = PipelineOwner( onSemanticsUpdate: (_) { }, ); final PipelineOwner childOfChild = PipelineOwner( onSemanticsUpdate: (_) { }, ); root.attach(manifold); root.adoptChild(child); child.adoptChild(childOfChild); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); expect(childOfChild.semanticsOwner, isNotNull); child.dropChild(childOfChild); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); expect(childOfChild.semanticsOwner, isNotNull); // Retained in case we get re-attached. final SemanticsHandle childSemantics = child.ensureSemantics(); root.dropChild(child); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNotNull); expect(childOfChild.semanticsOwner, isNotNull); // Retained in case we get re-attached. childSemantics.dispose(); expect(root.semanticsOwner, isNotNull); expect(child.semanticsOwner, isNull); expect(childOfChild.semanticsOwner, isNotNull); manifold.semanticsEnabled = false; expect(root.semanticsOwner, isNull); expect(childOfChild.semanticsOwner, isNotNull); root.adoptChild(childOfChild); expect(root.semanticsOwner, isNull); expect(childOfChild.semanticsOwner, isNull); // Disposed on re-attachment. manifold.semanticsEnabled = true; expect(root.semanticsOwner, isNotNull); expect(childOfChild.semanticsOwner, isNotNull); root.dropChild(childOfChild); expect(root.semanticsOwner, isNotNull); expect(childOfChild.semanticsOwner, isNotNull); childOfChild.dispose(); expect(root.semanticsOwner, isNotNull); expect(childOfChild.semanticsOwner, isNull); // Disposed on dispose. }); test('can adopt/drop children during own layout', () { final TestPipelineManifold manifold = TestPipelineManifold(); final PipelineOwner root = PipelineOwner(); final PipelineOwner child1 = PipelineOwner(); final PipelineOwner child2 = PipelineOwner(); final TestRenderObject rootRenderObject = TestRenderObject( onLayout: () { child1.dropChild(child2); root.dropChild(child1); root.adoptChild(child2); child2.adoptChild(child1); }, ); root.rootNode = rootRenderObject; rootRenderObject.scheduleInitialLayout(); root.adoptChild(child1); child1.adoptChild(child2); root.attach(manifold); expect(_treeWalk(root), <PipelineOwner>[root, child1, child2]); root.flushLayout(); expect(_treeWalk(root), <PipelineOwner>[root, child2, child1]); }); test('cannot adopt/drop children during child layout', () { final TestPipelineManifold manifold = TestPipelineManifold(); final PipelineOwner root = PipelineOwner(); final PipelineOwner child1 = PipelineOwner(); final PipelineOwner child2 = PipelineOwner(); final PipelineOwner child3 = PipelineOwner(); Object? droppingError; Object? adoptingError; final TestRenderObject childRenderObject = TestRenderObject( onLayout: () { child1.dropChild(child2); child1.adoptChild(child3); try { root.dropChild(child1); } catch (e) { droppingError = e; } try { root.adoptChild(child2); } catch (e) { adoptingError = e; } }, ); child1.rootNode = childRenderObject; childRenderObject.scheduleInitialLayout(); root.adoptChild(child1); child1.adoptChild(child2); root.attach(manifold); expect(_treeWalk(root), <PipelineOwner>[root, child1, child2]); root.flushLayout(); expect(adoptingError, isAssertionError.having((AssertionError e) => e.message, 'message', contains('Cannot modify child list after layout.'))); expect(droppingError, isAssertionError.having((AssertionError e) => e.message, 'message', contains('Cannot modify child list after layout.'))); }); test('visitChildren visits all children', () { final PipelineOwner root = PipelineOwner(); final PipelineOwner child1 = PipelineOwner(); final PipelineOwner child2 = PipelineOwner(); final PipelineOwner child3 = PipelineOwner(); final PipelineOwner childOfChild3 = PipelineOwner(); root.adoptChild(child1); root.adoptChild(child2); root.adoptChild(child3); child3.adoptChild(childOfChild3); final List<PipelineOwner> children = <PipelineOwner>[]; root.visitChildren((PipelineOwner child) { children.add(child); }); expect(children, <PipelineOwner>[child1, child2, child3]); children.clear(); child3.visitChildren((PipelineOwner child) { children.add(child); }); expect(children.single, childOfChild3); }); test('printing pipeline owner tree smoke test', () { final PipelineOwner root = PipelineOwner(); final PipelineOwner child1 = PipelineOwner() ..rootNode = FakeRenderView(); final PipelineOwner childOfChild1 = PipelineOwner() ..rootNode = FakeRenderView(); final PipelineOwner child2 = PipelineOwner() ..rootNode = FakeRenderView(); final PipelineOwner childOfChild2 = PipelineOwner() ..rootNode = FakeRenderView(); root.adoptChild(child1); child1.adoptChild(childOfChild1); root.adoptChild(child2); child2.adoptChild(childOfChild2); expect(root.toStringDeep(), equalsIgnoringHashCodes( 'PipelineOwner#00000\n' ' ├─PipelineOwner#00000\n' ' │ │ rootNode: FakeRenderView#00000 NEEDS-LAYOUT NEEDS-PAINT\n' ' │ │\n' ' │ └─PipelineOwner#00000\n' ' │ rootNode: FakeRenderView#00000 NEEDS-LAYOUT NEEDS-PAINT\n' ' │\n' ' └─PipelineOwner#00000\n' ' │ rootNode: FakeRenderView#00000 NEEDS-LAYOUT NEEDS-PAINT\n' ' │\n' ' └─PipelineOwner#00000\n' ' rootNode: FakeRenderView#00000 NEEDS-LAYOUT NEEDS-PAINT\n' )); }); } class TestPipelineManifold extends ChangeNotifier implements PipelineManifold { int requestVisualUpdateCount = 0; @override void requestVisualUpdate() { requestVisualUpdateCount++; } @override bool get semanticsEnabled => _semanticsEnabled; bool _semanticsEnabled = false; set semanticsEnabled(bool value) { if (value == _semanticsEnabled) { return; } _semanticsEnabled = value; notifyListeners(); } } class TestRenderObject extends RenderObject { TestRenderObject({this.onLayout, this.onPaint, this.onSemantics}); final VoidCallback? onLayout; final VoidCallback? onPaint; final VoidCallback? onSemantics; @override bool get isRepaintBoundary => true; @override void debugAssertDoesMeetConstraints() { } @override Rect get paintBounds => Rect.zero; @override void performLayout() { onLayout?.call(); } @override void paint(PaintingContext context, Offset offset) { onPaint?.call(); } @override void describeSemanticsConfiguration(SemanticsConfiguration config) { onSemantics?.call(); } @override void performResize() { } @override Rect get semanticBounds => Rect.zero; } List<PipelineOwner> _treeWalk(PipelineOwner root) { final List<PipelineOwner> results = <PipelineOwner>[root]; void visitor(PipelineOwner child) { results.add(child); child.visitChildren(visitor); } root.visitChildren(visitor); return results; } class FakeRenderView extends RenderBox { }