controller.dart 25.8 KB
Newer Older
1 2 3 4
// 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.

5 6
import 'dart:async';

7 8 9 10 11
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

import 'all_elements.dart';
12 13 14
import 'finders.dart';
import 'test_async_utils.dart';
import 'test_pointer.dart';
15

16 17 18 19 20 21
/// The default drag touch slop used to break up a large drag into multiple
/// smaller moves.
///
/// This value must be greater than [kTouchSlop].
const double kDragSlopDefault = 20.0;

22 23
/// Class that programmatically interacts with widgets.
///
24 25 26 27 28 29
/// For a variant of this class suited specifically for unit tests, see
/// [WidgetTester]. For one suitable for live tests on a device, consider
/// [LiveWidgetController].
///
/// Concrete subclasses must implement the [pump] method.
abstract class WidgetController {
30
  /// Creates a widget controller that uses the given binding.
31 32
  WidgetController(this.binding);

33
  /// A reference to the current instance of the binding.
34 35 36 37 38 39 40 41
  final WidgetsBinding binding;

  // FINDER API

  // TODO(ianh): verify that the return values are of type T and throw
  // a good message otherwise, in all the generic methods below

  /// Checks if `finder` exists in the tree.
42 43 44 45
  bool any(Finder finder) {
    TestAsyncUtils.guardSync();
    return finder.evaluate().isNotEmpty;
  }
46 47 48 49 50 51

  /// All widgets currently in the widget tree (lazy pre-order traversal).
  ///
  /// Can contain duplicates, since widgets can be used in multiple
  /// places in the widget tree.
  Iterable<Widget> get allWidgets {
52
    TestAsyncUtils.guardSync();
53
    return allElements.map<Widget>((Element element) => element.widget);
54 55 56 57 58 59
  }

  /// The matching widget in the widget tree.
  ///
  /// Throws a [StateError] if `finder` is empty or matches more than
  /// one widget.
60 61 62
  ///
  /// * Use [firstWidget] if you expect to match several widgets but only want the first.
  /// * Use [widgetList] if you expect to match several widgets and want all of them.
63
  T widget<T extends Widget>(Finder finder) {
64
    TestAsyncUtils.guardSync();
65 66 67 68 69 70 71
    return finder.evaluate().single.widget;
  }

  /// The first matching widget according to a depth-first pre-order
  /// traversal of the widget tree.
  ///
  /// Throws a [StateError] if `finder` is empty.
72 73
  ///
  /// * Use [widget] if you only expect to match one widget.
74
  T firstWidget<T extends Widget>(Finder finder) {
75
    TestAsyncUtils.guardSync();
76 77 78
    return finder.evaluate().first.widget;
  }

79 80 81 82
  /// The matching widgets in the widget tree.
  ///
  /// * Use [widget] if you only expect to match one widget.
  /// * Use [firstWidget] if you expect to match several but only want the first.
83
  Iterable<T> widgetList<T extends Widget>(Finder finder) {
84
    TestAsyncUtils.guardSync();
85
    return finder.evaluate().map<T>((Element element) {
86
      final T result = element.widget;
87 88
      return result;
    });
89 90
  }

91 92 93 94 95 96
  /// All elements currently in the widget tree (lazy pre-order traversal).
  ///
  /// The returned iterable is lazy. It does not walk the entire widget tree
  /// immediately, but rather a chunk at a time as the iteration progresses
  /// using [Iterator.moveNext].
  Iterable<Element> get allElements {
97
    TestAsyncUtils.guardSync();
98
    return collectAllElementsFrom(binding.renderViewElement, skipOffstage: false);
99 100 101 102 103 104
  }

  /// The matching element in the widget tree.
  ///
  /// Throws a [StateError] if `finder` is empty or matches more than
  /// one element.
105 106 107
  ///
  /// * Use [firstElement] if you expect to match several elements but only want the first.
  /// * Use [elementList] if you expect to match several elements and want all of them.
108
  T element<T extends Element>(Finder finder) {
109
    TestAsyncUtils.guardSync();
110 111 112 113 114 115 116
    return finder.evaluate().single;
  }

