independent_widget_layout_test.dart 4.29 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
// Copyright 2016 The Chromium 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_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

const Size _kTestViewSize = const Size(800.0, 600.0);

class OffscreenRenderView extends RenderView {
12
  OffscreenRenderView() : super(configuration: new ViewConfiguration(size: _kTestViewSize));
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101

  @override
  void compositeFrame() {
    // Don't draw to ui.window
  }
}

class OffscreenWidgetTree {
  OffscreenWidgetTree() {
    renderView.attach(pipelineOwner);
    renderView.scheduleInitialFrame();
  }

  final RenderView renderView = new OffscreenRenderView();
  final BuildOwner buildOwner = new BuildOwner();
  final PipelineOwner pipelineOwner = new PipelineOwner();
  RenderObjectToWidgetElement<RenderBox> root;

  void pumpWidget(Widget app) {
    root = new RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: app
    ).attachToRenderTree(buildOwner, root);
    pumpFrame();
  }

  void pumpFrame() {
    buildOwner.buildDirtyElements();
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    renderView.compositeFrame();
    if (SemanticsNode.hasListeners) {
      pipelineOwner.flushSemantics();
      SemanticsNode.sendSemanticsTree();
    }
    buildOwner.finalizeTree();
  }

}

class Counter {
  int count = 0;
}

class Trigger {
  VoidCallback callback;
  void fire() {
    if (callback != null)
      callback();
  }
}

class TriggerableWidget extends StatefulWidget {
  TriggerableWidget({ this.trigger, this.counter });
  final Trigger trigger;
  final Counter counter;
  @override
  TriggerableState createState() => new TriggerableState();
}

class TriggerableState extends State<TriggerableWidget> {
  @override
  void initState() {
    super.initState();
    config.trigger.callback = this.fire;
  }

  @override
  void didUpdateConfig(TriggerableWidget oldConfig) {
    config.trigger.callback = this.fire;
  }

  int _count = 0;
  void fire() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    config.counter.count++;
    return new Text("Bang $_count!");
  }
}

void main() {
102
  testWidgets('no crosstalk between widget build owners', (WidgetTester tester) async {
103 104 105 106 107 108 109 110 111
    Trigger trigger1 = new Trigger();
    Counter counter1 = new Counter();
    Trigger trigger2 = new Trigger();
    Counter counter2 = new Counter();
    OffscreenWidgetTree tree = new OffscreenWidgetTree();
    // Both counts should start at zero
    expect(counter1.count, equals(0));
    expect(counter2.count, equals(0));
    // Lay out the "onscreen" in the default test binding
112
    await tester.pumpWidget(new TriggerableWidget(trigger: trigger1, counter: counter1));
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
    // Only the "onscreen" widget should have built
    expect(counter1.count, equals(1));
    expect(counter2.count, equals(0));
    // Lay out the "offscreen" in a separate tree
    tree.pumpWidget(new TriggerableWidget(trigger: trigger2, counter: counter2));
    // Now both widgets should have built
    expect(counter1.count, equals(1));
    expect(counter2.count, equals(1));
    // Mark both as needing layout
    trigger1.fire();
    trigger2.fire();
    // Marking as needing layout shouldn't immediately build anything
    expect(counter1.count, equals(1));
    expect(counter2.count, equals(1));
    // Pump the "onscreen" layout
128
    await tester.pump();
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
    // Only the "onscreen" widget should have rebuilt
    expect(counter1.count, equals(2));
    expect(counter2.count, equals(1));
    // Pump the "offscreen" layout
    tree.pumpFrame();
    // Now both widgets should have rebuilt
    expect(counter1.count, equals(2));
    expect(counter2.count, equals(2));
    // Mark both as needing layout, again
    trigger1.fire();
    trigger2.fire();
    // Now pump the "offscreen" layout first
    tree.pumpFrame();
    // Only the "offscreen" widget should have rebuilt
    expect(counter1.count, equals(2));
    expect(counter2.count, equals(3));
    // Pump the "onscreen" layout
146
    await tester.pump();
147 148 149
    // Now both widgets should have rebuilt
    expect(counter1.count, equals(3));
    expect(counter2.count, equals(3));
150 151
  });
}