independent_widget_layout_test.dart 5.64 KB
Newer Older
1 2 3 4 5 6 7 8
// 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';

9
const Size _kTestViewSize = const Size(800.0, 600.0);
10 11

class OffscreenRenderView extends RenderView {
12
  OffscreenRenderView() : super(configuration: const 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

  @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() {
41
    buildOwner.buildScope(root);
42 43 44 45
    pipelineOwner.flushLayout();
    pipelineOwner.flushCompositingBits();
    pipelineOwner.flushPaint();
    renderView.compositeFrame();
46
    pipelineOwner.flushSemantics();
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
    buildOwner.finalizeTree();
  }

}

class Counter {
  int count = 0;
}

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

class TriggerableWidget extends StatefulWidget {
65
  const TriggerableWidget({ this.trigger, this.counter });
66 67 68 69 70 71 72 73 74 75
  final Trigger trigger;
  final Counter counter;
  @override
  TriggerableState createState() => new TriggerableState();
}

class TriggerableState extends State<TriggerableWidget> {
  @override
  void initState() {
    super.initState();
76
    widget.trigger.callback = fire;
77 78 79
  }

  @override
80
  void didUpdateWidget(TriggerableWidget oldWidget) {
81
    super.didUpdateWidget(oldWidget);
82
    widget.trigger.callback = fire;
83 84 85 86 87 88 89 90 91 92 93
  }

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

  @override
  Widget build(BuildContext context) {
94
    widget.counter.count++;
Ian Hickson's avatar
Ian Hickson committed
95
    return new Text('Bang $_count!', textDirection: TextDirection.ltr);
96 97 98
  }
}

99 100 101 102
class TestFocusable extends StatefulWidget {
  const TestFocusable({
    Key key,
    this.focusNode,
103
    this.autofocus = true,
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
  }) : super(key: key);

  final bool autofocus;
  final FocusNode focusNode;

  @override
  TestFocusableState createState() => new TestFocusableState();
}

class TestFocusableState extends State<TestFocusable> {
  bool _didAutofocus = false;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (!_didAutofocus && widget.autofocus) {
      _didAutofocus = true;
      FocusScope.of(context).autofocus(widget.focusNode);
    }
  }

  @override
  Widget build(BuildContext context) {
    return const Text('Test focus node', textDirection: TextDirection.ltr);
  }
}

131
void main() {
132
  testWidgets('no crosstalk between widget build owners', (WidgetTester tester) async {
133 134 135 136 137
    final Trigger trigger1 = new Trigger();
    final Counter counter1 = new Counter();
    final Trigger trigger2 = new Trigger();
    final Counter counter2 = new Counter();
    final OffscreenWidgetTree tree = new OffscreenWidgetTree();
138 139 140 141
    // 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
142
    await tester.pumpWidget(new TriggerableWidget(trigger: trigger1, counter: counter1));
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
    // 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
158
    await tester.pump();
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
    // 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
176
    await tester.pump();
177 178 179
    // Now both widgets should have rebuilt
    expect(counter1.count, equals(3));
    expect(counter2.count, equals(3));
180
  });
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204

  testWidgets('no crosstalk between focus nodes', (WidgetTester tester) async {
    final OffscreenWidgetTree tree = new OffscreenWidgetTree();
    final FocusNode onscreenFocus = new FocusNode();
    final FocusNode offscreenFocus = new FocusNode();
    await tester.pumpWidget(
      new TestFocusable(
        focusNode: onscreenFocus,
      ),
    );
    tree.pumpWidget(
      new TestFocusable(
        focusNode: offscreenFocus,
      ),
    );

    // Autofocus is delayed one frame.
    await tester.pump();
    tree.pumpFrame();

    expect(onscreenFocus.hasFocus, isTrue);
    expect(offscreenFocus.hasFocus, isTrue);
  });

205
}