  /// The first matching element according to a depth-first pre-order
  /// traversal of the widget tree.
  ///
  /// Throws a [StateError] if `finder` is empty.
117 118
  ///
  /// * Use [element] if you only expect to match one element.
119
  T firstElement<T extends Element>(Finder finder) {
120
    TestAsyncUtils.guardSync();
121 122 123
    return finder.evaluate().first;
  }

124 125 126 127
  /// The matching elements in the widget tree.
  ///
  /// * Use [element] if you only expect to match one element.
  /// * Use [firstElement] if you expect to match several but only want the first.
128
  Iterable<T> elementList<T extends Element>(Finder finder) {
129 130 131 132
    TestAsyncUtils.guardSync();
    return finder.evaluate();
  }

133 134 135 136 137 138
  /// All states currently in the widget tree (lazy pre-order traversal).
  ///
  /// The returned iterable is lazy. It does not walk the entire widget tree
  /// immediately, but rather a chunk at a time as the iteration progresses
  /// using [Iterator.moveNext].
  Iterable<State> get allStates {
139
    TestAsyncUtils.guardSync();
140
    return allElements.whereType<StatefulElement>().map<State>((StatefulElement element) => element.state);
141 142 143 144 145 146
  }

  /// The matching state in the widget tree.
  ///
  /// Throws a [StateError] if `finder` is empty, matches more than
  /// one state, or matches a widget that has no state.
147 148 149
  ///
  /// * Use [firstState] if you expect to match several states but only want the first.
  /// * Use [stateList] if you expect to match several states and want all of them.
150
  T state<T extends State>(Finder finder) {
151
    TestAsyncUtils.guardSync();
152
    return _stateOf<T>(finder.evaluate().single, finder);
153 154 155 156 157 158 159
  }

  /// The first matching state according to a depth-first pre-order
  /// traversal of the widget tree.
  ///
  /// Throws a [StateError] if `finder` is empty or if the first
  /// matching widget has no state.
160 161
  ///
  /// * Use [state] if you only expect to match one state.
162
  T firstState<T extends State>(Finder finder) {
163
    TestAsyncUtils.guardSync();
164
    return _stateOf<T>(finder.evaluate().first, finder);
165 166
  }

167 168 169 170 171 172 173
  /// The matching states in the widget tree.
  ///
  /// Throws a [StateError] if any of the elements in `finder` match a widget
  /// that has no state.
  ///
  /// * Use [state] if you only expect to match one state.
  /// * Use [firstState] if you expect to match several but only want the first.
174
  Iterable<T> stateList<T extends State>(Finder finder) {
175
    TestAsyncUtils.guardSync();
176
    return finder.evaluate().map<T>((Element element) => _stateOf<T>(element, finder));
177 178
  }

179
  T _stateOf<T extends State>(Element element, Finder finder) {
180
    TestAsyncUtils.guardSync();
181 182
    if (element is StatefulElement)
      return element.state;
183
    throw StateError('Widget of type ${element.widget.runtimeType}, with ${finder.description}, is not a StatefulWidget.');
184 185 186 187 188 189 190 191 192 193
  }

  /// Render objects of all the widgets currently in the widget tree
  /// (lazy pre-order traversal).
  ///
  /// This will almost certainly include many duplicates since the
  /// render object of a [StatelessWidget] or [StatefulWidget] is the
  /// render object of its child; only [RenderObjectWidget]s have
  /// their own render object.
  Iterable<RenderObject> get allRenderObjects {
194
    TestAsyncUtils.guardSync();
195
    return allElements.map<RenderObject>((Element element) => element.renderObject);
196 197 198 199 200 201
  }

  /// The render object of the matching widget in the widget tree.
  ///
  /// Throws a [StateError] if `finder` is empty or matches more than
  /// one widget (even if they all have the same render object).
202 203 204
  ///
  /// * Use [firstRenderObject] if you expect to match several render objects but only want the first.
  /// * Use [renderObjectList] if you expect to match several render objects and want all of them.
205
  T renderObject<T extends RenderObject>(Finder finder) {
206
    TestAsyncUtils.guardSync();
207 208 209 210 211 212 213
    return finder.evaluate().single.renderObject;
  }

