test_pointer.dart 6.29 KB
Newer Older
Adam Barth's avatar
Adam Barth committed
1 2 3 4
// 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.

5 6
import 'dart:async';

7
import 'package:flutter/gestures.dart';
8

9 10
import 'test_async_utils.dart';

11
export 'dart:ui' show Point;
12

13 14 15 16
/// A class for generating coherent artificial pointer events.
///
/// You can use this to manually simulate individual events, but the
/// simplest way to generate coherent gestures is to use [TestGesture].
17
class TestPointer {
18 19
  /// Creates a [TestPointer]. By default, the pointer identifier used is 1, however
  /// this can be overridden by providing an argument to the constructor.
20 21
  TestPointer([ this.pointer = 1 ]);

22 23 24 25
  /// The pointer identifier used for events generated by this object.
  ///
  /// Set when the object is constructed. Defaults to 1.
  final int pointer;
26

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
  /// Whether the pointer simulated by this object is currently down.
  ///
  /// A pointer is released (goes up) by calling [up] or [cancel].
  ///
  /// Once a pointer is released, it can no longer generate events.
  bool get isDown => _isDown;
  bool _isDown = false;

  /// The position of the last event sent by this object.
  ///
  /// If no event has ever been sent by this object, returns null.
  Point get location => _location;
  Point _location;

  /// Create a [PointerDownEvent] at the given location.
  ///
  /// By default, the time stamp on the event is [Duration.ZERO]. You
  /// can give a specific time stamp by passing the `timeStamp`
  /// argument.
  PointerDownEvent down(Point newLocation, { Duration timeStamp: Duration.ZERO }) {
47
    assert(!isDown);
48 49 50
    _isDown = true;
    _location = newLocation;
    return new PointerDownEvent(
Ian Hickson's avatar
Ian Hickson committed
51
      timeStamp: timeStamp,
52
      pointer: pointer,
Ian Hickson's avatar
Ian Hickson committed
53
      position: location
54 55 56
    );
  }

57 58 59 60 61 62
  /// Create a [PointerMoveEvent] to the given location.
  ///
  /// By default, the time stamp on the event is [Duration.ZERO]. You
  /// can give a specific time stamp by passing the `timeStamp`
  /// argument.
  PointerMoveEvent move(Point newLocation, { Duration timeStamp: Duration.ZERO }) {
63
    assert(isDown);
Ian Hickson's avatar
Ian Hickson committed
64
    Offset delta = newLocation - location;
65
    _location = newLocation;
Ian Hickson's avatar
Ian Hickson committed
66 67
    return new PointerMoveEvent(
      timeStamp: timeStamp,
68
      pointer: pointer,
Ian Hickson's avatar
Ian Hickson committed
69 70
      position: newLocation,
      delta: delta
71 72 73
    );
  }

74 75 76 77 78 79 80 81
  /// Create a [PointerUpEvent].
  ///
  /// By default, the time stamp on the event is [Duration.ZERO]. You
  /// can give a specific time stamp by passing the `timeStamp`
  /// argument.
  ///
  /// The object is no longer usable after this method has been called.
  PointerUpEvent up({ Duration timeStamp: Duration.ZERO }) {
82
    assert(isDown);
83
    _isDown = false;
Ian Hickson's avatar
Ian Hickson committed
84 85
    return new PointerUpEvent(
      timeStamp: timeStamp,
86
      pointer: pointer,
Ian Hickson's avatar
Ian Hickson committed
87
      position: location
88 89 90
    );
  }

91 92 93 94 95 96 97 98
  /// Create a [PointerCancelEvent].
  ///
  /// By default, the time stamp on the event is [Duration.ZERO]. You
  /// can give a specific time stamp by passing the `timeStamp`
  /// argument.
  ///
  /// The object is no longer usable after this method has been called.
  PointerCancelEvent cancel({ Duration timeStamp: Duration.ZERO }) {
99
    assert(isDown);
100
    _isDown = false;
Ian Hickson's avatar
Ian Hickson committed
101 102
    return new PointerCancelEvent(
      timeStamp: timeStamp,
103
      pointer: pointer,
Ian Hickson's avatar
Ian Hickson committed
104
      position: location
105 106
    );
  }
107 108
}

109
/// Signature for a callback that can dispatch events and returns a future that
110
/// completes when the event dispatch is complete.
111 112 113 114
typedef Future<Null> EventDispatcher(PointerEvent event, HitTestResult result);

/// Signature for callbacks that perform hit-testing at a given location.
typedef HitTestResult HitTester(Point location);
115

116 117 118 119 120
/// A class for performing gestures in tests.
///
/// The simplest way to create a [TestGesture] is to call
/// [WidgetTester.startGesture].
class TestGesture {
121 122
  TestGesture._(this._dispatcher, this._result, this._pointer);

123 124 125 126 127 128
  /// Create a [TestGesture] by starting with a pointerDown at the
  /// given point.
  ///
  /// By default, the pointer ID used is 1. This can be overridden by
  /// providing the `pointer` argument.
  ///
129 130 131
  /// A function to use for hit testing should be provided via the `hitTester`
  /// argument, and a function to use for dispatching events should be provided
  /// via the `dispatcher` argument.
132
  static Future<TestGesture> down(Point downLocation, {
133
    int pointer: 1,
134 135
    HitTester hitTester,
    EventDispatcher dispatcher
136
  }) async {
137
    assert(hitTester != null);
138
    assert(dispatcher != null);
139 140 141 142
    final Completer<TestGesture> completer = new Completer<TestGesture>();
    TestGesture result;
    TestAsyncUtils.guard(() async {
      // dispatch down event
143
      final HitTestResult hitTestResult = hitTester(downLocation);
144 145 146 147 148 149 150 151 152 153
      final TestPointer testPointer = new TestPointer(pointer);
      await dispatcher(testPointer.down(downLocation), hitTestResult);

      // create a TestGesture
      result = new TestGesture._(dispatcher, hitTestResult, testPointer);
      return null;
    }).whenComplete(() {
      completer.complete(result);
    });
    return completer.future;
154 155
  }

156
  final EventDispatcher _dispatcher;
157 158 159 160
  final HitTestResult _result;
  final TestPointer _pointer;

  /// Send a move event moving the pointer by the given offset.
161
  Future<Null> moveBy(Offset offset) {
162
    assert(_pointer._isDown);
163
    return moveTo(_pointer.location + offset);
164 165 166
  }

  /// Send a move event moving the pointer to the given location.
167 168 169 170 171
  Future<Null> moveTo(Point location) {
    return TestAsyncUtils.guard(() {
      assert(_pointer._isDown);
      return _dispatcher(_pointer.move(location), _result);
    });
172 173 174 175 176
  }

  /// End the gesture by releasing the pointer.
  ///
  /// The object is no longer usable after this method has been called.
177 178 179 180 181 182 183
  Future<Null> up() {
    return TestAsyncUtils.guard(() async {
      assert(_pointer._isDown);
      await _dispatcher(_pointer.up(), _result);
      assert(!_pointer._isDown);
      return null;
    });
184
  }
185

186 187 188 189 190
  /// End the gesture by canceling the pointer (as would happen if the
  /// system showed a modal dialog on top of the Flutter application,
  /// for instance).
  ///
  /// The object is no longer usable after this method has been called.
191 192 193 194 195 196 197
  Future<Null> cancel() {
    return TestAsyncUtils.guard(() async {
      assert(_pointer._isDown);
      await _dispatcher(_pointer.cancel(), _result);
      assert(!_pointer._isDown);
      return null;
    });
198
  }
199
}