From 8128f08603b95fd78a2819c93ac4e6738513767f Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Mon, 24 Mar 2025 09:53:04 -0700 Subject: [PATCH] Use SystemContextMenu by default on iOS (#165354) With this change, widgets based on EditableText will show the system context menu by default on iOS. Anyone with a custom contextMenuBuilder will not be affected and will have to opt-in to using SystemContextMenu. Also, this does not affect SelectionArea, which can't receive paste. Fixes https://github.com/flutter/flutter/issues/163067 --- .../flutter/lib/src/cupertino/text_field.dart | 3 + .../src/cupertino/text_form_field_row.dart | 4 ++ .../lib/src/material/search_anchor.dart | 4 ++ .../lib/src/material/selectable_text.dart | 3 + .../flutter/lib/src/material/text_field.dart | 3 + .../lib/src/material/text_form_field.dart | 4 ++ .../test/cupertino/text_field_test.dart | 60 +++++++++++++++++-- .../cupertino/text_form_field_row_test.dart | 58 ++++++++++++++++++ .../test/material/search_anchor_test.dart | 55 +++++++++++++++++ .../test/material/text_field_test.dart | 57 ++++++++++++++++++ .../test/material/text_form_field_test.dart | 57 ++++++++++++++++++ .../test/widgets/selectable_text_test.dart | 57 ++++++++++++++++++ .../test/platform_dispatcher_test.dart | 15 +++++ 13 files changed, 374 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index 0abad2588d6..a682133bb06 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -808,6 +808,9 @@ class CupertinoTextField extends StatefulWidget { BuildContext context, EditableTextState editableTextState, ) { + if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) { + return SystemContextMenu.editableText(editableTextState: editableTextState); + } return CupertinoAdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState); } diff --git a/packages/flutter/lib/src/cupertino/text_form_field_row.dart b/packages/flutter/lib/src/cupertino/text_form_field_row.dart index 7fa5f854c13..d8a863ec708 100644 --- a/packages/flutter/lib/src/cupertino/text_form_field_row.dart +++ b/packages/flutter/lib/src/cupertino/text_form_field_row.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 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -271,6 +272,9 @@ class CupertinoTextFormFieldRow extends FormField { BuildContext context, EditableTextState editableTextState, ) { + if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) { + return SystemContextMenu.editableText(editableTextState: editableTextState); + } return CupertinoAdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState); } diff --git a/packages/flutter/lib/src/material/search_anchor.dart b/packages/flutter/lib/src/material/search_anchor.dart index f7f6bc8c30c..6cfe2bcec6a 100644 --- a/packages/flutter/lib/src/material/search_anchor.dart +++ b/packages/flutter/lib/src/material/search_anchor.dart @@ -9,6 +9,7 @@ import 'dart:async'; import 'dart:math' as math; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -1536,6 +1537,9 @@ class SearchBar extends StatefulWidget { BuildContext context, EditableTextState editableTextState, ) { + if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) { + return SystemContextMenu.editableText(editableTextState: editableTextState); + } return AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState); } diff --git a/packages/flutter/lib/src/material/selectable_text.dart b/packages/flutter/lib/src/material/selectable_text.dart index 77d5cd8895d..00f5cdab528 100644 --- a/packages/flutter/lib/src/material/selectable_text.dart +++ b/packages/flutter/lib/src/material/selectable_text.dart @@ -447,6 +447,9 @@ class SelectableText extends StatefulWidget { BuildContext context, EditableTextState editableTextState, ) { + if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) { + return SystemContextMenu.editableText(editableTextState: editableTextState); + } return AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState); } diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 70db04ae2c5..809098870f2 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -849,6 +849,9 @@ class TextField extends StatefulWidget { BuildContext context, EditableTextState editableTextState, ) { + if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) { + return SystemContextMenu.editableText(editableTextState: editableTextState); + } return AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState); } diff --git a/packages/flutter/lib/src/material/text_form_field.dart b/packages/flutter/lib/src/material/text_form_field.dart index 564543f3062..5c00c6f4521 100644 --- a/packages/flutter/lib/src/material/text_form_field.dart +++ b/packages/flutter/lib/src/material/text_form_field.dart @@ -4,6 +4,7 @@ import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -326,6 +327,9 @@ class TextFormField extends FormField { BuildContext context, EditableTextState editableTextState, ) { + if (defaultTargetPlatform == TargetPlatform.iOS && SystemContextMenu.isSupported(context)) { + return SystemContextMenu.editableText(editableTextState: editableTextState); + } return AdaptiveTextSelectionToolbar.editableText(editableTextState: editableTextState); } diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 38cb82a910c..00b6b590c50 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -25,8 +25,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/clipboard_utils.dart'; -import '../widgets/editable_text_utils.dart' - show OverflowWidgetTextEditingController, isContextMenuProvidedByPlatform; +import '../widgets/editable_text_utils.dart'; import '../widgets/live_text_utils.dart'; import '../widgets/semantics_tester.dart'; import '../widgets/text_selection_toolbar_utils.dart'; @@ -238,10 +237,6 @@ void main() { return endpoints[0].point; } - // Web has a less threshold for downstream/upstream text position. - Offset textOffsetToPosition(WidgetTester tester, int offset) => - textOffsetToBottomLeftPosition(tester, offset) + const Offset(kIsWeb ? 1 : 0, -2); - setUp(() async { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( SystemChannels.platform, @@ -8840,6 +8835,39 @@ void main() { }, skip: kIsWeb, // [intended] on web the browser handles the context menu. ); + + testWidgets( + 'iOS uses the system context menu by default if supported', + (WidgetTester tester) async { + TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu = + true; + _updateMediaQueryFromView(tester); + addTearDown(() { + TestWidgetsFlutterBinding.instance.platformDispatcher + .resetSupportsShowingSystemContextMenu(); + _updateMediaQueryFromView(tester); + }); + + final TextEditingController controller = TextEditingController(text: 'one two three'); + addTearDown(controller.dispose); + await tester.pumpWidget(CupertinoApp(home: CupertinoTextField(controller: controller))); + + // No context menu shown. + expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsNothing); + + // Double tap to select the first word and show the menu. + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(SelectionOverlay.fadeDuration); + + expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsOneWidget); + }, + skip: kIsWeb, // [intended] on web the browser handles the context menu. + variant: TargetPlatformVariant.only(TargetPlatform.iOS), + ); }); group('magnifier', () { @@ -9880,3 +9908,23 @@ void main() { variant: TargetPlatformVariant.all(), ); } + +// Trigger MediaQuery to update itself based on the View, which is not +// recreated between tests. This is necessary when changing something on +// TestPlatformDispatcher and expecting it to be picked up by MediaQuery. +// TODO(justinmc): This hack can be removed if +// https://github.com/flutter/flutter/issues/165519 is fixed. +void _updateMediaQueryFromView(WidgetTester tester) { + expect(find.byType(MediaQuery), findsOneWidget); + final WidgetsBindingObserver widgetsBindingObserver = + tester.state( + find.ancestor( + of: find.byType(MediaQuery), + matching: find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_MediaQueryFromView', + ), + ), + ) + as WidgetsBindingObserver; + widgetsBindingObserver.didChangeMetrics(); +} diff --git a/packages/flutter/test/cupertino/text_form_field_row_test.dart b/packages/flutter/test/cupertino/text_form_field_row_test.dart index 7a7c3999aef..5b7fab8f6fc 100644 --- a/packages/flutter/test/cupertino/text_form_field_row_test.dart +++ b/packages/flutter/test/cupertino/text_form_field_row_test.dart @@ -3,10 +3,13 @@ // found in the LICENSE file. import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/src/services/spell_check.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/editable_text_utils.dart'; + void main() { testWidgets('Passes textAlign to underlying CupertinoTextField', (WidgetTester tester) async { const TextAlign alignment = TextAlign.center; @@ -518,4 +521,59 @@ void main() { expect(stateKey.currentState!.value, 'initialValue'); expect(value, 'initialValue'); }); + + group('context menu', () { + testWidgets( + 'iOS uses the system context menu by default if supported', + (WidgetTester tester) async { + TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu = + true; + _updateMediaQueryFromView(tester); + addTearDown(() { + TestWidgetsFlutterBinding.instance.platformDispatcher + .resetSupportsShowingSystemContextMenu(); + _updateMediaQueryFromView(tester); + }); + + final TextEditingController controller = TextEditingController(text: 'one two three'); + addTearDown(controller.dispose); + await tester.pumpWidget(CupertinoApp(home: CupertinoTextField(controller: controller))); + + // No context menu shown. + expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsNothing); + + // Double tap to select the first word and show the menu. + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(SelectionOverlay.fadeDuration); + + expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsOneWidget); + }, + skip: kIsWeb, // [intended] on web the browser handles the context menu. + variant: TargetPlatformVariant.only(TargetPlatform.iOS), + ); + }); +} + +// Trigger MediaQuery to update itself based on the View, which is not +// recreated between tests. This is necessary when changing something on +// TestPlatformDispatcher and expecting it to be picked up by MediaQuery. +// TODO(justinmc): This hack can be removed if +// https://github.com/flutter/flutter/issues/165519 is fixed. +void _updateMediaQueryFromView(WidgetTester tester) { + expect(find.byType(MediaQuery), findsOneWidget); + final WidgetsBindingObserver widgetsBindingObserver = + tester.state( + find.ancestor( + of: find.byType(MediaQuery), + matching: find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_MediaQueryFromView', + ), + ), + ) + as WidgetsBindingObserver; + widgetsBindingObserver.didChangeMetrics(); } diff --git a/packages/flutter/test/material/search_anchor_test.dart b/packages/flutter/test/material/search_anchor_test.dart index 4e7eec9096d..4b0ab2c7988 100644 --- a/packages/flutter/test/material/search_anchor_test.dart +++ b/packages/flutter/test/material/search_anchor_test.dart @@ -3687,6 +3687,41 @@ void main() { expect(find.byType(Placeholder), findsOneWidget); }); + + testWidgets( + 'iOS uses the system context menu by default if supported', + (WidgetTester tester) async { + TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu = + true; + _updateMediaQueryFromView(tester); + addTearDown(() { + TestWidgetsFlutterBinding.instance.platformDispatcher + .resetSupportsShowingSystemContextMenu(); + _updateMediaQueryFromView(tester); + }); + + final TextEditingController controller = TextEditingController(text: 'one two three'); + addTearDown(controller.dispose); + await tester.pumpWidget( + MaterialApp(home: Material(child: TextField(controller: controller))), + ); + + // No context menu shown. + expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsNothing); + + // Double tap to select the first word and show the menu. + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(SelectionOverlay.fadeDuration); + + expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsOneWidget); + }, + skip: kIsWeb, // [intended] on web the browser handles the context menu. + variant: TargetPlatformVariant.only(TargetPlatform.iOS), + ); }); testWidgets('SearchAnchor does not dispose external SearchController', ( @@ -4107,3 +4142,23 @@ Material getSearchViewMaterial(WidgetTester tester) { find.descendant(of: findViewContent(), matching: find.byType(Material)).first, ); } + +// Trigger MediaQuery to update itself based on the View, which is not +// recreated between tests. This is necessary when changing something on +// TestPlatformDispatcher and expecting it to be picked up by MediaQuery. +// TODO(justinmc): This hack can be removed if +// https://github.com/flutter/flutter/issues/165519 is fixed. +void _updateMediaQueryFromView(WidgetTester tester) { + expect(find.byType(MediaQuery), findsOneWidget); + final WidgetsBindingObserver widgetsBindingObserver = + tester.state( + find.ancestor( + of: find.byType(MediaQuery), + matching: find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_MediaQueryFromView', + ), + ), + ) + as WidgetsBindingObserver; + widgetsBindingObserver.didChangeMetrics(); +} diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 5dc6b955c3c..d1de5b61c4e 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -16147,6 +16147,43 @@ void main() { }, skip: kIsWeb, // [intended] on web the browser handles the context menu. ); + + testWidgets( + 'iOS uses the system context menu by default if supported', + (WidgetTester tester) async { + TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu = + true; + _updateMediaQueryFromView(tester); + addTearDown(() { + TestWidgetsFlutterBinding.instance.platformDispatcher + .resetSupportsShowingSystemContextMenu(); + _updateMediaQueryFromView(tester); + }); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: TextField(controller: _textEditingController(text: 'one two three')), + ), + ), + ); + + // No context menu shown. + expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsNothing); + + // Double tap to select the first word and show the menu. + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(SelectionOverlay.fadeDuration); + + expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsOneWidget); + }, + skip: kIsWeb, // [intended] on web the browser handles the context menu. + variant: TargetPlatformVariant.only(TargetPlatform.iOS), + ); }); group('magnifier builder', () { @@ -17668,3 +17705,23 @@ TextEditingController _textEditingController({String text = ''}) { addTearDown(result.dispose); return result; } + +// Trigger MediaQuery to update itself based on the View, which is not +// recreated between tests. This is necessary when changing something on +// TestPlatformDispatcher and expecting it to be picked up by MediaQuery. +// TODO(justinmc): This hack can be removed if +// https://github.com/flutter/flutter/issues/165519 is fixed. +void _updateMediaQueryFromView(WidgetTester tester) { + expect(find.byType(MediaQuery), findsOneWidget); + final WidgetsBindingObserver widgetsBindingObserver = + tester.state( + find.ancestor( + of: find.byType(MediaQuery), + matching: find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_MediaQueryFromView', + ), + ), + ) + as WidgetsBindingObserver; + widgetsBindingObserver.didChangeMetrics(); +} diff --git a/packages/flutter/test/material/text_form_field_test.dart b/packages/flutter/test/material/text_form_field_test.dart index 6bf0269c04c..9e6951c933d 100644 --- a/packages/flutter/test/material/text_form_field_test.dart +++ b/packages/flutter/test/material/text_form_field_test.dart @@ -1651,4 +1651,61 @@ void main() { expect(find.text('**validation error**'), findsOneWidget); }); + + group('context menu', () { + testWidgets( + 'iOS uses the system context menu by default if supported', + (WidgetTester tester) async { + TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu = + true; + _updateMediaQueryFromView(tester); + addTearDown(() { + TestWidgetsFlutterBinding.instance.platformDispatcher + .resetSupportsShowingSystemContextMenu(); + _updateMediaQueryFromView(tester); + }); + + final TextEditingController controller = TextEditingController(text: 'one two three'); + addTearDown(controller.dispose); + await tester.pumpWidget( + MaterialApp(home: Material(child: TextField(controller: controller))), + ); + + // No context menu shown. + expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsNothing); + + // Double tap to select the first word and show the menu. + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(SelectionOverlay.fadeDuration); + + expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsOneWidget); + }, + skip: kIsWeb, // [intended] on web the browser handles the context menu. + variant: TargetPlatformVariant.only(TargetPlatform.iOS), + ); + }); +} + +// Trigger MediaQuery to update itself based on the View, which is not +// recreated between tests. This is necessary when changing something on +// TestPlatformDispatcher and expecting it to be picked up by MediaQuery. +// TODO(justinmc): This hack can be removed if +// https://github.com/flutter/flutter/issues/165519 is fixed. +void _updateMediaQueryFromView(WidgetTester tester) { + expect(find.byType(MediaQuery), findsOneWidget); + final WidgetsBindingObserver widgetsBindingObserver = + tester.state( + find.ancestor( + of: find.byType(MediaQuery), + matching: find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_MediaQueryFromView', + ), + ), + ) + as WidgetsBindingObserver; + widgetsBindingObserver.didChangeMetrics(); } diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index 6927dca4169..362370a0b3f 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -5567,4 +5567,61 @@ void main() { expect(tester.takeException(), isNull); }); + + group('context menu', () { + testWidgets( + 'iOS uses the system context menu by default if supported', + (WidgetTester tester) async { + TestWidgetsFlutterBinding.instance.platformDispatcher.supportsShowingSystemContextMenu = + true; + _updateMediaQueryFromView(tester); + addTearDown(() { + TestWidgetsFlutterBinding.instance.platformDispatcher + .resetSupportsShowingSystemContextMenu(); + _updateMediaQueryFromView(tester); + }); + + final TextEditingController controller = TextEditingController(text: 'one two three'); + addTearDown(controller.dispose); + await tester.pumpWidget( + MaterialApp(home: Material(child: TextField(controller: controller))), + ); + + // No context menu shown. + expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsNothing); + + // Double tap to select the first word and show the menu. + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tapAt(textOffsetToPosition(tester, 1)); + await tester.pump(SelectionOverlay.fadeDuration); + + expect(find.byType(CupertinoAdaptiveTextSelectionToolbar), findsNothing); + expect(find.byType(SystemContextMenu), findsOneWidget); + }, + skip: kIsWeb, // [intended] on web the browser handles the context menu. + variant: TargetPlatformVariant.only(TargetPlatform.iOS), + ); + }); +} + +// Trigger MediaQuery to update itself based on the View, which is not +// recreated between tests. This is necessary when changing something on +// TestPlatformDispatcher and expecting it to be picked up by MediaQuery. +// TODO(justinmc): This hack can be removed if +// https://github.com/flutter/flutter/issues/165519 is fixed. +void _updateMediaQueryFromView(WidgetTester tester) { + expect(find.byType(MediaQuery), findsOneWidget); + final WidgetsBindingObserver widgetsBindingObserver = + tester.state( + find.ancestor( + of: find.byType(MediaQuery), + matching: find.byWidgetPredicate( + (Widget w) => '${w.runtimeType}' == '_MediaQueryFromView', + ), + ), + ) + as WidgetsBindingObserver; + widgetsBindingObserver.didChangeMetrics(); } diff --git a/packages/flutter_test/test/platform_dispatcher_test.dart b/packages/flutter_test/test/platform_dispatcher_test.dart index f393151ecf9..653e159a6d2 100644 --- a/packages/flutter_test/test/platform_dispatcher_test.dart +++ b/packages/flutter_test/test/platform_dispatcher_test.dart @@ -83,6 +83,21 @@ void main() { ); }); + testWidgets('TestPlatformDispatcher can fake supportsShowingSystemContextMenu', ( + WidgetTester tester, + ) async { + verifyPropertyFaked( + tester: tester, + realValue: PlatformDispatcher.instance.supportsShowingSystemContextMenu, + fakeValue: !PlatformDispatcher.instance.supportsShowingSystemContextMenu, + propertyRetriever: + () => WidgetsBinding.instance.platformDispatcher.supportsShowingSystemContextMenu, + propertyFaker: (TestWidgetsFlutterBinding binding, bool fakeValue) { + binding.platformDispatcher.supportsShowingSystemContextMenu = fakeValue; + }, + ); + }); + testWidgets('TestPlatformDispatcher can fake brieflyShowPassword', (WidgetTester tester) async { verifyPropertyFaked( tester: tester,