  /// The render object of the first matching widget according to a
  /// depth-first pre-order traversal of the widget tree.
  ///
  /// Throws a [StateError] if `finder` is empty.
214 215
  ///
  /// * Use [renderObject] if you only expect to match one render object.
216
  T firstRenderObject<T extends RenderObject>(Finder finder) {
217
    TestAsyncUtils.guardSync();
218 219 220
    return finder.evaluate().first.renderObject;
  }

221 222 223 224
  /// The render objects of the matching widgets in the widget tree.
  ///
  /// * Use [renderObject] if you only expect to match one render object.
  /// * Use [firstRenderObject] if you expect to match several but only want the first.
225
  Iterable<T> renderObjectList<T extends RenderObject>(Finder finder) {
226
    TestAsyncUtils.guardSync();
227
    return finder.evaluate().map<T>((Element element) {
228
      final T result = element.renderObject;
229 230
      return result;
    });
231 232
  }

233 234 235
  /// Returns a list of all the [Layer] objects in the rendering.
  List<Layer> get layers => _walkLayers(binding.renderView.layer).toList();
  Iterable<Layer> _walkLayers(Layer layer) sync* {
236
    TestAsyncUtils.guardSync();
237 238
    yield layer;
    if (layer is ContainerLayer) {
239
      final ContainerLayer root = layer;
240 241 242 243 244 245 246 247 248 249 250
      Layer child = root.firstChild;
      while (child != null) {
        yield* _walkLayers(child);
        child = child.nextSibling;
      }
    }
  }

  // INTERACTION

  /// Dispatch a pointer down / pointer up sequence at the center of
251 252 253 254
  /// the given widget, assuming it is exposed.
  ///
  /// If the center of the widget is not exposed, this might send events to
  /// another object.
255
  Future<void> tap(Finder finder, {int pointer}) {
256
    return tapAt(getCenter(finder), pointer: pointer);
257 258
  }

259
  /// Dispatch a pointer down / pointer up sequence at the given location.
260
  Future<void> tapAt(Offset location, {int pointer}) {
261
    return TestAsyncUtils.guard<void>(() async {
262
      final TestGesture gesture = await startGesture(location, pointer: pointer);
263 264
      await gesture.up();
    });
265 266
  }

267 268 269 270 271
  /// Dispatch a pointer down at the center of the given widget, assuming it is
  /// exposed.
  ///
  /// If the center of the widget is not exposed, this might send events to
  /// another object.
272
  Future<TestGesture> press(Finder finder, {int pointer}) {
273 274 275 276 277
    return TestAsyncUtils.guard<TestGesture>(() {
      return startGesture(getCenter(finder), pointer: pointer);
    });
  }

278 279
  /// Dispatch a pointer down / pointer up sequence (with a delay of
  /// [kLongPressTimeout] + [kPressTimeout] between the two events) at the
280 281 282 283
  /// center of the given widget, assuming it is exposed.
  ///
  /// If the center of the widget is not exposed, this might send events to
  /// another object.
284
  Future<void> longPress(Finder finder, {int pointer}) {
285 286 287 288 289
    return longPressAt(getCenter(finder), pointer: pointer);
  }

