flutter/packages/flutter_test/lib/src/widget_tester.dart

138 lines
4.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:ui' as ui show window;
import 'package:quiver/testing/async.dart';
import 'package:quiver/time.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'instrumentation.dart';
/// Enumeration of possible phases to reach in pumpWidget.
enum EnginePhase {
layout,
compositingBits,
paint,
composite,
flushSemantics,
sendSemanticsTree
}
class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding {
/// Creates and initializes the binding. This constructor is
/// idempotent; calling it a second time will just return the
/// previously-created instance.
static WidgetFlutterBinding ensureInitialized() {
if (WidgetFlutterBinding.instance == null)
new _SteppedWidgetFlutterBinding();
return WidgetFlutterBinding.instance;
}
EnginePhase phase = EnginePhase.sendSemanticsTree;
// Pump the rendering pipeline up to the given phase.
void beginFrame() {
buildDirtyElements();
_beginFrame();
Element.finalizeTree();
}
// Cloned from Renderer.beginFrame() but with early-exit semantics.
void _beginFrame() {
assert(renderView != null);
RenderObject.flushLayout();
if (phase == EnginePhase.layout)
return;
RenderObject.flushCompositingBits();
if (phase == EnginePhase.compositingBits)
return;
RenderObject.flushPaint();
if (phase == EnginePhase.paint)
return;
renderView.compositeFrame(); // this sends the bits to the GPU
if (phase == EnginePhase.composite)
return;
if (SemanticsNode.hasListeners) {
RenderObject.flushSemantics();
if (phase == EnginePhase.flushSemantics)
return;
SemanticsNode.sendSemanticsTree();
}
}
}
/// Helper class for flutter 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)
: super(binding: _SteppedWidgetFlutterBinding.ensureInitialized()),
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 FakeAsync async;
final Clock clock;
/// Calls [runApp()] with the given widget, then triggers a frame sequent and
/// flushes microtasks, by calling [pump()] with the same duration (if any).
/// The supplied EnginePhase is the final phase reached during the pump pass;
/// if not supplied, the whole pass is executed.
void pumpWidget(Widget widget, [ Duration duration, EnginePhase phase ]) {
if (binding is _SteppedWidgetFlutterBinding) {
// Some tests call WidgetFlutterBinding.ensureInitialized() manually, so
// we can't actually be sure we have a stepped binding.
_SteppedWidgetFlutterBinding steppedBinding = binding;
steppedBinding.phase = phase ?? EnginePhase.sendSemanticsTree;
} else {
// Can't step to a given phase in that case
assert(phase == null);
}
runApp(widget);
pump(duration);
}
/// Artificially calls dispatchLocaleChanged on the Widget binding,
/// then flushes microtasks.
void setLocale(String languageCode, String countryCode) {
Locale locale = new Locale(languageCode, countryCode);
binding.dispatchLocaleChanged(locale);
async.flushMicrotasks();
}
/// Triggers a frame sequence (build/layout/paint/etc),
/// then flushes microtasks.
///
/// If duration is set, then advances the clock by that much first.
/// Doing this flushes microtasks.
void pump([ Duration duration ]) {
if (duration != null)
async.elapse(duration);
binding.handleBeginFrame(new Duration(
milliseconds: clock.now().millisecondsSinceEpoch)
);
async.flushMicrotasks();
}
void dispatchEvent(PointerEvent event, HitTestResult result) {
super.dispatchEvent(event, result);
async.flushMicrotasks();
}
}
void testWidgets(callback(WidgetTester tester)) {
new FakeAsync().run((FakeAsync async) {
callback(new WidgetTester._(async));
});
}