// Copyright 2014 The Flutter 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:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; // ignore: deprecated_member_use import 'package:test_api/test_api.dart' as test_package; import 'binding.dart'; /// Ensure the [WidgetsBinding] is initialized. WidgetsBinding ensureInitialized([@visibleForTesting Map? environment]) { environment ??= Platform.environment; if (WidgetsBinding.instance == null) { if (environment.containsKey('FLUTTER_TEST') && environment['FLUTTER_TEST'] != 'false') { AutomatedTestWidgetsFlutterBinding(); } else { LiveTestWidgetsFlutterBinding(); } } assert(WidgetsBinding.instance is TestWidgetsFlutterBinding); return WidgetsBinding.instance!; } /// Setup mocking of the global [HttpClient]. void setupHttpOverrides() { HttpOverrides.global = _MockHttpOverrides(); } /// Setup mocking of platform assets if `UNIT_TEST_ASSETS` is defined. void mockFlutterAssets() { if (!Platform.environment.containsKey('UNIT_TEST_ASSETS')) { return; } final String assetFolderPath = Platform.environment['UNIT_TEST_ASSETS']!; assert(Platform.environment['APP_NAME'] != null); final String prefix = 'packages/${Platform.environment['APP_NAME']!}/'; /// Navigation related actions (pop, push, replace) broadcasts these actions via /// platform messages. SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async {}); ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData? message) async { assert(message != null); String key = utf8.decode(message!.buffer.asUint8List()); File asset = File(path.join(assetFolderPath, key)); if (!asset.existsSync()) { // For tests in package, it will load assets with its own package prefix. // In this case, we do a best-effort look up. 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.value(encoded.buffer.asByteData()); }); } /// Provides a default [HttpClient] which always returns empty 400 responses. /// /// If another [HttpClient] is provided using [HttpOverrides.runZoned], that will /// take precedence over this provider. class _MockHttpOverrides extends HttpOverrides { bool warningPrinted = false; @override HttpClient createHttpClient(SecurityContext? _) { if (!warningPrinted) { test_package.printOnFailure( 'Warning: At least one test in this suite creates an HttpClient. When\n' 'running a test suite that uses TestWidgetsFlutterBinding, all HTTP\n' 'requests will return status code 400, and no network request will\n' 'actually be made. Any test expecting a real network connection and\n' 'status code will fail.\n' 'To test code that needs an HttpClient, provide your own HttpClient\n' 'implementation to the code under test, so that your test can\n' 'consistently provide a testable response to the code under test.'); warningPrinted = true; } return _MockHttpClient(); } } /// A mocked [HttpClient] which always returns a [_MockHttpRequest]. class _MockHttpClient implements HttpClient { @override bool autoUncompress = true; @override Duration? connectionTimeout; @override Duration idleTimeout = const Duration(seconds: 15); @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 Function(Uri url, String scheme, String realm)? f) { } @override set authenticateProxy(Future 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 delete(String host, int port, String path) { return Future.value(_MockHttpRequest()); } @override Future deleteUrl(Uri url) { return Future.value(_MockHttpRequest()); } @override set findProxy(String Function(Uri url)? f) { } @override Future get(String host, int port, String path) { return Future.value(_MockHttpRequest()); } @override Future getUrl(Uri url) { return Future.value(_MockHttpRequest()); } @override Future head(String host, int port, String path) { return Future.value(_MockHttpRequest()); } @override Future headUrl(Uri url) { return Future.value(_MockHttpRequest()); } @override Future open(String method, String host, int port, String path) { return Future.value(_MockHttpRequest()); } @override Future openUrl(String method, Uri url) { return Future.value(_MockHttpRequest()); } @override Future patch(String host, int port, String path) { return Future.value(_MockHttpRequest()); } @override Future patchUrl(Uri url) { return Future.value(_MockHttpRequest()); } @override Future post(String host, int port, String path) { return Future.value(_MockHttpRequest()); } @override Future postUrl(Uri url) { return Future.value(_MockHttpRequest()); } @override Future put(String host, int port, String path) { return Future.value(_MockHttpRequest()); } @override Future putUrl(Uri url) { return Future.value(_MockHttpRequest()); } } /// A mocked [HttpClientRequest] which always returns a [_MockHttpClientResponse]. class _MockHttpRequest extends HttpClientRequest { @override late Encoding encoding; @override final HttpHeaders headers = _MockHttpHeaders(); @override void add(List data) { } @override void addError(Object error, [ StackTrace? stackTrace ]) { } @override Future addStream(Stream> stream) { return Future.value(); } @override Future close() { return Future.value(_MockHttpResponse()); } @override void abort([Object? exception, StackTrace? stackTrace]) {} @override HttpConnectionInfo? get connectionInfo => null; @override List get cookies => []; @override Future get done async => _MockHttpResponse(); @override Future flush() { return Future.value(); } @override String get method => ''; @override Uri get uri => Uri(); @override void write(Object? obj) { } @override void writeAll(Iterable objects, [ String separator = '' ]) { } @override void writeCharCode(int charCode) { } @override void writeln([ Object? obj = '' ]) { } } /// A mocked [HttpClientResponse] which is empty and has a [statusCode] of 400. // TODO(tvolkert): Change to `extends Stream` once // https://dart-review.googlesource.com/c/sdk/+/104525 is rolled into the framework. class _MockHttpResponse implements HttpClientResponse { final Stream _delegate = Stream.fromIterable(const Iterable.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 get cookies => []; @override Future detachSocket() { return Future.error(UnsupportedError('Mocked response')); } @override bool get isRedirect => false; @override StreamSubscription listen(void Function(Uint8List event)? onData, { Function? onError, void Function()? onDone, bool? cancelOnError }) { return const Stream.empty().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); } @override bool get persistentConnection => false; @override String get reasonPhrase => ''; @override Future redirect([ String? method, Uri? url, bool? followLoops ]) { return Future.error(UnsupportedError('Mocked response')); } @override List get redirects => []; @override int get statusCode => 400; @override Future any(bool Function(Uint8List element) test) { return _delegate.any(test); } @override Stream asBroadcastStream({ void Function(StreamSubscription subscription)? onListen, void Function(StreamSubscription subscription)? onCancel, }) { return _delegate.asBroadcastStream(onListen: onListen, onCancel: onCancel); } @override Stream asyncExpand(Stream? Function(Uint8List event) convert) { return _delegate.asyncExpand(convert); } @override Stream asyncMap(FutureOr Function(Uint8List event) convert) { return _delegate.asyncMap(convert); } @override Stream cast() { return _delegate.cast(); } @override Future contains(Object? needle) { return _delegate.contains(needle); } @override Stream distinct([bool Function(Uint8List previous, Uint8List next)? equals]) { return _delegate.distinct(equals); } @override Future drain([E? futureValue]) { return _delegate.drain(futureValue); } @override Future elementAt(int index) { return _delegate.elementAt(index); } @override Future every(bool Function(Uint8List element) test) { return _delegate.every(test); } @override Stream expand(Iterable Function(Uint8List element) convert) { return _delegate.expand(convert); } @override Future get first => _delegate.first; @override Future firstWhere( bool Function(Uint8List element) test, { List Function()? orElse, }) { return _delegate.firstWhere(test, orElse: orElse == null ? null : () { return Uint8List.fromList(orElse()); }); } @override Future fold(S initialValue, S Function(S previous, Uint8List element) combine) { return _delegate.fold(initialValue, combine); } @override Future forEach(void Function(Uint8List element) action) { return _delegate.forEach(action); } @override Stream handleError( Function onError, { bool Function(dynamic error)? test, }) { return _delegate.handleError(onError, test: test); } @override bool get isBroadcast => _delegate.isBroadcast; @override Future get isEmpty => _delegate.isEmpty; @override Future join([String separator = '']) { return _delegate.join(separator); } @override Future get last => _delegate.last; @override Future lastWhere( bool Function(Uint8List element) test, { List Function()? orElse, }) { return _delegate.lastWhere(test, orElse: orElse == null ? null : () { return Uint8List.fromList(orElse()); }); } @override Future get length => _delegate.length; @override Stream map(S Function(Uint8List event) convert) { return _delegate.map(convert); } @override Future pipe(StreamConsumer> streamConsumer) { return _delegate.cast>().pipe(streamConsumer); } @override Future reduce(List Function(Uint8List previous, Uint8List element) combine) { return _delegate.reduce((Uint8List previous, Uint8List element) { return Uint8List.fromList(combine(previous, element)); }); } @override Future get single => _delegate.single; @override Future singleWhere(bool Function(Uint8List element) test, {List Function()? orElse}) { return _delegate.singleWhere(test, orElse: orElse == null ? null : () { return Uint8List.fromList(orElse()); }); } @override Stream skip(int count) { return _delegate.skip(count); } @override Stream skipWhile(bool Function(Uint8List element) test) { return _delegate.skipWhile(test); } @override Stream take(int count) { return _delegate.take(count); } @override Stream takeWhile(bool Function(Uint8List element) test) { return _delegate.takeWhile(test); } @override Stream timeout( Duration timeLimit, { void Function(EventSink sink)? onTimeout, }) { return _delegate.timeout(timeLimit, onTimeout: onTimeout); } @override Future> toList() { return _delegate.toList(); } @override Future> toSet() { return _delegate.toSet(); } @override Stream transform(StreamTransformer, S> streamTransformer) { return _delegate.cast>().transform(streamTransformer); } @override Stream where(bool Function(Uint8List event) test) { return _delegate.where(test); } } /// A mocked [HttpHeaders] that ignores all writes. class _MockHttpHeaders extends HttpHeaders { @override List? operator [](String name) => []; @override void add(String name, Object value, {bool preserveHeaderCase = false}) { } @override void clear() { } @override void forEach(void Function(String name, List 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, {bool preserveHeaderCase = false}) { } @override String? value(String name) => null; }