test_pointer.dart 6.66 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/foundation.dart';
8
import 'package:flutter/gestures.dart';
9

10 11
import 'test_async_utils.dart';

12
export 'dart:ui' show Offset;
13

14 15 16 17
/// 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].
18
class TestPointer {
19 20 21 22 23 24
  /// Creates a [TestPointer]. By default, the pointer identifier used is 1,
  /// however this can be overridden by providing an argument to the
  /// constructor.
  ///
  /// Multiple [TestPointer]s created with the same pointer identifier will
  /// interfere with each other if they are used in parallel.
25 26
  TestPointer([ this.pointer = 1 ]);

27 28 29 30
  /// The pointer identifier used for events generated by this object.
  ///
  /// Set when the object is constructed. Defaults to 1.
  final int pointer;
31

32 33 34 35 36 37 38 39 40 41 42
  /// 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.
43 44
  Offset get location => _location;
  Offset _location;
45 46 47 48 49 50

  /// 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.
51
  PointerDownEvent down(Offset newLocation, { Duration timeStamp: Duration.ZERO }) {
52
    assert(!isDown);
53 54 55
    _isDown = true;
    _location = newLocation;
    return new PointerDownEvent(
Ian Hickson's avatar
Ian Hickson committed
56
      timeStamp: timeStamp,
57
      pointer: pointer,
Ian Hickson's avatar
Ian Hickson committed
58
      position: location
59 60 61
    );
  }

62 63 64 65 66
  /// 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.
67
  PointerMoveEvent move(Offset newLocation, { Duration timeStamp: Duration.ZERO }) {
68
    assert(isDown);
69
    final Offset delta = newLocation - location;
70
    _location = newLocation;
Ian Hickson's avatar
Ian Hickson committed
71 72
    return new PointerMoveEvent(
      timeStamp: timeStamp,
73
      pointer: pointer,
Ian Hickson's avatar
Ian Hickson committed
74 75
      position: newLocation,
      delta: delta
76 77 78
    );
  }

79 80 81 82 83 84 85 86
  /// 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 }) {
87
    assert(isDown);
88
    _isDown = false;
Ian Hickson's avatar
Ian Hickson committed
89 90
    return new PointerUpEvent(
      timeStamp: timeStamp,
91
      pointer: pointer,
Ian Hickson's avatar
Ian Hickson committed
92
      position: location
93 94 95
    );
  }

96 97 98 99 100 101 102 103
  /// 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 }) {
104
    assert(isDown);
105
    _isDown = false;
Ian Hickson's avatar
Ian Hickson committed
106 107
    return new PointerCancelEvent(
      timeStamp: timeStamp,
108
      pointer: pointer,
Ian Hickson's avatar
Ian Hickson committed
109
      position: location
110 111
    );
  }
112 113
}

114
/// Signature for a callback that can dispatch events and returns a future that
115
/// completes when the event dispatch is complete.
116 117 118
typedef Future<Null> EventDispatcher(PointerEvent event, HitTestResult result);

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

121 122 123 124 125
/// A class for performing gestures in tests.
///
/// The simplest way to create a [TestGesture] is to call
/// [WidgetTester.startGesture].
class TestGesture {
126 127
  TestGesture._(this._dispatcher, this._result, this._pointer);

128 129 130
  /// Create a [TestGesture] by starting with a pointerDown at the
  /// given point.
  ///
131
  /// By default, the pointer identifier used is 1. This can be overridden by
132 133
  /// providing the `pointer` argument.
  ///
134 135 136
  /// 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.
137
  static Future<TestGesture> down(Offset downLocation, {
138
    int pointer: 1,
139 140
    @required HitTester hitTester,
    @required EventDispatcher dispatcher,
141
  }) async {
142
    assert(hitTester != null);
143
    assert(dispatcher != null);
144
    TestGesture result;
145
    return TestAsyncUtils.guard(() async {
146
      // dispatch down event
147
      final HitTestResult hitTestResult = hitTester(downLocation);
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;
154 155 156 157
    }).then<TestGesture>((Null value) {
      return result;
    }, onError: (dynamic error, StackTrace stack) {
      return new Future<TestGesture>.error(error, stack);
158
    });
159 160
  }

161
  final EventDispatcher _dispatcher;
162 163 164 165
  final HitTestResult _result;
  final TestPointer _pointer;

  /// Send a move event moving the pointer by the given offset.
166
  Future<Null> moveBy(Offset offset, { Duration timeStamp: Duration.ZERO }) {
167
    assert(_pointer._isDown);
168
    return moveTo(_pointer.location + offset, timeStamp: timeStamp);
169 170 171
  }

  /// Send a move event moving the pointer to the given location.
172
  Future<Null> moveTo(Offset location, { Duration timeStamp: Duration.ZERO }) {
173 174
    return TestAsyncUtils.guard(() {
      assert(_pointer._isDown);
175
      return _dispatcher(_pointer.move(location, timeStamp: timeStamp), _result);
176
    });
177 178 179 180 181
  }

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

191 192 193 194 195
  /// 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.
196 197 198 199 200 201 202
  Future<Null> cancel() {
    return TestAsyncUtils.guard(() async {
      assert(_pointer._isDown);
      await _dispatcher(_pointer.cancel(), _result);
      assert(!_pointer._isDown);
      return null;
    });
203
  }
204
}