  /// Dispatch a pointer down / pointer up sequence at the given location with
  /// a delay of [kLongPressTimeout] + [kPressTimeout] between the two events.
290
  Future<void> longPressAt(Offset location, {int pointer}) {
291
    return TestAsyncUtils.guard<void>(() async {
292
      final TestGesture gesture = await startGesture(location, pointer: pointer);
293 294 295 296 297
      await pump(kLongPressTimeout + kPressTimeout);
      await gesture.up();
    });
  }

298
  /// Attempts a fling gesture starting from the center of the given
299
  /// widget, moving the given distance, reaching the given speed.
300 301 302
  ///
  /// If the middle of the widget is not exposed, this might send
  /// events to another object.
303 304 305
  ///
  /// This can pump frames. See [flingFrom] for a discussion of how the
  /// `offset`, `velocity` and `frameInterval` arguments affect this.
306 307 308 309 310
  ///
  /// The `speed` is in pixels per second in the direction given by `offset`.
  ///
  /// A fling is essentially a drag that ends at a particular speed. If you
  /// just want to drag and end without a fling, use [drag].
Ian Hickson's avatar
Ian Hickson committed
311 312 313 314 315 316 317
  ///
  /// The `initialOffset` argument, if non-zero, causes the pointer to first
  /// apply that offset, then pump a delay of `initialOffsetDelay`. This can be
  /// used to simulate a drag followed by a fling, including dragging in the
  /// opposite direction of the fling (e.g. dragging 200 pixels to the right,
  /// then fling to the left over 200 pixels, ending at the exact point that the
  /// drag started).
318 319 320 321
  Future<void> fling(
    Finder finder,
    Offset offset,
    double speed, {
322
    int pointer,
323 324 325
    Duration frameInterval = const Duration(milliseconds: 16),
    Offset initialOffset = Offset.zero,
    Duration initialOffsetDelay = const Duration(seconds: 1),
326
  }) {
Ian Hickson's avatar
Ian Hickson committed
327 328 329 330 331 332 333 334 335
    return flingFrom(
      getCenter(finder),
      offset,
      speed,
      pointer: pointer,
      frameInterval: frameInterval,
      initialOffset: initialOffset,
      initialOffsetDelay: initialOffsetDelay,
    );
336 337
  }

338 339
  /// Attempts a fling gesture starting from the given location, moving the
  /// given distance, reaching the given speed.
340 341 342
  ///
  /// Exactly 50 pointer events are synthesized.
  ///
343 344 345
  /// The offset and speed control the interval between each pointer event. For
  /// example, if the offset is 200 pixels down, and the speed is 800 pixels per
  /// second, the pointer events will be sent for each increment of 4 pixels
346 347 348 349 350 351
  /// (200/50), over 250ms (200/800), meaning events will be sent every 1.25ms
  /// (250/200).
  ///
  /// To make tests more realistic, frames may be pumped during this time (using
  /// calls to [pump]). If the total duration is longer than `frameInterval`,
  /// then one frame is pumped each time that amount of time elapses while
352
  /// sending events, or each time an event is synthesized, whichever is rarer.
353 354 355
  ///
  /// A fling is essentially a drag that ends at a particular speed. If you
  /// just want to drag and end without a fling, use [dragFrom].
Ian Hickson's avatar
Ian Hickson committed
356 357 358 359 360 361 362
  ///
  /// The `initialOffset` argument, if non-zero, causes the pointer to first
  /// apply that offset, then pump a delay of `initialOffsetDelay`. This can be
  /// used to simulate a drag followed by a fling, including dragging in the
  /// opposite direction of the fling (e.g. dragging 200 pixels to the right,
  /// then fling to the left over 200 pixels, ending at the exact point that the
  /// drag started).
363 364 365 366
  Future<void> flingFrom(
    Offset startLocation,
    Offset offset,
    double speed, {
Ian Hickson's avatar
Ian Hickson committed
367
    int pointer,
368 369 370
    Duration frameInterval = const Duration(milliseconds: 16),
    Offset initialOffset = Offset.zero,
    Duration initialOffsetDelay = const Duration(seconds: 1),
Ian Hickson's avatar
Ian Hickson committed
371
  }) {
372
    assert(offset.distance > 0.0);
373
    assert(speed > 0.0); // speed is pixels/second
374
    return TestAsyncUtils.guard<void>(() async {
375
      final TestPointer testPointer = TestPointer(pointer ?? _getNextPointer());
376
      final HitTestResult result = hitTestOnBinding(startLocation);
377
      const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
378
      final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * speed);
379
      double timeStamp = 0.0;
380
      double lastTimeStamp = timeStamp;
381
      await sendEventToBinding(testPointer.down(startLocation, timeStamp: Duration(milliseconds: timeStamp.round())), result);
Ian Hickson's avatar
Ian Hickson committed
382
      if (initialOffset.distance > 0.0) {
383
        await sendEventToBinding(testPointer.move(startLocation + initialOffset, timeStamp: Duration(milliseconds: timeStamp.round())), result);
Ian Hickson's avatar
Ian Hickson committed
384 385 386
        timeStamp += initialOffsetDelay.inMilliseconds;
        await pump(initialOffsetDelay);
      }
387
      for (int i = 0; i <= kMoveCount; i += 1) {
Ian Hickson's avatar
Ian Hickson committed
388
        final Offset location = startLocation + initialOffset + Offset.lerp(Offset.zero, offset, i / kMoveCount);
389
        await sendEventToBinding(testPointer.move(location, timeStamp: Duration(milliseconds: timeStamp.round())), result);
390
        timeStamp += timeStampDelta;
391
        if (timeStamp - lastTimeStamp > frameInterval.inMilliseconds) {
392
          await pump(Duration(milliseconds: (timeStamp - lastTimeStamp).truncate()));
393 394
          lastTimeStamp = timeStamp;
        }
395
      }
396
      await sendEventToBinding(testPointer.up(timeStamp: Duration(milliseconds: timeStamp.round())), result);
397
    });
398 399
  }

