Commit 3053c0ad authored by Eric Seidel's avatar Eric Seidel

Split out Instrumentation logic from WidgetTester

This will allow writing tests/benchmark which want to use
the engine's default beginFrame and normal passage of time.

@Hixie
parent 81ab91a6
// 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:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'test_pointer.dart';
typedef Point SizeToPointFunction(Size size);
/// Helper class for flutter tests providing event dispatch.
///
/// This class provides hooks for accessing the rendering tree and dispatching
/// fake tap/drag/etc. events.
class Instrumentation {
Instrumentation() : binding = WidgetFlutterBinding.ensureInitialized();
final WidgetFlutterBinding binding;
// TODO(ianh): This should not be O(N) hidden behind a getter!
List<Layer> _layers(Layer layer) {
List<Layer> result = <Layer>[layer];
if (layer is ContainerLayer) {
ContainerLayer root = layer;
Layer child = root.firstChild;
while (child != null) {
result.addAll(_layers(child));
child = child.nextSibling;
}
}
return result;
}
List<Layer> get layers => _layers(binding.renderView.layer);
void walkElements(ElementVisitor visitor) {
void walk(Element element) {
visitor(element);
element.visitChildren(walk);
}
binding.renderViewElement.visitChildren(walk);
}
Element findElement(bool predicate(Element element)) {
try {
walkElements((Element element) {
if (predicate(element))
throw element;
});
} on Element catch (e) {
return e;
}
return null;
}
Element findElementByKey(Key key) {
return findElement((Element element) => element.widget.key == key);
}
Element findText(String text) {
return findElement((Element element) {
return element.widget is Text && element.widget.data == text;
});
}
State findStateOfType(Type type) {
StatefulComponentElement element = findElement((Element element) {
return element is StatefulComponentElement && element.state.runtimeType == type;
});
return element?.state;
}
State findStateByConfig(Widget config) {
StatefulComponentElement element = findElement((Element element) {
return element is StatefulComponentElement && element.state.config == config;
});
return element?.state;
}
Point getCenter(Element element) {
return _getElementPoint(element, (Size size) => size.center(Point.origin));
}
Point getTopLeft(Element element) {
return _getElementPoint(element, (_) => Point.origin);
}
Point getTopRight(Element element) {
return _getElementPoint(element, (Size size) => size.topRight(Point.origin));
}
Point getBottomLeft(Element element) {
return _getElementPoint(element, (Size size) => size.bottomLeft(Point.origin));
}
Point getBottomRight(Element element) {
return _getElementPoint(element, (Size size) => size.bottomRight(Point.origin));
}
Point _getElementPoint(Element element, SizeToPointFunction sizeToPoint) {
assert(element != null);
RenderBox box = element.renderObject as RenderBox;
assert(box != null);
return box.localToGlobal(sizeToPoint(box.size));
}
void tap(Element element, { int pointer: 1 }) {
tapAt(getCenter(element), pointer: pointer);
}
void tapAt(Point location, { int pointer: 1 }) {
HitTestResult result = _hitTest(location);
TestPointer p = new TestPointer(pointer);
_dispatchEvent(p.down(location), result);
_dispatchEvent(p.up(), result);
}
void fling(Element element, Offset offset, double velocity, { int pointer: 1 }) {
flingFrom(getCenter(element), offset, velocity, pointer: pointer);
}
void flingFrom(Point startLocation, Offset offset, double velocity, { int pointer: 1 }) {
assert(offset.distance > 0.0);
assert(velocity != 0.0); // velocity is pixels/second
final TestPointer p = new TestPointer(pointer);
final HitTestResult result = _hitTest(startLocation);
const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * velocity);
double timeStamp = 0.0;
_dispatchEvent(p.down(startLocation, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
for(int i = 0; i < kMoveCount; i++) {
final Point location = startLocation + Offset.lerp(Offset.zero, offset, i / kMoveCount);
_dispatchEvent(p.move(location, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
timeStamp += timeStampDelta;
}
_dispatchEvent(p.up(timeStamp: new Duration(milliseconds: timeStamp.round())), result);
}
void scroll(Element element, Offset offset, { int pointer: 1 }) {
scrollAt(getCenter(element), offset, pointer: pointer);
}
void scrollAt(Point startLocation, Offset offset, { int pointer: 1 }) {
Point endLocation = startLocation + offset;
TestPointer p = new TestPointer(pointer);
// Events for the entire press-drag-release gesture are dispatched
// to the widgets "hit" by the pointer down event.
HitTestResult result = _hitTest(startLocation);
_dispatchEvent(p.down(startLocation), result);
_dispatchEvent(p.move(endLocation), result);
_dispatchEvent(p.up(), result);
}
void dispatchEvent(PointerEvent event, Point location) {
_dispatchEvent(event, _hitTest(location));
}
HitTestResult _hitTest(Point location) {
HitTestResult result = new HitTestResult();
binding.hitTest(result, location);
return result;
}
void _dispatchEvent(PointerEvent event, HitTestResult result) {
binding.dispatchEvent(event, result);
}
}
......@@ -4,28 +4,28 @@
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:quiver/testing/async.dart';
import 'package:quiver/time.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'test_pointer.dart';
import 'instrumentation.dart';
typedef Point SizeToPointFunction(Size size);
class WidgetTester {
/// Helper class for fluter tests providing fake async.
///
/// This class extends Instrumentation to also abstract away the beginFrame
/// and async/clock access to allow writing tests which depend on the passage
/// of time without actually moving the clock forward.
class WidgetTester extends Instrumentation {
WidgetTester._(FakeAsync async)
: binding = WidgetFlutterBinding.ensureInitialized(),
async = async,
: async = async,
clock = async.getClock(new DateTime.utc(2015, 1, 1)) {
timeDilation = 1.0;
ui.window.onBeginFrame = null;
runApp(new ErrorWidget()); // flush out the last build entirely
}
final WidgetFlutterBinding binding;
final FakeAsync async;
final Clock clock;
......@@ -48,155 +48,6 @@ class WidgetTester {
);
async.flushMicrotasks();
}
List<Layer> _layers(Layer layer) {
List<Layer> result = <Layer>[layer];
if (layer is ContainerLayer) {
ContainerLayer root = layer;
Layer child = root.firstChild;
while (child != null) {
result.addAll(_layers(child));
child = child.nextSibling;
}
}
return result;
}
List<Layer> get layers => _layers(binding.renderView.layer);
void walkElements(ElementVisitor visitor) {
void walk(Element element) {
visitor(element);
element.visitChildren(walk);
}
binding.renderViewElement.visitChildren(walk);
}
Element findElement(bool predicate(Element element)) {
try {
walkElements((Element element) {
if (predicate(element))
throw element;
});
} on Element catch (e) {
return e;
}
return null;
}
Element findElementByKey(Key key) {
return findElement((Element element) => element.widget.key == key);
}
Element findText(String text) {
return findElement((Element element) {
return element.widget is Text && element.widget.data == text;
});
}
State findStateOfType(Type type) {
StatefulComponentElement element = findElement((Element element) {
return element is StatefulComponentElement && element.state.runtimeType == type;
});
return element?.state;
}
State findStateByConfig(Widget config) {
StatefulComponentElement element = findElement((Element element) {
return element is StatefulComponentElement && element.state.config == config;
});
return element?.state;
}
Point getCenter(Element element) {
return _getElementPoint(element, (Size size) => size.center(Point.origin));
}
Point getTopLeft(Element element) {
return _getElementPoint(element, (_) => Point.origin);
}
Point getTopRight(Element element) {
return _getElementPoint(element, (Size size) => size.topRight(Point.origin));
}
Point getBottomLeft(Element element) {
return _getElementPoint(element, (Size size) => size.bottomLeft(Point.origin));
}
Point getBottomRight(Element element) {
return _getElementPoint(element, (Size size) => size.bottomRight(Point.origin));
}
Point _getElementPoint(Element element, SizeToPointFunction sizeToPoint) {
assert(element != null);
RenderBox box = element.renderObject as RenderBox;
assert(box != null);
return box.localToGlobal(sizeToPoint(box.size));
}
void tap(Element element, { int pointer: 1 }) {
tapAt(getCenter(element), pointer: pointer);
}
void tapAt(Point location, { int pointer: 1 }) {
HitTestResult result = _hitTest(location);
TestPointer p = new TestPointer(pointer);
_dispatchEvent(p.down(location), result);
_dispatchEvent(p.up(), result);
}
void fling(Element element, Offset offset, double velocity, { int pointer: 1 }) {
flingFrom(getCenter(element), offset, velocity, pointer: pointer);
}
void flingFrom(Point startLocation, Offset offset, double velocity, { int pointer: 1 }) {
assert(offset.distance > 0.0);
assert(velocity != 0.0); // velocity is pixels/second
final TestPointer p = new TestPointer(pointer);
final HitTestResult result = _hitTest(startLocation);
const int kMoveCount = 50; // Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * velocity);
double timeStamp = 0.0;
_dispatchEvent(p.down(startLocation, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
for(int i = 0; i < kMoveCount; i++) {
final Point location = startLocation + Offset.lerp(Offset.zero, offset, i / kMoveCount);
_dispatchEvent(p.move(location, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
timeStamp += timeStampDelta;
}
_dispatchEvent(p.up(timeStamp: new Duration(milliseconds: timeStamp.round())), result);
}
void scroll(Element element, Offset offset, { int pointer: 1 }) {
scrollAt(getCenter(element), offset, pointer: pointer);
}
void scrollAt(Point startLocation, Offset offset, { int pointer: 1 }) {
Point endLocation = startLocation + offset;
TestPointer p = new TestPointer(pointer);
// Events for the entire press-drag-release gesture are dispatched
// to the widgets "hit" by the pointer down event.
HitTestResult result = _hitTest(startLocation);
_dispatchEvent(p.down(startLocation), result);
_dispatchEvent(p.move(endLocation), result);
_dispatchEvent(p.up(), result);
}
void dispatchEvent(PointerEvent event, Point location) {
_dispatchEvent(event, _hitTest(location));
}
HitTestResult _hitTest(Point location) {
HitTestResult result = new HitTestResult();
binding.hitTest(result, location);
return result;
}
void _dispatchEvent(PointerEvent event, HitTestResult result) {
binding.dispatchEvent(event, result);
}
}
void testWidgets(callback(WidgetTester tester)) {
......
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