diff --git a/dev/automated_tests/flutter_test/ticker_test.dart b/dev/automated_tests/flutter_test/ticker_test.dart index 48d53f5500..6881468b2f 100644 --- a/dev/automated_tests/flutter_test/ticker_test.dart +++ b/dev/automated_tests/flutter_test/ticker_test.dart @@ -11,6 +11,6 @@ void main() { Ticker((Duration duration) { }).start(); final ByteData? message = const StringCodec().encodeMessage('AppLifecycleState.paused'); - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) {}); + await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) {}); }); } diff --git a/dev/integration_tests/web_e2e_tests/test_driver/text_editing_integration.dart b/dev/integration_tests/web_e2e_tests/test_driver/text_editing_integration.dart index ef84b1b3a3..36e57531c2 100644 --- a/dev/integration_tests/web_e2e_tests/test_driver/text_editing_integration.dart +++ b/dev/integration_tests/web_e2e_tests/test_driver/text_editing_integration.dart @@ -20,9 +20,6 @@ void main() { app.main(); await tester.pumpAndSettle(); - // TODO(nurhan): https://github.com/flutter/flutter/issues/51885 - SystemChannels.textInput.setMockMethodCallHandler(null); - // Focus on a TextFormField. final Finder finder = find.byKey(const Key('input')); expect(finder, findsOneWidget); @@ -48,9 +45,6 @@ void main() { app.main(); await tester.pumpAndSettle(); - // TODO(nurhan): https://github.com/flutter/flutter/issues/51885 - SystemChannels.textInput.setMockMethodCallHandler(null); - // Focus on a TextFormField. final Finder finder = find.byKey(const Key('empty-input')); expect(finder, findsOneWidget); @@ -76,9 +70,6 @@ void main() { app.main(); await tester.pumpAndSettle(); - // TODO(nurhan): https://github.com/flutter/flutter/issues/51885 - SystemChannels.textInput.setMockMethodCallHandler(null); - // This text will show no-enter initially. It will have 'enter-pressed' // after `onFieldSubmitted` of TextField is triggered. final Finder textFinder = find.byKey(const Key('text')); @@ -112,9 +103,6 @@ void main() { app.main(); await tester.pumpAndSettle(); - // TODO(nurhan): https://github.com/flutter/flutter/issues/51885 - SystemChannels.textInput.setMockMethodCallHandler(null); - // Focus on a TextFormField. final Finder finder = find.byKey(const Key('input')); expect(finder, findsOneWidget); @@ -147,9 +135,6 @@ void main() { app.main(); await tester.pumpAndSettle(); - // TODO(nurhan): https://github.com/flutter/flutter/issues/51885 - SystemChannels.textInput.setMockMethodCallHandler(null); - // Focus on a TextFormField. final Finder finder = find.byKey(const Key('input')); expect(finder, findsOneWidget); @@ -197,9 +182,6 @@ void main() { app.main(); await tester.pumpAndSettle(); - // TODO(nurhan): https://github.com/flutter/flutter/issues/51885 - SystemChannels.textInput.setMockMethodCallHandler(null); - // Select something from the selectable text. final Finder finder = find.byKey(const Key('selectable')); expect(finder, findsOneWidget); diff --git a/examples/hello_world/test_driver/smoke_web_engine_test.dart b/examples/hello_world/test_driver/smoke_web_engine_test.dart index 2ca900c064..b2892225b9 100644 --- a/examples/hello_world/test_driver/smoke_web_engine_test.dart +++ b/examples/hello_world/test_driver/smoke_web_engine_test.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'package:flutter_driver/flutter_driver.dart'; -import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; +import 'package:test/test.dart'; import 'package:webdriver/async_io.dart'; /// The following test is used as a simple smoke test for verifying Flutter @@ -33,6 +33,7 @@ void main() { test('enable accessibility', () async { await driver.enableAccessibility(); + // TODO(ianh): this delay violates our style guide. We should instead wait for a triggering event. await Future.delayed(const Duration(seconds: 2)); // Elements with tag "flt-semantics" would show up after enabling diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart index 97a9237d69..71fe7cd043 100644 --- a/packages/flutter/lib/src/foundation/binding.dart +++ b/packages/flutter/lib/src/foundation/binding.dart @@ -104,8 +104,8 @@ abstract class BindingBase { /// A number of additional bindings are defined as extensions of /// [BindingBase], e.g., [ServicesBinding], [RendererBinding], and /// [WidgetsBinding]. Each of these bindings define behaviors that interact - /// with a [ui.PlatformDispatcher], e.g., [ServicesBinding] registers a - /// [ui.PlatformDispatcher.onPlatformMessage] handler, and [RendererBinding] + /// with a [ui.PlatformDispatcher], e.g., [ServicesBinding] registers + /// listeners with the [ChannelBuffers], and [RendererBinding] /// registers [ui.PlatformDispatcher.onMetricsChanged], /// [ui.PlatformDispatcher.onTextScaleFactorChanged], /// [ui.PlatformDispatcher.onSemanticsEnabledChanged], and diff --git a/packages/flutter/lib/src/services/binary_messenger.dart b/packages/flutter/lib/src/services/binary_messenger.dart index 39ac547554..1acf6f5cb2 100644 --- a/packages/flutter/lib/src/services/binary_messenger.dart +++ b/packages/flutter/lib/src/services/binary_messenger.dart @@ -2,12 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'dart:typed_data'; import 'dart:ui' as ui; -import 'binding.dart'; - /// A function which takes a platform message and asynchronously returns an encoded response. typedef MessageHandler = Future? Function(ByteData? message); @@ -19,12 +16,39 @@ abstract class BinaryMessenger { /// const constructors so that they can be used in const expressions. const BinaryMessenger(); - /// Calls the handler registered for the given channel. + /// Queues a message. /// - /// Typically called by [ServicesBinding] to handle platform messages received - /// from [dart:ui.PlatformDispatcher.onPlatformMessage]. + /// The returned future completes immediately. + /// + /// This method adds the provided message to the given channel (named by the + /// `channel` argument) of the [ChannelBuffers] object. This simulates what + /// happens when a plugin on the platform thread (e.g. Kotlin or Swift code) + /// sends a message to the plugin package on the Dart thread. + /// + /// The `data` argument contains the message as encoded bytes. (The format + /// used for the message depends on the channel.) + /// + /// The `callback` argument, if non-null, is eventually invoked with the + /// response that would have been sent to the platform thread. + /// + /// In production code, it is more efficient to call + /// `ServicesBinding.instance.channelBuffers.push` directly. + /// + /// In tests, consider using + /// `tester.binding.defaultBinaryMessenger.handlePlatformMessage` (see + /// [WidgetTester], [TestWidgetsFlutterBinding], [TestDefaultBinaryMessenger], + /// and [TestDefaultBinaryMessenger.handlePlatformMessage] respectively). /// /// To register a handler for a given message channel, see [setMessageHandler]. + /// + /// To send a message _to_ a plugin on the platform thread, see [send]. + // TODO(ianh): deprecate this method once cocoon and other customer_tests are migrated: + // @NotYetDeprecated( + // 'Instead of calling this method, use ServicesBinding.instance.channelBuffers.push. ' + // 'In tests, consider using tester.binding.defaultBinaryMessenger.handlePlatformMessage ' + // 'or TestDefaultBinaryMessenger.instance.defaultBinaryMessenger.handlePlatformMessage. ' + // 'This feature was deprecated after v2.1.0-10.0.pre.' + // ) Future handlePlatformMessage(String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback); /// Send a binary message to the platform plugins on the given channel. @@ -43,37 +67,6 @@ abstract class BinaryMessenger { /// The handler's return value, if non-null, is sent as a response, unencoded. void setMessageHandler(String channel, MessageHandler? handler); - /// Returns true if the `handler` argument matches the `handler` previously - /// passed to [setMessageHandler]. - /// - /// This method is useful for tests or test harnesses that want to assert the - /// handler for the specified channel has not been altered by a previous test. - /// - /// Passing null for the `handler` returns true if the handler for the - /// `channel` is not set. - bool checkMessageHandler(String channel, MessageHandler? handler); - - /// Set a mock callback for intercepting messages from the [send] method on - /// this class, on the given channel, without decoding them. - /// - /// The given callback will replace the currently registered mock callback for - /// that channel, if any. To remove the mock handler, pass null as the - /// `handler` argument. - /// - /// The handler's return value, if non-null, is used as a response, unencoded. - /// - /// This is intended for testing. Messages intercepted in this manner are not - /// sent to platform plugins. - void setMockMessageHandler(String channel, MessageHandler? handler); - - /// Returns true if the `handler` argument matches the `handler` previously - /// passed to [setMockMessageHandler]. - /// - /// This method is useful for tests or test harnesses that want to assert the - /// mock handler for the specified channel has not been altered by a previous - /// test. - /// - /// Passing null for the `handler` returns true if the handler for the - /// `channel` is not set. - bool checkMockMessageHandler(String channel, MessageHandler? handler); + // Looking for setMockMessageHandler or checkMockMessageHandler? + // See this shim package: packages/flutter_test/lib/src/deprecated.dart } diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart index 9ddca8a8c5..d0e6bcaf04 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -30,7 +30,6 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { _instance = this; _defaultBinaryMessenger = createBinaryMessenger(); _restorationManager = createRestorationManager(); - window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage; initLicenses(); SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object)); SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage); @@ -49,6 +48,28 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger; late BinaryMessenger _defaultBinaryMessenger; + /// The low level buffering and dispatch mechanism for messages sent by + /// plugins on the engine side to their corresponding plugin code on + /// the framework side. + /// + /// This exposes the [dart:ui.channelBuffers] object. Bindings can override + /// this getter to intercept calls to the [ChannelBuffers] mechanism (for + /// example, for tests). + /// + /// In production, direct access to this object should not be necessary. + /// Messages are received and dispatched by the [defaultBinaryMessenger]. This + /// object is primarily used to send mock messages in tests, via the + /// [ChannelBuffers.push] method (simulating a plugin sending a message to the + /// framework). + /// + /// See also: + /// + /// * [PlatformDispatcher.sendPlatformMessage], which is used for sending + /// messages to plugins from the framework (the opposite of + /// [channelBuffers]). + /// * [platformDispatcher], the [PlatformDispatcher] singleton. + ui.ChannelBuffers get channelBuffers => ui.channelBuffers; + /// Creates a default [BinaryMessenger] instance that can be used for sending /// platform messages. @protected @@ -56,7 +77,6 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { return const _DefaultBinaryMessenger._(); } - /// Called when the operating system notifies the application of a memory /// pressure situation. /// @@ -253,17 +273,20 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { class _DefaultBinaryMessenger extends BinaryMessenger { const _DefaultBinaryMessenger._(); - // Handlers for incoming messages from platform plugins. - // This is static so that this class can have a const constructor. - static final Map _handlers = - {}; + @override + Future handlePlatformMessage( + String channel, + ByteData? message, + ui.PlatformMessageResponseCallback? callback, + ) async { + ui.channelBuffers.push(channel, message, (ByteData? data) { + if (callback != null) + callback(data); + }); + } - // Mock handlers that intercept and respond to outgoing messages. - // This is static so that this class can have a const constructor. - static final Map _mockHandlers = - {}; - - Future _sendPlatformMessage(String channel, ByteData? message) { + @override + Future send(String channel, ByteData? message) { final Completer completer = Completer(); // ui.PlatformDispatcher.instance is accessed directly instead of using // ServicesBinding.instance.platformDispatcher because this method might be @@ -272,6 +295,8 @@ class _DefaultBinaryMessenger extends BinaryMessenger { // ui.PlatformDispatcher.instance because the PlatformDispatcher may be // dependency injected elsewhere with a different instance. However, static // access at this location seems to be the least bad option. + // TODO(ianh): Use ServicesBinding.instance once we have better diagnostics + // on that getter. ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) { try { completer.complete(reply); @@ -287,70 +312,27 @@ class _DefaultBinaryMessenger extends BinaryMessenger { return completer.future; } - @override - // TODO(goderbauer): Add pragma (and enable test in - // break_on_framework_exceptions_test.dart) when it works on async methods, - // https://github.com/dart-lang/sdk/issues/45673 - // @pragma('vm:notify-debugger-on-exception') - Future handlePlatformMessage( - String channel, - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) async { - ByteData? response; - try { - final MessageHandler? handler = _handlers[channel]; - if (handler != null) { - response = await handler(data); - } else { - ui.channelBuffers.push(channel, data, callback!); - callback = null; - } - } catch (exception, stack) { - FlutterError.reportError(FlutterErrorDetails( - exception: exception, - stack: stack, - library: 'services library', - context: ErrorDescription('during a platform message callback'), - )); - } finally { - if (callback != null) { - callback(response); - } - } - } - - @override - Future? send(String channel, ByteData? message) { - final MessageHandler? handler = _mockHandlers[channel]; - if (handler != null) - return handler(message); - return _sendPlatformMessage(channel, message); - } - @override void setMessageHandler(String channel, MessageHandler? handler) { if (handler == null) { - _handlers.remove(channel); + ui.channelBuffers.clearListener(channel); } else { - _handlers[channel] = handler; - ui.channelBuffers.drain(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async { - await handlePlatformMessage(channel, data, callback); + ui.channelBuffers.setListener(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async { + ByteData? response; + try { + response = await handler(data); + } catch (exception, stack) { + + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'services library', + context: ErrorDescription('during a platform message callback'), + )); + } finally { + callback(response); + } }); } } - - @override - bool checkMessageHandler(String channel, MessageHandler? handler) => _handlers[channel] == handler; - - @override - void setMockMessageHandler(String channel, MessageHandler? handler) { - if (handler == null) - _mockHandlers.remove(channel); - else - _mockHandlers[channel] = handler; - } - - @override - bool checkMockMessageHandler(String channel, MessageHandler? handler) => _mockHandlers[channel] == handler; } diff --git a/packages/flutter/lib/src/services/message_codec.dart b/packages/flutter/lib/src/services/message_codec.dart index c31297203c..ba99134778 100644 --- a/packages/flutter/lib/src/services/message_codec.dart +++ b/packages/flutter/lib/src/services/message_codec.dart @@ -95,7 +95,6 @@ abstract class MethodCodec { ByteData encodeErrorEnvelope({ required String code, String? message, Object? details}); } - /// Thrown to indicate that a platform interaction failed in the platform /// plugin. /// diff --git a/packages/flutter/lib/src/services/platform_channel.dart b/packages/flutter/lib/src/services/platform_channel.dart index c9bad0e210..c93c445466 100644 --- a/packages/flutter/lib/src/services/platform_channel.dart +++ b/packages/flutter/lib/src/services/platform_channel.dart @@ -75,31 +75,10 @@ class BasicMessageChannel { } } - /// Sets a mock callback for intercepting messages sent on this channel. - /// Messages may be null. - /// - /// The given callback will replace the currently registered mock callback for - /// this channel, if any. To remove the mock handler, pass null as the - /// `handler` argument. - /// - /// The handler's return value is used as a message reply. It may be null. - /// - /// This is intended for testing. Messages intercepted in this manner are not - /// sent to platform plugins. - void setMockMessageHandler(Future Function(T? message)? handler) { - if (handler == null) { - binaryMessenger.setMockMessageHandler(name, null); - } else { - binaryMessenger.setMockMessageHandler(name, (ByteData? message) async { - return codec.encodeMessage(await handler(codec.decodeMessage(message))); - }); - } - } + // Looking for setMockMessageHandler? + // See this shim package: packages/flutter_test/lib/src/deprecated.dart } -Expando _methodChannelHandlers = Expando(); -Expando _methodChannelMockHandlers = Expando(); - /// A named channel for communicating with platform plugins using asynchronous /// method calls. /// @@ -374,7 +353,6 @@ class MethodChannel { /// similarly to what happens if no method call handler has been set. /// Any other exception results in an error envelope being sent. void setMethodCallHandler(Future Function(MethodCall call)? handler) { - _methodChannelHandlers[this] = handler; binaryMessenger.setMessageHandler( name, handler == null @@ -383,53 +361,7 @@ class MethodChannel { ); } - /// Returns true if the `handler` argument matches the `handler` previously - /// passed to [setMethodCallHandler]. - /// - /// This method is useful for tests or test harnesses that want to assert the - /// handler for the specified channel has not been altered by a previous test. - /// - /// Passing null for the `handler` returns true if the handler for the channel - /// is not set. - bool checkMethodCallHandler(Future Function(MethodCall call)? handler) => _methodChannelHandlers[this] == handler; - - /// Sets a mock callback for intercepting method invocations on this channel. - /// - /// The given callback will replace the currently registered mock callback for - /// this channel, if any. To remove the mock handler, pass null as the - /// `handler` argument. - /// - /// Later calls to [invokeMethod] will result in a successful result, - /// a [PlatformException] or a [MissingPluginException], determined by how - /// the future returned by the mock callback completes. The [codec] of this - /// channel is used to encode and decode values and errors. - /// - /// This is intended for testing. Method calls intercepted in this manner are - /// not sent to platform plugins. - /// - /// The provided `handler` must return a `Future` that completes with the - /// return value of the call. The value will be encoded using - /// [MethodCodec.encodeSuccessEnvelope], to act as if platform plugin had - /// returned that value. - void setMockMethodCallHandler(Future? Function(MethodCall call)? handler) { - _methodChannelMockHandlers[this] = handler; - binaryMessenger.setMockMessageHandler( - name, - handler == null ? null : (ByteData? message) => _handleAsMethodCall(message, handler), - ); - } - - /// Returns true if the `handler` argument matches the `handler` previously - /// passed to [setMockMethodCallHandler]. - /// - /// This method is useful for tests or test harnesses that want to assert the - /// handler for the specified channel has not been altered by a previous test. - /// - /// Passing null for the `handler` returns true if the handler for the channel - /// is not set. - bool checkMockMethodCallHandler(Future Function(MethodCall call)? handler) => _methodChannelMockHandlers[this] == handler; - - Future _handleAsMethodCall(ByteData? message, Future? Function(MethodCall call) handler) async { + Future _handleAsMethodCall(ByteData? message, Future Function(MethodCall call) handler) async { final MethodCall call = codec.decodeMethodCall(message); try { return codec.encodeSuccessEnvelope(await handler(call)); @@ -445,6 +377,9 @@ class MethodChannel { return codec.encodeErrorEnvelope(code: 'error', message: e.toString(), details: null); } } + + // Looking for setMockMethodCallHandler or checkMethodCallHandler? + // See this shim package: packages/flutter_test/lib/src/deprecated.dart } /// A [MethodChannel] that ignores missing platform plugins. @@ -472,7 +407,6 @@ class OptionalMethodChannel extends MethodChannel { final Map? result = await invokeMethod>(method, arguments); return result?.cast(); } - } /// A named channel for communicating with platform plugins using event streams. diff --git a/packages/flutter/lib/src/services/restoration.dart b/packages/flutter/lib/src/services/restoration.dart index ab42efa24a..43956999da 100644 --- a/packages/flutter/lib/src/services/restoration.dart +++ b/packages/flutter/lib/src/services/restoration.dart @@ -166,7 +166,6 @@ class RestorationManager extends ChangeNotifier { /// that communications channel, or to set it up differently, as necessary. @protected void initChannels() { - assert(!SystemChannels.restoration.checkMethodCallHandler(_methodHandler)); SystemChannels.restoration.setMethodCallHandler(_methodHandler); } diff --git a/packages/flutter/lib/src/services/system_channels.dart b/packages/flutter/lib/src/services/system_channels.dart index 43e6bf29f2..4ab2b27d02 100644 --- a/packages/flutter/lib/src/services/system_channels.dart +++ b/packages/flutter/lib/src/services/system_channels.dart @@ -120,6 +120,11 @@ class SystemChannels { /// they apply, so that stale messages referencing past transactions can be /// ignored. /// + /// In debug builds, messages sent with a client ID of -1 are always accepted. + /// This allows tests to smuggle messages without having to mock the engine's + /// text handling (for example, allowing the engine to still handle the text + /// input messages in an integration test). + /// /// The methods described below are wrapped in a more convenient form by the /// [TextInput] and [TextInputConnection] class. /// @@ -152,9 +157,15 @@ class SystemChannels { /// is a transaction identifier. Calls for stale transactions should be ignored. /// /// * `TextInputClient.updateEditingState`: The user has changed the contents - /// of the text control. The second argument is a [String] containing a - /// JSON-encoded object with seven keys, in the form expected by - /// [TextEditingValue.fromJSON]. + /// of the text control. The second argument is an object with seven keys, + /// in the form expected by [TextEditingValue.fromJSON]. + /// + /// * `TextInputClient.updateEditingStateWithTag`: One or more text controls + /// were autofilled by the platform's autofill service. The first argument + /// (the client ID) is ignored, the second argument is a map of tags to + /// objects in the form expected by [TextEditingValue.fromJSON]. See + /// [AutofillScope.getAutofillClient] for details on the interpretation of + /// the tag. /// /// * `TextInputClient.performAction`: The user has triggered an action. The /// second argument is a [String] consisting of the stringification of one @@ -165,7 +176,8 @@ class SystemChannels { /// one. The framework should call `TextInput.setClient` and /// `TextInput.setEditingState` again with its most recent information. If /// there is no existing state on the framework side, the call should - /// fizzle. + /// fizzle. (This call is made without a client ID; indeed, without any + /// arguments at all.) /// /// * `TextInputClient.onConnectionClosed`: The text input connection closed /// on the platform side. For example the application is moved to diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index bac572f6ea..0e0c637e49 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -1327,9 +1327,11 @@ class TextInput { final List args = methodCall.arguments as List; + // The updateEditingStateWithTag request (autofill) can come up even to a + // text field that doesn't have a connection. if (method == 'TextInputClient.updateEditingStateWithTag') { + assert(_currentConnection!._client != null); final TextInputClient client = _currentConnection!._client; - assert(client != null); final AutofillScope? scope = client.currentAutofillScope; final Map editingValue = args[1] as Map; for (final String tag in editingValue.keys) { @@ -1343,9 +1345,22 @@ class TextInput { } final int client = args[0] as int; - // The incoming message was for a different client. - if (client != _currentConnection!._id) - return; + if (client != _currentConnection!._id) { + // If the client IDs don't match, the incoming message was for a different + // client. + bool debugAllowAnyway = false; + assert(() { + // In debug builds we allow "-1" as a magical client ID that ignores + // this verification step so that tests can always get through, even + // when they are not mocking the engine side of text input. + if (client == -1) + debugAllowAnyway = true; + return true; + }()); + if (!debugAllowAnyway) + return; + } + switch (method) { case 'TextInputClient.updateEditingState': _currentConnection!._client.updateEditingValue(TextEditingValue.fromJSON(args[1] as Map)); @@ -1502,7 +1517,7 @@ class TextInput { assert(shouldSave != null); TextInput._instance._channel.invokeMethod( 'TextInput.finishAutofillContext', - shouldSave , + shouldSave, ); } } diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 498550e2cf..4b1fbdb0ee 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2112,7 +2112,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (_hasFocus) { _openInputConnection(); } else { - widget.focusNode.requestFocus(); + widget.focusNode.requestFocus(); // This eventually calls _openInputConnection also, see _handleFocusChanged. } } @@ -2360,11 +2360,13 @@ class EditableTextState extends State with AutomaticKeepAliveClien void _cursorWaitForStart(Timer timer) { assert(_kCursorBlinkHalfPeriod > _fadeDuration); + assert(!EditableText.debugDeterministicCursor); _cursorTimer?.cancel(); _cursorTimer = Timer.periodic(_kCursorBlinkHalfPeriod, _cursorTick); } void _startCursorTimer() { + assert(_cursorTimer == null); _targetCursorVisibility = true; _cursorBlinkOpacityController.value = 1.0; if (EditableText.debugDeterministicCursor) diff --git a/packages/flutter/test/cupertino/picker_test.dart b/packages/flutter/test/cupertino/picker_test.dart index df0fd15a0e..6e5f655c07 100644 --- a/packages/flutter/test/cupertino/picker_test.dart +++ b/packages/flutter/test/cupertino/picker_test.dart @@ -201,7 +201,7 @@ void main() { final List selectedItems = []; final List systemCalls = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { systemCalls.add(methodCall); }); @@ -254,7 +254,7 @@ void main() { final List selectedItems = []; final List systemCalls = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { systemCalls.add(methodCall); }); diff --git a/packages/flutter/test/cupertino/refresh_test.dart b/packages/flutter/test/cupertino/refresh_test.dart index e01e24cabc..02b41cc918 100644 --- a/packages/flutter/test/cupertino/refresh_test.dart +++ b/packages/flutter/test/cupertino/refresh_test.dart @@ -175,7 +175,7 @@ void main() { testWidgets('drag past threshold triggers refresh task', (WidgetTester tester) async { final List platformCallLog = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { platformCallLog.add(methodCall); }); diff --git a/packages/flutter/test/cupertino/scrollbar_test.dart b/packages/flutter/test/cupertino/scrollbar_test.dart index 8977e2a2e0..ba14bae2f3 100644 --- a/packages/flutter/test/cupertino/scrollbar_test.dart +++ b/packages/flutter/test/cupertino/scrollbar_test.dart @@ -132,9 +132,9 @@ void main() { await tester.pump(); int hapticFeedbackCalls = 0; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'HapticFeedback.vibrate') { - hapticFeedbackCalls++; + hapticFeedbackCalls += 1; } }); @@ -692,9 +692,9 @@ void main() { await tester.pump(); int hapticFeedbackCalls = 0; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'HapticFeedback.vibrate') { - hapticFeedbackCalls++; + hapticFeedbackCalls += 1; } }); diff --git a/packages/flutter/test/cupertino/switch_test.dart b/packages/flutter/test/cupertino/switch_test.dart index 9c475b43be..6cd48bfbec 100644 --- a/packages/flutter/test/cupertino/switch_test.dart +++ b/packages/flutter/test/cupertino/switch_test.dart @@ -47,7 +47,7 @@ void main() { final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); @@ -87,7 +87,7 @@ void main() { bool value2 = false; final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); @@ -154,7 +154,7 @@ void main() { bool value = false; final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); @@ -192,7 +192,7 @@ void main() { bool value = false; final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 86da35abdd..ca6edb2335 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -162,7 +162,7 @@ class PathPointsMatcher extends Matcher { void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); // Returns the first RenderEditable. RenderEditable findRenderEditable(WidgetTester tester) { @@ -3228,7 +3228,7 @@ void main() { testWidgets('text field respects keyboardAppearance from theme', (WidgetTester tester) async { final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); @@ -3251,7 +3251,7 @@ void main() { testWidgets('text field can override keyboardAppearance from theme', (WidgetTester tester) async { final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); diff --git a/packages/flutter/test/cupertino/text_selection_test.dart b/packages/flutter/test/cupertino/text_selection_test.dart index b67d27ec1d..3f27490005 100644 --- a/packages/flutter/test/cupertino/text_selection_test.dart +++ b/packages/flutter/test/cupertino/text_selection_test.dart @@ -66,7 +66,7 @@ const _LongCupertinoLocalizations longLocalizations = _LongCupertinoLocalization void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); // Returns true iff the button is visually enabled. bool appearsEnabled(WidgetTester tester, String text) { diff --git a/packages/flutter/test/foundation/service_extensions_test.dart b/packages/flutter/test/foundation/service_extensions_test.dart index dd7ae1a4bd..a36f5bc477 100644 --- a/packages/flutter/test/foundation/service_extensions_test.dart +++ b/packages/flutter/test/foundation/service_extensions_test.dart @@ -21,7 +21,8 @@ class TestServiceExtensionsBinding extends BindingBase PaintingBinding, SemanticsBinding, RendererBinding, - WidgetsBinding { + WidgetsBinding, + TestDefaultBinaryMessengerBinding { final Map extensions = {}; @@ -467,7 +468,7 @@ void main() { bool completed; completed = false; - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData? message) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData? message) async { expect(utf8.decode(message!.buffer.asUint8List()), 'test'); completed = true; return ByteData(5); // 0x0000000000 @@ -494,7 +495,7 @@ void main() { }); expect(data, isFalse); expect(completed, isTrue); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', null); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('flutter/assets', null); }); test('Service extensions - exit', () async { diff --git a/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart b/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart index 3c3e397706..65d8f7611d 100644 --- a/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart +++ b/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart @@ -2,12 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Logically this file should be part of `gesture_binding_test.dart` but is here -// due to conflict of `flutter_test` and `package:test`. -// See https://github.com/dart-lang/matcher/issues/98 -// TODO(CareF): Consider combine this file back to `gesture_binding_test.dart` -// after #98 is fixed. - import 'dart:ui' as ui; import 'package:clock/clock.dart'; diff --git a/packages/flutter/test/material/checkbox_test.dart b/packages/flutter/test/material/checkbox_test.dart index d4775884bb..6c03f41aad 100644 --- a/packages/flutter/test/material/checkbox_test.dart +++ b/packages/flutter/test/material/checkbox_test.dart @@ -267,7 +267,7 @@ void main() { testWidgets('has semantic events', (WidgetTester tester) async { dynamic semanticEvent; bool? checkboxValue = false; - SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, (dynamic message) async { semanticEvent = message; }); final SemanticsTester semanticsTester = SemanticsTester(tester); @@ -300,7 +300,7 @@ void main() { }); expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true); - SystemChannels.accessibility.setMockMessageHandler(null); + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); semanticsTester.dispose(); }); diff --git a/packages/flutter/test/material/feedback_test.dart b/packages/flutter/test/material/feedback_test.dart index 06a016c41b..3f80ff838f 100644 --- a/packages/flutter/test/material/feedback_test.dart +++ b/packages/flutter/test/material/feedback_test.dart @@ -27,14 +27,14 @@ void main () { setUp(() { semanticEvents = >[]; - SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, (dynamic message) async { final Map typedMessage = message as Map; semanticEvents.add(typedMessage.cast()); }); }); tearDown(() { - SystemChannels.accessibility.setMockMessageHandler(null); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); }); testWidgets('forTap', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/feedback_tester.dart b/packages/flutter/test/material/feedback_tester.dart index 5f1c469f6c..c65d9949b9 100644 --- a/packages/flutter/test/material/feedback_tester.dart +++ b/packages/flutter/test/material/feedback_tester.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; /// Tracks how often feedback has been requested since its instantiation. /// @@ -10,13 +11,7 @@ import 'package:flutter/services.dart'; /// cannot be used in combination with other classes that do the same. class FeedbackTester { FeedbackTester() { - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { - if (methodCall.method == 'HapticFeedback.vibrate') - _hapticCount++; - if (methodCall.method == 'SystemSound.play' && - methodCall.arguments == SystemSoundType.click.toString()) - _clickSoundCount++; - }); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, _handler); } /// Number of times haptic feedback was requested (vibration). @@ -27,8 +22,17 @@ class FeedbackTester { int get clickSoundCount => _clickSoundCount; int _clickSoundCount = 0; + Future _handler(MethodCall methodCall) async { + if (methodCall.method == 'HapticFeedback.vibrate') + _hapticCount++; + if (methodCall.method == 'SystemSound.play' && + methodCall.arguments == SystemSoundType.click.toString()) + _clickSoundCount++; + } + /// Stops tracking. void dispose() { - SystemChannels.platform.setMockMethodCallHandler(null); + assert(TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.checkMockMessageHandler(SystemChannels.platform.name, _handler)); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); } } diff --git a/packages/flutter/test/material/input_date_picker_form_field_test.dart b/packages/flutter/test/material/input_date_picker_form_field_test.dart index 1ffe81281e..2e9c3769d1 100644 --- a/packages/flutter/test/material/input_date_picker_form_field_test.dart +++ b/packages/flutter/test/material/input_date_picker_form_field_test.dart @@ -241,9 +241,9 @@ void main() { // Fill the clipboard so that the Paste option is available in the text // selection menu. - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); - addTearDown(() => SystemChannels.platform.setMockMethodCallHandler(null)); + addTearDown(() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null)); await tester.pumpWidget(_inputDatePickerField(autofocus: true)); await tester.pumpAndSettle(); diff --git a/packages/flutter/test/material/radio_list_tile_test.dart b/packages/flutter/test/material/radio_list_tile_test.dart index 07f4ced5a2..332ee8f044 100644 --- a/packages/flutter/test/material/radio_list_tile_test.dart +++ b/packages/flutter/test/material/radio_list_tile_test.dart @@ -537,7 +537,7 @@ void main() { final Key key = UniqueKey(); dynamic semanticEvent; int? radioValue = 2; - SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, (dynamic message) async { semanticEvent = message; }); @@ -568,7 +568,7 @@ void main() { expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true); semantics.dispose(); - SystemChannels.accessibility.setMockMessageHandler(null); + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); }); testWidgets('RadioListTile can autofocus unless disabled.', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/radio_test.dart b/packages/flutter/test/material/radio_test.dart index bd8efbb20c..048e1470c4 100644 --- a/packages/flutter/test/material/radio_test.dart +++ b/packages/flutter/test/material/radio_test.dart @@ -292,7 +292,7 @@ void main() { final Key key = UniqueKey(); dynamic semanticEvent; int? radioValue = 2; - SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, (dynamic message) async { semanticEvent = message; }); @@ -319,7 +319,7 @@ void main() { expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true); semantics.dispose(); - SystemChannels.accessibility.setMockMessageHandler(null); + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); }); testWidgets('Radio ink ripple is displayed correctly', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index c3444bbe64..32c968fa80 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -32,12 +32,12 @@ void main() { setUp(() async { // Fill the clipboard so that the Paste option is available in the text // selection menu. - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); }); tearDown(() { - SystemChannels.platform.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); }); testWidgets('Can open and close search', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/switch_test.dart b/packages/flutter/test/material/switch_test.dart index b3d8f32a0b..34199a76f7 100644 --- a/packages/flutter/test/material/switch_test.dart +++ b/packages/flutter/test/material/switch_test.dart @@ -577,7 +577,7 @@ void main() { testWidgets('switch has semantic events', (WidgetTester tester) async { dynamic semanticEvent; bool value = false; - SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, (dynamic message) async { semanticEvent = message; }); final SemanticsTester semanticsTester = SemanticsTester(tester); @@ -615,13 +615,13 @@ void main() { expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true); semanticsTester.dispose(); - SystemChannels.accessibility.setMockMessageHandler(null); + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); }); testWidgets('switch sends semantic events from parent if fully merged', (WidgetTester tester) async { dynamic semanticEvent; bool value = false; - SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, (dynamic message) async { semanticEvent = message; }); final SemanticsTester semanticsTester = SemanticsTester(tester); @@ -665,7 +665,7 @@ void main() { expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true); semanticsTester.dispose(); - SystemChannels.accessibility.setMockMessageHandler(null); + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); }); testWidgets('Switch.adaptive', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 9f40e3671b..1332a9b751 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -2,6 +2,7 @@ // 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:math' as math; import 'dart:ui' as ui show window, BoxHeightStyle, BoxWidthStyle; @@ -164,14 +165,20 @@ void main() { setUp(() async { debugResetSemanticsIdCounter(); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + mockClipboard.handleMethodCall, + ); // Fill the clipboard so that the Paste option is available in the text // selection menu. await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); }); tearDown(() { - SystemChannels.platform.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + null, + ); }); final Key textFieldKey = UniqueKey(); @@ -339,7 +346,7 @@ void main() { testWidgets('TextField has consistent size', (WidgetTester tester) async { final Key textFieldKey = UniqueKey(); - late String textFieldValue; + String? textFieldValue; await tester.pumpWidget( overlay( @@ -362,15 +369,16 @@ void main() { Future checkText(String testValue) async { return TestAsyncUtils.guard(() async { + expect(textFieldValue, isNull); await tester.enterText(find.byType(TextField), testValue); // Check that the onChanged event handler fired. expect(textFieldValue, equals(testValue)); + textFieldValue = null; await skipPastScrollingAnimation(tester); }); } await checkText(' '); - expect(findTextFieldBox(), equals(inputBox)); expect(inputBox.size, equals(emptyInputSize)); @@ -416,6 +424,8 @@ void main() { text: 'X', selection: TextSelection.collapsed(offset: 1), )); + await tester.idle(); + expect(tester.state(find.byType(EditableText)), editableText); await checkCursorToggle(); }); @@ -4694,8 +4704,8 @@ void main() { ); String clipboardContent = ''; - SystemChannels.platform - .setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.setData') clipboardContent = methodCall.arguments['text'] as String; else if (methodCall.method == 'Clipboard.getData') @@ -4767,8 +4777,8 @@ void main() { ); String clipboardContent = ''; - SystemChannels.platform - .setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.setData') clipboardContent = methodCall.arguments['text'] as String; else if (methodCall.method == 'Clipboard.getData') @@ -4840,8 +4850,8 @@ void main() { ); const String clipboardContent = 'I love Flutter!'; - SystemChannels.platform - .setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') return {'text': clipboardContent}; return null; @@ -4890,8 +4900,8 @@ void main() { maxLines: 3, ); String clipboardContent = ''; - SystemChannels.platform - .setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.setData') clipboardContent = methodCall.arguments['text'] as String; else if (methodCall.method == 'Clipboard.getData') @@ -4964,8 +4974,8 @@ void main() { obscureText: true, ); String clipboardContent = ''; - SystemChannels.platform - .setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.setData') clipboardContent = methodCall.arguments['text'] as String; else if (methodCall.method == 'Clipboard.getData') @@ -9370,8 +9380,8 @@ void main() { ); bool triedToReadClipboard = false; - SystemChannels.platform - .setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') { triedToReadClipboard = true; } diff --git a/packages/flutter/test/material/text_form_field_test.dart b/packages/flutter/test/material/text_form_field_test.dart index d7c5d21220..af077b9392 100644 --- a/packages/flutter/test/material/text_form_field_test.dart +++ b/packages/flutter/test/material/text_form_field_test.dart @@ -32,7 +32,7 @@ class MockClipboard { void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); setUp(() async { // Fill the clipboard so that the Paste option is available in the text diff --git a/packages/flutter/test/material/text_selection_test.dart b/packages/flutter/test/material/text_selection_test.dart index 91bfb43b0e..4fa7537321 100644 --- a/packages/flutter/test/material/text_selection_test.dart +++ b/packages/flutter/test/material/text_selection_test.dart @@ -28,7 +28,7 @@ class MockClipboard { void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); setUp(() async { await Clipboard.setData(const ClipboardData(text: 'clipboard data')); diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index 7f37413da0..f1f6055fb9 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -1193,7 +1193,7 @@ void main() { testWidgets('has semantic events', (WidgetTester tester) async { final List semanticEvents = []; - SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, (dynamic message) async { semanticEvents.add(message); }); final SemanticsTester semantics = SemanticsTester(tester); @@ -1230,7 +1230,7 @@ void main() { }, ])); semantics.dispose(); - SystemChannels.accessibility.setMockMessageHandler(null); + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); }); testWidgets('default Tooltip debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); diff --git a/packages/flutter/test/material/tooltip_theme_test.dart b/packages/flutter/test/material/tooltip_theme_test.dart index e18adf1313..f81d8cc834 100644 --- a/packages/flutter/test/material/tooltip_theme_test.dart +++ b/packages/flutter/test/material/tooltip_theme_test.dart @@ -1153,7 +1153,7 @@ void main() { testWidgets('has semantic events by default - ThemeData.tooltipTheme', (WidgetTester tester) async { final List semanticEvents = []; - SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, (dynamic message) async { semanticEvents.add(message); }); final SemanticsTester semantics = SemanticsTester(tester); @@ -1191,12 +1191,12 @@ void main() { }, ])); semantics.dispose(); - SystemChannels.accessibility.setMockMessageHandler(null); + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); }); testWidgets('has semantic events by default - TooltipTheme', (WidgetTester tester) async { final List semanticEvents = []; - SystemChannels.accessibility.setMockMessageHandler((dynamic message) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, (dynamic message) async { semanticEvents.add(message); }); final SemanticsTester semantics = SemanticsTester(tester); @@ -1236,7 +1236,7 @@ void main() { }, ])); semantics.dispose(); - SystemChannels.accessibility.setMockMessageHandler(null); + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); }); testWidgets('default Tooltip debugFillProperties', (WidgetTester tester) async { diff --git a/packages/flutter/test/rendering/first_frame_test.dart b/packages/flutter/test/rendering/first_frame_test.dart index 6b766c9675..c6fe2c76b2 100644 --- a/packages/flutter/test/rendering/first_frame_test.dart +++ b/packages/flutter/test/rendering/first_frame_test.dart @@ -12,11 +12,11 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { + final TestRenderBinding binding = TestRenderBinding(); test('Flutter dispatches first frame event on the web only', () async { final Completer completer = Completer(); - final TestRenderBinding binding = TestRenderBinding(); const MethodChannel firstFrameChannel = MethodChannel('flutter/service_worker'); - firstFrameChannel.setMockMethodCallHandler((MethodCall methodCall) async { + binding.defaultBinaryMessenger.setMockMethodCallHandler(firstFrameChannel, (MethodCall methodCall) async { completer.complete(); }); @@ -27,4 +27,10 @@ void main() { }, skip: !kIsWeb); } -class TestRenderBinding extends BindingBase with SchedulerBinding, ServicesBinding, GestureBinding, SemanticsBinding, RendererBinding {} +class TestRenderBinding extends BindingBase + with SchedulerBinding, + ServicesBinding, + GestureBinding, + SemanticsBinding, + RendererBinding, + TestDefaultBinaryMessengerBinding { } diff --git a/packages/flutter/test/rendering/mouse_tracker_cursor_test.dart b/packages/flutter/test/rendering/mouse_tracker_cursor_test.dart index 7e8423f637..331b2a3624 100644 --- a/packages/flutter/test/rendering/mouse_tracker_cursor_test.dart +++ b/packages/flutter/test/rendering/mouse_tracker_cursor_test.dart @@ -56,14 +56,14 @@ void main() { setUp(() { _binding.postFrameCallbacks.clear(); - SystemChannels.mouseCursor.setMockMethodCallHandler((MethodCall call) async { + _binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.mouseCursor, (MethodCall call) async { if (_methodCallHandler != null) return _methodCallHandler!(call); }); }); tearDown(() { - SystemChannels.mouseCursor.setMockMethodCallHandler(null); + _binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.mouseCursor, null); }); test('Should work on platforms that does not support mouse cursor', () async { diff --git a/packages/flutter/test/rendering/mouse_tracker_test_utils.dart b/packages/flutter/test/rendering/mouse_tracker_test_utils.dart index a5bd1ed94e..c4c09064f0 100644 --- a/packages/flutter/test/rendering/mouse_tracker_test_utils.dart +++ b/packages/flutter/test/rendering/mouse_tracker_test_utils.dart @@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart' show TestDefaultBinaryMessengerBinding; class _TestHitTester extends RenderBox { _TestHitTester(this.hitTestOverride); @@ -24,7 +25,7 @@ class _TestHitTester extends RenderBox { // A binding used to test MouseTracker, allowing the test to override hit test // searching. class TestMouseTrackerFlutterBinding extends BindingBase - with SchedulerBinding, ServicesBinding, GestureBinding, SemanticsBinding, RendererBinding { + with SchedulerBinding, ServicesBinding, GestureBinding, SemanticsBinding, RendererBinding, TestDefaultBinaryMessengerBinding { @override void initInstances() { super.initInstances(); diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart index b6b0dd9508..65ba5ae80d 100644 --- a/packages/flutter/test/rendering/rendering_tester.dart +++ b/packages/flutter/test/rendering/rendering_tester.dart @@ -9,12 +9,12 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart' show EnginePhase, fail; +import 'package:flutter_test/flutter_test.dart' show TestDefaultBinaryMessengerBinding, EnginePhase, fail; export 'package:flutter/foundation.dart' show FlutterError, FlutterErrorDetails; -export 'package:flutter_test/flutter_test.dart' show EnginePhase; +export 'package:flutter_test/flutter_test.dart' show TestDefaultBinaryMessengerBinding, EnginePhase; -class TestRenderingFlutterBinding extends BindingBase with SchedulerBinding, ServicesBinding, GestureBinding, PaintingBinding, SemanticsBinding, RendererBinding { +class TestRenderingFlutterBinding extends BindingBase with SchedulerBinding, ServicesBinding, GestureBinding, PaintingBinding, SemanticsBinding, RendererBinding, TestDefaultBinaryMessengerBinding { /// Creates a binding for testing rendering library functionality. /// /// If [onErrors] is not null, it is called if [FlutterError] caught any errors diff --git a/packages/flutter/test/semantics/semantics_service_test.dart b/packages/flutter/test/semantics/semantics_service_test.dart index b384e0fbf1..a7ff5e0b1a 100644 --- a/packages/flutter/test/semantics/semantics_service_test.dart +++ b/packages/flutter/test/semantics/semantics_service_test.dart @@ -13,12 +13,13 @@ void main() { test('Semantic announcement', () async { final List> log = >[]; + Future handleMessage(dynamic mockMessage) async { final Map message = mockMessage as Map; log.add(message); } - SystemChannels.accessibility.setMockMessageHandler(handleMessage); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, handleMessage); await SemanticsService.announce('announcement 1', TextDirection.ltr); await SemanticsService.announce('announcement 2', TextDirection.rtl); diff --git a/packages/flutter/test/services/autofill_test.dart b/packages/flutter/test/services/autofill_test.dart index 428f6c74fe..5af15da712 100644 --- a/packages/flutter/test/services/autofill_test.dart +++ b/packages/flutter/test/services/autofill_test.dart @@ -191,15 +191,6 @@ class FakeTextChannel implements MethodChannel { incoming = handler; } - @override - bool checkMethodCallHandler(Future Function(MethodCall call)? handler) => throw UnimplementedError(); - - @override - void setMockMethodCallHandler(Future? Function(MethodCall call)? handler) => throw UnimplementedError(); - - @override - bool checkMockMethodCallHandler(Future Function(MethodCall call)? handler) => throw UnimplementedError(); - void validateOutgoingMethodCalls(List calls) { expect(outgoingCalls.length, calls.length); bool hasError = false; diff --git a/packages/flutter/test/services/binding_test.dart b/packages/flutter/test/services/binding_test.dart index 68308105bc..ff92a14baa 100644 --- a/packages/flutter/test/services/binding_test.dart +++ b/packages/flutter/test/services/binding_test.dart @@ -34,7 +34,7 @@ L2Paragraph2 L2Paragraph3'''; -const String licenses = ''' +const String combinedLicenses = ''' $license1 -------------------------------------------------------------------------------- $license2 @@ -42,29 +42,25 @@ $license2 class TestBinding extends BindingBase with SchedulerBinding, ServicesBinding { @override - BinaryMessenger createBinaryMessenger() { - return super.createBinaryMessenger() - ..setMockMessageHandler('flutter/assets', (ByteData? message) async { - if (const StringCodec().decodeMessage(message) == 'NOTICES') { - return const StringCodec().encodeMessage(licenses); - } - return null; - }) - ..setMockMessageHandler('flutter/assets', (ByteData? message) async { - if (const StringCodec().decodeMessage(message) == 'NOTICES.Z' && !kIsWeb) { - return Uint8List.fromList(gzip.encode(utf8.encode(licenses))).buffer.asByteData(); - } - if (const StringCodec().decodeMessage(message) == 'NOTICES' && kIsWeb) { - return const StringCodec().encodeMessage(licenses); - } - return null; - }); + TestDefaultBinaryMessenger get defaultBinaryMessenger => super.defaultBinaryMessenger as TestDefaultBinaryMessenger; + + @override + TestDefaultBinaryMessenger createBinaryMessenger() { + return TestDefaultBinaryMessenger(super.createBinaryMessenger()); } } void main() { test('Adds rootBundle LICENSES to LicenseRegistry', () async { - TestBinding(); // The test binding registers a mock handler that returns licenses for the LICENSE key + TestBinding().defaultBinaryMessenger.setMockMessageHandler('flutter/assets', (ByteData? message) async { + if (const StringCodec().decodeMessage(message) == 'NOTICES.Z' && !kIsWeb) { + return Uint8List.fromList(gzip.encode(utf8.encode(combinedLicenses))).buffer.asByteData(); + } + if (const StringCodec().decodeMessage(message) == 'NOTICES' && kIsWeb) { + return const StringCodec().encodeMessage(combinedLicenses); + } + return null; + }); final List licenses = await LicenseRegistry.licenses.toList(); diff --git a/packages/flutter/test/services/default_binary_messenger_test.dart b/packages/flutter/test/services/default_binary_messenger_test.dart index e6a768d486..4ce14e82bf 100644 --- a/packages/flutter/test/services/default_binary_messenger_test.dart +++ b/packages/flutter/test/services/default_binary_messenger_test.dart @@ -2,9 +2,10 @@ // 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:typed_data'; -import 'dart:ui' as ui; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -19,36 +20,39 @@ void main() { } test('default binary messenger calls callback once', () async { - int count = 0; + int countInbound = 0; + int countOutbound = 0; const String channel = 'foo'; - ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + final ByteData bar = _makeByteData('bar'); + final Completer done = Completer(); + ServicesBinding.instance!.channelBuffers.push( channel, - _makeByteData('bar'), + bar, (ByteData? message) async { - count += 1; + expect(message, isNull); + countOutbound += 1; + done.complete(); }, ); - expect(count, equals(0)); - await ui.channelBuffers.drain(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async { - callback(null); - }); - expect(count, equals(1)); - }); - - test('can check the handler', () { - Future handler(ByteData? call) => Future.value(null); - final BinaryMessenger messenger = ServicesBinding.instance!.defaultBinaryMessenger; - - expect(messenger.checkMessageHandler('test_channel', null), true); - expect(messenger.checkMessageHandler('test_channel', handler), false); - messenger.setMessageHandler('test_channel', handler); - expect(messenger.checkMessageHandler('test_channel', handler), true); - messenger.setMessageHandler('test_channel', null); + expect(countInbound, equals(0)); + expect(countOutbound, equals(0)); + ServicesBinding.instance!.defaultBinaryMessenger.setMessageHandler( + channel, + (ByteData? message) async { + expect(message, bar); + countInbound += 1; + }, + ); + expect(countInbound, equals(0)); + expect(countOutbound, equals(0)); + await done.future; + expect(countInbound, equals(1)); + expect(countOutbound, equals(1)); }); test('can check the mock handler', () { Future handler(ByteData? call) => Future.value(null); - final BinaryMessenger messenger = ServicesBinding.instance!.defaultBinaryMessenger; + final TestDefaultBinaryMessenger messenger = TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger; expect(messenger.checkMockMessageHandler('test_channel', null), true); expect(messenger.checkMockMessageHandler('test_channel', handler), false); diff --git a/packages/flutter/test/services/deferred_component_test.dart b/packages/flutter/test/services/deferred_component_test.dart index cc40b8e651..6665f28467 100644 --- a/packages/flutter/test/services/deferred_component_test.dart +++ b/packages/flutter/test/services/deferred_component_test.dart @@ -11,7 +11,7 @@ void main() { test('installDeferredComponent test', () async { final List log = []; - SystemChannels.deferredComponent.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.deferredComponent, (MethodCall methodCall) async { log.add(methodCall); }); @@ -27,7 +27,7 @@ void main() { test('uninstallDeferredComponent test', () async { final List log = []; - SystemChannels.deferredComponent.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.deferredComponent, (MethodCall methodCall) async { log.add(methodCall); }); diff --git a/packages/flutter/test/services/fake_platform_views.dart b/packages/flutter/test/services/fake_platform_views.dart index 9efdce71de..7695c4a92c 100644 --- a/packages/flutter/test/services/fake_platform_views.dart +++ b/packages/flutter/test/services/fake_platform_views.dart @@ -8,6 +8,7 @@ import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; /// Used in internal testing. class FakePlatformViewController extends PlatformViewController { @@ -122,7 +123,7 @@ class FakeAndroidViewController implements AndroidViewController { class FakeAndroidPlatformViewsController { FakeAndroidPlatformViewsController() { - SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform_views, _onMethodCall); } Iterable get views => _views.values; @@ -301,7 +302,7 @@ class FakeAndroidPlatformViewsController { class FakeIosPlatformViewsController { FakeIosPlatformViewsController() { - SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform_views, _onMethodCall); } Iterable get views => _views.values; @@ -396,7 +397,7 @@ class FakeIosPlatformViewsController { class FakeHtmlPlatformViewsController { FakeHtmlPlatformViewsController() { - SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform_views, _onMethodCall); } Iterable get views => _views.values; diff --git a/packages/flutter/test/services/haptic_feedback_test.dart b/packages/flutter/test/services/haptic_feedback_test.dart index 6234a97579..fa4c67e555 100644 --- a/packages/flutter/test/services/haptic_feedback_test.dart +++ b/packages/flutter/test/services/haptic_feedback_test.dart @@ -11,7 +11,7 @@ void main() { test('Haptic feedback control test', () async { final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); @@ -25,7 +25,7 @@ void main() { Future callAndVerifyHapticFunction(Function hapticFunction, String platformMethodArgument) async { final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); diff --git a/packages/flutter/test/services/platform_channel_test.dart b/packages/flutter/test/services/platform_channel_test.dart index 7b7f135fda..6adc07b65d 100644 --- a/packages/flutter/test/services/platform_channel_test.dart +++ b/packages/flutter/test/services/platform_channel_test.dart @@ -12,7 +12,7 @@ void main() { const MessageCodec string = StringCodec(); const BasicMessageChannel channel = BasicMessageChannel('ch', string); test('can send string message and get reply', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch', (ByteData? message) async => string.encodeMessage(string.decodeMessage(message)! + ' world'), ); @@ -23,7 +23,7 @@ void main() { test('can receive string message and send reply', () async { channel.setMessageHandler((String? message) async => message! + ' world'); String? reply; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( 'ch', const StringCodec().encodeMessage('hello'), (ByteData? replyBinary) { @@ -40,7 +40,7 @@ void main() { const MethodChannel channel = MethodChannel('ch7', jsonMethod); const OptionalMethodChannel optionalMethodChannel = OptionalMethodChannel('ch8', jsonMethod); test('can invoke method and get result', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch7', (ByteData? message) async { final Map methodCall = jsonMessage.decodeMessage(message) as Map; @@ -56,7 +56,7 @@ void main() { }); test('can invoke list method and get result', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch7', (ByteData? message) async { final Map methodCall = jsonMessage.decodeMessage(message) as Map; @@ -72,7 +72,7 @@ void main() { }); test('can invoke list method and get null result', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch7', (ByteData? message) async { final Map methodCall = jsonMessage.decodeMessage(message) as Map; @@ -87,7 +87,7 @@ void main() { }); test('can invoke map method and get result', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch7', (ByteData? message) async { final Map methodCall = jsonMessage.decodeMessage(message) as Map; @@ -103,7 +103,7 @@ void main() { }); test('can invoke map method and get null result', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch7', (ByteData? message) async { final Map methodCall = jsonMessage.decodeMessage(message) as Map; @@ -118,7 +118,7 @@ void main() { }); test('can invoke method and get error', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch7', (ByteData? message) async { return jsonMessage.encodeMessage([ @@ -141,7 +141,7 @@ void main() { }); test('can invoke unimplemented method', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch7', (ByteData? message) async => null, ); @@ -157,7 +157,7 @@ void main() { }); test('can invoke unimplemented method (optional)', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch8', (ByteData? message) async => null, ); @@ -169,7 +169,7 @@ void main() { channel.setMethodCallHandler(null); final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData? envelope; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { envelope = result; }); await null; // just in case there's something async happening @@ -179,7 +179,7 @@ void main() { test('can handle method call with no registered plugin (setting after)', () async { final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData? envelope; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { envelope = result; }); channel.setMethodCallHandler(null); @@ -193,7 +193,7 @@ void main() { }); final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData? envelope; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { envelope = result; }); expect(envelope, isNull); @@ -203,7 +203,7 @@ void main() { channel.setMethodCallHandler((MethodCall call) async => '${call.arguments}, world'); final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData? envelope; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { envelope = result; }); expect(jsonMethod.decodeEnvelope(envelope!), equals('hello, world')); @@ -215,7 +215,7 @@ void main() { }); final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData? envelope; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { envelope = result; }); try { @@ -235,7 +235,7 @@ void main() { }); final ByteData call = jsonMethod.encodeMethodCall(const MethodCall('sayHello', 'hello')); ByteData? envelope; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('ch7', call, (ByteData? result) { envelope = result; }); try { @@ -249,24 +249,14 @@ void main() { } }); - test('can check the handler', () { + test('can check the mock handler', () async { Future handler(MethodCall call) => Future.value(null); const MethodChannel channel = MethodChannel('test_handler'); - expect(channel.checkMethodCallHandler(null), true); - expect(channel.checkMethodCallHandler(handler), false); - channel.setMethodCallHandler(handler); - expect(channel.checkMethodCallHandler(handler), true); - }); - - test('can check the mock handler', () { - Future handler(MethodCall call) => Future.value(null); - - const MethodChannel channel = MethodChannel('test_handler'); - expect(channel.checkMockMethodCallHandler(null), true); - expect(channel.checkMockMethodCallHandler(handler), false); - channel.setMockMethodCallHandler(handler); - expect(channel.checkMockMethodCallHandler(handler), true); + expect(TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.checkMockMessageHandler(channel.name, null), true); + expect(TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.checkMockMessageHandler(channel.name, handler), false); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(channel, handler); + expect(TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.checkMockMessageHandler(channel.name, handler), true); }); }); @@ -275,7 +265,7 @@ void main() { const MethodCodec jsonMethod = JSONMethodCodec(); const EventChannel channel = EventChannel('ch', jsonMethod); void emitEvent(ByteData? event) { - ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( 'ch', event, (ByteData? reply) {}, @@ -283,7 +273,7 @@ void main() { } test('can receive event stream', () async { bool canceled = false; - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch', (ByteData? message) async { final Map methodCall = jsonMessage.decodeMessage(message) as Map; @@ -308,7 +298,7 @@ void main() { }); test('can receive error event', () async { - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( 'ch', (ByteData? message) async { final Map methodCall = jsonMessage.decodeMessage(message) as Map; diff --git a/packages/flutter/test/services/platform_messages_test.dart b/packages/flutter/test/services/platform_messages_test.dart index 9aa9bf1ed8..8fd3937f5c 100644 --- a/packages/flutter/test/services/platform_messages_test.dart +++ b/packages/flutter/test/services/platform_messages_test.dart @@ -7,24 +7,23 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - test('Mock binary message handler control test', () async { - // Initialize all bindings because defaultBinaryMessenger.send() needs a window. - TestWidgetsFlutterBinding.ensureInitialized(); + TestWidgetsFlutterBinding.ensureInitialized(); + test('Mock binary message handler control test', () async { final List log = []; - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('test1', (ByteData? message) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('test1', (ByteData? message) async { log.add(message); return null; }); final ByteData message = ByteData(2)..setUint16(0, 0xABCD); - await ServicesBinding.instance!.defaultBinaryMessenger.send('test1', message); + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.send('test1', message); expect(log, equals([message])); log.clear(); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('test1', null); - await ServicesBinding.instance!.defaultBinaryMessenger.send('test1', message); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('test1', null); + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.send('test1', message); expect(log, isEmpty); }); } diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart index 7f65991532..3bbc185b06 100644 --- a/packages/flutter/test/services/raw_keyboard_test.dart +++ b/packages/flutter/test/services/raw_keyboard_test.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -30,6 +29,7 @@ void main() { RawKeyboard.instance.removeListener(handleKey); } }); + testWidgets('No character is produced for non-printables', (WidgetTester tester) async { for (final String platform in ['linux', 'android', 'macos', 'fuchsia', 'windows', 'web']) { void handleKey(RawKeyEvent event) { @@ -40,6 +40,7 @@ void main() { RawKeyboard.instance.removeListener(handleKey); } }); + testWidgets('keysPressed is maintained', (WidgetTester tester) async { for (final String platform in ['linux', 'android', 'macos', 'fuchsia', 'windows', 'ios']) { RawKeyboard.instance.clearKeysPressed(); @@ -209,7 +210,7 @@ void main() { // when this event is received, but it's not in keysPressed yet. data['modifiers'] |= RawKeyEventDataMacOs.modifierLeftShift | RawKeyEventDataMacOs.modifierShift; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -234,7 +235,7 @@ void main() { // when this event is received, but it's not in keysPressed yet. data['modifiers'] |= RawKeyEventDataMacOs.modifierLeftShift | RawKeyEventDataMacOs.modifierShift; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -259,7 +260,7 @@ void main() { // when this event is received, but it's not in keysPressed yet. data['modifiers'] |= RawKeyEventDataWindows.modifierLeftShift | RawKeyEventDataWindows.modifierShift; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -284,7 +285,7 @@ void main() { // when this event is received, but it's not in keysPressed yet. data['metaState'] |= RawKeyEventDataAndroid.modifierLeftShift | RawKeyEventDataAndroid.modifierShift; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -309,7 +310,7 @@ void main() { // when this event is received, but it's not in keysPressed yet. data['modifiers'] |= RawKeyEventDataFuchsia.modifierLeftShift; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -334,7 +335,7 @@ void main() { // when this event is received, but it's not in keysPressed yet. data['modifiers'] |= GLFWKeyHelper.modifierShift; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -364,7 +365,7 @@ void main() { isDown: true, )..['metaState'] |= RawKeyEventDataWeb.modifierShift; // Dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -456,7 +457,7 @@ void main() { RawKeyEventDataAndroid.modifierControl | RawKeyEventDataAndroid.modifierMeta; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -494,7 +495,7 @@ void main() { RawKeyEventDataMacOs.modifierCommand | RawKeyEventDataMacOs.modifierControl; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -570,7 +571,7 @@ void main() { RawKeyEventDataWindows.modifierAlt | RawKeyEventDataWindows.modifierControl; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -607,7 +608,7 @@ void main() { GLFWKeyHelper.modifierControl | GLFWKeyHelper.modifierMeta; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -645,7 +646,7 @@ void main() { RawKeyEventDataWeb.modifierControl | RawKeyEventDataWeb.modifierMeta; // dispatch the modified data. - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) {}, @@ -665,12 +666,6 @@ void main() { }); testWidgets('RawKeyboard asserts if no keys are in keysPressed after receiving a key down event', (WidgetTester tester) async { - FlutterErrorDetails? errorDetails; - final FlutterExceptionHandler? oldHandler = FlutterError.onError; - FlutterError.onError = (FlutterErrorDetails details) { - errorDetails = details; - }; - final Map keyEventMessage; if (kIsWeb) { keyEventMessage = const { @@ -692,19 +687,15 @@ void main() { } try { - await ServicesBinding.instance!.defaultBinaryMessenger - .handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(keyEventMessage), - (ByteData? data) {}, + (ByteData? data) { }, ); - } finally { - FlutterError.onError = oldHandler; + fail('Expected an exception, but did not get one.'); + } on AssertionError catch (error) { + expect(error.toString(), contains('Attempted to send a key down event when no keys are in keysPressed')); } - expect(errorDetails, isNotNull); - expect(errorDetails!.stack, isNotNull); - final String fullErrorMessage = errorDetails.toString().replaceAll('\n', ' '); - expect(fullErrorMessage, contains('Attempted to send a key down event when no keys are in keysPressed')); }); }); @@ -757,6 +748,7 @@ void main() { } } }); + test('modifier keys are recognized when combined', () { for (final int modifier in modifierTests.keys) { if (modifier == RawKeyEventDataAndroid.modifierFunction) { @@ -799,6 +791,7 @@ void main() { } } }); + test('Printable keyboard keys are correctly translated', () { final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage({ 'type': 'keydown', @@ -817,6 +810,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.keyA)); expect(data.keyLabel, equals('a')); }); + test('Control keyboard keys are correctly translated', () { final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -833,6 +827,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.escape)); expect(data.keyLabel, isEmpty); }); + test('Modifier keyboard keys are correctly translated', () { final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -850,6 +845,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft)); expect(data.keyLabel, isEmpty); }); + test('DPAD keys from a joystick give physical key mappings', () { final RawKeyEvent joystickDpadDown = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -867,6 +863,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.arrowDown)); expect(data.keyLabel, isEmpty); }); + test('Arrow keys from a keyboard give correct physical key mappings', () { final RawKeyEvent joystickDpadDown = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -883,6 +880,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.arrowDown)); expect(data.keyLabel, isEmpty); }); + test('DPAD center from a game pad gives physical key mappings', () { final RawKeyEvent joystickDpadCenter = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -900,6 +898,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.select)); expect(data.keyLabel, isEmpty); }); + test('Device id is read from message', () { final RawKeyEvent joystickDpadCenter = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -915,6 +914,7 @@ void main() { final RawKeyEventDataAndroid data = joystickDpadCenter.data as RawKeyEventDataAndroid; expect(data.deviceId, equals(10)); }); + test('Repeat count is passed correctly', () { final RawKeyEvent repeatCountEvent = RawKeyEvent.fromMessage({ 'type': 'keydown', @@ -931,6 +931,7 @@ void main() { final RawKeyEventDataAndroid data = repeatCountEvent.data as RawKeyEventDataAndroid; expect(data.repeatCount, equals(42)); }); + testWidgets('Key events are responded to correctly.', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. @@ -940,7 +941,7 @@ void main() { isDown: true, ); Map? message; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) { @@ -963,7 +964,7 @@ void main() { focusNode.requestFocus(); await tester.pump(); - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) { @@ -971,7 +972,7 @@ void main() { }, ); expect(message, equals({ 'handled': true })); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler(SystemChannels.keyEvent.name, null); + tester.binding.defaultBinaryMessenger.setMockMessageHandler(SystemChannels.keyEvent.name, null); }); }, skip: isBrowser); // This is an Android-specific group. @@ -1019,6 +1020,7 @@ void main() { } } }); + test('modifier keys are recognized when combined', () { for (final int modifier in modifierTests.keys) { if (modifier == RawKeyEventDataFuchsia.modifierCapsLock) { @@ -1052,6 +1054,7 @@ void main() { } } }); + test('Printable keyboard keys are correctly translated', () { final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage({ 'type': 'keydown', @@ -1065,6 +1068,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.keyA)); expect(data.keyLabel, equals('a')); }); + test('Control keyboard keys are correctly translated', () { final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1136,6 +1140,7 @@ void main() { } } }); + test('modifier keys are recognized when combined', () { for (final int modifier in modifierTests.keys) { if (modifier == RawKeyEventDataMacOs.modifierCapsLock) { @@ -1175,6 +1180,7 @@ void main() { } } }); + test('Printable keyboard keys are correctly translated', () { const String unmodifiedCharacter = 'a'; final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const { @@ -1190,6 +1196,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.keyA)); expect(data.keyLabel, equals('a')); }); + test('Control keyboard keys are correctly translated', () { final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1281,6 +1288,7 @@ void main() { } } }); + test('modifier keys are recognized when combined', () { for (final int modifier in modifierTests.keys) { if (modifier == RawKeyEventDataIos.modifierCapsLock) { @@ -1320,6 +1328,7 @@ void main() { } } }); + test('Printable keyboard keys are correctly translated', () { const String unmodifiedCharacter = 'a'; final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const { @@ -1335,6 +1344,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.keyA)); expect(data.keyLabel, equals('a')); }); + test('Control keyboard keys are correctly translated', () { final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1427,6 +1437,7 @@ void main() { } } }); + test('modifier keys are recognized when combined', () { for (final int modifier in modifierTests.keys) { if (modifier == RawKeyEventDataWindows.modifierCaps) { @@ -1466,6 +1477,7 @@ void main() { } } }); + test('Printable keyboard keys are correctly translated', () { const int unmodifiedCharacter = 97; // ASCII value for 'a'. final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const { @@ -1481,6 +1493,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.keyA)); expect(data.keyLabel, equals('a')); }); + test('Control keyboard keys are correctly translated', () { final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1495,6 +1508,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.escape)); expect(data.keyLabel, isEmpty); }); + test('Modifier keyboard keys are correctly translated', () { final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1509,6 +1523,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft)); expect(data.keyLabel, isEmpty); }); + test('Unprintable keyboard keys are correctly translated', () { final RawKeyEvent leftArrowKey = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1522,6 +1537,7 @@ void main() { expect(data.physicalKey, equals(PhysicalKeyboardKey.arrowLeft)); expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft)); }); + testWidgets('Win32 VK_PROCESSKEY events are skipped', (WidgetTester tester) async { const String platform = 'windows'; bool lastHandled = true; @@ -1620,6 +1636,7 @@ void main() { } } }); + test('modifier keys are recognized when combined', () { for (final int modifier in modifierTests.keys) { if (modifier == GLFWKeyHelper.modifierControl) { @@ -1660,6 +1677,7 @@ void main() { } } }); + test('Printable keyboard keys are correctly translated', () { final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1675,6 +1693,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.keyQ)); expect(data.keyLabel, equals('q')); }); + test('Code points with two Unicode scalar values are allowed', () { final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1723,6 +1742,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.escape)); expect(data.keyLabel, isEmpty); }); + test('Modifier keyboard keys are correctly translated', () { final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1804,6 +1824,7 @@ void main() { } } }); + test('modifier keys are recognized when combined', () { for (final int modifier in modifierTests.keys) { if (modifier == GtkKeyHelper.modifierControl) { @@ -1844,6 +1865,7 @@ void main() { } } }); + test('Printable keyboard keys are correctly translated', () { final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1859,6 +1881,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.keyQ)); expect(data.keyLabel, equals('q')); }); + test('Code points with two Unicode scalar values are allowed', () { final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1907,6 +1930,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.escape)); expect(data.keyLabel, isEmpty); }); + test('Modifier keyboard keys are correctly translated', () { final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -1960,6 +1984,7 @@ void main() { } } }); + test('modifier keys are recognized when combined', () { for (final int modifier in modifierTests.keys) { if (modifier == RawKeyEventDataWeb.modifierMeta) { @@ -1992,6 +2017,7 @@ void main() { } } }); + test('Printable keyboard keys are correctly translated', () { final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -2005,6 +2031,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.keyA)); expect(data.keyLabel, equals('a')); }); + test('Control keyboard keys are correctly translated', () { final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -2017,6 +2044,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.escape)); expect(data.keyLabel, isEmpty); }); + test('Modifier keyboard keys are correctly translated', () { final RawKeyEvent shiftKeyEvent = RawKeyEvent.fromMessage(const { 'type': 'keydown', @@ -2029,6 +2057,7 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft)); expect(data.keyLabel, isEmpty); }); + test('Arrow keys from a keyboard give correct physical key mappings', () { final RawKeyEvent arrowKeyDown = RawKeyEvent.fromMessage(const { 'type': 'keydown', diff --git a/packages/flutter/test/services/restoration_test.dart b/packages/flutter/test/services/restoration_test.dart index 2def380ec8..7a92d4de47 100644 --- a/packages/flutter/test/services/restoration_test.dart +++ b/packages/flutter/test/services/restoration_test.dart @@ -18,7 +18,7 @@ void main() { testWidgets('root bucket retrieval', (WidgetTester tester) async { final List callsToEngine = []; final Completer> result = Completer>(); - SystemChannels.restoration.setMockMethodCallHandler((MethodCall call) { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.restoration, (MethodCall call) { callsToEngine.add(call); return result.future; }); @@ -64,7 +64,7 @@ void main() { testWidgets('root bucket received from engine before retrieval', (WidgetTester tester) async { SystemChannels.restoration.setMethodCallHandler(null); final List callsToEngine = []; - SystemChannels.restoration.setMockMethodCallHandler((MethodCall call) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.restoration, (MethodCall call) async { callsToEngine.add(call); }); final RestorationManager manager = RestorationManager(); @@ -83,7 +83,7 @@ void main() { SystemChannels.restoration.setMethodCallHandler(null); final List callsToEngine = []; final Completer> result = Completer>(); - SystemChannels.restoration.setMockMethodCallHandler((MethodCall call) { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.restoration, (MethodCall call) { callsToEngine.add(call); return result.future; }); @@ -110,7 +110,7 @@ void main() { }); testWidgets('root bucket is properly replaced when new data is available', (WidgetTester tester) async { - SystemChannels.restoration.setMockMethodCallHandler((MethodCall call) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.restoration, (MethodCall call) async { return _createEncodedRestorationData1(); }); final RestorationManager manager = RestorationManager(); @@ -152,7 +152,7 @@ void main() { testWidgets('returns null as root bucket when restoration is disabled', (WidgetTester tester) async { final List callsToEngine = []; final Completer> result = Completer>(); - SystemChannels.restoration.setMockMethodCallHandler((MethodCall call) { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.restoration, (MethodCall call) { callsToEngine.add(call); return result.future; }); @@ -195,7 +195,7 @@ void main() { testWidgets('flushData', (WidgetTester tester) async { final List callsToEngine = []; final Completer> result = Completer>(); - SystemChannels.restoration.setMockMethodCallHandler((MethodCall call) { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.restoration, (MethodCall call) { callsToEngine.add(call); return result.future; }); @@ -230,7 +230,7 @@ void main() { testWidgets('isReplacing', (WidgetTester tester) async { final Completer> result = Completer>(); - SystemChannels.restoration.setMockMethodCallHandler((MethodCall call) { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.restoration, (MethodCall call) { return result.future; }); diff --git a/packages/flutter/test/services/system_chrome_test.dart b/packages/flutter/test/services/system_chrome_test.dart index 5356e4de2b..3b196c8ace 100644 --- a/packages/flutter/test/services/system_chrome_test.dart +++ b/packages/flutter/test/services/system_chrome_test.dart @@ -7,6 +7,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + testWidgets('SystemChrome overlay style test', (WidgetTester tester) async { // The first call is a cache miss and will queue a microtask SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light); @@ -24,7 +26,7 @@ void main() { test('setPreferredOrientations control test', () async { final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); @@ -42,7 +44,7 @@ void main() { test('setApplicationSwitcherDescription control test', () async { final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); @@ -60,7 +62,7 @@ void main() { test('setApplicationSwitcherDescription missing plugin', () async { final List log = []; - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('flutter/platform', (ByteData? message) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler('flutter/platform', (ByteData? message) async { log.add(message); }); @@ -74,7 +76,7 @@ void main() { test('setEnabledSystemUIOverlays control test', () async { final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); diff --git a/packages/flutter/test/services/system_navigator_test.dart b/packages/flutter/test/services/system_navigator_test.dart index bc699856d8..251bca6902 100644 --- a/packages/flutter/test/services/system_navigator_test.dart +++ b/packages/flutter/test/services/system_navigator_test.dart @@ -12,7 +12,7 @@ void main() { test('System navigator control test', () async { final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); diff --git a/packages/flutter/test/services/system_sound_test.dart b/packages/flutter/test/services/system_sound_test.dart index 20a6d5adb2..823d7f083a 100644 --- a/packages/flutter/test/services/system_sound_test.dart +++ b/packages/flutter/test/services/system_sound_test.dart @@ -12,7 +12,7 @@ void main() { test('System sound control test', () async { final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); diff --git a/packages/flutter/test/services/text_input_test.dart b/packages/flutter/test/services/text_input_test.dart index 7d03836808..ca3ec02e2e 100644 --- a/packages/flutter/test/services/text_input_test.dart +++ b/packages/flutter/test/services/text_input_test.dart @@ -442,16 +442,6 @@ class FakeTextChannel implements MethodChannel { @override void setMethodCallHandler(Future Function(MethodCall call)? handler) => incoming = handler; - @override - bool checkMethodCallHandler(Future Function(MethodCall call)? handler) => throw UnimplementedError(); - - - @override - void setMockMethodCallHandler(Future? Function(MethodCall call)? handler) => throw UnimplementedError(); - - @override - bool checkMockMethodCallHandler(Future Function(MethodCall call)? handler) => throw UnimplementedError(); - void validateOutgoingMethodCalls(List calls) { expect(outgoingCalls.length, calls.length); bool hasError = false; diff --git a/packages/flutter/test/widgets/draggable_test.dart b/packages/flutter/test/widgets/draggable_test.dart index 27b14ebb94..99a292baad 100644 --- a/packages/flutter/test/widgets/draggable_test.dart +++ b/packages/flutter/test/widgets/draggable_test.dart @@ -3107,7 +3107,7 @@ Future _testLongPressDraggableHapticFeedback({ required WidgetTester teste bool onDragStartedCalled = false; int hapticFeedbackCalls = 0; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'HapticFeedback.vibrate') { hapticFeedbackCalls++; } diff --git a/packages/flutter/test/widgets/editable_text_cursor_test.dart b/packages/flutter/test/widgets/editable_text_cursor_test.dart index b05571a818..b069b4e977 100644 --- a/packages/flutter/test/widgets/editable_text_cursor_test.dart +++ b/packages/flutter/test/widgets/editable_text_cursor_test.dart @@ -79,7 +79,7 @@ void main() { // Populate a fake clipboard. const String clipboardContent = ' '; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') return const {'text': clipboardContent}; return null; @@ -131,7 +131,7 @@ void main() { // Populate a fake clipboard. const String clipboardContent = ' '; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') return const {'text': clipboardContent}; return null; @@ -853,7 +853,7 @@ void main() { // Populate a fake clipboard. const String clipboardContent = 'Hello world!'; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') return const {'text': clipboardContent}; return null; @@ -911,7 +911,7 @@ void main() { // Populate a fake clipboard. const String clipboardContent = 'Hello world!'; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') return const {'text': clipboardContent}; return null; diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 109767fb70..644d90c728 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -65,9 +65,9 @@ class MockClipboard { } void main() { - TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + (TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding) + .defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); setUp(() async { debugResetSemanticsIdCounter(); @@ -1982,7 +1982,7 @@ void main() { await tester.pump(); // An extra pump to allow focus request to go through. final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); @@ -3234,7 +3234,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/22212. final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); @@ -3265,7 +3265,7 @@ void main() { testWidgets('location of widget is sent on show keyboard', (WidgetTester tester) async { final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); @@ -3302,7 +3302,7 @@ void main() { testWidgets('transform and size is reset when text connection opens', (WidgetTester tester) async { final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); @@ -3390,7 +3390,7 @@ void main() { testWidgets('size and transform are sent when they change', (WidgetTester tester) async { final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); @@ -3432,7 +3432,7 @@ void main() { testWidgets('text styling info is sent on show keyboard', (WidgetTester tester) async { final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); @@ -3519,7 +3519,7 @@ void main() { await tester.showKeyboard(find.byType(EditableText)); final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); setState(() { @@ -3748,7 +3748,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/22212. final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); @@ -5720,7 +5720,7 @@ void main() { testWidgets('Synchronous test of local and remote editing values', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/65059 final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); final TextInputFormatter formatter = TextInputFormatter.withFunction((TextEditingValue oldValue, TextEditingValue newValue) { @@ -5848,7 +5848,7 @@ void main() { testWidgets('Send text input state to engine when the input formatter rejects user input', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/67828 final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); final TextInputFormatter formatter = TextInputFormatter.withFunction((TextEditingValue oldValue, TextEditingValue newValue) { @@ -5927,7 +5927,7 @@ void main() { testWidgets('Repeatedly receiving [TextEditingValue] will not trigger a keyboard request', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/66036 final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); final TextEditingController controller = TextEditingController(); @@ -6048,7 +6048,7 @@ void main() { testWidgets('TextEditingController.clear() behavior test', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/66316 final List log = []; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall methodCall) async { log.add(methodCall); }); final TextEditingController controller = TextEditingController(); diff --git a/packages/flutter/test/widgets/modal_barrier_test.dart b/packages/flutter/test/widgets/modal_barrier_test.dart index 310ec98e5b..050941dd4a 100644 --- a/packages/flutter/test/widgets/modal_barrier_test.dart +++ b/packages/flutter/test/widgets/modal_barrier_test.dart @@ -159,7 +159,7 @@ void main() { testWidgets('ModalBarrier plays system alert sound when user tries to dismiss it', (WidgetTester tester) async { final List playedSystemSounds = []; try { - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'SystemSound.play') playedSystemSounds.add(methodCall.arguments as String); }); @@ -176,7 +176,7 @@ void main() { await tester.tap(find.text('target'), warnIfMissed: false); await tester.pumpWidget(subject); } finally { - SystemChannels.platform.setMockMethodCallHandler(null); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); } expect(playedSystemSounds, hasLength(1)); expect(playedSystemSounds[0], SystemSoundType.alert.toString()); diff --git a/packages/flutter/test/widgets/mouse_region_test.dart b/packages/flutter/test/widgets/mouse_region_test.dart index 71773d3449..ce9ae726fe 100644 --- a/packages/flutter/test/widgets/mouse_region_test.dart +++ b/packages/flutter/test/widgets/mouse_region_test.dart @@ -1591,7 +1591,7 @@ void main() { final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(location: const Offset(100, 100)); addTearDown(gesture.removePointer); - SystemChannels.mouseCursor.setMockMethodCallHandler((_) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.mouseCursor, (_) async { logCursors.add('cursor'); }); diff --git a/packages/flutter/test/widgets/platform_view_test.dart b/packages/flutter/test/widgets/platform_view_test.dart index 5e3dcd1428..774604c7f9 100644 --- a/packages/flutter/test/widgets/platform_view_test.dart +++ b/packages/flutter/test/widgets/platform_view_test.dart @@ -1075,7 +1075,7 @@ void main() { await tester.pump(); late int lastPlatformViewTextClient; - SystemChannels.textInput.setMockMethodCallHandler((MethodCall call) { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (MethodCall call) { if (call.method == 'TextInput.setPlatformViewClient') { lastPlatformViewTextClient = call.arguments as int; } diff --git a/packages/flutter/test/widgets/route_notification_messages_test.dart b/packages/flutter/test/widgets/route_notification_messages_test.dart index ea6188875f..d6c50c6b88 100644 --- a/packages/flutter/test/widgets/route_notification_messages_test.dart +++ b/packages/flutter/test/widgets/route_notification_messages_test.dart @@ -55,7 +55,7 @@ void main() { final List log = []; - SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (MethodCall methodCall) async { log.add(methodCall); }); @@ -110,7 +110,7 @@ void main() { testWidgets('Navigator does not report route name by default', (WidgetTester tester) async { final List log = []; - SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (MethodCall methodCall) async { log.add(methodCall); }); @@ -160,7 +160,7 @@ void main() { final List log = []; - SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (MethodCall methodCall) async { log.add(methodCall); }); @@ -215,7 +215,7 @@ void main() { testWidgets('Nameless routes should send platform messages', (WidgetTester tester) async { final List log = []; - SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (MethodCall methodCall) async { log.add(methodCall); }); @@ -262,7 +262,7 @@ void main() { testWidgets('PlatformRouteInformationProvider reports URL', (WidgetTester tester) async { final List log = []; - SystemChannels.navigation.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.navigation, (MethodCall methodCall) async { log.add(methodCall); }); diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index c3894c00dd..f38ac554f8 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -124,7 +124,7 @@ double getOpacity(WidgetTester tester, Finder finder) { void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); const String kThreeLines = 'First line of text is\n' @@ -1626,7 +1626,7 @@ void main() { final FocusNode focusNode = FocusNode(); String clipboardContent = ''; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.setData') clipboardContent = methodCall.arguments['text'] as String; else if (methodCall.method == 'Clipboard.getData') diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart index 8192d15728..48107e8f0d 100644 --- a/packages/flutter/test/widgets/text_selection_test.dart +++ b/packages/flutter/test/widgets/text_selection_test.dart @@ -19,15 +19,15 @@ class MockClipboard { 'text': null, }; - Future handleMethodCall(MethodCall methodCall) async { + Future handleMethodCall(MethodCall methodCall) async { switch (methodCall.method) { case 'Clipboard.getData': - if (getDataThrows) { + if (getDataThrows) throw Exception(); - } return _clipboardData; case 'Clipboard.setData': _clipboardData = methodCall.arguments; + break; } } } @@ -758,11 +758,11 @@ void main() { group('when Clipboard fails', () { setUp(() { final MockClipboard mockClipboard = MockClipboard(getDataThrows: true); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); }); tearDown(() { - SystemChannels.platform.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); }); test('Clipboard API failure is gracefully recovered from', () async { @@ -778,11 +778,11 @@ void main() { final MockClipboard mockClipboard = MockClipboard(); setUp(() { - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); }); tearDown(() { - SystemChannels.platform.setMockMethodCallHandler(null); + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); }); test('update sets value based on clipboard contents', () async { diff --git a/packages/flutter/test/widgets/title_test.dart b/packages/flutter/test/widgets/title_test.dart index 9e7a91eaad..b3e9a87942 100644 --- a/packages/flutter/test/widgets/title_test.dart +++ b/packages/flutter/test/widgets/title_test.dart @@ -36,7 +36,7 @@ void main() { testWidgets('should not pass "null" to setApplicationSwitcherDescription', (WidgetTester tester) async { final List log = []; - SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { log.add(methodCall); }); diff --git a/packages/flutter_driver/lib/src/extension/extension.dart b/packages/flutter_driver/lib/src/extension/extension.dart index 2150a6e119..c9caa19281 100644 --- a/packages/flutter_driver/lib/src/extension/extension.dart +++ b/packages/flutter_driver/lib/src/extension/extension.dart @@ -30,7 +30,7 @@ const String _extensionMethodName = 'driver'; /// eventually completes to a string response. typedef DataHandler = Future Function(String? message); -class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, GestureBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding { +class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, GestureBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding, TestDefaultBinaryMessengerBinding { _DriverBinding(this._handler, this._silenceErrors, this._enableTextEntryEmulation, this.finders, this.commands); final DataHandler? _handler; @@ -51,11 +51,6 @@ class _DriverBinding extends BindingBase with SchedulerBinding, ServicesBinding, registerWebServiceExtension(extension.call); } } - - @override - BinaryMessenger createBinaryMessenger() { - return TestDefaultBinaryMessenger(super.createBinaryMessenger()); - } } /// Enables Flutter Driver VM service extension. @@ -330,11 +325,11 @@ class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory, registerTextInput(); } - for(final FinderExtension finder in finders) { + for (final FinderExtension finder in finders) { _finderExtensions[finder.finderType] = finder; } - for(final CommandExtension command in commands) { + for (final CommandExtension command in commands) { _commandExtensions[command.commandKind] = command; } } @@ -418,7 +413,7 @@ class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory, @override Command deserializeCommand(Map params, DeserializeFinderFactory finderFactory) { final String? kind = params['command']; - if(_commandExtensions.containsKey(kind)) { + if (_commandExtensions.containsKey(kind)) { return _commandExtensions[kind]!.deserialize(params, finderFactory, this); } @@ -434,7 +429,7 @@ class FlutterDriverExtension with DeserializeFinderFactory, CreateFinderFactory, @override Future handleCommand(Command command, WidgetController prober, CreateFinderFactory finderFactory) { final String kind = command.kind; - if(_commandExtensions.containsKey(kind)) { + if (_commandExtensions.containsKey(kind)) { return _commandExtensions[kind]!.call(command, prober, finderFactory, this); } diff --git a/packages/flutter_driver/test/src/real_tests/extension_test.dart b/packages/flutter_driver/test/src/real_tests/extension_test.dart index 6857a2130f..24c39834da 100644 --- a/packages/flutter_driver/test/src/real_tests/extension_test.dart +++ b/packages/flutter_driver/test/src/real_tests/extension_test.dart @@ -279,7 +279,7 @@ void main() { 'waiting for NoPendingPlatformMessages returns until a single method channel call returns', (WidgetTester tester) async { const MethodChannel channel = MethodChannel('helloChannel', JSONMethodCodec()); const MessageCodec jsonMessage = JSONMessageCodec(); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + tester.binding.defaultBinaryMessenger.setMockMessageHandler( 'helloChannel', (ByteData? message) { return Future.delayed( const Duration(milliseconds: 10), @@ -313,7 +313,7 @@ void main() { const MessageCodec jsonMessage = JSONMessageCodec(); // Configures channel 1 const MethodChannel channel1 = MethodChannel('helloChannel1', JSONMethodCodec()); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + tester.binding.defaultBinaryMessenger.setMockMessageHandler( 'helloChannel1', (ByteData? message) { return Future.delayed( const Duration(milliseconds: 10), @@ -322,7 +322,7 @@ void main() { // Configures channel 2 const MethodChannel channel2 = MethodChannel('helloChannel2', JSONMethodCodec()); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + tester.binding.defaultBinaryMessenger.setMockMessageHandler( 'helloChannel2', (ByteData? message) { return Future.delayed( const Duration(milliseconds: 20), @@ -362,7 +362,7 @@ void main() { const MessageCodec jsonMessage = JSONMessageCodec(); // Configures channel 1 const MethodChannel channel1 = MethodChannel('helloChannel1', JSONMethodCodec()); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + tester.binding.defaultBinaryMessenger.setMockMessageHandler( 'helloChannel1', (ByteData? message) { return Future.delayed( const Duration(milliseconds: 10), @@ -371,7 +371,7 @@ void main() { // Configures channel 2 const MethodChannel channel2 = MethodChannel('helloChannel2', JSONMethodCodec()); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + tester.binding.defaultBinaryMessenger.setMockMessageHandler( 'helloChannel2', (ByteData? message) { return Future.delayed( const Duration(milliseconds: 20), @@ -413,7 +413,7 @@ void main() { const MessageCodec jsonMessage = JSONMessageCodec(); // Configures channel 1 const MethodChannel channel1 = MethodChannel('helloChannel1', JSONMethodCodec()); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + tester.binding.defaultBinaryMessenger.setMockMessageHandler( 'helloChannel1', (ByteData? message) { return Future.delayed( const Duration(milliseconds: 20), @@ -422,7 +422,7 @@ void main() { // Configures channel 2 const MethodChannel channel2 = MethodChannel('helloChannel2', JSONMethodCodec()); - ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler( + tester.binding.defaultBinaryMessenger.setMockMessageHandler( 'helloChannel2', (ByteData? message) { return Future.delayed( const Duration(milliseconds: 10), diff --git a/packages/flutter_test/lib/flutter_test.dart b/packages/flutter_test/lib/flutter_test.dart index fbeb6f1360..d7531fff1a 100644 --- a/packages/flutter_test/lib/flutter_test.dart +++ b/packages/flutter_test/lib/flutter_test.dart @@ -61,6 +61,7 @@ export 'src/all_elements.dart'; export 'src/animation_sheet.dart'; export 'src/binding.dart'; export 'src/controller.dart'; +export 'src/deprecated.dart'; export 'src/event_simulation.dart'; export 'src/finders.dart'; export 'src/frame_timing_summarizer.dart'; @@ -73,6 +74,7 @@ export 'src/restoration.dart'; export 'src/stack_manipulation.dart'; export 'src/test_async_utils.dart'; export 'src/test_compat.dart'; +export 'src/test_default_binary_messenger.dart'; export 'src/test_exception_reporter.dart'; export 'src/test_pointer.dart'; export 'src/test_text_input.dart'; diff --git a/packages/flutter_test/lib/src/_binding_io.dart b/packages/flutter_test/lib/src/_binding_io.dart index 19ec7dbd1f..f09b3e1937 100644 --- a/packages/flutter_test/lib/src/_binding_io.dart +++ b/packages/flutter_test/lib/src/_binding_io.dart @@ -13,8 +13,8 @@ import 'package:path/path.dart' as path; // ignore: deprecated_member_use import 'package:test_api/test_api.dart' as test_package; - import 'binding.dart'; +import 'deprecated.dart'; /// Ensure the [WidgetsBinding] is initialized. WidgetsBinding ensureInitialized([@visibleForTesting Map? environment]) { diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 4a22d99cd3..fce5aa1186 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -15,8 +15,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart' show TestWindow; import 'package:stack_trace/stack_trace.dart' as stack_trace; -// ignore: deprecated_member_use -import 'package:test_api/test_api.dart' as test_package; +import 'package:test_api/test_api.dart' as test_package; // ignore: deprecated_member_use import 'package:vector_math/vector_math_64.dart'; import '_binding_io.dart' if (dart.library.html) '_binding_web.dart' as binding; @@ -25,6 +24,7 @@ import 'platform.dart'; import 'restoration.dart'; import 'stack_manipulation.dart'; import 'test_async_utils.dart'; +import 'test_default_binary_messenger.dart'; import 'test_exception_reporter.dart'; import 'test_text_input.dart'; @@ -79,74 +79,29 @@ enum TestBindingEventSource { const Size _kDefaultTestViewportSize = Size(800.0, 600.0); -/// A [BinaryMessenger] subclass that is used as the default binary messenger -/// under testing environment. +/// Overrides the [ServicesBinding]'s binary messenger logic to use +/// [TestDefaultBinaryMessenger]. /// -/// It tracks status of data sent across the Flutter platform barrier, which is -/// useful for testing frameworks to monitor and synchronize against the -/// platform messages. -class TestDefaultBinaryMessenger extends BinaryMessenger { - /// Creates a [TestDefaultBinaryMessenger] instance. - /// - /// The [delegate] instance must not be null. - TestDefaultBinaryMessenger(this.delegate): assert(delegate != null); - - /// The delegate [BinaryMessenger]. - final BinaryMessenger delegate; - - final List> _pendingMessages = >[]; - - /// The number of incomplete/pending calls sent to the platform channels. - int get pendingMessageCount => _pendingMessages.length; - +/// Test bindings that are used by tests that mock message handlers for plugins +/// should mix in this binding to enable the use of the +/// [TestDefaultBinaryMessenger] APIs. +mixin TestDefaultBinaryMessengerBinding on BindingBase, ServicesBinding { @override - Future? send(String channel, ByteData? message) { - final Future? resultFuture = delegate.send(channel, message); - if (resultFuture != null) { - _pendingMessages.add(resultFuture); - resultFuture - .catchError((Object error) { /* errors are the responsibility of the caller */ }) - .whenComplete(() => _pendingMessages.remove(resultFuture)); - } - return resultFuture; + void initInstances() { + super.initInstances(); + _instance = this; } - /// Returns a Future that completes after all the platform calls are finished. - /// - /// If a new platform message is sent after this method is called, this new - /// message is not tracked. Use with [pendingMessageCount] to guarantee no - /// pending message calls. - Future get platformMessagesFinished { - return Future.wait(_pendingMessages); - } + /// The current [TestDefaultBinaryMessengerBinding], if one has been created. + static TestDefaultBinaryMessengerBinding? get instance => _instance; + static TestDefaultBinaryMessengerBinding? _instance; @override - Future handlePlatformMessage( - String channel, - ByteData? data, - ui.PlatformMessageResponseCallback? callback, - ) { - return delegate.handlePlatformMessage(channel, data, callback); - } + TestDefaultBinaryMessenger get defaultBinaryMessenger => super.defaultBinaryMessenger as TestDefaultBinaryMessenger; @override - void setMessageHandler(String channel, MessageHandler? handler) { - delegate.setMessageHandler(channel, handler); - } - - @override - bool checkMessageHandler(String channel, MessageHandler? handler) { - return delegate.checkMessageHandler(channel, handler); - } - - @override - void setMockMessageHandler(String channel, MessageHandler? handler) { - delegate.setMockMessageHandler(channel, handler); - } - - @override - bool checkMockMessageHandler(String channel, MessageHandler? handler) { - return delegate.checkMockMessageHandler(channel, handler); + TestDefaultBinaryMessenger createBinaryMessenger() { + return TestDefaultBinaryMessenger(super.createBinaryMessenger()); } } @@ -171,7 +126,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase SemanticsBinding, RendererBinding, PaintingBinding, - WidgetsBinding { + WidgetsBinding, + TestDefaultBinaryMessengerBinding { /// Constructor for [TestWidgetsFlutterBinding]. /// @@ -195,9 +151,15 @@ abstract class TestWidgetsFlutterBinding extends BindingBase /// Called by the test framework at the beginning of a widget test to /// prepare the binding for the next test. + /// + /// If [registerTestTextInput] returns true when this method is called, + /// the [testTextInput] is configured to simulate the keyboard. void reset() { _restorationManager = null; resetGestureBinding(); + testTextInput.reset(); + if (registerTestTextInput) + _testTextInput.register(); } @override @@ -237,7 +199,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase @protected bool get overrideHttpClient => true; - /// Determines whether the binding automatically registers [testTextInput]. + /// Determines whether the binding automatically registers [testTextInput] as + /// a fake keyboard implementation. /// /// Unit tests make use of this to mock out text input communication for /// widgets. An integration test would set this to false, to test real IME @@ -245,6 +208,19 @@ abstract class TestWidgetsFlutterBinding extends BindingBase /// /// [TestTextInput.isRegistered] reports whether the text input mock is /// registered or not. + /// + /// Some of the properties and methods on [testTextInput] are only valid if + /// [registerTestTextInput] returns true when a test starts. If those + /// members are accessed when using a binding that sets this flag to false, + /// they will throw. + /// + /// If this property returns true when a test ends, the [testTextInput] is + /// unregistered. + /// + /// This property should not change the value it returns during the lifetime + /// of the binding. Changing the value of this property risks very confusing + /// behavior as the [TestTextInput] may be inconsistently registered or + /// unregistered. @protected bool get registerTestTextInput => true; @@ -319,9 +295,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase binding.setupHttpOverrides(); } _testTextInput = TestTextInput(onCleared: _resetFocusedEditable); - if (registerTestTextInput) { - _testTextInput.register(); - } } @override @@ -331,11 +304,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase // doesn't get generated for tests. } - @override - BinaryMessenger createBinaryMessenger() { - return TestDefaultBinaryMessenger(super.createBinaryMessenger()); - } - /// Whether there is currently a test executing. bool get inTest; @@ -515,12 +483,20 @@ abstract class TestWidgetsFlutterBinding extends BindingBase TestTextInput get testTextInput => _testTextInput; late TestTextInput _testTextInput; - /// The current client of the onscreen keyboard. Callers must pump - /// an additional frame after setting this property to complete the - /// focus change. + /// The [State] of the current [EditableText] client of the onscreen keyboard. + /// + /// Setting this property to a new value causes the given [EditableTextState] + /// to focus itself and request the keyboard to establish a + /// [TextInputConnection]. + /// + /// Callers must pump an additional frame after setting this property to + /// complete the focus change. /// /// Instead of setting this directly, consider using /// [WidgetTester.showKeyboard]. + // + // TODO(ianh): We should just remove this property and move the call to + // requestKeyboard to the WidgetTester.showKeyboard method. EditableTextState? get focusedEditable => _focusedEditable; EditableTextState? _focusedEditable; set focusedEditable(EditableTextState? value) { @@ -799,6 +775,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase // alone so that we don't cause more spurious errors. runApp(Container(key: UniqueKey(), child: _postTestMessage)); // Unmount any remaining widgets. await pump(); + if (registerTestTextInput) + _testTextInput.unregister(); invariantTester(); _verifyAutoUpdateGoldensUnset(autoUpdateGoldensBeforeTest && !isBrowser); _verifyReportTestExceptionUnset(reportTestExceptionBeforeTest); diff --git a/packages/flutter_test/lib/src/deprecated.dart b/packages/flutter_test/lib/src/deprecated.dart new file mode 100644 index 0000000000..b1e1d63a21 --- /dev/null +++ b/packages/flutter_test/lib/src/deprecated.dart @@ -0,0 +1,109 @@ +// 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 'package:flutter/services.dart'; + +import 'binding.dart'; + +// TODO(ianh): Once https://github.com/dart-lang/dartdoc/issues/2033 is fixed, update the hyperlinks marked HYPERLINK below. +// TODO(ianh): Once cocoon and other customer_tests are migrated, deprecate these transitional APIs + +/// Shim to support the obsolete [setMockMessageHandler] and +/// [checkMockMessageHandler] methods on [BinaryMessenger] in tests. +/// +/// The implementations defer to [TestDefaultBinaryMessengerBinding.defaultBinaryMessenger]. +/// +/// Rather than calling [setMockMessageHandler] on the +/// `ServicesBinding.defaultBinaryMessenger`, use +/// `tester.binding.defaultBinaryMessenger.setMockMessageHandler` directly. This +/// more accurately represents the actual method invocation. +extension TestBinaryMessengerExtension on BinaryMessenger { + /// Shim for `TestDefaultBinaryMessenger.setMockMessageHandler`. + // HYPERLINK ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // TODO(ianh): deprecate this method: @NotYetDeprecated( + // 'Use tester.binding.defaultBinaryMessenger.setMockMessageHandler or ' + // 'TestDefaultBinaryMessenger.instance.defaultBinaryMessenger.setMockMessageHandler instead. ' + // 'This feature was deprecated after v2.1.0-10.0.pre.' + // ) + void setMockMessageHandler(String channel, MessageHandler? handler) { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMessageHandler(channel, handler); + } + + /// Shim for `TestDefaultBinaryMessenger.checkMockMessageHandler`. + // HYPERLINK ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // TODO(ianh): deprecate this method: @NotYetDeprecated( + // 'Use tester.binding.defaultBinaryMessenger.checkMockMessageHandler or ' + // 'TestDefaultBinaryMessenger.instance.defaultBinaryMessenger.checkMockMessageHandler instead.' + // 'This feature was deprecated after v2.1.0-10.0.pre.' + // ) + bool checkMockMessageHandler(String channel, Object? handler) { + return TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.checkMockMessageHandler(channel, handler); + } +} + +/// Shim to support the obsolete [setMockMessageHandler] and +/// [checkMockMessageHandler] methods on [BasicMessageChannel] in tests. +/// +/// The implementations defer to [TestDefaultBinaryMessengerBinding.defaultBinaryMessenger]. +/// +/// Rather than calling [setMockMessageHandler] on the message channel, use +/// `tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler` +/// directly. This more accurately represents the actual method invocation. +extension TestBasicMessageChannelExtension on BasicMessageChannel { + /// Shim for `TestDefaultBinaryMessenger.setMockDecodedMessageHandler`. + // HYPERLINK ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // TODO(ianh): deprecate this method: @NotYetDeprecated( + // 'Use tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler or ' + // 'TestDefaultBinaryMessenger.instance.defaultBinaryMessenger.setMockDecodedMessageHandler instead. ' + // 'This feature was deprecated after v2.1.0-10.0.pre.' + // ) + void setMockMessageHandler(Future Function(T? message)? handler) { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockDecodedMessageHandler(this, handler); + } + + /// Shim for `TestDefaultBinaryMessenger.checkMockMessageHandler`. + // HYPERLINK ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // TODO(ianh): deprecate this method: @NotYetDeprecated( + // 'Use tester.binding.defaultBinaryMessenger.checkMockMessageHandler or ' + // 'TestDefaultBinaryMessenger.instance.defaultBinaryMessenger.checkMockMessageHandler instead. ' + // 'For the first argument, pass channel.name. ' + // 'This feature was deprecated after v2.1.0-10.0.pre.' + // ) + bool checkMockMessageHandler(Object? handler) { + return TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.checkMockMessageHandler(name, handler); + } +} + +/// Shim to support the obsolete [setMockMethodCallHandler] and +/// [checkMockMethodCallHandler] methods on [MethodChannel] in tests. +/// +/// The implementations defer to [TestDefaultBinaryMessengerBinding.defaultBinaryMessenger]. +/// +/// Rather than calling [setMockMethodCallHandler] on the method channel, use +/// `tester.binding.defaultBinaryMessenger.setMockMethodCallHandler` directly. +/// This more accurately represents the actual method invocation. +extension TestMethodChannelExtension on MethodChannel { + /// Shim for `TestDefaultBinaryMessenger.setMockMethodCallHandler`. + // HYPERLINK ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // TODO(ianh): deprecate this method: @NotYetDeprecated( + // 'Use tester.binding.defaultBinaryMessenger.setMockMethodCallHandler or ' + // 'TestDefaultBinaryMessenger.instance.defaultBinaryMessenger.setMockMethodCallHandler instead. ' + // 'This feature was deprecated after v2.1.0-10.0.pre.' + // ) + void setMockMethodCallHandler(Future? Function(MethodCall call)? handler) { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(this, handler); + } + + /// Shim for `TestDefaultBinaryMessenger.checkMockMessageHandler`. + // HYPERLINK ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // TODO(ianh): deprecate this method: @NotYetDeprecated( + // 'Use tester.binding.defaultBinaryMessenger.checkMockMessageHandler or ' + // 'TestDefaultBinaryMessenger.instance.defaultBinaryMessenger.checkMockMessageHandler instead. ' + // 'For the first argument, pass channel.name. ' + // 'This feature was deprecated after v2.1.0-10.0.pre.' + // ) + bool checkMockMethodCallHandler(Object? handler) { + return TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.checkMockMessageHandler(name, handler); + } +} diff --git a/packages/flutter_test/lib/src/event_simulation.dart b/packages/flutter_test/lib/src/event_simulation.dart index 121c20d17c..f769ab0749 100644 --- a/packages/flutter_test/lib/src/event_simulation.dart +++ b/packages/flutter_test/lib/src/event_simulation.dart @@ -2,10 +2,13 @@ // 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:io'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/services.dart'; + +import 'binding.dart'; import 'test_async_utils.dart'; // TODO(gspencergoog): Replace this with more robust key simulation code once @@ -637,21 +640,20 @@ class KeyEventSimulator { assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation'); final Map data = getKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey); - bool result = false; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + final Completer result = Completer(); + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) { if (data == null) { + result.complete(false); return; } final Map decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map; - if (decoded['handled'] as bool) { - result = true; - } + result.complete(decoded['handled'] as bool); } ); - return result; + return result.future; }); } @@ -677,7 +679,7 @@ class KeyEventSimulator { final Map data = getKeyData(key, platform: platform!, isDown: false, physicalKey: physicalKey); bool result = false; - await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData? data) { diff --git a/packages/flutter_test/lib/src/test_default_binary_messenger.dart b/packages/flutter_test/lib/src/test_default_binary_messenger.dart new file mode 100644 index 0000000000..d0e18ac5a6 --- /dev/null +++ b/packages/flutter_test/lib/src/test_default_binary_messenger.dart @@ -0,0 +1,306 @@ +// 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:ui' as ui; + +import 'package:fake_async/fake_async.dart'; +import 'package:flutter/services.dart'; + +/// A [BinaryMessenger] subclass that is used as the default binary messenger +/// under testing environment. +/// +/// It tracks status of data sent across the Flutter platform barrier, which is +/// useful for testing frameworks to monitor and synchronize against the +/// platform messages. +/// +/// ## Messages from the framework to the platform +/// +/// Messages are sent from the framework to the platform via the +/// [send] method. +/// +/// To intercept a message sent from the framework to the platform, +/// consider using [setMockMessageHandler], +/// [setMockDecodedMessageHandler], and [setMockMethodCallHandler] +/// (see also [checkMockMessageHandler]). +/// +/// To wait for all pending framework-to-platform messages, the +/// [platformMessagesFinished] getter provides an appropriate +/// [Future]. The [pendingMessageCount] getter returns the current +/// number of outstanding messages. +/// +/// ## Messages from the platform to the framework +/// +/// The platform sends messages via the [ChannelBuffers] API. Mock +/// messages can be sent to the framework using +/// [handlePlatformMessage]. +/// +/// Listeners for these messages are configured using [setMessageHandler]. +class TestDefaultBinaryMessenger extends BinaryMessenger { + /// Creates a [TestDefaultBinaryMessenger] instance. + /// + /// The [delegate] instance must not be null. + TestDefaultBinaryMessenger(this.delegate): assert(delegate != null); + + /// The delegate [BinaryMessenger]. + final BinaryMessenger delegate; + + // The handlers for messages from the engine (including fake + // messages sent by handlePlatformMessage). + final Map _inboundHandlers = {}; + + /// Send a mock message to the framework as if it came from the platform. + /// + /// If a listener has been set using [setMessageHandler], that listener is + /// invoked to handle the message, and this method returns a future that + /// completes with that handler's result. + /// + /// {@template flutter.flutter_test.TestDefaultBinaryMessenger.handlePlatformMessage.asyncHandlers} + /// It is strongly recommended that all handlers used with this API be + /// synchronous (not requiring any microtasks to complete), because + /// [testWidgets] tests run in a [FakeAsync] zone in which microtasks do not + /// progress except when time is explicitly advanced (e.g. with + /// [WidgetTester.pump]), which means that `await`ing a [Future] will result + /// in the test hanging. + /// {@endtemplate} + /// + /// If no listener is configured, this method returns right away with null. + /// + /// The `callback` argument, if non-null, will be called just before this + /// method's future completes, either with the result of the listener + /// registered with [setMessageHandler], or with null if no listener has + /// been registered. + /// + /// Messages can also be sent via [ChannelBuffers.push] (see + /// [ServicesBinding.channelBuffers]); the effect is the same, though that API + /// will not wait for a response. + // TODO(ianh): When the superclass `handlePlatformMessage` is removed, + // remove this @override (but leave the method). + @override + Future handlePlatformMessage( + String channel, + ByteData? data, + ui.PlatformMessageResponseCallback? callback, + ) { + Future? result; + if (_inboundHandlers.containsKey(channel)) + result = _inboundHandlers[channel]!(data); + result ??= Future.value(null); + if (callback != null) + result = result.then((ByteData? result) { callback(result); return result; }); + return result; + } + + @override + void setMessageHandler(String channel, MessageHandler? handler) { + if (handler == null) { + _inboundHandlers.remove(channel); + delegate.setMessageHandler(channel, null); + } else { + _inboundHandlers[channel] = handler; // used to handle fake messages sent via handlePlatformMessage + delegate.setMessageHandler(channel, handler); // used to handle real messages from the engine + } + } + + final List> _pendingMessages = >[]; + + /// The number of incomplete/pending calls sent to the platform channels. + int get pendingMessageCount => _pendingMessages.length; + + // Handlers that intercept and respond to outgoing messages, + // pretending to be the platform. + final Map _outboundHandlers = {}; + + // The outbound callbacks that were actually registered, so that we + // can implement the [checkMockMessageHandler] method. + final Map _outboundHandlerIdentities = {}; + + @override + Future? send(String channel, ByteData? message) { + final Future? resultFuture; + final MessageHandler? handler = _outboundHandlers[channel]; + if (handler != null) { + resultFuture = handler(message); + } else { + resultFuture = delegate.send(channel, message); + } + if (resultFuture != null) { + _pendingMessages.add(resultFuture); + resultFuture + .catchError((Object error) { /* errors are the responsibility of the caller */ }) + .whenComplete(() => _pendingMessages.remove(resultFuture)); + } + return resultFuture; + } + + /// Returns a Future that completes after all the platform calls are finished. + /// + /// If a new platform message is sent after this method is called, this new + /// message is not tracked. Use with [pendingMessageCount] to guarantee no + /// pending message calls. + Future get platformMessagesFinished { + return Future.wait(_pendingMessages); + } + + /// Set a callback for intercepting messages sent to the platform on + /// the given channel, without decoding them. + /// + /// Intercepted messages are not forwarded to the platform. + /// + /// The given callback will replace the currently registered + /// callback for that channel, if any. To stop intercepting messages + /// at all, pass null as the handler. + /// + /// The handler's return value, if non-null, is used as a response, + /// unencoded. + /// + /// {@macro flutter.flutter_test.TestDefaultBinaryMessenger.handlePlatformMessage.asyncHandlers} + /// + /// The `identity` argument, if non-null, is used to identify the + /// callback when checked by [checkMockMessageHandler]. If null, the + /// `handler` is used instead. (This allows closures to be passed as + /// the `handler` with an alias used as the `identity` so that a + /// reference to the closure need not be used. In practice, this is + /// used by [setMockDecodedMessageHandler] and + /// [setMockMethodCallHandler] to allow [checkMockMessageHandler] to + /// recognize the closures that were passed to those methods even + /// though those methods wrap those closures when passing them to + /// this method.) + /// + /// Registered callbacks are cleared after each test. + /// + /// See also: + /// + /// * [checkMockMessageHandler], which can verify if a handler is still + /// registered, which is useful in tests to ensure that no unexpected + /// handlers are being registered. + /// + /// * [setMockDecodedMessageHandler], which wraps this method but + /// decodes the messages using a [MessageCodec]. + /// + /// * [setMockMethodCallHandler], which wraps this method but decodes + /// the messages using a [MethodCodec]. + void setMockMessageHandler(String channel, MessageHandler? handler, [ Object? identity ]) { + if (handler == null) { + _outboundHandlers.remove(channel); + _outboundHandlerIdentities.remove(channel); + } else { + identity ??= handler; + _outboundHandlers[channel] = handler; + _outboundHandlerIdentities[channel] = identity; + } + } + + /// Set a callback for intercepting messages sent to the platform on + /// the given channel. + /// + /// Intercepted messages are not forwarded to the platform. + /// + /// The given callback will replace the currently registered + /// callback for that channel, if any. To stop intercepting messages + /// at all, pass null as the handler. + /// + /// Messages are decoded using the codec of the channel. + /// + /// The handler's return value, if non-null, is used as a response, + /// after encoding it using the channel's codec. + /// + /// {@macro flutter.flutter_test.TestDefaultBinaryMessenger.handlePlatformMessage.asyncHandlers} + /// + /// Registered callbacks are cleared after each test. + /// + /// See also: + /// + /// * [checkMockMessageHandler], which can verify if a handler is still + /// registered, which is useful in tests to ensure that no unexpected + /// handlers are being registered. + /// + /// * [setMockMessageHandler], which is similar but provides raw + /// access to the underlying bytes. + /// + /// * [setMockMethodCallHandler], which is similar but decodes + /// the messages using a [MethodCodec]. + void setMockDecodedMessageHandler(BasicMessageChannel channel, Future Function(T? message)? handler) { + if (handler == null) { + setMockMessageHandler(channel.name, null); + return; + } + setMockMessageHandler(channel.name, (ByteData? message) async { + return channel.codec.encodeMessage(await handler(channel.codec.decodeMessage(message))); + }, handler); + } + + /// Set a callback for intercepting method calls sent to the + /// platform on the given channel. + /// + /// Intercepted method calls are not forwarded to the platform. + /// + /// The given callback will replace the currently registered + /// callback for that channel, if any. To stop intercepting messages + /// at all, pass null as the handler. + /// + /// Methods are decoded using the codec of the channel. + /// + /// The handler's return value, if non-null, is used as a response, + /// after re-encoding it using the channel's codec. + /// + /// To send an error, throw a [PlatformException] in the handler. + /// Other exceptions are not caught. + /// + /// {@macro flutter.flutter_test.TestDefaultBinaryMessenger.handlePlatformMessage.asyncHandlers} + /// + /// Registered callbacks are cleared after each test. + /// + /// See also: + /// + /// * [checkMockMessageHandler], which can verify if a handler is still + /// registered, which is useful in tests to ensure that no unexpected + /// handlers are being registered. + /// + /// * [setMockMessageHandler], which is similar but provides raw + /// access to the underlying bytes. + /// + /// * [setMockDecodedMessageHandler], which is similar but decodes + /// the messages using a [MessageCodec]. + void setMockMethodCallHandler(MethodChannel channel, Future? Function(MethodCall message)? handler) { + if (handler == null) { + setMockMessageHandler(channel.name, null); + return; + } + setMockMessageHandler(channel.name, (ByteData? message) async { + final MethodCall call = channel.codec.decodeMethodCall(message); + try { + return channel.codec.encodeSuccessEnvelope(await handler(call)); + } on PlatformException catch (error) { + return channel.codec.encodeErrorEnvelope( + code: error.code, + message: error.message, + details: error.details, + ); + } on MissingPluginException { + return null; + } catch (error) { + return channel.codec.encodeErrorEnvelope(code: 'error', message: '$error', details: null); + } + }, handler); + } + + /// Returns true if the `handler` argument matches the `handler` + /// previously passed to [setMockMessageHandler], + /// [setMockDecodedMessageHandler], or [setMockMethodCallHandler]. + /// + /// Specifically, it compares the argument provided to the `identity` + /// argument provided to [setMockMessageHandler], defaulting to the + /// `handler` argument passed to that method is `identity` was null. + /// + /// This method is useful for tests or test harnesses that want to assert the + /// mock handler for the specified channel has not been altered by a previous + /// test. + /// + /// Passing null for the `handler` returns true if the handler for the + /// `channel` is not set. + /// + /// Registered callbacks are cleared after each test. + bool checkMockMessageHandler(String channel, Object? handler) => _outboundHandlerIdentities[channel] == handler; +} diff --git a/packages/flutter_test/lib/src/test_text_input.dart b/packages/flutter_test/lib/src/test_text_input.dart index c20fd56a27..a99bcef060 100644 --- a/packages/flutter_test/lib/src/test_text_input.dart +++ b/packages/flutter_test/lib/src/test_text_input.dart @@ -8,12 +8,28 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'binding.dart' show TestDefaultBinaryMessengerBinding; +import 'deprecated.dart'; +import 'test_async_utils.dart'; + export 'package:flutter/services.dart' show TextEditingValue, TextInputAction; /// A testing stub for the system's onscreen keyboard. /// /// Typical app tests will not need to use this class directly. /// +/// The [TestWidgetsFlutterBinding] class registers a [TestTextInput] instance +/// ([TestWidgetsFlutterBinding.testTextInput]) as a stub keyboard +/// implementation if its [TestWidgetsFlutterBinding.registerTestTextInput] +/// property returns true when a test starts, and unregisters it when the test +/// ends (unless it ends with a failure). +/// +/// See [register], [unregister], and [isRegistered] for details. +/// +/// The [enterText], [updateEditingValue], [receiveAction], and +/// [closeConnection] methods can be used even when the [TestTextInput] is not +/// registered. All other methods will assert if [isRegistered] is false. +/// /// See also: /// /// * [WidgetTester.enterText], which uses this class to simulate keyboard input. @@ -33,61 +49,76 @@ class TestTextInput { /// first be requested, e.g. using [WidgetTester.showKeyboard]. final VoidCallback? onCleared; - /// The messenger which sends the bytes for this channel, not null. - BinaryMessenger get _binaryMessenger => ServicesBinding.instance!.defaultBinaryMessenger; - - /// Resets any internal state of this object and calls [register]. - /// - /// This method is invoked by the testing framework between tests. It should - /// not ordinarily be called by tests directly. - void resetAndRegister() { - log.clear(); - editingState = null; - setClientArgs = null; - _client = 0; - _isVisible = false; - register(); - } - /// Installs this object as a mock handler for [SystemChannels.textInput]. - void register() => SystemChannels.textInput.setMockMethodCallHandler(_handleTextInputCall); - - /// Removes this object as a mock handler for [SystemChannels.textInput]. - /// - /// After calling this method, the channel will exchange messages with the - /// Flutter engine. Use this with [FlutterDriver] tests that need to display - /// on-screen keyboard provided by the operating system. - void unregister() => SystemChannels.textInput.setMockMethodCallHandler(null); - /// Log for method calls. /// /// For all registered channels, handled calls are added to the list. Can /// be cleaned using `log.clear()`. final List log = []; + /// Installs this object as a mock handler for [SystemChannels.textInput]. + /// + /// Called by the binding at the top of a test when + /// [TestWidgetsFlutterBinding.registerTestTextInput] is true. + void register() => SystemChannels.textInput.setMockMethodCallHandler(_handleTextInputCall); + + /// Removes this object as a mock handler for [SystemChannels.textInput]. + /// + /// After calling this method, the channel will exchange messages with the + /// Flutter engine instead of the stub. + /// + /// Called by the binding at the end of a (successful) test when + /// [TestWidgetsFlutterBinding.registerTestTextInput] is true. + void unregister() => SystemChannels.textInput.setMockMethodCallHandler(null); + /// Whether this [TestTextInput] is registered with [SystemChannels.textInput]. /// - /// Use [register] and [unregister] methods to control this value. + /// The binding uses the [register] and [unregister] methods to control this + /// value when [TestWidgetsFlutterBinding.registerTestTextInput] is true. bool get isRegistered => SystemChannels.textInput.checkMockMethodCallHandler(_handleTextInputCall); + int? _client; + /// Whether there are any active clients listening to text input. bool get hasAnyClients { assert(isRegistered); - return _client > 0; + return _client != null && _client! > 0; } - int _client = 0; - - /// Arguments supplied to the TextInput.setClient method call. + /// The last set of arguments supplied to the `TextInput.setClient` and + /// `TextInput.updateConfig` methods of this stub implementation. Map? setClientArgs; /// The last set of arguments that [TextInputConnection.setEditingState] sent - /// to the embedder. + /// to this stub implementation (i.e. the arguments set to + /// `TextInput.setEditingState`). /// /// This is a map representation of a [TextEditingValue] object. For example, /// it will have a `text` entry whose value matches the most recent /// [TextEditingValue.text] that was sent to the embedder. Map? editingState; + /// Whether the onscreen keyboard is visible to the user. + /// + /// Specifically, this reflects the last call to `TextInput.show` or + /// `TextInput.hide` received by the stub implementation. + bool get isVisible { + assert(isRegistered); + return _isVisible; + } + bool _isVisible = false; + + /// Resets any internal state of this object. + /// + /// This method is invoked by the testing framework between tests. It should + /// not ordinarily be called by tests directly. + void reset() { + log.clear(); + _client = null; + setClientArgs = null; + editingState = null; + _isVisible = false; + } + Future _handleTextInputCall(MethodCall methodCall) async { log.add(methodCall); switch (methodCall.method) { @@ -99,7 +130,7 @@ class TestTextInput { setClientArgs = methodCall.arguments as Map; break; case 'TextInput.clearClient': - _client = 0; + _client = null; _isVisible = false; onCleared?.call(); break; @@ -115,87 +146,86 @@ class TestTextInput { } } - /// Whether the onscreen keyboard is visible to the user. - bool get isVisible { - assert(isRegistered); - return _isVisible; - } - bool _isVisible = false; - - /// Simulates the user changing the [TextEditingValue] to the given value. - void updateEditingValue(TextEditingValue value) { - assert(isRegistered); - // Not using the `expect` function because in the case of a FlutterDriver - // test this code does not run in a package:test test zone. - if (_client == 0) - throw TestFailure('Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.'); - _binaryMessenger.handlePlatformMessage( - SystemChannels.textInput.name, - SystemChannels.textInput.codec.encodeMethodCall( - MethodCall( - 'TextInputClient.updateEditingState', - [_client, value.toJSON()], - ), - ), - (ByteData? data) { /* response from framework is discarded */ }, - ); - } - - /// Simulates the user closing the text input connection. + /// Simulates the user hiding the onscreen keyboard. /// - /// For example: - /// - User pressed the home button and sent the application to background. - /// - User closed the virtual keyboard. - void closeConnection() { + /// This does nothing but set the internal flag. + void hide() { assert(isRegistered); - // Not using the `expect` function because in the case of a FlutterDriver - // test this code does not run in a package:test test zone. - if (_client == 0) - throw TestFailure('Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.'); - _binaryMessenger.handlePlatformMessage( - SystemChannels.textInput.name, - SystemChannels.textInput.codec.encodeMethodCall( - MethodCall( - 'TextInputClient.onConnectionClosed', - [_client,] - ), - ), - (ByteData? data) { /* response from framework is discarded */ }, - ); + _isVisible = false; } - /// Simulates the user typing the given text. + /// Simulates the user changing the text of the focused text field, and resets + /// the selection. /// /// Calling this method replaces the content of the connected input field with /// `text`, and places the caret at the end of the text. + /// + /// To update the UI under test after this method is invoked, use + /// [WidgetTester.pump]. + /// + /// This can be called even if the [TestTextInput] has not been [register]ed. + /// + /// If this is used to inject text when there is a real IME connection, for + /// example when using the [integration_test] library, there is a risk that + /// the real IME will become confused as to the current state of input. + /// + /// See also: + /// + /// * [updateEditingValue], which takes a [TextEditingValue] so that one can + /// also change the selection. void enterText(String text) { - assert(isRegistered); updateEditingValue(TextEditingValue( text: text, selection: TextSelection.collapsed(offset: text.length), )); } + /// Simulates the user changing the [TextEditingValue] to the given value. + /// + /// To update the UI under test after this method is invoked, use + /// [WidgetTester.pump]. + /// + /// This can be called even if the [TestTextInput] has not been [register]ed. + /// + /// If this is used to inject text when there is a real IME connection, for + /// example when using the [integration_test] library, there is a risk that + /// the real IME will become confused as to the current state of input. + /// + /// See also: + /// + /// * [enterText], which is similar but takes only a String and resets the + /// selection. + void updateEditingValue(TextEditingValue value) { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + SystemChannels.textInput.name, + SystemChannels.textInput.codec.encodeMethodCall( + MethodCall( + 'TextInputClient.updateEditingState', + [_client ?? -1, value.toJSON()], + ), + ), + (ByteData? data) { /* ignored */ }, + ); + } + /// Simulates the user pressing one of the [TextInputAction] buttons. /// Does not check that the [TextInputAction] performed is an acceptable one /// based on the `inputAction` [setClientArgs]. + /// + /// This can be called even if the [TestTextInput] has not been [register]ed. + /// + /// If this is used to inject an action when there is a real IME connection, + /// for example when using the [integration_test] library, there is a risk + /// that the real IME will become confused as to the current state of input. Future receiveAction(TextInputAction action) async { - assert(isRegistered); return TestAsyncUtils.guard(() { - // Not using the `expect` function because in the case of a FlutterDriver - // test this code does not run in a package:test test zone. - if (_client == 0) { - throw TestFailure('Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.'); - } - final Completer completer = Completer(); - - _binaryMessenger.handlePlatformMessage( + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( SystemChannels.textInput.name, SystemChannels.textInput.codec.encodeMethodCall( MethodCall( 'TextInputClient.performAction', - [_client, action.toString()], + [_client ?? -1, action.toString()], ), ), (ByteData? data) { @@ -204,8 +234,7 @@ class TestTextInput { // Decoding throws a PlatformException if the data represents an // error, and that's all we care about here. SystemChannels.textInput.codec.decodeEnvelope(data!); - - // No error was found. Complete without issue. + // If we reach here then no error was found. Complete without issue. completer.complete(); } catch (error) { // An exception occurred as a result of receiveAction()'ing. Report @@ -214,14 +243,32 @@ class TestTextInput { } }, ); - return completer.future; }); } - /// Simulates the user hiding the onscreen keyboard. - void hide() { - assert(isRegistered); - _isVisible = false; + /// Simulates the user closing the text input connection. + /// + /// For example: + /// + /// * User pressed the home button and sent the application to background. + /// * User closed the virtual keyboard. + /// + /// This can be called even if the [TestTextInput] has not been [register]ed. + /// + /// If this is used to inject text when there is a real IME connection, for + /// example when using the [integration_test] library, there is a risk that + /// the real IME will become confused as to the current state of input. + void closeConnection() { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + SystemChannels.textInput.name, + SystemChannels.textInput.codec.encodeMethodCall( + MethodCall( + 'TextInputClient.onConnectionClosed', + [_client ?? -1], + ), + ), + (ByteData? data) { /* response from framework is discarded */ }, + ); } } diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index e5ca5d920f..50c23a5d91 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -129,7 +129,7 @@ void testWidgets( dynamic tags, }) { assert(variant != null); - assert(variant.values.isNotEmpty, 'There must be at least on value to test in the testing variant'); + assert(variant.values.isNotEmpty, 'There must be at least one value to test in the testing variant.'); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding; final WidgetTester tester = WidgetTester._(binding); for (final dynamic value in variant.values) { @@ -147,9 +147,8 @@ void testWidgets( test_package.addTearDown(binding.postTest); return binding.runTest( () async { - binding.reset(); + binding.reset(); // TODO(ianh): the binding should just do this itself in _runTest debugResetSemanticsIdCounter(); - tester.resetTestTextInput(); Object? memento; try { memento = await variant.setUp(value); @@ -918,10 +917,12 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker /// Acts as if the application went idle. /// /// Runs all remaining microtasks, including those scheduled as a result of - /// running them, until there are no more microtasks scheduled. + /// running them, until there are no more microtasks scheduled. Then, runs any + /// previously scheduled timers with zero time, and completes the returned future. /// - /// Does not run timers. May result in an infinite loop or run out of memory - /// if microtasks continue to recursively schedule new microtasks. + /// May result in an infinite loop or run out of memory if microtasks continue + /// to recursively schedule new microtasks. Will not run any timers scheduled + /// after this method was invoked, even if they are zero-time timers. Future idle() { return TestAsyncUtils.guard(() => binding.idle()); } @@ -1002,18 +1003,13 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker /// /// Typical app tests will not need to use this value. To add text to widgets /// like [TextField] or [TextFormField], call [enterText]. - TestTextInput get testTextInput => binding.testTextInput; - - /// Ensures that [testTextInput] is registered and [TestTextInput.log] is - /// reset. /// - /// This is called by the testing framework before test runs, so that if a - /// previous test has set its own handler on [SystemChannels.textInput], the - /// [testTextInput] regains control and the log is fresh for the new test. - /// It should not typically need to be called by tests. - void resetTestTextInput() { - testTextInput.resetAndRegister(); - } + /// Some of the properties and methods on this value are only valid if the + /// binding's [TestWidgetsFlutterBinding.registerTestTextInput] flag is set to + /// true as a test is starting (meaning that the keyboard is to be simulated + /// by the test framework). If those members are accessed when using a binding + /// that sets this flag to false, they will throw. + TestTextInput get testTextInput => binding.testTextInput; /// Give the text input widget specified by [finder] the focus, as if the /// onscreen keyboard had appeared. @@ -1035,6 +1031,9 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker matchRoot: true, ), ); + // Setting focusedEditable causes the binding to call requestKeyboard() + // on the EditableTextState, which itself eventually calls TextInput.attach + // to establish the connection. binding.focusedEditable = editable; await pump(); }); @@ -1052,6 +1051,12 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker /// /// To just give [finder] the focus without entering any text, /// see [showKeyboard]. + /// + /// To enter text into other widgets (e.g. a custom widget that maintains a + /// TextInputConnection the way that a [EditableText] does), first ensure that + /// that widget has an open connection (e.g. by using [tap] to to focus it), + /// then call `testTextInput.enterText` directly (see + /// [TestTextInput.enterText]). Future enterText(Finder finder, String text) async { return TestAsyncUtils.guard(() async { await showKeyboard(finder); diff --git a/packages/flutter_test/lib/src/window.dart b/packages/flutter_test/lib/src/window.dart index 500dd68eaf..c27c57d944 100644 --- a/packages/flutter_test/lib/src/window.dart +++ b/packages/flutter_test/lib/src/window.dart @@ -379,8 +379,16 @@ class TestWindow implements ui.SingletonFlutterWindow { platformDispatcher.sendPlatformMessage(name, data, callback); } + @Deprecated( + 'Instead of calling this callback, use ServicesBinding.instance.channelBuffers.push. ' + 'This feature was deprecated after v2.1.0-10.0.pre.' + ) @override ui.PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage; + @Deprecated( + 'Instead of setting this callback, use ServicesBinding.instance.defaultBinaryMessenger.setMessageHandler. ' + 'This feature was deprecated after v2.1.0-10.0.pre.' + ) @override set onPlatformMessage(ui.PlatformMessageCallback? callback) { platformDispatcher.onPlatformMessage = callback; diff --git a/packages/flutter_test/test/bindings_test.dart b/packages/flutter_test/test/bindings_test.dart index 15e14318c3..bc98524c79 100644 --- a/packages/flutter_test/test/bindings_test.dart +++ b/packages/flutter_test/test/bindings_test.dart @@ -11,6 +11,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:test_api/test_api.dart' as test_package; void main() { + final AutomatedTestWidgetsFlutterBinding binding = AutomatedTestWidgetsFlutterBinding(); + group(TestViewConfiguration, () { test('is initialized with top-level window if one is not provided', () { // The code below will throw without the default. @@ -20,15 +22,32 @@ void main() { group(AutomatedTestWidgetsFlutterBinding, () { test('allows setting defaultTestTimeout to 5 minutes', () { - final AutomatedTestWidgetsFlutterBinding binding = AutomatedTestWidgetsFlutterBinding(); binding.defaultTestTimeout = const test_package.Timeout(Duration(minutes: 5)); expect(binding.defaultTestTimeout.duration, const Duration(minutes: 5)); }); }); + // The next three tests must run in order -- first using `test`, then `testWidgets`, then `test` again. + + int order = 0; + test('Initializes httpOverrides and testTextInput', () async { - final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding; - expect(binding.testTextInput.isRegistered, true); + assert(order == 0); + expect(binding.testTextInput, isNotNull); + expect(binding.testTextInput.isRegistered, isFalse); expect(HttpOverrides.current, isNotNull); + order += 1; + }); + + testWidgets('Registers testTextInput', (WidgetTester tester) async { + assert(order == 1); + expect(tester.testTextInput.isRegistered, isTrue); + order += 1; + }); + + test('Unregisters testTextInput', () async { + assert(order == 2); + expect(binding.testTextInput.isRegistered, isFalse); + order += 1; }); } diff --git a/packages/flutter_test/test/test_default_binary_messenger_test.dart b/packages/flutter_test/test/test_default_binary_messenger_test.dart index 82cb18fd64..30cf93ddf6 100644 --- a/packages/flutter_test/test/test_default_binary_messenger_test.dart +++ b/packages/flutter_test/test/test_default_binary_messenger_test.dart @@ -20,12 +20,6 @@ class TestDelegate extends BinaryMessenger { Future handlePlatformMessage(String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback) => throw UnimplementedError(); @override void setMessageHandler(String channel, MessageHandler? handler) => throw UnimplementedError(); - @override - bool checkMessageHandler(String channel, MessageHandler? handler) => throw UnimplementedError(); - @override - void setMockMessageHandler(String channel, MessageHandler? handler) => throw UnimplementedError(); - @override - bool checkMockMessageHandler(String channel, MessageHandler? handler) => throw UnimplementedError(); } void main() { diff --git a/packages/flutter_web_plugins/lib/src/plugin_registry.dart b/packages/flutter_web_plugins/lib/src/plugin_registry.dart index ecd273fc16..cc397b7c3c 100644 --- a/packages/flutter_web_plugins/lib/src/plugin_registry.dart +++ b/packages/flutter_web_plugins/lib/src/plugin_registry.dart @@ -61,7 +61,7 @@ class Registrar extends BinaryMessenger { /// the [dart:ui] library. That function is only available when /// compiling for the web. void registerMessageHandler() { - // The function below is only defined in the Web dart:ui. + // The `ui.webOnlySetPluginHandler` function below is only defined in the Web dart:ui. // ignore: undefined_function ui.webOnlySetPluginHandler(handleFrameworkMessage); } @@ -141,7 +141,7 @@ class Registrar extends BinaryMessenger { @override Future send(String channel, ByteData? message) { final Completer completer = Completer(); - ui.window.onPlatformMessage!(channel, message, (ByteData? reply) { + ui.channelBuffers.push(channel, message, (ByteData? reply) { try { completer.complete(reply); } catch (exception, stack) { @@ -163,26 +163,6 @@ class Registrar extends BinaryMessenger { else _handlers[channel] = handler; } - - @override - bool checkMessageHandler(String channel, MessageHandler? handler) => _handlers[channel] == handler; - - @override - void setMockMessageHandler( - String channel, - MessageHandler? handler, - ) { - throw FlutterError( - 'Setting mock handlers is not supported on the platform side.', - ); - } - - @override - bool checkMockMessageHandler(String channel, MessageHandler? handler) { - throw FlutterError( - 'Setting mock handlers is not supported on the platform side.', - ); - } } /// This class was previously separate from [Registrar] but was merged into it diff --git a/packages/flutter_web_plugins/test/plugin_registry_test.dart b/packages/flutter_web_plugins/test/plugin_registry_test.dart index 008d721391..73cb681e07 100644 --- a/packages/flutter_web_plugins/test/plugin_registry_test.dart +++ b/packages/flutter_web_plugins/test/plugin_registry_test.dart @@ -71,12 +71,5 @@ void main() { 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)), - throwsFlutterError); - }); }); }