mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[flutter_web_plugins] Migrate to null safety. (#69844)
This commit is contained in:
parent
2b512781a0
commit
e148bf8785
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
/// The platform channels and plugin registry implementations for
|
||||
/// the web implementations of Flutter plugins.
|
||||
///
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
@JS()
|
||||
library js_location_strategy;
|
||||
|
||||
@ -16,7 +14,7 @@ import 'package:meta/meta.dart';
|
||||
|
||||
import 'url_strategy.dart';
|
||||
|
||||
typedef _JsSetUrlStrategy = void Function(JsUrlStrategy);
|
||||
typedef _JsSetUrlStrategy = void Function(JsUrlStrategy?);
|
||||
|
||||
/// A JavaScript hook to customize the URL strategy of a Flutter app.
|
||||
//
|
||||
@ -29,7 +27,7 @@ external _JsSetUrlStrategy get jsSetUrlStrategy;
|
||||
|
||||
typedef _PathGetter = String Function();
|
||||
|
||||
typedef _StateGetter = Object Function();
|
||||
typedef _StateGetter = Object? Function();
|
||||
|
||||
typedef _AddPopStateListener = ui.VoidCallback Function(html.EventListener);
|
||||
|
||||
@ -43,10 +41,6 @@ typedef _HistoryMove = Future<void> Function(int count);
|
||||
/// Given a Dart implementation of URL strategy, converts it to a JavaScript
|
||||
/// URL strategy to be passed through JS interop.
|
||||
JsUrlStrategy convertToJsUrlStrategy(UrlStrategy strategy) {
|
||||
if (strategy == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsUrlStrategy(
|
||||
getPath: allowInterop(strategy.getPath),
|
||||
getState: allowInterop(strategy.getState),
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:html' as html;
|
||||
import 'dart:ui' as ui;
|
||||
@ -14,8 +12,12 @@ import 'utils.dart';
|
||||
/// Change the strategy to use for handling browser URL.
|
||||
///
|
||||
/// Setting this to null disables all integration with the browser history.
|
||||
void setUrlStrategy(UrlStrategy strategy) {
|
||||
jsSetUrlStrategy(convertToJsUrlStrategy(strategy));
|
||||
void setUrlStrategy(UrlStrategy? strategy) {
|
||||
JsUrlStrategy? jsUrlStrategy;
|
||||
if (strategy != null) {
|
||||
jsUrlStrategy = convertToJsUrlStrategy(strategy);
|
||||
}
|
||||
jsSetUrlStrategy(jsUrlStrategy);
|
||||
}
|
||||
|
||||
/// Represents and reads route state from the browser's URL.
|
||||
@ -37,7 +39,7 @@ abstract class UrlStrategy {
|
||||
/// The state of the current browser history entry.
|
||||
///
|
||||
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state
|
||||
Object getState();
|
||||
Object? getState();
|
||||
|
||||
/// Given a path that's internal to the app, create the external url that
|
||||
/// will be used in the browser.
|
||||
@ -101,7 +103,7 @@ class HashUrlStrategy extends UrlStrategy {
|
||||
String getPath() {
|
||||
// the hash value is always prefixed with a `#`
|
||||
// and if it is empty then it will stay empty
|
||||
final String path = _platformLocation.hash ?? '';
|
||||
final String path = _platformLocation.hash;
|
||||
assert(path.isEmpty || path.startsWith('#'));
|
||||
|
||||
// We don't want to return an empty string as a path. Instead we default to "/".
|
||||
@ -113,7 +115,7 @@ class HashUrlStrategy extends UrlStrategy {
|
||||
}
|
||||
|
||||
@override
|
||||
Object getState() => _platformLocation.state;
|
||||
Object? getState() => _platformLocation.state;
|
||||
|
||||
@override
|
||||
String prepareExternalUrl(String internalUrl) {
|
||||
@ -148,7 +150,7 @@ class HashUrlStrategy extends UrlStrategy {
|
||||
/// `history.back` transition.
|
||||
Future<void> _waitForPopState() {
|
||||
final Completer<void> completer = Completer<void>();
|
||||
ui.VoidCallback unsubscribe;
|
||||
late ui.VoidCallback unsubscribe;
|
||||
unsubscribe = addPopStateListener((_) {
|
||||
unsubscribe();
|
||||
completer.complete();
|
||||
@ -238,7 +240,7 @@ abstract class PlatformLocation {
|
||||
/// The `state` in the current history entry.
|
||||
///
|
||||
/// See: https://developer.mozilla.org/en-US/docs/Web/API/History/state
|
||||
Object get state;
|
||||
Object? get state;
|
||||
|
||||
/// Adds a new entry to the browser history stack.
|
||||
///
|
||||
@ -266,7 +268,7 @@ abstract class PlatformLocation {
|
||||
/// The base href where the Flutter app is being served.
|
||||
///
|
||||
/// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
String getBaseHref();
|
||||
String? getBaseHref();
|
||||
}
|
||||
|
||||
/// Delegates to real browser APIs to provide platform location functionality.
|
||||
@ -274,6 +276,14 @@ class BrowserPlatformLocation extends PlatformLocation {
|
||||
/// Default constructor for [BrowserPlatformLocation].
|
||||
const BrowserPlatformLocation();
|
||||
|
||||
// Default value for [pathname] when it's not set in window.location.
|
||||
// According to MDN this should be ''. Chrome seems to return '/'.
|
||||
static const String _defaultPathname = '';
|
||||
|
||||
// Default value for [search] when it's not set in window.location.
|
||||
// According to both chrome, and the MDN, this is ''.
|
||||
static const String _defaultSearch = '';
|
||||
|
||||
html.Location get _location => html.window.location;
|
||||
html.History get _history => html.window.history;
|
||||
|
||||
@ -288,16 +298,16 @@ class BrowserPlatformLocation extends PlatformLocation {
|
||||
}
|
||||
|
||||
@override
|
||||
String get pathname => _location.pathname;
|
||||
String get pathname => _location.pathname ?? _defaultPathname;
|
||||
|
||||
@override
|
||||
String get search => _location.search;
|
||||
String get search => _location.search ?? _defaultSearch;
|
||||
|
||||
@override
|
||||
String get hash => _location.hash;
|
||||
|
||||
@override
|
||||
Object get state => _history.state;
|
||||
Object? get state => _history.state;
|
||||
|
||||
@override
|
||||
void pushState(Object state, String title, String url) {
|
||||
@ -315,6 +325,5 @@ class BrowserPlatformLocation extends PlatformLocation {
|
||||
}
|
||||
|
||||
@override
|
||||
String getBaseHref() => getBaseElementHrefFromDom();
|
||||
// String getBaseHref() => html.document.baseUri;
|
||||
String? getBaseHref() => getBaseElementHrefFromDom();
|
||||
}
|
||||
|
@ -2,43 +2,33 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:html';
|
||||
|
||||
AnchorElement _urlParsingNode;
|
||||
// TODO(mdebbar): Use the `URI` class instead?
|
||||
final AnchorElement _urlParsingNode = AnchorElement();
|
||||
|
||||
/// Extracts the pathname part of a full [url].
|
||||
///
|
||||
/// Example: for the url `http://example.com/foo`, the extracted pathname will
|
||||
/// be `/foo`.
|
||||
String extractPathname(String url) {
|
||||
// TODO(mdebbar): Use the `URI` class instead?
|
||||
_urlParsingNode ??= AnchorElement();
|
||||
_urlParsingNode.href = url;
|
||||
final String pathname = _urlParsingNode.pathname;
|
||||
final String pathname = _urlParsingNode.pathname ?? '';
|
||||
return (pathname.isEmpty || pathname[0] == '/') ? pathname : '/$pathname';
|
||||
}
|
||||
|
||||
Element _baseElement;
|
||||
// The <base> element in the document.
|
||||
final Element? _baseElement = document.querySelector('base');
|
||||
|
||||
/// Finds the <base> element in the document and returns its `href` attribute.
|
||||
/// Returns the `href` attribute of the <base> element in the document.
|
||||
///
|
||||
/// Returns null if the element isn't found.
|
||||
String getBaseElementHrefFromDom() {
|
||||
if (_baseElement == null) {
|
||||
_baseElement = document.querySelector('base');
|
||||
if (_baseElement == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return _baseElement.getAttribute('href');
|
||||
}
|
||||
String? getBaseElementHrefFromDom() => _baseElement?.getAttribute('href');
|
||||
|
||||
/// Checks that [baseHref] is set.
|
||||
///
|
||||
/// Throws an exception otherwise.
|
||||
String checkBaseHref(String baseHref) {
|
||||
String checkBaseHref(String? baseHref) {
|
||||
if (baseHref == null) {
|
||||
throw Exception('Please add a <base> element to your index.html');
|
||||
}
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
@ -28,14 +26,14 @@ import 'plugin_registry.dart';
|
||||
///
|
||||
/// The first method is `listen`. When called, it begins forwarding
|
||||
/// messages to the framework side when they are added to the
|
||||
/// [controller]. This triggers the [StreamController.onListen] callback
|
||||
/// on the [controller].
|
||||
/// `controller`. This triggers the [StreamController.onListen] callback
|
||||
/// on the `controller`.
|
||||
///
|
||||
/// The other method is `cancel`. When called, it stops forwarding
|
||||
/// events to the framework. This triggers the [StreamController.onCancel]
|
||||
/// callback on the [controller].
|
||||
/// callback on the `controller`.
|
||||
///
|
||||
/// Events added to the [controller] when the framework is not
|
||||
/// Events added to the `controller` when the framework is not
|
||||
/// subscribed are silently discarded.
|
||||
class PluginEventChannel<T> {
|
||||
/// Creates a new plugin event channel.
|
||||
@ -45,8 +43,8 @@ class PluginEventChannel<T> {
|
||||
this.name, [
|
||||
this.codec = const StandardMethodCodec(),
|
||||
this.binaryMessenger,
|
||||
]) : assert(name != null),
|
||||
assert(codec != null);
|
||||
]) : assert(name != null), // ignore: unnecessary_null_comparison
|
||||
assert(codec != null); // ignore: unnecessary_null_comparison
|
||||
|
||||
/// The logical channel on which communication happens.
|
||||
///
|
||||
@ -63,7 +61,7 @@ class PluginEventChannel<T> {
|
||||
/// When this is null, the [pluginBinaryMessenger] is used instead,
|
||||
/// which sends messages from the platform-side to the
|
||||
/// framework-side.
|
||||
final BinaryMessenger binaryMessenger;
|
||||
final BinaryMessenger? binaryMessenger;
|
||||
|
||||
/// Use [setController] instead.
|
||||
///
|
||||
@ -81,7 +79,7 @@ class PluginEventChannel<T> {
|
||||
///
|
||||
/// Setting the controller to null disconnects from the channel (setting
|
||||
/// the message handler on the [binaryMessenger] to null).
|
||||
void setController(StreamController<T> controller) {
|
||||
void setController(StreamController<T>? controller) {
|
||||
final BinaryMessenger messenger = binaryMessenger ?? pluginBinaryMessenger;
|
||||
if (controller == null) {
|
||||
messenger.setMessageHandler(name, null);
|
||||
@ -105,16 +103,21 @@ class PluginEventChannel<T> {
|
||||
}
|
||||
|
||||
class _EventChannelHandler<T> {
|
||||
_EventChannelHandler(this.name, this.codec, this.controller, this.messenger) : assert(messenger != null);
|
||||
_EventChannelHandler(
|
||||
this.name,
|
||||
this.codec,
|
||||
this.controller,
|
||||
this.messenger,
|
||||
) : assert(messenger != null); // ignore: unnecessary_null_comparison
|
||||
|
||||
final String name;
|
||||
final MethodCodec codec;
|
||||
final StreamController<T> controller;
|
||||
final BinaryMessenger messenger;
|
||||
|
||||
StreamSubscription<T> subscription;
|
||||
StreamSubscription<T>? subscription;
|
||||
|
||||
Future<ByteData> handle(ByteData message) {
|
||||
Future<ByteData>? handle(ByteData? message) {
|
||||
final MethodCall call = codec.decodeMethodCall(message);
|
||||
switch (call.method) {
|
||||
case 'listen':
|
||||
@ -128,9 +131,8 @@ class _EventChannelHandler<T> {
|
||||
}
|
||||
|
||||
Future<ByteData> _listen() async {
|
||||
if (subscription != null) {
|
||||
await subscription.cancel();
|
||||
}
|
||||
// Cancel any existing subscription.
|
||||
await subscription?.cancel();
|
||||
subscription = controller.stream.listen((dynamic event) {
|
||||
messenger.send(name, codec.encodeSuccessEnvelope(event));
|
||||
}, onError: (dynamic error) {
|
||||
@ -146,7 +148,7 @@ class _EventChannelHandler<T> {
|
||||
message: 'No active subscription to cancel.',
|
||||
);
|
||||
}
|
||||
await subscription.cancel();
|
||||
await subscription!.cancel();
|
||||
subscription = null;
|
||||
return codec.encodeSuccessEnvelope(null);
|
||||
}
|
||||
|
@ -2,15 +2,14 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
typedef _MessageHandler = Future<ByteData> Function(ByteData);
|
||||
// TODO(hterkelsen): Why is this _MessageHandler duplicated here?
|
||||
typedef _MessageHandler = Future<ByteData?>? Function(ByteData?);
|
||||
|
||||
/// This class registers web platform plugins.
|
||||
///
|
||||
@ -97,12 +96,12 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
|
||||
@override
|
||||
Future<void> handlePlatformMessage(
|
||||
String channel,
|
||||
ByteData data,
|
||||
ui.PlatformMessageResponseCallback callback,
|
||||
ByteData? data,
|
||||
ui.PlatformMessageResponseCallback? callback,
|
||||
) async {
|
||||
ByteData response;
|
||||
ByteData? response;
|
||||
try {
|
||||
final MessageHandler handler = _handlers[channel];
|
||||
final MessageHandler? handler = _handlers[channel];
|
||||
if (handler != null) {
|
||||
response = await handler(data);
|
||||
}
|
||||
@ -122,9 +121,9 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
|
||||
|
||||
/// Sends a platform message from the platform side back to the framework.
|
||||
@override
|
||||
Future<ByteData> send(String channel, ByteData message) {
|
||||
Future<ByteData> send(String channel, ByteData? message) {
|
||||
final Completer<ByteData> completer = Completer<ByteData>();
|
||||
ui.window.onPlatformMessage(channel, message, (ByteData reply) {
|
||||
ui.window.onPlatformMessage!(channel, message, (ByteData? reply) {
|
||||
try {
|
||||
completer.complete(reply);
|
||||
} catch (exception, stack) {
|
||||
@ -140,7 +139,7 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
|
||||
}
|
||||
|
||||
@override
|
||||
void setMessageHandler(String channel, MessageHandler handler) {
|
||||
void setMessageHandler(String channel, MessageHandler? handler) {
|
||||
if (handler == null)
|
||||
_handlers.remove(channel);
|
||||
else
|
||||
@ -148,12 +147,12 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
|
||||
}
|
||||
|
||||
@override
|
||||
bool checkMessageHandler(String channel, MessageHandler handler) => _handlers[channel] == handler;
|
||||
bool checkMessageHandler(String channel, MessageHandler? handler) => _handlers[channel] == handler;
|
||||
|
||||
@override
|
||||
void setMockMessageHandler(
|
||||
String channel,
|
||||
Future<ByteData> Function(ByteData message) handler,
|
||||
MessageHandler? handler,
|
||||
) {
|
||||
throw FlutterError(
|
||||
'Setting mock handlers is not supported on the platform side.',
|
||||
@ -161,7 +160,7 @@ class _PlatformBinaryMessenger extends BinaryMessenger {
|
||||
}
|
||||
|
||||
@override
|
||||
bool checkMockMessageHandler(String channel, MessageHandler handler) {
|
||||
bool checkMockMessageHandler(String channel, MessageHandler? handler) {
|
||||
throw FlutterError(
|
||||
'Setting mock handlers is not supported on the platform side.',
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ author: Flutter Authors <flutter-dev@googlegroups.com>
|
||||
homepage: http://flutter.dev
|
||||
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev.28.0 <3.0.0"
|
||||
sdk: ">=2.12.0-0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:html';
|
||||
|
||||
@TestOn('chrome') // Uses web-only Flutter SDK
|
||||
@ -13,16 +11,12 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
|
||||
void main() {
|
||||
group('$HashUrlStrategy', () {
|
||||
TestPlatformLocation location;
|
||||
late TestPlatformLocation location;
|
||||
|
||||
setUp(() {
|
||||
location = TestPlatformLocation();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
location = null;
|
||||
});
|
||||
|
||||
test('leading slash is optional', () {
|
||||
final HashUrlStrategy strategy = HashUrlStrategy(location);
|
||||
|
||||
@ -48,16 +42,12 @@ void main() {
|
||||
});
|
||||
|
||||
group('$PathUrlStrategy', () {
|
||||
TestPlatformLocation location;
|
||||
late TestPlatformLocation location;
|
||||
|
||||
setUp(() {
|
||||
location = TestPlatformLocation();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
location = null;
|
||||
});
|
||||
|
||||
test('validates base href', () {
|
||||
location.baseHref = '/';
|
||||
expect(
|
||||
@ -153,7 +143,7 @@ class TestPlatformLocation extends PlatformLocation {
|
||||
String hash = '';
|
||||
|
||||
@override
|
||||
dynamic state;
|
||||
Object? Function() state = () => null;
|
||||
|
||||
/// Mocks the base href of the document.
|
||||
String baseHref = '';
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
@TestOn('browser') // Uses web-only Flutter SDK
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
@TestOn('chrome') // Uses web-only Flutter SDK
|
||||
|
||||
import 'dart:async';
|
||||
@ -29,7 +27,7 @@ void main() {
|
||||
PluginEventChannel<String>('test');
|
||||
|
||||
final StreamController<String> controller = StreamController<String>();
|
||||
sendingChannel.controller = controller;
|
||||
sendingChannel.setController(controller);
|
||||
|
||||
expect(listeningChannel.receiveBroadcastStream(),
|
||||
emitsInOrder(<String>['hello', 'world']));
|
||||
@ -61,7 +59,7 @@ void main() {
|
||||
PluginEventChannel<String>('test2');
|
||||
|
||||
final StreamController<String> controller = StreamController<String>();
|
||||
sendingChannel.controller = controller;
|
||||
sendingChannel.setController(controller);
|
||||
|
||||
expect(
|
||||
listeningChannel.receiveBroadcastStream(),
|
||||
@ -96,7 +94,7 @@ void main() {
|
||||
|
||||
final StreamController<String> controller = StreamController<String>(
|
||||
onListen: expectAsync0<void>(() {}, count: 1));
|
||||
sendingChannel.controller = controller;
|
||||
sendingChannel.setController(controller);
|
||||
|
||||
expect(listeningChannel.receiveBroadcastStream(),
|
||||
emitsInOrder(<String>['hello']));
|
||||
@ -128,11 +126,11 @@ void main() {
|
||||
|
||||
final StreamController<String> controller =
|
||||
StreamController<String>(onCancel: expectAsync0<void>(() {}));
|
||||
sendingChannel.controller = controller;
|
||||
sendingChannel.setController(controller);
|
||||
|
||||
final Stream<dynamic> eventStream =
|
||||
listeningChannel.receiveBroadcastStream();
|
||||
StreamSubscription<dynamic> subscription;
|
||||
late StreamSubscription<dynamic> subscription;
|
||||
subscription =
|
||||
eventStream.listen(expectAsync1<void, dynamic>((dynamic x) {
|
||||
expect(x, equals('hello'));
|
||||
@ -153,7 +151,7 @@ void main() {
|
||||
|
||||
final Stream<dynamic> eventStream =
|
||||
listeningChannel.receiveBroadcastStream();
|
||||
StreamSubscription<dynamic> subscription;
|
||||
late StreamSubscription<dynamic> subscription;
|
||||
subscription =
|
||||
eventStream.listen(expectAsync1<void, dynamic>((dynamic x) {
|
||||
expect(x, equals('hello'));
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
@TestOn('chrome') // Uses web-only Flutter SDK
|
||||
|
||||
import 'dart:ui' as ui; // ignore: unused_import, it looks unused as web-only elements are the only elements used.
|
||||
@ -56,8 +54,8 @@ void main() {
|
||||
const StandardMessageCodec codec = StandardMessageCodec();
|
||||
|
||||
final List<String> loggedMessages = <String>[];
|
||||
ServicesBinding.instance.defaultBinaryMessenger
|
||||
.setMessageHandler('test_send', (ByteData data) {
|
||||
ServicesBinding.instance!.defaultBinaryMessenger
|
||||
.setMessageHandler('test_send', (ByteData? data) {
|
||||
loggedMessages.add(codec.decodeMessage(data) as String);
|
||||
return null;
|
||||
});
|
||||
@ -70,14 +68,14 @@ void main() {
|
||||
'test_send', codec.encodeMessage('world'));
|
||||
expect(loggedMessages, equals(<String>['hello', 'world']));
|
||||
|
||||
ServicesBinding.instance.defaultBinaryMessenger
|
||||
ServicesBinding.instance!.defaultBinaryMessenger
|
||||
.setMessageHandler('test_send', null);
|
||||
});
|
||||
|
||||
test('throws when trying to set a mock handler', () {
|
||||
expect(
|
||||
() => pluginBinaryMessenger.setMockMessageHandler(
|
||||
'test', (ByteData data) async => ByteData(0)),
|
||||
'test', (ByteData? data) async => ByteData(0)),
|
||||
throwsFlutterError);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user