// Copyright 2016 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: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; import 'binding.dart'; /// Ensure the [WidgetsBinding] is initialized. WidgetsBinding ensureInitialized([@visibleForTesting Map<String, String> 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']; 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) { 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<ByteData>.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 { @override HttpClient createHttpClient(SecurityContext _) { return _MockHttpClient(); } } /// A mocked [HttpClient] which always returns a [_MockHttpRequest]. 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()); } } /// A mocked [HttpClientRequest] which always returns a [_MockHttpClientResponse]. 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 = '' ]) { } } /// A mocked [HttpClientResponse] which is empty and has a [statusCode] of 400. // TODO(tvolkert): Change to `extends Stream<Uint8List>` once // https://dart-review.googlesource.com/c/sdk/+/104525 is rolled into the framework. 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); } } /// A mocked [HttpHeaders] that ignores all writes. 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; }