flutter/packages/flutter_test/lib/src/test_pointer.dart
2016-05-18 12:09:39 -07:00

205 lines
6.4 KiB
Dart

// 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:async';
import 'package:flutter/gestures.dart';
import 'test_async_utils.dart';
export 'dart:ui' show Point;
/// 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].
class TestPointer {
/// Creates a [TestPointer]. By default, the pointer identifier used is 1, however
/// this can be overridden by providing an argument to the constructor.
TestPointer([ this.pointer = 1 ]);
/// The pointer identifier used for events generated by this object.
///
/// Set when the object is constructed. Defaults to 1.
final int pointer;
/// 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 }) {
assert(!isDown);
_isDown = true;
_location = newLocation;
return new PointerDownEvent(
timeStamp: timeStamp,
pointer: pointer,
position: location
);
}
/// 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 }) {
assert(isDown);
Offset delta = newLocation - location;
_location = newLocation;
return new PointerMoveEvent(
timeStamp: timeStamp,
pointer: pointer,
down: _isDown,
position: newLocation,
delta: delta
);
}
/// 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 }) {
assert(isDown);
_isDown = false;
return new PointerUpEvent(
timeStamp: timeStamp,
pointer: pointer,
position: location
);
}
/// 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 }) {
assert(isDown);
_isDown = false;
return new PointerCancelEvent(
timeStamp: timeStamp,
pointer: pointer,
position: location
);
}
}
/// An callback that can dispatch events and returns a future that
/// completes when the event dispatch is complete.
typedef Future<Null> AsyncHitTestDispatcher(PointerEvent event, HitTestResult result);
/// A class for performing gestures in tests.
///
/// The simplest way to create a [TestGesture] is to call
/// [WidgetTester.startGesture].
class TestGesture {
TestGesture._(this._dispatcher, this._result, this._pointer);
/// 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.
///
/// By default, the global binding is used for hit testing. The
/// object to use for hit testing can be overridden by providing
/// `hitTestTarget`.
///
/// An object to use for dispatching events must be provided via the
/// `dispatcher` argument.
static Future<TestGesture> down(Point downLocation, {
int pointer: 1,
HitTestable target,
AsyncHitTestDispatcher dispatcher
}) async {
assert(dispatcher != null);
final Completer<TestGesture> completer = new Completer<TestGesture>();
TestGesture result;
TestAsyncUtils.guard(() async {
// hit test
final HitTestResult hitTestResult = new HitTestResult();
target ??= GestureBinding.instance;
assert(target != null);
target.hitTest(hitTestResult, downLocation);
// dispatch down event
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;
}
final AsyncHitTestDispatcher _dispatcher;
final HitTestResult _result;
final TestPointer _pointer;
/// Send a move event moving the pointer by the given offset.
Future<Null> moveBy(Offset offset) {
assert(_pointer._isDown);
return moveTo(_pointer.location + offset);
}
/// Send a move event moving the pointer to the given location.
Future<Null> moveTo(Point location) {
return TestAsyncUtils.guard(() {
assert(_pointer._isDown);
return _dispatcher(_pointer.move(location), _result);
});
}
/// End the gesture by releasing the pointer.
///
/// The object is no longer usable after this method has been called.
Future<Null> up() {
return TestAsyncUtils.guard(() async {
assert(_pointer._isDown);
await _dispatcher(_pointer.up(), _result);
assert(!_pointer._isDown);
return null;
});
}
/// 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.
Future<Null> cancel() {
return TestAsyncUtils.guard(() async {
assert(_pointer._isDown);
await _dispatcher(_pointer.cancel(), _result);
assert(!_pointer._isDown);
return null;
});
}
}