mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

- force the time dilation to 1.0 for the Widget tests, so that a local change doesn't break all the tests during development. - add missing license block to all the files. - set ui.window.onBeginFrame to null when you use WidgetTester, so that the engine doesn't trigger any confusing frames after our fake frames.
209 lines
6.3 KiB
Dart
209 lines
6.3 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:ui' as ui;
|
|
|
|
import 'package:flutter/animation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:quiver/testing/async.dart';
|
|
import 'package:quiver/time.dart';
|
|
|
|
import '../engine/mock_events.dart';
|
|
|
|
class RootComponent extends StatefulComponent {
|
|
RootComponentState createState() => new RootComponentState();
|
|
}
|
|
|
|
class RootComponentState extends State<RootComponent> {
|
|
Widget _child = new DecoratedBox(decoration: new BoxDecoration());
|
|
Widget get child => _child;
|
|
void set child(Widget value) {
|
|
if (value != _child) {
|
|
setState(() {
|
|
_child = value;
|
|
});
|
|
}
|
|
}
|
|
Widget build(BuildContext context) => child;
|
|
}
|
|
|
|
typedef Point SizeToPointFunction(Size size);
|
|
|
|
class WidgetTester {
|
|
WidgetTester._(FakeAsync async)
|
|
: async = async,
|
|
clock = async.getClock(new DateTime.utc(2015, 1, 1)) {
|
|
timeDilation = 1.0;
|
|
ui.window.onBeginFrame = null;
|
|
}
|
|
|
|
final FakeAsync async;
|
|
final Clock clock;
|
|
|
|
void pumpWidget(Widget widget, [ Duration duration ]) {
|
|
runApp(widget);
|
|
pump(duration);
|
|
}
|
|
|
|
void pump([ Duration duration ]) {
|
|
if (duration != null)
|
|
async.elapse(duration);
|
|
scheduler.beginFrame(new Duration(milliseconds: clock.now().millisecondsSinceEpoch));
|
|
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(FlutterBinding.instance.renderView.layer);
|
|
|
|
|
|
void walkElements(ElementVisitor visitor) {
|
|
void walk(Element element) {
|
|
visitor(element);
|
|
element.visitChildren(walk);
|
|
}
|
|
WidgetFlutterBinding.instance.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);
|
|
final 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: timeStamp), result);
|
|
for(int i = 0; i < kMoveCount; i++) {
|
|
final Point location = startLocation + Offset.lerp(Offset.zero, offset, i / kMoveCount);
|
|
_dispatchEvent(p.move(location, timeStamp: timeStamp), result);
|
|
timeStamp += timeStampDelta;
|
|
}
|
|
_dispatchEvent(p.up(timeStamp: timeStamp), 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(InputEvent event, Point location) {
|
|
_dispatchEvent(event, _hitTest(location));
|
|
}
|
|
|
|
HitTestResult _hitTest(Point location) => WidgetFlutterBinding.instance.hitTest(location);
|
|
|
|
void _dispatchEvent(InputEvent event, HitTestResult result) {
|
|
WidgetFlutterBinding.instance.dispatchEvent(event, result);
|
|
}
|
|
|
|
}
|
|
|
|
void testWidgets(callback(WidgetTester tester)) {
|
|
new FakeAsync().run((FakeAsync async) {
|
|
callback(new WidgetTester._(async));
|
|
});
|
|
}
|