400 401 402 403 404
  /// Called to indicate that time should advance.
  ///
  /// This is invoked by [flingFrom], for instance, so that the sequence of
  /// pointer events occurs over time.
  ///
405
  /// The [WidgetTester] subclass implements this by deferring to the [binding].
406 407 408
  ///
  /// See also [SchedulerBinding.endOfFrame], which returns a future that could
  /// be appropriate to return in the implementation of this method.
409
  Future<void> pump(Duration duration);
410

411 412 413 414 415
  /// Attempts to drag the given widget by the given offset, by
  /// starting a drag in the middle of the widget.
  ///
  /// If the middle of the widget is not exposed, this might send
  /// events to another object.
416 417 418
  ///
  /// If you want the drag to end with a speed so that the gesture recognition
  /// system identifies the gesture as a fling, consider using [fling] instead.
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
  ///
  /// {@template flutter.flutter_test.drag}
  /// By default, if the x or y component of offset is greater than [kTouchSlop], the
  /// gesture is broken up into two separate moves calls. Changing 'touchSlopX' or
  /// `touchSlopY` will change the minimum amount of movement in the respective axis
  /// before the drag will be broken into multiple calls. To always send the
  /// drag with just a single call to [TestGesture.moveBy], `touchSlopX` and `touchSlopY`
  /// should be set to 0.
  ///
  /// Breaking the drag into multiple moves is necessary for accurate execution
  /// of drag update calls with a [DragStartBehavior] variable set to
  /// [DragStartBehavior.start]. Without such a change, the dragUpdate callback
  /// from a drag recognizer will never be invoked.
  ///
  /// To force this function to a send a single move event, the 'touchSlopX' and
  /// 'touchSlopY' variables should be set to 0. However, generally, these values
  /// should be left to their default values.
  /// {@end template}
  Future<void> drag(Finder finder, Offset offset, { int pointer, double touchSlopX = kDragSlopDefault, double touchSlopY = kDragSlopDefault }) {
    assert(kDragSlopDefault > kTouchSlop);
    return dragFrom(getCenter(finder), offset, pointer: pointer, touchSlopX: touchSlopX, touchSlopY: touchSlopY);
440 441 442 443
  }

  /// Attempts a drag gesture consisting of a pointer down, a move by
  /// the given offset, and a pointer up.
444 445 446 447
  ///
  /// If you want the drag to end with a speed so that the gesture recognition
  /// system identifies the gesture as a fling, consider using [flingFrom]
  /// instead.
