Unverified Commit 91ef92d2 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

hasStrings support for eliminating clipboard notifications (#87678)

Use the hasStrings clipboard method when possible instead of reading the contents of the clipboard.
parent b2adffa9
......@@ -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<bool> hasStrings() async {
final Map<String, dynamic>? result = await SystemChannels.platform.invokeMethod(
'Clipboard.hasStrings',
Clipboard.kTextPlain,
);
if (result == null) {
return false;
}
return result['value'] as bool;
}
}
......@@ -1579,28 +1579,13 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget
/// Check the [Clipboard] and update [value] if needed.
Future<void> 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<ClipboardStatus> 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
......
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<dynamic> 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]) {
......
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<dynamic> 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<CupertinoLocalizations> {
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: <Widget>[
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>{ 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');
......
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<dynamic> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'Clipboard.getData':
return _clipboardData;
case 'Clipboard.setData':
_clipboardData = methodCall.arguments;
break;
}
}
}
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<dynamic> 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();
......
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<dynamic> 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<MaterialLocalizations> {
@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);
}
});
......
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<dynamic> 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();
......
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<dynamic> 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>{ 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: <Widget>[
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>{ TargetPlatform.iOS })
);
}
// 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 = <String, dynamic>{
'text': null,
};
Future<Object?> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'Clipboard.getData':
return _clipboardData;
case 'Clipboard.hasStrings':
if (hasStringsThrows)
throw Exception();
final Map<String, dynamic>? clipboardDataMap = _clipboardData as Map<String, dynamic>?;
final String? text = clipboardDataMap?['text'] as String?;
return <String, bool>{'value': text != null && text.isNotEmpty};
case 'Clipboard.setData':
_clipboardData = methodCall.arguments;
break;
}
}
}
......@@ -82,6 +82,8 @@ void main() {
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
if (methodCall.method == 'Clipboard.getData')
return const <String, dynamic>{'text': clipboardContent};
if (methodCall.method == 'Clipboard.hasStrings')
return <String, dynamic>{'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 <String, dynamic>{'text': clipboardContent};
if (methodCall.method == 'Clipboard.hasStrings')
return <String, dynamic>{'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 <String, dynamic>{'text': clipboardContent};
if (methodCall.method == 'Clipboard.hasStrings')
return <String, dynamic>{'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 <String, dynamic>{'text': clipboardContent};
if (methodCall.method == 'Clipboard.hasStrings')
return <String, dynamic>{'value': clipboardContent.isNotEmpty};
return null;
});
......
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<dynamic> 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<EditableTextState>(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(
......
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<dynamic> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'Clipboard.getData':
return _clipboardData;
case 'Clipboard.setData':
_clipboardData = methodCall.arguments;
break;
}
}
}
class MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
@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,
......
......@@ -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 = <String, dynamic>{
'text': null,
};
Future<Object?> 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);
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment