widget_tester.dart 6.02 KB
Newer Older
Hixie's avatar
Hixie committed
1 2 3 4 5 6
// Copyright 2015 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 'dart:ui' as ui;

7
import 'package:flutter/gestures.dart';
8
import 'package:flutter/rendering.dart';
9
import 'package:flutter/scheduler.dart';
10
import 'package:flutter/widgets.dart';
11 12
import 'package:quiver/testing/async.dart';
import 'package:quiver/time.dart';
13

Adam Barth's avatar
Adam Barth committed
14
import 'test_pointer.dart';
15

16 17
typedef Point SizeToPointFunction(Size size);

18
class WidgetTester {
19 20
  WidgetTester._(FakeAsync async)
    : async = async,
Hixie's avatar
Hixie committed
21 22 23 24
      clock = async.getClock(new DateTime.utc(2015, 1, 1)) {
    timeDilation = 1.0;
    ui.window.onBeginFrame = null;
  }
Adam Barth's avatar
Adam Barth committed
25

26 27
  final FakeAsync async;
  final Clock clock;
Adam Barth's avatar
Adam Barth committed
28

29
  void pumpWidget(Widget widget, [ Duration duration ]) {
Adam Barth's avatar
Adam Barth committed
30
    runApp(widget);
31
    pump(duration);
32 33
  }

34 35 36
  void pump([ Duration duration ]) {
    if (duration != null)
      async.elapse(duration);
37 38
    scheduler.handleBeginFrame(new Duration(
        milliseconds: clock.now().millisecondsSinceEpoch));
39
    async.flushMicrotasks();
Adam Barth's avatar
Adam Barth committed
40
  }
41

42
  List<Layer> _layers(Layer layer) {
43
    List<Layer> result = <Layer>[layer];
44 45 46
    if (layer is ContainerLayer) {
      ContainerLayer root = layer;
      Layer child = root.firstChild;
47
      while (child != null) {
48 49 50 51 52 53
        result.addAll(_layers(child));
        child = child.nextSibling;
      }
    }
    return result;
  }
54
  List<Layer> get layers => _layers(FlutterBinding.instance.renderView.layer);
55

56

Adam Barth's avatar
Adam Barth committed
57 58 59 60 61 62
  void walkElements(ElementVisitor visitor) {
    void walk(Element element) {
      visitor(element);
      element.visitChildren(walk);
    }
    WidgetFlutterBinding.instance.renderViewElement.visitChildren(walk);
63 64
  }

Adam Barth's avatar
Adam Barth committed
65
  Element findElement(bool predicate(Element element)) {
66
    try {
Adam Barth's avatar
Adam Barth committed
67 68 69
      walkElements((Element element) {
        if (predicate(element))
          throw element;
70
      });
Adam Barth's avatar
Adam Barth committed
71 72
    } on Element catch (e) {
      return e;
73 74 75 76
    }
    return null;
  }

Adam Barth's avatar
Adam Barth committed
77 78 79 80 81 82 83
  Element findElementByKey(Key key) {
    return findElement((Element element) => element.widget.key == key);
  }