448 449 450 451
  ///
  /// {@macro flutter.flutter_test.drag}
  Future<void> dragFrom(Offset startLocation, Offset offset, { int pointer, double touchSlopX = kDragSlopDefault, double touchSlopY = kDragSlopDefault }) {
    assert(kDragSlopDefault > kTouchSlop);
452
    return TestAsyncUtils.guard<void>(() async {
453
      final TestGesture gesture = await startGesture(startLocation, pointer: pointer);
454
      assert(gesture != null);
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519

      final double xSign = offset.dx.sign;
      final double ySign = offset.dy.sign;

      final double offsetX = offset.dx;
      final double offsetY = offset.dy;

      final bool separateX = offset.dx.abs() > touchSlopX && touchSlopX > 0;
      final bool separateY = offset.dy.abs() > touchSlopY && touchSlopY > 0;

      if (separateY || separateX) {
        final double offsetSlope = offsetY / offsetX;
        final double inverseOffsetSlope = offsetX / offsetY;
        final double slopSlope = touchSlopY / touchSlopX;
        final double absoluteOffsetSlope = offsetSlope.abs();
        final double signedSlopX = touchSlopX * xSign;
        final double signedSlopY = touchSlopY * ySign;
        if (absoluteOffsetSlope != slopSlope) {
          // The drag goes through one or both of the extents of the edges of the box.
          if (absoluteOffsetSlope < slopSlope) {
            assert(offsetX.abs() > touchSlopX);
            // The drag goes through the vertical edge of the box.
            // It is guaranteed that the |offsetX| > touchSlopX.
            final double diffY = offsetSlope.abs() * touchSlopX * ySign;

            // The vector from the origin to the vertical edge.
            await gesture.moveBy(Offset(signedSlopX, diffY));
            if (offsetY.abs() <= touchSlopY) {
              // The drag ends on or before getting to the horizontal extension of the horizontal edge.
              await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - diffY));
            } else {
              final double diffY2 = signedSlopY - diffY;
              final double diffX2 = inverseOffsetSlope * diffY2;

              // The vector from the edge of the box to the horizontal extension of the horizontal edge.
              await gesture.moveBy(Offset(diffX2, diffY2));
              await gesture.moveBy(Offset(offsetX - diffX2 - signedSlopX, offsetY - signedSlopY));
            }
          } else {
            assert(offsetY.abs() > touchSlopY);
            // The drag goes through the horizontal edge of the box.
            // It is guaranteed that the |offsetY| > touchSlopY.
            final double diffX = inverseOffsetSlope.abs() * touchSlopY * xSign;

            // The vector from the origin to the vertical edge.
            await gesture.moveBy(Offset(diffX, signedSlopY));
            if (offsetX.abs() <= touchSlopX) {
              // The drag ends on or before getting to the vertical extension of the vertical edge.
              await gesture.moveBy(Offset(offsetX - diffX, offsetY - signedSlopY));
            } else {
              final double diffX2 = signedSlopX - diffX;
              final double diffY2 = offsetSlope * diffX2;

              // The vector from the edge of the box to the vertical extension of the vertical edge.
              await gesture.moveBy(Offset(diffX2, diffY2));
              await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - diffY2 - signedSlopY));
            }
          }
        } else { // The drag goes through the corner of the box.
          await gesture.moveBy(Offset(signedSlopX, signedSlopY));
          await gesture.moveBy(Offset(offsetX - signedSlopX, offsetY - signedSlopY));
        }
      } else { // The drag ends inside the box.
        await gesture.moveBy(offset);
      }
520 521
      await gesture.up();
    });
522 523
  }

524 525 526 527 528 529 530 531 532 533 534 535
  /// The next available pointer identifier.
  ///
  /// This is the default pointer identifier that will be used the next time the
  /// [startGesture] method is called without an explicit pointer identifier.
  int nextPointer = 1;

  int _getNextPointer() {
    final int result = nextPointer;
    nextPointer += 1;
    return result;
  }

536 537 538 539 540 541 542
  /// Creates gesture and returns the [TestGesture] object which you can use
  /// to continue the gesture using calls on the [TestGesture] object.
  ///
  /// You can use [startGesture] instead if your gesture begins with a down
  /// event.
  Future<TestGesture> createGesture({int pointer, PointerDeviceKind kind = PointerDeviceKind.touch}) async {
    return TestGesture(
543 544
      hitTester: hitTestOnBinding,
      dispatcher: sendEventToBinding,
545 546
      kind: kind,
      pointer: pointer ?? _getNextPointer(),
547
    );
548 549
  }

