diff --git a/packages/flutter/lib/src/services/clipboard.dart b/packages/flutter/lib/src/services/clipboard.dart index 48ae122d07d..408923cf19c 100644 --- a/packages/flutter/lib/src/services/clipboard.dart +++ b/packages/flutter/lib/src/services/clipboard.dart @@ -59,4 +59,20 @@ class Clipboard { return null; return ClipboardData(text: result['text'] as String?); } + + /// Returns a future that resolves to true iff the clipboard contains string + /// data. + /// + /// See also: + /// * [The iOS hasStrings method](https://developer.apple.com/documentation/uikit/uipasteboard/1829416-hasstrings?language=objc). + static Future hasStrings() async { + final Map? result = await SystemChannels.platform.invokeMethod( + 'Clipboard.hasStrings', + Clipboard.kTextPlain, + ); + if (result == null) { + return false; + } + return result['value'] as bool; + } } diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index fdffef548e8..be4e416d25e 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -1579,28 +1579,13 @@ class ClipboardStatusNotifier extends ValueNotifier with Widget /// Check the [Clipboard] and update [value] if needed. Future update() async { - // iOS 14 added a notification that appears when an app accesses the - // clipboard. To avoid the notification, don't access the clipboard on iOS, - // and instead always show the paste button, even when the clipboard is - // empty. - // TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that - // won't trigger the notification. - // https://github.com/flutter/flutter/issues/60145 - switch (defaultTargetPlatform) { - case TargetPlatform.iOS: - value = ClipboardStatus.pasteable; - return; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.macOS: - case TargetPlatform.windows: - break; + if (_disposed) { + return; } - ClipboardData? data; + final bool hasStrings; try { - data = await Clipboard.getData(Clipboard.kTextPlain); + hasStrings = await Clipboard.hasStrings(); } catch (stacktrace) { // In the case of an error from the Clipboard API, set the value to // unknown so that it will try to update again later. @@ -1611,13 +1596,14 @@ class ClipboardStatusNotifier extends ValueNotifier with Widget return; } - final ClipboardStatus clipboardStatus = data != null && data.text != null && data.text!.isNotEmpty + final ClipboardStatus nextStatus = hasStrings ? ClipboardStatus.pasteable : ClipboardStatus.notPasteable; - if (_disposed || clipboardStatus == value) { + + if (_disposed || nextStatus == value) { return; } - value = clipboardStatus; + value = nextStatus; } @override diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index a9aac666d42..ddc43c72970 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -18,27 +18,12 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; +import '../widgets/clipboard_utils.dart'; import '../widgets/semantics_tester.dart'; // On web, the context menu (aka toolbar) is provided by the browser. final bool isContextMenuProvidedByPlatform = isBrowser; -class MockClipboard { - Object _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments! as Object; - break; - } - } -} - class MockTextSelectionControls extends TextSelectionControls { @override Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight, [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) { diff --git a/packages/flutter/test/cupertino/text_selection_test.dart b/packages/flutter/test/cupertino/text_selection_test.dart index 95e662124fe..70cf5596865 100644 --- a/packages/flutter/test/cupertino/text_selection_test.dart +++ b/packages/flutter/test/cupertino/text_selection_test.dart @@ -11,24 +11,9 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart' show textOffsetToPosition, findRenderEditable; -class MockClipboard { - Object _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments! as Object; - break; - } - } -} - class _LongCupertinoLocalizationsDelegate extends LocalizationsDelegate { const _LongCupertinoLocalizationsDelegate(); @@ -69,7 +54,6 @@ const _LongCupertinoLocalizations _longLocalizations = _LongCupertinoLocalizatio void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); // Returns true iff the button is visually enabled. bool appearsEnabled(WidgetTester tester, String text) { @@ -92,6 +76,23 @@ void main() { }).toList(); } + setUp(() async { + 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(() { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + null, + ); + }); + group('canSelectAll', () { Widget createEditableText({ Key? key, @@ -185,76 +186,6 @@ void main() { }); }); - // TODO(justinmc): https://github.com/flutter/flutter/issues/60145 - testWidgets('Paste always appears regardless of clipboard content on iOS', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController( - text: 'Atwater Peel Sherbrooke Bonaventure', - ); - await tester.pumpWidget( - CupertinoApp( - home: Column( - children: [ - CupertinoTextField( - controller: controller, - ), - ], - ), - ), - ); - - // Make sure the clipboard is empty to start. - await Clipboard.setData(const ClipboardData(text: '')); - - // Double tap to select the first word. - const int index = 4; - await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pump(const Duration(milliseconds: 50)); - await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pumpAndSettle(); - expect(controller.selection.isCollapsed, isFalse); - expect(controller.selection.baseOffset, 0); - expect(controller.selection.extentOffset, 7); - - // Paste is showing even though clipboard is empty. - expect(find.text('Paste'), findsOneWidget); - expect(find.text('Copy'), findsOneWidget); - expect(find.text('Cut'), findsOneWidget); - expect(find.descendant( - of: find.byType(Overlay), - matching: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TextSelectionHandleOverlay'), - ), findsNWidgets(2)); - - // Tap copy to add something to the clipboard and close the menu. - await tester.tapAt(tester.getCenter(find.text('Copy'))); - await tester.pumpAndSettle(); - - // The menu is gone, but the handles are visible on the existing selection. - expect(find.text('Copy'), findsNothing); - expect(find.text('Cut'), findsNothing); - expect(find.text('Paste'), findsNothing); - expect(controller.selection.isCollapsed, isFalse); - expect(controller.selection.baseOffset, 0); - expect(controller.selection.extentOffset, 7); - expect(find.descendant( - of: find.byType(Overlay), - matching: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TextSelectionHandleOverlay'), - ), findsNWidgets(2)); - - // Double tap to show the menu again. - await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pump(const Duration(milliseconds: 50)); - await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pumpAndSettle(); - - // Paste still shows. - expect(find.text('Paste'), findsOneWidget); - expect(find.text('Copy'), findsOneWidget); - expect(find.text('Cut'), findsOneWidget); - }, - skip: isBrowser, // [intended] We do not use Flutter-rendered context menu on the Web. - variant: const TargetPlatformVariant({ TargetPlatform.iOS }), - ); - group('Text selection menu overflow (iOS)', () { testWidgets('All menu items show when they fit.', (WidgetTester tester) async { final TextEditingController controller = TextEditingController(text: 'abc def ghi'); 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 37f6a191a56..047727dcdf0 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 @@ -6,6 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/clipboard_utils.dart'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); @@ -301,19 +303,3 @@ void main() { }); } - -class MockClipboard { - dynamic _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments; - break; - } - } -} diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index f3e38a50c8b..56ee34a87e2 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -7,24 +7,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/clipboard_utils.dart'; import '../widgets/semantics_tester.dart'; -class MockClipboard { - dynamic _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments; - break; - } - } -} - void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index a1e184d300b..cca0af72753 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -19,6 +19,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart' show findRenderEditable, globalize, textOffsetToPosition; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; @@ -31,22 +32,6 @@ final bool isContextMenuProvidedByPlatform = isBrowser; // On web, key events in text fields are handled by the browser. final bool areKeyEventsHandledByPlatform = isBrowser; -class MockClipboard { - Object _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments as Object; - break; - } - } -} - class MaterialLocalizationsDelegate extends LocalizationsDelegate { @override bool isSupported(Locale locale) => true; @@ -6220,8 +6205,8 @@ void main() { semantics.dispose(); - // On web (just like iOS), we don't check for pasteability because that - // triggers a permission dialog in the browser. + // On web, we don't check for pasteability because that triggers a + // permission dialog in the browser. // https://github.com/flutter/flutter/pull/57139#issuecomment-629048058 }, skip: isBrowser); // [intended] see above. @@ -9550,11 +9535,19 @@ void main() { ), ); - bool triedToReadClipboard = false; + bool calledGetData = false; + bool calledHasStrings = false; tester.binding.defaultBinaryMessenger .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { - if (methodCall.method == 'Clipboard.getData') { - triedToReadClipboard = true; + switch (methodCall.method) { + case 'Clipboard.getData': + calledGetData = true; + break; + case 'Clipboard.hasStrings': + calledHasStrings = true; + break; + default: + break; } return null; }); @@ -9567,14 +9560,16 @@ void main() { await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(); + // getData is not called unless something is pasted. hasStrings is used to + // check the status of the clipboard. + expect(calledGetData, false); if (kIsWeb) { - // The clipboard is not checked because it requires user permissions and - // web doesn't show a custom text selection menu. - expect(triedToReadClipboard, false); + // hasStrings is not checked because web doesn't show a custom text + // selection menu. + expect(calledHasStrings, false); } else { - // The clipboard is checked in order to decide if the content can be - // pasted. - expect(triedToReadClipboard, true); + // hasStrings is checked in order to decide if the content can be pasted. + expect(calledHasStrings, true); } }); diff --git a/packages/flutter/test/material/text_form_field_test.dart b/packages/flutter/test/material/text_form_field_test.dart index 36f8b5b5a7c..ac30c593a6d 100644 --- a/packages/flutter/test/material/text_form_field_test.dart +++ b/packages/flutter/test/material/text_form_field_test.dart @@ -11,24 +11,9 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; +import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart'; -class MockClipboard { - Object _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments as Object; - break; - } - } -} - void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); diff --git a/packages/flutter/test/material/text_selection_test.dart b/packages/flutter/test/material/text_selection_test.dart index 0ce861ea747..0ed4bacf792 100644 --- a/packages/flutter/test/material/text_selection_test.dart +++ b/packages/flutter/test/material/text_selection_test.dart @@ -7,33 +7,31 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart' show findRenderEditable, globalize, textOffsetToPosition; -class MockClipboard { - Object _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments as Object; - break; - } - } -} - void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); setUp(() async { + 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(() { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + null, + ); + }); + group('canSelectAll', () { Widget createEditableText({ required Key key, @@ -671,59 +669,4 @@ void main() { skip: isBrowser, // [intended] we don't supply the cut/copy/paste buttons on the web. variant: const TargetPlatformVariant({ TargetPlatform.android }) ); - - // TODO(justinmc): https://github.com/flutter/flutter/issues/60145 - testWidgets('Paste always appears regardless of clipboard content on iOS', (WidgetTester tester) async { - final TextEditingController controller = TextEditingController( - text: 'Atwater Peel Sherbrooke Bonaventure', - ); - await tester.pumpWidget( - MaterialApp( - home: Material( - child: Column( - children: [ - TextField( - controller: controller, - ), - ], - ), - ), - ), - ); - - // Make sure the clipboard is empty. - await Clipboard.setData(const ClipboardData(text: '')); - - // Double tap to select the first word. - const int index = 4; - await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pump(const Duration(milliseconds: 50)); - await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pumpAndSettle(); - - // Paste is showing even though clipboard is empty. - expect(find.text('Paste'), findsOneWidget); - expect(find.text('Copy'), findsOneWidget); - expect(find.text('Cut'), findsOneWidget); - - // Tap copy to add something to the clipboard and close the menu. - await tester.tapAt(tester.getCenter(find.text('Copy'))); - await tester.pumpAndSettle(); - expect(find.text('Copy'), findsNothing); - expect(find.text('Cut'), findsNothing); - - // Double tap to show the menu again. - await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pump(const Duration(milliseconds: 50)); - await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pumpAndSettle(); - - // Paste still shows. - expect(find.text('Copy'), findsOneWidget); - expect(find.text('Cut'), findsOneWidget); - expect(find.text('Paste'), findsOneWidget); - }, - skip: isBrowser, // [intended] we don't supply the cut/copy/paste buttons on the web. - variant: const TargetPlatformVariant({ TargetPlatform.iOS }) - ); } diff --git a/packages/flutter/test/widgets/clipboard_utils.dart b/packages/flutter/test/widgets/clipboard_utils.dart new file mode 100644 index 00000000000..b7a27d0791b --- /dev/null +++ b/packages/flutter/test/widgets/clipboard_utils.dart @@ -0,0 +1,33 @@ +// 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'; + +class MockClipboard { + MockClipboard({ + this.hasStringsThrows = false, + }); + + final bool hasStringsThrows; + + dynamic _clipboardData = { + 'text': null, + }; + + Future handleMethodCall(MethodCall methodCall) async { + switch (methodCall.method) { + case 'Clipboard.getData': + return _clipboardData; + case 'Clipboard.hasStrings': + if (hasStringsThrows) + throw Exception(); + final Map? clipboardDataMap = _clipboardData as Map?; + final String? text = clipboardDataMap?['text'] as String?; + return {'value': text != null && text.isNotEmpty}; + case 'Clipboard.setData': + _clipboardData = methodCall.arguments; + break; + } + } +} diff --git a/packages/flutter/test/widgets/editable_text_cursor_test.dart b/packages/flutter/test/widgets/editable_text_cursor_test.dart index e7e44f85430..a562b539163 100644 --- a/packages/flutter/test/widgets/editable_text_cursor_test.dart +++ b/packages/flutter/test/widgets/editable_text_cursor_test.dart @@ -82,6 +82,8 @@ void main() { tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') return const {'text': clipboardContent}; + if (methodCall.method == 'Clipboard.hasStrings') + return {'value': clipboardContent.isNotEmpty}; return null; }); @@ -134,6 +136,8 @@ void main() { tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') return const {'text': clipboardContent}; + if (methodCall.method == 'Clipboard.hasStrings') + return {'value': clipboardContent.isNotEmpty}; return null; }); @@ -860,6 +864,8 @@ void main() { tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') return const {'text': clipboardContent}; + if (methodCall.method == 'Clipboard.hasStrings') + return {'value': clipboardContent.isNotEmpty}; return null; }); @@ -918,6 +924,8 @@ void main() { tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { if (methodCall.method == 'Clipboard.getData') return const {'text': clipboardContent}; + if (methodCall.method == 'Clipboard.hasStrings') + return {'value': clipboardContent.isNotEmpty}; return null; }); diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index ed19cc5b6dd..3faaa076b7d 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -11,6 +11,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; +import '../widgets/clipboard_utils.dart'; import 'editable_text_utils.dart'; import 'semantics_tester.dart'; @@ -48,22 +49,6 @@ enum HandlePositionInViewport { leftEdge, rightEdge, within, } -class MockClipboard { - Object _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments as Object; - break; - } - } -} - void main() { final MockClipboard mockClipboard = MockClipboard(); (TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding) @@ -1336,6 +1321,56 @@ void main() { expect(find.text('Paste'), kIsWeb ? findsNothing : findsOneWidget); }); + testWidgets('Paste is shown only when there is something to paste', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: EditableText( + backgroundCursorColor: Colors.grey, + controller: controller, + focusNode: focusNode, + style: textStyle, + cursorColor: cursorColor, + selectionControls: materialTextSelectionControls, + ), + ), + ); + + final EditableTextState state = + tester.state(find.byType(EditableText)); + + // Make sure the clipboard has a valid string on it. + await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); + + // Show the toolbar. + state.renderEditable.selectWordsInRange( + from: Offset.zero, + cause: SelectionChangedCause.tap, + ); + await tester.pump(); + + // The Paste button is shown (except on web, which doesn't show the Flutter + // toolbar). + expect(state.showToolbar(), kIsWeb ? isFalse : isTrue); + await tester.pumpAndSettle(); + expect(find.text('Paste'), kIsWeb ? findsNothing : findsOneWidget); + + // Hide the menu again. + state.hideToolbar(); + await tester.pump(); + expect(find.text('Paste'), findsNothing); + + // Clear the clipboard + await Clipboard.setData(const ClipboardData(text: '')); + + // Show the toolbar again. + expect(state.showToolbar(), kIsWeb ? isFalse : isTrue); + await tester.pumpAndSettle(); + + // Paste is not shown. + await tester.pumpAndSettle(); + expect(find.text('Paste'), findsNothing); + }); + testWidgets('can show the toolbar after clearing all text', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/35998. await tester.pumpWidget( diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index f91790cf95f..3cdb542053e 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -13,25 +13,10 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart' show textOffsetToPosition; import '../widgets/semantics_tester.dart'; -class MockClipboard { - dynamic _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments; - break; - } - } -} - class MaterialLocalizationsDelegate extends LocalizationsDelegate { @override bool isSupported(Locale locale) => true; @@ -126,7 +111,6 @@ double getOpacity(WidgetTester tester, Finder finder) { void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); const String kThreeLines = 'First line of text is\n' @@ -165,11 +149,22 @@ void main() { setUp(() async { debugResetSemanticsIdCounter(); + 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(() { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler( + SystemChannels.platform, + null, + ); + }); + Widget selectableTextBuilder({ String text = '', int? maxLines = 1, diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart index 48107e8f0df..46606fcd1ce 100644 --- a/packages/flutter/test/widgets/text_selection_test.dart +++ b/packages/flutter/test/widgets/text_selection_test.dart @@ -8,29 +8,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -class MockClipboard { - MockClipboard({ - this.getDataThrows = false, - }); - - final bool getDataThrows; - - dynamic _clipboardData = { - 'text': null, - }; - - Future handleMethodCall(MethodCall methodCall) async { - switch (methodCall.method) { - case 'Clipboard.getData': - if (getDataThrows) - throw Exception(); - return _clipboardData; - case 'Clipboard.setData': - _clipboardData = methodCall.arguments; - break; - } - } -} +import 'clipboard_utils.dart'; void main() { late int tapCount; @@ -757,7 +735,7 @@ void main() { group('ClipboardStatusNotifier', () { group('when Clipboard fails', () { setUp(() { - final MockClipboard mockClipboard = MockClipboard(getDataThrows: true); + final MockClipboard mockClipboard = MockClipboard(hasStringsThrows: true); TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); });