  Element findText(String text) {
    return findElement((Element element) {
      return element.widget is Text && element.widget.data == text;
84 85 86
    });
  }

Adam Barth's avatar
Adam Barth committed
87 88 89 90 91
  State findStateOfType(Type type) {
    StatefulComponentElement element = findElement((Element element) {
      return element is StatefulComponentElement && element.state.runtimeType == type;
    });
    return element?.state;
92 93
  }

Adam Barth's avatar
Adam Barth committed
94 95 96 97 98
  State findStateByConfig(Widget config) {
    StatefulComponentElement element = findElement((Element element) {
      return element is StatefulComponentElement && element.state.config == config;
    });
    return element?.state;
99 100
  }

Adam Barth's avatar
Adam Barth committed
101 102
  Point getCenter(Element element) {
    return _getElementPoint(element, (Size size) => size.center(Point.origin));
103 104
  }

Adam Barth's avatar
Adam Barth committed
105 106
  Point getTopLeft(Element element) {
    return _getElementPoint(element, (_) => Point.origin);
107 108
  }

Adam Barth's avatar
Adam Barth committed
109 110
  Point getTopRight(Element element) {
    return _getElementPoint(element, (Size size) => size.topRight(Point.origin));
111 112
  }

Adam Barth's avatar
Adam Barth committed
113 114
  Point getBottomLeft(Element element) {
    return _getElementPoint(element, (Size size) => size.bottomLeft(Point.origin));
115 116
  }

Adam Barth's avatar
Adam Barth committed
117 118 119
  Point getBottomRight(Element element) {
    return _getElementPoint(element, (Size size) => size.bottomRight(Point.origin));
  }
120

121
  Point _getElementPoint(Element element, SizeToPointFunction sizeToPoint) {
Adam Barth's avatar
Adam Barth committed
122 123 124 125
    assert(element != null);
    RenderBox box = element.renderObject as RenderBox;
    assert(box != null);
    return box.localToGlobal(sizeToPoint(box.size));
126 127
  }

Adam Barth's avatar
Adam Barth committed
128 129 130

  void tap(Element element, { int pointer: 1 }) {
    tapAt(getCenter(element), pointer: pointer);
131 132 133
  }

  void tapAt(Point location, { int pointer: 1 }) {
134
    HitTestResult result = _hitTest(location);
135 136 137
    TestPointer p = new TestPointer(pointer);
    _dispatchEvent(p.down(location), result);
    _dispatchEvent(p.up(), result);
138 139
  }

140
  void fling(Element element, Offset offset, double velocity, { int pointer: 1 }) {
141 142 143 144 145 146 147 148
    flingFrom(getCenter(element), offset, velocity, pointer: pointer);
  }

  void flingFrom(Point startLocation, Offset offset, double velocity, { int pointer: 1 }) {
    assert(offset.distance > 0.0);
    assert(velocity != 0.0);   // velocity is pixels/second
    final TestPointer p = new TestPointer(pointer);
    final HitTestResult result = _hitTest(startLocation);
149
    const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
150 151
    final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * velocity);
    double timeStamp = 0.0;
152
    _dispatchEvent(p.down(startLocation, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
153 154
    for(int i = 0; i < kMoveCount; i++) {
      final Point location = startLocation + Offset.lerp(Offset.zero, offset, i / kMoveCount);
155
      _dispatchEvent(p.move(location, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
156 157
      timeStamp += timeStampDelta;
    }
158
    _dispatchEvent(p.up(timeStamp: new Duration(milliseconds: timeStamp.round())), result);
159 160
  }

Adam Barth's avatar
Adam Barth committed
161
  void scroll(Element element, Offset offset, { int pointer: 1 }) {
162 163 164 165
    scrollAt(getCenter(element), offset, pointer: pointer);
  }

  void scrollAt(Point startLocation, Offset offset, { int pointer: 1 }) {
166
    Point endLocation = startLocation + offset;
167
    TestPointer p = new TestPointer(pointer);
168 169 170
    // Events for the entire press-drag-release gesture are dispatched
    // to the widgets "hit" by the pointer down event.
    HitTestResult result = _hitTest(startLocation);
171 172 173
    _dispatchEvent(p.down(startLocation), result);
    _dispatchEvent(p.move(endLocation), result);
    _dispatchEvent(p.up(), result);
174 175
  }

176
  void dispatchEvent(InputEvent event, Point location) {
177
    _dispatchEvent(event, _hitTest(location));
178 179
  }

Adam Barth's avatar
Adam Barth committed
180
  HitTestResult _hitTest(Point location) => WidgetFlutterBinding.instance.hitTest(location);
181

182
  void _dispatchEvent(InputEvent event, HitTestResult result) {
Adam Barth's avatar
Adam Barth committed
183
    WidgetFlutterBinding.instance.dispatchEvent(event, result);
Hixie's avatar
Hixie committed
184 185
  }

186
}
187 188 189 190 191 192

void testWidgets(callback(WidgetTester tester)) {
  new FakeAsync().run((FakeAsync async) {
    callback(new WidgetTester._(async));
  });
}