Commit b7af64ee authored by Ian Hickson's avatar Ian Hickson

Refactor TestGesture (#3461)

Moves TestGesture into test_pointer.dart and makes it more
self-contained.

This is part of a general refactoring of flutter_test.

Depends on https://github.com/flutter/flutter/pull/3459
parent 3142aba4
...@@ -214,14 +214,14 @@ class FlutterDriverExtension { ...@@ -214,14 +214,14 @@ class FlutterDriverExtension {
HitTestResult hitTest = new HitTestResult(); HitTestResult hitTest = new HitTestResult();
prober.binding.hitTest(hitTest, startLocation); prober.binding.hitTest(hitTest, startLocation);
prober.dispatchEvent(pointer.down(startLocation), hitTest); prober.binding.dispatchEvent(pointer.down(startLocation), hitTest);
await new Future<Null>.value(); // so that down and move don't happen in the same microtask await new Future<Null>.value(); // so that down and move don't happen in the same microtask
for (int moves = 0; moves < totalMoves; moves++) { for (int moves = 0; moves < totalMoves; moves++) {
currentLocation = currentLocation + delta; currentLocation = currentLocation + delta;
prober.dispatchEvent(pointer.move(currentLocation), hitTest); prober.binding.dispatchEvent(pointer.move(currentLocation), hitTest);
await new Future<Null>.delayed(pause); await new Future<Null>.delayed(pause);
} }
prober.dispatchEvent(pointer.up(), hitTest); prober.binding.dispatchEvent(pointer.up(), hitTest);
return new ScrollResult(); return new ScrollResult();
} }
......
...@@ -24,13 +24,16 @@ enum EnginePhase { ...@@ -24,13 +24,16 @@ enum EnginePhase {
} }
class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding { class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding {
_SteppedWidgetFlutterBinding._(this.async);
final FakeAsync async;
/// Creates and initializes the binding. This constructor is /// Creates and initializes the binding. This constructor is
/// idempotent; calling it a second time will just return the /// idempotent; calling it a second time will just return the
/// previously-created instance. /// previously-created instance.
static WidgetFlutterBinding ensureInitialized() { static WidgetFlutterBinding ensureInitialized(FakeAsync async) {
if (WidgetFlutterBinding.instance == null) if (WidgetFlutterBinding.instance == null)
new _SteppedWidgetFlutterBinding(); new _SteppedWidgetFlutterBinding._(async);
return WidgetFlutterBinding.instance; return WidgetFlutterBinding.instance;
} }
...@@ -66,6 +69,12 @@ class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding { ...@@ -66,6 +69,12 @@ class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding {
SemanticsNode.sendSemanticsTree(); SemanticsNode.sendSemanticsTree();
} }
} }
@override
void dispatchEvent(PointerEvent event, HitTestResult result) {
super.dispatchEvent(event, result);
async.flushMicrotasks();
}
} }
/// Helper class for flutter tests providing fake async. /// Helper class for flutter tests providing fake async.
...@@ -77,7 +86,7 @@ class ElementTreeTester extends Instrumentation { ...@@ -77,7 +86,7 @@ class ElementTreeTester extends Instrumentation {
ElementTreeTester._(FakeAsync async) ElementTreeTester._(FakeAsync async)
: async = async, : async = async,
clock = async.getClock(new DateTime.utc(2015, 1, 1)), clock = async.getClock(new DateTime.utc(2015, 1, 1)),
super(binding: _SteppedWidgetFlutterBinding.ensureInitialized()) { super(binding: _SteppedWidgetFlutterBinding.ensureInitialized(async)) {
timeDilation = 1.0; timeDilation = 1.0;
ui.window.onBeginFrame = null; ui.window.onBeginFrame = null;
debugPrint = _synchronousDebugPrint; debugPrint = _synchronousDebugPrint;
...@@ -138,12 +147,6 @@ class ElementTreeTester extends Instrumentation { ...@@ -138,12 +147,6 @@ class ElementTreeTester extends Instrumentation {
async.flushMicrotasks(); async.flushMicrotasks();
} }
@override
void dispatchEvent(PointerEvent event, HitTestResult result) {
super.dispatchEvent(event, result);
async.flushMicrotasks();
}
/// Returns the exception most recently caught by the Flutter framework. /// Returns the exception most recently caught by the Flutter framework.
/// ///
/// Call this if you expect an exception during a test. If an exception is /// Call this if you expect an exception during a test. If an exception is
......
...@@ -182,8 +182,8 @@ class Instrumentation { ...@@ -182,8 +182,8 @@ class Instrumentation {
void tapAt(Point location, { int pointer: 1 }) { void tapAt(Point location, { int pointer: 1 }) {
HitTestResult result = _hitTest(location); HitTestResult result = _hitTest(location);
TestPointer p = new TestPointer(pointer); TestPointer p = new TestPointer(pointer);
dispatchEvent(p.down(location), result); binding.dispatchEvent(p.down(location), result);
dispatchEvent(p.up(), result); binding.dispatchEvent(p.up(), result);
} }
/// Attempts a fling gesture starting from the center of the given /// Attempts a fling gesture starting from the center of the given
...@@ -205,13 +205,13 @@ class Instrumentation { ...@@ -205,13 +205,13 @@ class Instrumentation {
const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * velocity); final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * velocity);
double timeStamp = 0.0; double timeStamp = 0.0;
dispatchEvent(p.down(startLocation, timeStamp: new Duration(milliseconds: timeStamp.round())), result); binding.dispatchEvent(p.down(startLocation, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
for(int i = 0; i <= kMoveCount; i++) { for(int i = 0; i <= kMoveCount; i++) {
final Point location = startLocation + Offset.lerp(Offset.zero, offset, i / kMoveCount); final Point location = startLocation + Offset.lerp(Offset.zero, offset, i / kMoveCount);
dispatchEvent(p.move(location, timeStamp: new Duration(milliseconds: timeStamp.round())), result); binding.dispatchEvent(p.move(location, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
timeStamp += timeStampDelta; timeStamp += timeStampDelta;
} }
dispatchEvent(p.up(timeStamp: new Duration(milliseconds: timeStamp.round())), result); binding.dispatchEvent(p.up(timeStamp: new Duration(milliseconds: timeStamp.round())), result);
} }
/// Attempts to drag the given element by the given offset, by /// Attempts to drag the given element by the given offset, by
...@@ -231,18 +231,15 @@ class Instrumentation { ...@@ -231,18 +231,15 @@ class Instrumentation {
// Events for the entire press-drag-release gesture are dispatched // Events for the entire press-drag-release gesture are dispatched
// to the widgets "hit" by the pointer down event. // to the widgets "hit" by the pointer down event.
HitTestResult result = _hitTest(startLocation); HitTestResult result = _hitTest(startLocation);
dispatchEvent(p.down(startLocation), result); binding.dispatchEvent(p.down(startLocation), result);
dispatchEvent(p.move(endLocation), result); binding.dispatchEvent(p.move(endLocation), result);
dispatchEvent(p.up(), result); binding.dispatchEvent(p.up(), result);
} }
/// Begins a gesture at a particular point, and returns the /// Begins a gesture at a particular point, and returns the
/// [TestGesture] object which you can use to continue the gesture. /// [TestGesture] object which you can use to continue the gesture.
TestGesture startGesture(Point downLocation, { int pointer: 1 }) { TestGesture startGesture(Point downLocation, { int pointer: 1 }) {
TestPointer p = new TestPointer(pointer); return new TestGesture(downLocation, pointer: pointer);
HitTestResult result = _hitTest(downLocation);
dispatchEvent(p.down(downLocation), result);
return new TestGesture._(this, result, p);
} }
HitTestResult _hitTest(Point location) { HitTestResult _hitTest(Point location) {
...@@ -250,55 +247,6 @@ class Instrumentation { ...@@ -250,55 +247,6 @@ class Instrumentation {
binding.hitTest(result, location); binding.hitTest(result, location);
return result; return result;
} }
/// Sends a [PointerEvent] at a particular [HitTestResult].
///
/// Generally speaking, it is preferred to use one of the more
/// semantically meaningful ways to dispatch events in tests, in
/// particular: [tap], [tapAt], [fling], [flingFrom], [scroll],
/// [scrollAt], or [startGesture].
void dispatchEvent(PointerEvent event, HitTestResult result) {
binding.dispatchEvent(event, result);
}
}
/// A class for performing gestures in tests. To create a
/// [TestGesture], call [WidgetTester.startGesture].
class TestGesture {
TestGesture._(this._target, this._result, this.pointer);
final Instrumentation _target;
final HitTestResult _result;
final TestPointer pointer;
bool _isDown = true;
/// Send a move event moving the pointer to the given location.
void moveTo(Point location) {
assert(_isDown);
_target.dispatchEvent(pointer.move(location), _result);
}
/// Send a move event moving the pointer by the given offset.
void moveBy(Offset offset) {
assert(_isDown);
moveTo(pointer.location + offset);
}
/// End the gesture by releasing the pointer.
void up() {
assert(_isDown);
_isDown = false;
_target.dispatchEvent(pointer.up(), _result);
}
/// 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).
void cancel() {
assert(_isDown);
_isDown = false;
_target.dispatchEvent(pointer.cancel(), _result);
}
} }
class _DepthFirstChildIterable extends IterableBase<Element> { class _DepthFirstChildIterable extends IterableBase<Element> {
......
...@@ -6,28 +6,59 @@ import 'package:flutter/gestures.dart'; ...@@ -6,28 +6,59 @@ import 'package:flutter/gestures.dart';
export 'dart:ui' show Point; 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 { 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 ]); TestPointer([ this.pointer = 1 ]);
int pointer; /// The pointer identifier used for events generated by this object.
bool isDown = false; ///
Point location; /// Set when the object is constructed. Defaults to 1.
final int pointer;
PointerDownEvent down(Point newLocation, { Duration timeStamp: const Duration() }) { /// 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); assert(!isDown);
isDown = true; _isDown = true;
location = newLocation; _location = newLocation;
return new PointerDownEvent( return new PointerDownEvent(
timeStamp: timeStamp, timeStamp: timeStamp,
pointer: pointer, pointer: pointer,
position: location position: location
); );
} }
PointerMoveEvent move(Point newLocation, { Duration timeStamp: const Duration() }) { /// 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); assert(isDown);
Offset delta = newLocation - location; Offset delta = newLocation - location;
location = newLocation; _location = newLocation;
return new PointerMoveEvent( return new PointerMoveEvent(
timeStamp: timeStamp, timeStamp: timeStamp,
pointer: pointer, pointer: pointer,
...@@ -36,9 +67,16 @@ class TestPointer { ...@@ -36,9 +67,16 @@ class TestPointer {
); );
} }
PointerUpEvent up({ Duration timeStamp: const Duration() }) { /// 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); assert(isDown);
isDown = false; _isDown = false;
return new PointerUpEvent( return new PointerUpEvent(
timeStamp: timeStamp, timeStamp: timeStamp,
pointer: pointer, pointer: pointer,
...@@ -46,14 +84,96 @@ class TestPointer { ...@@ -46,14 +84,96 @@ class TestPointer {
); );
} }
PointerCancelEvent cancel({ Duration timeStamp: const Duration() }) { /// 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); assert(isDown);
isDown = false; _isDown = false;
return new PointerCancelEvent( return new PointerCancelEvent(
timeStamp: timeStamp, timeStamp: timeStamp,
pointer: pointer, pointer: pointer,
position: location position: location
); );
} }
}
/// A class for performing gestures in tests.
///
/// The simplest way to create a [TestGesture] is to call
/// [WidgetTester.startGesture].
class TestGesture {
/// 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 both for hit testing and
/// for dispatching of events. The object to use for hit testing can
/// be overridden by providing `hitTestTarget`, and the object to
/// use for dispatching events can be overridden by providing an
/// `dispatcher`.
factory TestGesture(Point downLocation, {
int pointer: 1,
HitTestable target,
HitTestDispatcher dispatcher
}) {
// hit test
final HitTestResult result = new HitTestResult();
target ??= Gesturer.instance;
assert(target != null);
target.hitTest(result, downLocation);
// dispatch down event
final TestPointer testPointer = new TestPointer(pointer);
dispatcher ??= Gesturer.instance;
assert(dispatcher != null);
dispatcher.dispatchEvent(testPointer.down(downLocation), result);
// create a TestGesture
return new TestGesture._(dispatcher, result, testPointer);
}
const TestGesture._(this._dispatcher, this._result, this._pointer);
final HitTestDispatcher _dispatcher;
final HitTestResult _result;
final TestPointer _pointer;
/// Send a move event moving the pointer by the given offset.
void moveBy(Offset offset) {
assert(_pointer._isDown);
moveTo(_pointer.location + offset);
}
/// Send a move event moving the pointer to the given location.
void moveTo(Point location) {
assert(_pointer._isDown);
_dispatcher.dispatchEvent(_pointer.move(location), _result);
}
/// End the gesture by releasing the pointer.
///
/// The object is no longer usable after this method has been called.
void up() {
assert(_pointer._isDown);
_dispatcher.dispatchEvent(_pointer.up(), _result);
assert(!_pointer._isDown);
}
/// 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.
void cancel() {
assert(_pointer._isDown);
_dispatcher.dispatchEvent(_pointer.cancel(), _result);
assert(!_pointer._isDown);
}
} }
...@@ -8,7 +8,7 @@ import 'package:quiver/testing/async.dart'; ...@@ -8,7 +8,7 @@ import 'package:quiver/testing/async.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'element_tree_tester.dart'; import 'element_tree_tester.dart';
import 'instrumentation.dart'; import 'test_pointer.dart';
/// Runs the [callback] inside the Flutter test environment. /// Runs the [callback] inside the Flutter test environment.
/// ///
...@@ -81,10 +81,8 @@ class WidgetTester { ...@@ -81,10 +81,8 @@ class WidgetTester {
} }
/// Sends an [event] at [result] location. /// Sends an [event] at [result] location.
///
/// See [ElementTreeTester.dispatchEvent] for details.
void dispatchEvent(PointerEvent event, HitTestResult result) { void dispatchEvent(PointerEvent event, HitTestResult result) {
elementTreeTester.dispatchEvent(event, result); binding.dispatchEvent(event, result);
} }
/// Returns the exception most recently caught by the Flutter framework. /// Returns the exception most recently caught by the Flutter framework.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment