import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart' show TestWindow;
import 'package:quiver/testing/async.dart';
import 'package:quiver/time.dart';
import 'package:path/path.dart' as path;
import 'package:test_api/test_api.dart' as test_package;
import 'package:stack_trace/stack_trace.dart' as stack_trace;
import 'package:vector_math/vector_math_64.dart';
import 'goldens.dart';
import 'platform.dart';
import 'stack_manipulation.dart';
import 'test_async_utils.dart';
import 'test_exception_reporter.dart';
import 'test_text_input.dart';
enum EnginePhase {
build,
layout,
compositingBits,
paint,
composite,
flushSemantics,
sendSemanticsUpdate,
}
enum TestBindingEventSource {
test,
device,
}
const Size _kDefaultTestViewportSize = Size(800.0, 600.0);
abstract class TestWidgetsFlutterBinding extends BindingBase
with ServicesBinding,
SchedulerBinding,
GestureBinding,
SemanticsBinding,
RendererBinding,
PaintingBinding,
WidgetsBinding {
TestWidgetsFlutterBinding() : _window = TestWindow(window: ui.window) {
debugPrint = debugPrintOverride;
debugDisableShadows = disableShadows;
debugCheckIntrinsicSizes = checkIntrinsicSizes;
}
@override
TestWindow get window => _window;
final TestWindow _window;
@protected
DebugPrintCallback get debugPrintOverride => debugPrint;
@protected
bool get disableShadows => false;
void addTime(Duration duration);
@protected
bool get checkIntrinsicSizes => false;
static WidgetsBinding ensureInitialized([@visibleForTesting Map<String, String> environment]) {
if (!isBrowser) {
environment ??= Platform.environment;
}
if (WidgetsBinding.instance == null) {
if (isBrowser) {
AutomatedTestWidgetsFlutterBinding();
} else if (environment.containsKey('FLUTTER_TEST') && environment['FLUTTER_TEST'] != 'false') {
AutomatedTestWidgetsFlutterBinding();
} else {
LiveTestWidgetsFlutterBinding();
}
}
assert(WidgetsBinding.instance is TestWidgetsFlutterBinding);
return WidgetsBinding.instance;
}
@override
void initInstances() {
timeDilation = 1.0;
HttpOverrides.global = _MockHttpOverrides();
_testTextInput = TestTextInput(onCleared: _resetFocusedEditable)..register();
super.initInstances();
}
@override
void initLicenses() {
}
bool get inTest;
int get microtaskCount;
test_package.Timeout get defaultTestTimeout;
Clock get clock;
Future<void> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]);
Future<T> runAsync<T>(
Future<T> callback(), {
Duration additionalTime = const Duration(milliseconds: 1000),
});
Future<void> setLocale(String languageCode, String countryCode) {
return TestAsyncUtils.guard<void>(() async {
assert(inTest);
final Locale locale = Locale(languageCode, countryCode == '' ? null : countryCode);
if (isBrowser) {
return;
}
dispatchLocalesChanged(<Locale>[locale]);
});
}
Future<void> setLocales(List<Locale> locales) {
return TestAsyncUtils.guard<void>(() async {
assert(inTest);
dispatchLocalesChanged(locales);
});
}
void readTestInitialLifecycleStateFromNativeWindow() {
readInitialLifecycleStateFromNativeWindow();
}
Size _surfaceSize;
Future<void> setSurfaceSize(Size size) {
return TestAsyncUtils.guard<void>(() async {
assert(inTest);
if (_surfaceSize == size)
return;
_surfaceSize = size;
handleMetricsChanged();
});
}
@override
ViewConfiguration createViewConfiguration() {
final double devicePixelRatio = window.devicePixelRatio;
final Size size = _surfaceSize ?? window.physicalSize / devicePixelRatio;
return ViewConfiguration(
size: size,
devicePixelRatio: devicePixelRatio,
);
}
Future<void> idle() {
return TestAsyncUtils.guard<void>(() {
final Completer<void> completer = Completer<void>();
Timer.run(() {
completer.complete();
});
return completer.future;
});
}
Offset globalToLocal(Offset point) => point;
Offset localToGlobal(Offset point) => point;
@override
void dispatchEvent(
PointerEvent event,
HitTestResult hitTestResult, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
assert(source == TestBindingEventSource.test);
super.dispatchEvent(event, hitTestResult);
}
TestTextInput get testTextInput => _testTextInput;
TestTextInput _testTextInput;
EditableTextState get focusedEditable => _focusedEditable;
EditableTextState _focusedEditable;
set focusedEditable(EditableTextState value) {
if (_focusedEditable != value) {
_focusedEditable = value;
value?.requestKeyboard();
}
}
void _resetFocusedEditable() {
_focusedEditable = null;
}
dynamic takeException() {
assert(inTest);
final dynamic result = _pendingExceptionDetails?.exception;
_pendingExceptionDetails = null;
return result;
}
FlutterExceptionHandler _oldExceptionHandler;
FlutterErrorDetails _pendingExceptionDetails;
static const TextStyle _messageStyle = TextStyle(
color: Color(0xFF917FFF),
fontSize: 40.0,
);
static const Widget _preTestMessage = Center(
child: Text(
'Test starting...',
style: _messageStyle,
textDirection: TextDirection.ltr,
),
);
static const Widget _postTestMessage = Center(
child: Text(
'Test finished.',
style: _messageStyle,
textDirection: TextDirection.ltr,
),
);
bool showAppDumpInErrors = false;
Future<void> runTest(Future<void> testBody(), VoidCallback invariantTester, { String description = '', Duration timeout });
void asyncBarrier() {
TestAsyncUtils.verifyAllScopesClosed();
}
Zone _parentZone;
VoidCallback _createTestCompletionHandler(String testDescription, Completer<void> completer) {
return () {
assert(Zone.current == _parentZone);
if (_pendingExceptionDetails != null) {
debugPrint = debugPrintOverride;
reportTestException(_pendingExceptionDetails, testDescription);
_pendingExceptionDetails = null;
}
if (!completer.isCompleted)
completer.complete();
};
}
@protected
void reportExceptionNoticed(FlutterErrorDetails exception) {
}
Future<void> _runTest(
Future<void> testBody(),
VoidCallback invariantTester,
String description, {
Future<void> timeout,
}) {
assert(description != null);
assert(inTest);
_oldExceptionHandler = FlutterError.onError;
int _exceptionCount = 0;
FlutterError.onError = (FlutterErrorDetails details) {
if (_pendingExceptionDetails != null) {
debugPrint = debugPrintOverride;
if (_exceptionCount == 0) {
_exceptionCount = 2;
FlutterError.dumpErrorToConsole(_pendingExceptionDetails, forceReport: true);
} else {
_exceptionCount += 1;
}
FlutterError.dumpErrorToConsole(details, forceReport: true);
_pendingExceptionDetails = FlutterErrorDetails(
exception: 'Multiple exceptions ($_exceptionCount) were detected during the running of the current test, and at least one was unexpected.',
library: 'Flutter test framework',
);
} else {
reportExceptionNoticed(details);
_pendingExceptionDetails = details;
}
};
final Completer<void> testCompleter = Completer<void>();
final VoidCallback testCompletionHandler = _createTestCompletionHandler(description, testCompleter);
void handleUncaughtError(dynamic exception, StackTrace stack) {
if (testCompleter.isCompleted) {
debugPrint = debugPrintOverride;
FlutterError.dumpErrorToConsole(FlutterErrorDetails(
exception: exception,
stack: _unmangle(stack),
context: ErrorDescription('running a test (but after the test had completed)'),
library: 'Flutter test framework',
), forceReport: true);
return;
}
DiagnosticsNode treeDump;
try {
treeDump = renderViewElement?.toDiagnosticsNode() ?? DiagnosticsNode.message('<no tree>');
treeDump.toStringDeep();
} catch (exception) {
treeDump = DiagnosticsNode.message('<additional error caught while dumping tree: $exception>', level: DiagnosticLevel.error);
}
final List<DiagnosticsNode> omittedFrames = <DiagnosticsNode>[];
final int stackLinesToOmit = reportExpectCall(stack, omittedFrames);
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: _unmangle(stack),
context: ErrorDescription('running a test'),
library: 'Flutter test framework',
stackFilter: (Iterable<String> frames) {
return FlutterError.defaultStackFilter(frames.skip(stackLinesToOmit));
},
informationCollector: () sync* {
if (stackLinesToOmit > 0)
yield* omittedFrames;
if (showAppDumpInErrors) {
yield DiagnosticsProperty<DiagnosticsNode>('At the time of the failure, the widget tree looked as follows', treeDump, linePrefix: '# ', style: DiagnosticsTreeStyle.flat);
}
if (description.isNotEmpty)
yield DiagnosticsProperty<String>('The test description was', description, style: DiagnosticsTreeStyle.errorProperty);
},
));
assert(_parentZone != null);
assert(_pendingExceptionDetails != null, 'A test overrode FlutterError.onError but either failed to return it to its original state, or had unexpected additional errors that it could not handle. Typically, this is caused by using expect() before restoring FlutterError.onError.');
_parentZone.run<void>(testCompletionHandler);
}
final ZoneSpecification errorHandlingZoneSpecification = ZoneSpecification(
handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, dynamic exception, StackTrace stack) {
handleUncaughtError(exception, stack);
}
);
_parentZone = Zone.current;
final Zone testZone = _parentZone.fork(specification: errorHandlingZoneSpecification);
testZone.runBinary<Future<void>, Future<void> Function(), VoidCallback>(_runTestBody, testBody, invariantTester)
.whenComplete(testCompletionHandler);
timeout?.catchError(handleUncaughtError);
return testCompleter.future;
}
Future<void> _runTestBody(Future<void> testBody(), VoidCallback invariantTester) async {
assert(inTest);
runApp(Container(key: UniqueKey(), child: _preTestMessage));
await pump();
final bool autoUpdateGoldensBeforeTest = autoUpdateGoldenFiles && !isBrowser;
final TestExceptionReporter reportTestExceptionBeforeTest = reportTestException;
final ErrorWidgetBuilder errorWidgetBuilderBeforeTest = ErrorWidget.builder;
await testBody();
asyncBarrier();
if (_pendingExceptionDetails == null) {
runApp(Container(key: UniqueKey(), child: _postTestMessage));
await pump();
invariantTester();
_verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser);
_verifyReportTestExceptionUnset(reportTestExceptionBeforeTest);
_verifyErrorWidgetBuilderUnset(errorWidgetBuilderBeforeTest);
_verifyInvariants();
}
assert(inTest);
asyncBarrier();
}
void _verifyInvariants() {
assert(debugAssertNoTransientCallbacks(
'An animation is still running even after the widget tree was disposed.'
));
assert(debugAssertAllFoundationVarsUnset(
'The value of a foundation debug variable was changed by the test.',
debugPrintOverride: debugPrintOverride,
));
assert(debugAssertAllGesturesVarsUnset(
'The value of a gestures debug variable was changed by the test.',
));
assert(debugAssertAllPaintingVarsUnset(
'The value of a painting debug variable was changed by the test.',
debugDisableShadowsOverride: disableShadows,
));
assert(debugAssertAllRenderVarsUnset(
'The value of a rendering debug variable was changed by the test.',
debugCheckIntrinsicSizesOverride: checkIntrinsicSizes,
));
assert(debugAssertAllWidgetVarsUnset(
'The value of a widget debug variable was changed by the test.',
));
assert(debugAssertAllSchedulerVarsUnset(
'The value of a scheduler debug variable was changed by the test.',
));
}
void _verifyAutoUpdateGoldensUnset(bool valueBeforeTest) {
assert(() {
if (autoUpdateGoldenFiles != valueBeforeTest) {
FlutterError.reportError(FlutterErrorDetails(
exception: FlutterError(
'The value of autoUpdateGoldenFiles was changed by the test.',
),
stack: StackTrace.current,
library: 'Flutter test framework',
));
}
return true;
}());
}
void _verifyReportTestExceptionUnset(TestExceptionReporter valueBeforeTest) {
assert(() {
if (reportTestException != valueBeforeTest) {
reportTestException = valueBeforeTest;
FlutterError.reportError(FlutterErrorDetails(
exception: FlutterError(
'The value of reportTestException was changed by the test.',
),
stack: StackTrace.current,
library: 'Flutter test framework',
));
}
return true;
}());
}
void _verifyErrorWidgetBuilderUnset(ErrorWidgetBuilder valueBeforeTest) {
assert(() {
if (ErrorWidget.builder != valueBeforeTest) {
FlutterError.reportError(FlutterErrorDetails(
exception: FlutterError(
'The value of ErrorWidget.builder was changed by the test.',
),
stack: StackTrace.current,
library: 'Flutter test framework',
));
}
return true;
}());
}
void postTest() {
assert(inTest);
FlutterError.onError = _oldExceptionHandler;
_pendingExceptionDetails = null;
_parentZone = null;
buildOwner.focusManager = FocusManager();
assert(!RendererBinding.instance.mouseTracker.mouseIsConnected,
'The MouseTracker thinks that there is still a mouse connected, which indicates that a '
'test has not removed the mouse pointer which it added. Call removePointer on the '
'active mouse gesture to remove the mouse pointer.');
}
}
class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
@override
void initInstances() {
super.initInstances();
window.onBeginFrame = null;
window.onDrawFrame = null;
_mockFlutterAssets();
}
FakeAsync _currentFakeAsync;
Completer<void> _pendingAsyncTasks;
@override
Clock get clock => _clock;
Clock _clock;
@override
DebugPrintCallback get debugPrintOverride => debugPrintSynchronously;
@override
bool get disableShadows => true;
@override
bool get checkIntrinsicSizes => true;
@override
test_package.Timeout get defaultTestTimeout => const test_package.Timeout(Duration(minutes: 10));
@override
bool get inTest => _currentFakeAsync != null;
@override
int get microtaskCount => _currentFakeAsync.microtaskCount;
void _mockFlutterAssets() {
if (isBrowser) {
return;
}
if (!Platform.environment.containsKey('UNIT_TEST_ASSETS')) {
return;
}
final String assetFolderPath = Platform.environment['UNIT_TEST_ASSETS'];
final String prefix = 'packages/${Platform.environment['APP_NAME']}/';
SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async {});
defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData message) {
String key = utf8.decode(message.buffer.asUint8List());
File asset = File(path.join(assetFolderPath, key));
if (!asset.existsSync()) {
if (!key.startsWith(prefix)) {
return null;
}
key = key.replaceFirst(prefix, '');
asset = File(path.join(assetFolderPath, key));
if (!asset.existsSync()) {
return null;
}
}
final Uint8List encoded = Uint8List.fromList(asset.readAsBytesSync());
return Future<ByteData>.value(encoded.buffer.asByteData());
});
}
@override
Future<void> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
return TestAsyncUtils.guard<void>(() {
assert(inTest);
assert(_clock != null);
if (duration != null)
_currentFakeAsync.elapse(duration);
_phase = newPhase;
if (hasScheduledFrame) {
addTime(const Duration(milliseconds: 500));
_currentFakeAsync.flushMicrotasks();
handleBeginFrame(Duration(
milliseconds: _clock.now().millisecondsSinceEpoch,
));
_currentFakeAsync.flushMicrotasks();
handleDrawFrame();
}
_currentFakeAsync.flushMicrotasks();
return Future<void>.value();
});
}
@override
Future<T> runAsync<T>(
Future<T> callback(), {
Duration additionalTime = const Duration(milliseconds: 1000),
}) {
assert(additionalTime != null);
assert(() {
if (_pendingAsyncTasks == null)
return true;
throw test_package.TestFailure(
'Reentrant call to runAsync() denied.\n'
'runAsync() was called, then before its future completed, it '
'was called again. You must wait for the first returned future '
'to complete before calling runAsync() again.'
);
}());
final Zone realAsyncZone = Zone.current.fork(
specification: ZoneSpecification(
scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone zone, void f()) {
Zone.root.scheduleMicrotask(f);
},
createTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f()) {
return Zone.root.createTimer(duration, f);
},
createPeriodicTimer: (Zone self, ZoneDelegate parent, Zone zone, Duration period, void f(Timer timer)) {
return Zone.root.createPeriodicTimer(period, f);
},
),
);
addTime(additionalTime);
return realAsyncZone.run<Future<T>>(() {
_pendingAsyncTasks = Completer<void>();
return callback().catchError((dynamic exception, StackTrace stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'Flutter test framework',
context: ErrorDescription('while running async test code'),
));
return null;
}).whenComplete(() {
_pendingAsyncTasks.complete();
_pendingAsyncTasks = null;
});
});
}
@override
void scheduleWarmUpFrame() {
handleBeginFrame(null);
_currentFakeAsync.flushMicrotasks();
handleDrawFrame();
_currentFakeAsync.flushMicrotasks();
}
@override
Future<void> idle() {
final Future<void> result = super.idle();
_currentFakeAsync.elapse(Duration.zero);
return result;
}
EnginePhase _phase = EnginePhase.sendSemanticsUpdate;
@override
void drawFrame() {
assert(inTest);
try {
debugBuildingDirtyElements = true;
buildOwner.buildScope(renderViewElement);
if (_phase != EnginePhase.build) {
assert(renderView != null);
pipelineOwner.flushLayout();
if (_phase != EnginePhase.layout) {
pipelineOwner.flushCompositingBits();
if (_phase != EnginePhase.compositingBits) {
pipelineOwner.flushPaint();
if (_phase != EnginePhase.paint) {
renderView.compositeFrame();
if (_phase != EnginePhase.composite) {
pipelineOwner.flushSemantics();
assert(_phase == EnginePhase.flushSemantics ||
_phase == EnginePhase.sendSemanticsUpdate);
}
}
}
}
}
buildOwner.finalizeTree();
} finally {
debugBuildingDirtyElements = false;
}
}
Duration _timeout;
Stopwatch _timeoutStopwatch;
Timer _timeoutTimer;
Completer<void> _timeoutCompleter;
void _checkTimeout(Timer timer) {
assert(_timeoutTimer == timer);
assert(_timeout != null);
if (_timeoutStopwatch.elapsed > _timeout) {
_timeoutCompleter.completeError(
TimeoutException(
'The test exceeded the timeout. It may have hung.\n'
'Consider using "tester.binding.addTime" to increase the timeout before expensive operations.',
_timeout,
),
);
}
}
@override
void addTime(Duration duration) {
if (_timeout != null)
_timeout += duration;
}
@override
Future<void> runTest(
Future<void> testBody(),
VoidCallback invariantTester, {
String description = '',
Duration timeout,
}) {
assert(description != null);
assert(!inTest);
assert(_currentFakeAsync == null);
assert(_clock == null);
_timeout = timeout;
if (_timeout != null) {
_timeoutStopwatch = Stopwatch()..start();
_timeoutTimer = Timer.periodic(const Duration(seconds: 1), _checkTimeout);
_timeoutCompleter = Completer<void>();
}
final FakeAsync fakeAsync = FakeAsync();
_currentFakeAsync = fakeAsync;
_clock = fakeAsync.getClock(DateTime.utc(2015, 1, 1));
Future<void> testBodyResult;
fakeAsync.run((FakeAsync localFakeAsync) {
assert(fakeAsync == _currentFakeAsync);
assert(fakeAsync == localFakeAsync);
testBodyResult = _runTest(testBody, invariantTester, description, timeout: _timeoutCompleter?.future);
assert(inTest);
});
return Future<void>.microtask(() async {
final Future<void> resultFuture = testBodyResult.then<void>((_) {
});
fakeAsync.flushMicrotasks();
while (_pendingAsyncTasks != null) {
await _pendingAsyncTasks.future;
fakeAsync.flushMicrotasks();
}
return resultFuture;
});
}
@override
void asyncBarrier() {
assert(_currentFakeAsync != null);
_currentFakeAsync.flushMicrotasks();
super.asyncBarrier();
}
@override
void _verifyInvariants() {
super._verifyInvariants();
assert(
_currentFakeAsync.periodicTimerCount == 0,
'A periodic Timer is still running even after the widget tree was disposed.'
);
assert(
_currentFakeAsync.nonPeriodicTimerCount == 0,
'A Timer is still pending even after the widget tree was disposed.'
);
assert(_currentFakeAsync.microtaskCount == 0);
}
@override
void postTest() {
super.postTest();
assert(_currentFakeAsync != null);
assert(_clock != null);
_clock = null;
_currentFakeAsync = null;
_timeoutCompleter = null;
_timeoutTimer?.cancel();
_timeoutTimer = null;
_timeoutStopwatch = null;
_timeout = null;
}
}
enum LiveTestWidgetsFlutterBindingFramePolicy {
onlyPumps,
fadePointers,
fullyLive,
benchmark,
}
class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
@override
bool get inTest => _inTest;
bool _inTest = false;
@override
Clock get clock => const Clock();
@override
int get microtaskCount {
assert(false, 'microtaskCount cannot be reported when running in real time');
return -1;
}
@override
test_package.Timeout get defaultTestTimeout => test_package.Timeout.none;
Completer<void> _pendingFrame;
bool _expectingFrame = false;
bool _viewNeedsPaint = false;
bool _runningAsyncTasks = false;
LiveTestWidgetsFlutterBindingFramePolicy framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fadePointers;
@override
void addTime(Duration duration) {
}
@override
void scheduleFrame() {
if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
return;
super.scheduleFrame();
}
@override
void scheduleForcedFrame() {
if (framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark)
return;
super.scheduleForcedFrame();
}
bool _doDrawThisFrame;
@override
void handleBeginFrame(Duration rawTimeStamp) {
assert(_doDrawThisFrame == null);
if (_expectingFrame ||
(framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fullyLive) ||
(framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.benchmark) ||
(framePolicy == LiveTestWidgetsFlutterBindingFramePolicy.fadePointers && _viewNeedsPaint)) {
_doDrawThisFrame = true;
super.handleBeginFrame(rawTimeStamp);
} else {
_doDrawThisFrame = false;
}
}
@override
void handleDrawFrame() {
assert(_doDrawThisFrame != null);
if (_doDrawThisFrame)
super.handleDrawFrame();
_doDrawThisFrame = null;
_viewNeedsPaint = false;
if (_expectingFrame) {
assert(_pendingFrame != null);
_pendingFrame.complete();
_pendingFrame = null;
_expectingFrame = false;
} else if (framePolicy != LiveTestWidgetsFlutterBindingFramePolicy.benchmark) {
window.scheduleFrame();
}
}
@override
void initRenderView() {
assert(renderView == null);
renderView = _LiveTestRenderView(
configuration: createViewConfiguration(),
onNeedPaint: _handleViewNeedsPaint,
window: window,
);
renderView.scheduleInitialFrame();
}
@override
_LiveTestRenderView get renderView => super.renderView;
void _handleViewNeedsPaint() {
_viewNeedsPaint = true;
renderView.markNeedsPaint();
}
HitTestDispatcher deviceEventDispatcher;
@override
void dispatchEvent(
PointerEvent event,
HitTestResult hitTestResult, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
switch (source) {
case TestBindingEventSource.test:
if (!renderView._pointers.containsKey(event.pointer)) {
assert(event.down);
renderView._pointers[event.pointer] = _LiveTestPointerRecord(event.pointer, event.position);
} else {
renderView._pointers[event.pointer].position = event.position;
if (!event.down)
renderView._pointers[event.pointer].decay = _kPointerDecay;
}
_handleViewNeedsPaint();
super.dispatchEvent(event, hitTestResult, source: source);
break;
case TestBindingEventSource.device:
if (deviceEventDispatcher != null)
deviceEventDispatcher.dispatchEvent(event, hitTestResult);
break;
}
}
@override
Future<void> pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) {
assert(newPhase == EnginePhase.sendSemanticsUpdate);
assert(inTest);
assert(!_expectingFrame);
assert(_pendingFrame == null);
return TestAsyncUtils.guard<void>(() {
if (duration != null) {
Timer(duration, () {
_expectingFrame = true;
scheduleFrame();
});
} else {
_expectingFrame = true;
scheduleFrame();
}
_pendingFrame = Completer<void>();
return _pendingFrame.future;
});
}
@override
Future<T> runAsync<T>(
Future<T> callback(), {
Duration additionalTime = const Duration(milliseconds: 1000),
}) async {
assert(() {
if (!_runningAsyncTasks)
return true;
throw test_package.TestFailure(
'Reentrant call to runAsync() denied.\n'
'runAsync() was called, then before its future completed, it '
'was called again. You must wait for the first returned future '
'to complete before calling runAsync() again.'
);
}());
addTime(additionalTime);
_runningAsyncTasks = true;
try {
return await callback();
} catch (error, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: error,
stack: stack,
library: 'Flutter test framework',
context: ErrorSummary('while running async test code'),
));
return null;
} finally {
_runningAsyncTasks = false;
}
}
@override
Future<void> runTest(Future<void> testBody(), VoidCallback invariantTester, { String description = '', Duration timeout }) async {
assert(description != null);
assert(!inTest);
_inTest = true;
renderView._setDescription(description);
return _runTest(testBody, invariantTester, description);
}
@override
void reportExceptionNoticed(FlutterErrorDetails exception) {
final DebugPrintCallback testPrint = debugPrint;
debugPrint = debugPrintOverride;
debugPrint('(The following exception is now available via WidgetTester.takeException:)');
FlutterError.dumpErrorToConsole(exception, forceReport: true);
debugPrint(
'(If WidgetTester.takeException is called, the above exception will be ignored. '
'If it is not, then the above exception will be dumped when another exception is '
'caught by the framework or when the test ends, whichever happens first, and then '
'the test will fail due to having not caught or expected the exception.)'
);
debugPrint = testPrint;
}
@override
void postTest() {
super.postTest();
assert(!_expectingFrame);
assert(_pendingFrame == null);
_inTest = false;
}
@override
ViewConfiguration createViewConfiguration() {
return TestViewConfiguration(
size: _surfaceSize ?? _kDefaultTestViewportSize,
window: window,
);
}
@override
Offset globalToLocal(Offset point) {
final Matrix4 transform = renderView.configuration.toHitTestMatrix();
final double det = transform.invert();
assert(det != 0.0);
final Offset result = MatrixUtils.transformPoint(transform, point);
return result;
}
@override
Offset localToGlobal(Offset point) {
final Matrix4 transform = renderView.configuration.toHitTestMatrix();
return MatrixUtils.transformPoint(transform, point);
}
}
class TestViewConfiguration extends ViewConfiguration {
factory TestViewConfiguration({
Size size = _kDefaultTestViewportSize,
ui.Window window,
}) {
return TestViewConfiguration._(size, window ?? ui.window);
}
TestViewConfiguration._(Size size, ui.Window window)
: _paintMatrix = _getMatrix(size, window.devicePixelRatio, window),
_hitTestMatrix = _getMatrix(size, 1.0, window),
super(size: size);
static Matrix4 _getMatrix(Size size, double devicePixelRatio, ui.Window window) {
final double inverseRatio = devicePixelRatio / window.devicePixelRatio;
final double actualWidth = window.physicalSize.width * inverseRatio;
final double actualHeight = window.physicalSize.height * inverseRatio;
final double desiredWidth = size.width;
final double desiredHeight = size.height;
double scale, shiftX, shiftY;
if ((actualWidth / actualHeight) > (desiredWidth / desiredHeight)) {
scale = actualHeight / desiredHeight;
shiftX = (actualWidth - desiredWidth * scale) / 2.0;
shiftY = 0.0;
} else {
scale = actualWidth / desiredWidth;
shiftX = 0.0;
shiftY = (actualHeight - desiredHeight * scale) / 2.0;
}
final Matrix4 matrix = Matrix4.compose(
Vector3(shiftX, shiftY, 0.0),
Quaternion.identity(),
Vector3(scale, scale, 1.0),
);
return matrix;
}
final Matrix4 _paintMatrix;
final Matrix4 _hitTestMatrix;
@override
Matrix4 toMatrix() => _paintMatrix.clone();
Matrix4 toHitTestMatrix() => _hitTestMatrix.clone();
@override
String toString() => 'TestViewConfiguration';
}
const int _kPointerDecay = -2;
class _LiveTestPointerRecord {
_LiveTestPointerRecord(
this.pointer,
this.position,
) : color = HSVColor.fromAHSV(0.8, (35.0 * pointer) % 360.0, 1.0, 1.0).toColor(),
decay = 1;
final int pointer;
final Color color;
Offset position;
int decay;
}
class _LiveTestRenderView extends RenderView {
_LiveTestRenderView({
ViewConfiguration configuration,
this.onNeedPaint,
@required ui.Window window,
}) : super(configuration: configuration, window: window);
@override
TestViewConfiguration get configuration => super.configuration;
@override
set configuration(covariant TestViewConfiguration value) { super.configuration = value; }
final VoidCallback onNeedPaint;
final Map<int, _LiveTestPointerRecord> _pointers = <int, _LiveTestPointerRecord>{};
TextPainter _label;
static const TextStyle _labelStyle = TextStyle(
fontFamily: 'sans-serif',
fontSize: 10.0,
);
void _setDescription(String value) {
assert(value != null);
if (value.isEmpty) {
_label = null;
return;
}
_label ??= TextPainter(textAlign: TextAlign.left, textDirection: TextDirection.ltr);
_label.text = TextSpan(text: value, style: _labelStyle);
_label.layout();
if (onNeedPaint != null)
onNeedPaint();
}
@override
bool hitTest(HitTestResult result, { Offset position }) {
final Matrix4 transform = configuration.toHitTestMatrix();
final double det = transform.invert();
assert(det != 0.0);
position = MatrixUtils.transformPoint(transform, position);
return super.hitTest(result, position: position);
}
@override
void paint(PaintingContext context, Offset offset) {
assert(offset == Offset.zero);
super.paint(context, offset);
if (_pointers.isNotEmpty) {
final double radius = configuration.size.shortestSide * 0.05;
final Path path = Path()
..addOval(Rect.fromCircle(center: Offset.zero, radius: radius))
..moveTo(0.0, -radius * 2.0)
..lineTo(0.0, radius * 2.0)
..moveTo(-radius * 2.0, 0.0)
..lineTo(radius * 2.0, 0.0);
final Canvas canvas = context.canvas;
final Paint paint = Paint()
..strokeWidth = radius / 10.0
..style = PaintingStyle.stroke;
bool dirty = false;
for (int pointer in _pointers.keys) {
final _LiveTestPointerRecord record = _pointers[pointer];
paint.color = record.color.withOpacity(record.decay < 0 ? (record.decay / (_kPointerDecay - 1)) : 1.0);
canvas.drawPath(path.shift(record.position), paint);
if (record.decay < 0)
dirty = true;
record.decay += 1;
}
_pointers
.keys
.where((int pointer) => _pointers[pointer].decay == 0)
.toList()
.forEach(_pointers.remove);
if (dirty && onNeedPaint != null)
scheduleMicrotask(onNeedPaint);
}
_label?.paint(context.canvas, offset - const Offset(0.0, 10.0));
}
}
StackTrace _unmangle(StackTrace stack) {
if (stack is stack_trace.Trace)
return stack.vmTrace;
if (stack is stack_trace.Chain)
return stack.toTrace().vmTrace;
return stack;
}
class _MockHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext _) {
return _MockHttpClient();
}
}
class _MockHttpClient implements HttpClient {
@override
bool autoUncompress;
@override
Duration connectionTimeout;
@override
Duration idleTimeout;
@override
int maxConnectionsPerHost;
@override
String userAgent;
@override
void addCredentials(Uri url, String realm, HttpClientCredentials credentials) { }
@override
void addProxyCredentials(String host, int port, String realm, HttpClientCredentials credentials) { }
@override
set authenticate(Future<bool> Function(Uri url, String scheme, String realm) f) { }
@override
set authenticateProxy(Future<bool> Function(String host, int port, String scheme, String realm) f) { }
@override
set badCertificateCallback(bool Function(X509Certificate cert, String host, int port) callback) { }
@override
void close({ bool force = false }) { }
@override
Future<HttpClientRequest> delete(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> deleteUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
set findProxy(String Function(Uri url) f) { }
@override
Future<HttpClientRequest> get(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> getUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> head(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> headUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> open(String method, String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> openUrl(String method, Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> patch(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> patchUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> post(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> postUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> put(String host, int port, String path) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
@override
Future<HttpClientRequest> putUrl(Uri url) {
return Future<HttpClientRequest>.value(_MockHttpRequest());
}
}
class _MockHttpRequest extends HttpClientRequest {
@override
Encoding encoding;
@override
final HttpHeaders headers = _MockHttpHeaders();
@override
void add(List<int> data) { }
@override
void addError(Object error, [ StackTrace stackTrace ]) { }
@override
Future<void> addStream(Stream<List<int>> stream) {
return Future<void>.value();
}
@override
Future<HttpClientResponse> close() {
return Future<HttpClientResponse>.value(_MockHttpResponse());
}
@override
HttpConnectionInfo get connectionInfo => null;
@override
List<Cookie> get cookies => null;
@override
Future<HttpClientResponse> get done async => null;
@override
Future<void> flush() {
return Future<void>.value();
}
@override
String get method => null;
@override
Uri get uri => null;
@override
void write(Object obj) { }
@override
void writeAll(Iterable<Object> objects, [ String separator = '' ]) { }
@override
void writeCharCode(int charCode) { }
@override
void writeln([ Object obj = '' ]) { }
}
class _MockHttpResponse implements HttpClientResponse {
final Stream<Uint8List> _delegate = Stream<Uint8List>.fromIterable(const Iterable<Uint8List>.empty());
@override
final HttpHeaders headers = _MockHttpHeaders();
@override
X509Certificate get certificate => null;
@override
HttpConnectionInfo get connectionInfo => null;
@override
int get contentLength => -1;
@override
HttpClientResponseCompressionState get compressionState {
return HttpClientResponseCompressionState.decompressed;
}
@override
List<Cookie> get cookies => null;
@override
Future<Socket> detachSocket() {
return Future<Socket>.error(UnsupportedError('Mocked response'));
}
@override
bool get isRedirect => false;
@override
StreamSubscription<Uint8List> listen(void Function(Uint8List event) onData, { Function onError, void Function() onDone, bool cancelOnError }) {
return const Stream<Uint8List>.empty().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
}
@override
bool get persistentConnection => null;
@override
String get reasonPhrase => null;
@override
Future<HttpClientResponse> redirect([ String method, Uri url, bool followLoops ]) {
return Future<HttpClientResponse>.error(UnsupportedError('Mocked response'));
}
@override
List<RedirectInfo> get redirects => <RedirectInfo>[];
@override
int get statusCode => 400;
@override
Future<bool> any(bool Function(Uint8List element) test) {
return _delegate.any(test);
}
@override
Stream<Uint8List> asBroadcastStream({
void Function(StreamSubscription<Uint8List> subscription) onListen,
void Function(StreamSubscription<Uint8List> subscription) onCancel,
}) {
return _delegate.asBroadcastStream(onListen: onListen, onCancel: onCancel);
}
@override
Stream<E> asyncExpand<E>(Stream<E> Function(Uint8List event) convert) {
return _delegate.asyncExpand<E>(convert);
}
@override
Stream<E> asyncMap<E>(FutureOr<E> Function(Uint8List event) convert) {
return _delegate.asyncMap<E>(convert);
}
@override
Stream<R> cast<R>() {
return _delegate.cast<R>();
}
@override
Future<bool> contains(Object needle) {
return _delegate.contains(needle);
}
@override
Stream<Uint8List> distinct([bool Function(Uint8List previous, Uint8List next) equals]) {
return _delegate.distinct(equals);
}
@override
Future<E> drain<E>([E futureValue]) {
return _delegate.drain<E>(futureValue);
}
@override
Future<Uint8List> elementAt(int index) {
return _delegate.elementAt(index);
}
@override
Future<bool> every(bool Function(Uint8List element) test) {
return _delegate.every(test);
}
@override
Stream<S> expand<S>(Iterable<S> Function(Uint8List element) convert) {
return _delegate.expand(convert);
}
@override
Future<Uint8List> get first => _delegate.first;
@override
Future<Uint8List> firstWhere(
bool Function(Uint8List element) test, {
List<int> Function() orElse,
}) {
return _delegate.firstWhere(test, orElse: () {
return Uint8List.fromList(orElse());
});
}
@override
Future<S> fold<S>(S initialValue, S Function(S previous, Uint8List element) combine) {
return _delegate.fold<S>(initialValue, combine);
}
@override
Future<dynamic> forEach(void Function(Uint8List element) action) {
return _delegate.forEach(action);
}
@override
Stream<Uint8List> handleError(
Function onError, {
bool Function(dynamic error) test,
}) {
return _delegate.handleError(onError, test: test);
}
@override
bool get isBroadcast => _delegate.isBroadcast;
@override
Future<bool> get isEmpty => _delegate.isEmpty;
@override
Future<String> join([String separator = '']) {
return _delegate.join(separator);
}
@override
Future<Uint8List> get last => _delegate.last;
@override
Future<Uint8List> lastWhere(
bool Function(Uint8List element) test, {
List<int> Function() orElse,
}) {
return _delegate.lastWhere(test, orElse: () {
return Uint8List.fromList(orElse());
});
}
@override
Future<int> get length => _delegate.length;
@override
Stream<S> map<S>(S Function(Uint8List event) convert) {
return _delegate.map<S>(convert);
}
@override
Future<dynamic> pipe(StreamConsumer<List<int>> streamConsumer) {
return _delegate.cast<List<int>>().pipe(streamConsumer);
}
@override
Future<Uint8List> reduce(List<int> Function(Uint8List previous, Uint8List element) combine) {
return _delegate.reduce((Uint8List previous, Uint8List element) {
return Uint8List.fromList(combine(previous, element));
});
}
@override
Future<Uint8List> get single => _delegate.single;
@override
Future<Uint8List> singleWhere(bool Function(Uint8List element) test, {List<int> Function() orElse}) {
return _delegate.singleWhere(test, orElse: () {
return Uint8List.fromList(orElse());
});
}
@override
Stream<Uint8List> skip(int count) {
return _delegate.skip(count);
}
@override
Stream<Uint8List> skipWhile(bool Function(Uint8List element) test) {
return _delegate.skipWhile(test);
}
@override
Stream<Uint8List> take(int count) {
return _delegate.take(count);
}
@override
Stream<Uint8List> takeWhile(bool Function(Uint8List element) test) {
return _delegate.takeWhile(test);
}
@override
Stream<Uint8List> timeout(
Duration timeLimit, {
void Function(EventSink<Uint8List> sink) onTimeout,
}) {
return _delegate.timeout(timeLimit, onTimeout: onTimeout);
}
@override
Future<List<Uint8List>> toList() {
return _delegate.toList();
}
@override
Future<Set<Uint8List>> toSet() {
return _delegate.toSet();
}
@override
Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) {
return _delegate.cast<List<int>>().transform<S>(streamTransformer);
}
@override
Stream<Uint8List> where(bool Function(Uint8List event) test) {
return _delegate.where(test);
}
}
class _MockHttpHeaders extends HttpHeaders {
@override
List<String> operator [](String name) => <String>[];
@override
void add(String name, Object value) { }
@override
void clear() { }
@override
void forEach(void Function(String name, List<String> values) f) { }
@override
void noFolding(String name) { }
@override
void remove(String name, Object value) { }
@override
void removeAll(String name) { }
@override
void set(String name, Object value) { }
@override
String value(String name) => null;
}