550 551 552 553 554 555
  /// Creates a gesture with an initial down gesture at a particular point, and
  /// returns the [TestGesture] object which you can use to continue the
  /// gesture.
  ///
  /// You can use [createGesture] if your gesture doesn't begin with an initial
  /// down gesture.
556 557 558 559 560 561
  Future<TestGesture> startGesture(
    Offset downLocation, {
    int pointer,
    PointerDeviceKind kind = PointerDeviceKind.touch,
  }) async {
    final TestGesture result = await createGesture(pointer: pointer, kind: kind);
562 563 564 565
    await result.down(downLocation);
    return result;
  }

566
  /// Forwards the given location to the binding's hitTest logic.
567
  HitTestResult hitTestOnBinding(Offset location) {
568
    final HitTestResult result = HitTestResult();
569 570 571 572
    binding.hitTest(result, location);
    return result;
  }

573
  /// Forwards the given pointer event to the binding.
574 575
  Future<void> sendEventToBinding(PointerEvent event, HitTestResult result) {
    return TestAsyncUtils.guard<void>(() async {
576 577
      binding.dispatchEvent(event, result);
    });
578 579
  }

580 581 582
  // GEOMETRY

  /// Returns the point at the center of the given widget.
583 584
  Offset getCenter(Finder finder) {
    return _getElementPoint(finder, (Size size) => size.center(Offset.zero));
585 586 587
  }

  /// Returns the point at the top left of the given widget.
588 589
  Offset getTopLeft(Finder finder) {
    return _getElementPoint(finder, (Size size) => Offset.zero);
590 591 592 593
  }

  /// Returns the point at the top right of the given widget. This
  /// point is not inside the object's hit test area.
594 595
  Offset getTopRight(Finder finder) {
    return _getElementPoint(finder, (Size size) => size.topRight(Offset.zero));
596 597 598 599
  }

  /// Returns the point at the bottom left of the given widget. This
  /// point is not inside the object's hit test area.
600 601
  Offset getBottomLeft(Finder finder) {
    return _getElementPoint(finder, (Size size) => size.bottomLeft(Offset.zero));
602 603 604 605
  }

  /// Returns the point at the bottom right of the given widget. This
  /// point is not inside the object's hit test area.
606 607
  Offset getBottomRight(Finder finder) {
    return _getElementPoint(finder, (Size size) => size.bottomRight(Offset.zero));
608 609
  }

610
  Offset _getElementPoint(Finder finder, Offset sizeToPoint(Size size)) {
611
    TestAsyncUtils.guardSync();
612 613
    final Element element = finder.evaluate().single;
    final RenderBox box = element.renderObject;
614 615 616 617 618 619 620
    assert(box != null);
    return box.localToGlobal(sizeToPoint(box.size));
  }

  /// Returns the size of the given widget. This is only valid once
  /// the widget's render object has been laid out at least once.
  Size getSize(Finder finder) {
621
    TestAsyncUtils.guardSync();
622 623
    final Element element = finder.evaluate().single;
    final RenderBox box = element.renderObject;
624 625 626
    assert(box != null);
    return box.size;
  }
627 628 629 630

  /// Returns the rect of the given widget. This is only valid once
  /// the widget's render object has been laid out at least once.
  Rect getRect(Finder finder) => getTopLeft(finder) & getSize(finder);
631
}
632 633 634 635 636 637 638 639 640 641

/// Variant of [WidgetController] that can be used in tests running
/// on a device.
///
/// This is used, for instance, by [FlutterDriver].
class LiveWidgetController extends WidgetController {
  /// Creates a widget controller that uses the given binding.
  LiveWidgetController(WidgetsBinding binding) : super(binding);

  @override
642
  Future<void> pump(Duration duration) async {
643
    if (duration != null)
644
      await Future<void>.delayed(duration);
645 646 647 648
    binding.scheduleFrame();
    await binding.endOfFrame;
  }
}