From 32547dcc7ee36a9d387dbf2d111b21a12b8192be Mon Sep 17 00:00:00 2001 From: nturgut Date: Fri, 15 May 2020 10:42:37 -0700 Subject: [PATCH] Revert " Bring back paste button hide behavior 3 (#57139)" (#57286) This reverts commit 8de07d5527bcdc6b02e43e8efed19219a84bf82e. --- .../lib/src/tests/text_field_constants.dart | 3 - .../lib/src/tests/text_field_page.dart | 3 +- .../test_driver/main_test.dart | 32 --- .../lib/src/cupertino/text_selection.dart | 195 ++++-------------- .../lib/src/material/text_selection.dart | 102 ++------- .../lib/src/widgets/editable_text.dart | 19 +- .../lib/src/widgets/text_selection.dart | 106 +--------- .../test/cupertino/text_field_test.dart | 33 ++- .../test/cupertino/text_selection_test.dart | 73 +------ .../test/material/date_picker_test.dart | 31 +-- .../flutter/test/material/search_test.dart | 30 --- .../test/material/text_field_test.dart | 123 +++-------- .../test/material/text_selection_test.dart | 89 +------- .../widgets/editable_text_cursor_test.dart | 9 +- .../test/widgets/editable_text_test.dart | 23 +-- 15 files changed, 119 insertions(+), 752 deletions(-) diff --git a/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_constants.dart b/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_constants.dart index ebc396d1b17..701446f58d9 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_constants.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_constants.dart @@ -10,6 +10,3 @@ const String normalTextFieldKeyValue = 'textFieldNormal'; /// The string supplied to the [ValueKey] for the password text field. const String passwordTextFieldKeyValue = 'passwordField'; - -/// The string supplied to the [ValueKey] for the page navigation back button. -const String backButtonKeyValue = 'back'; diff --git a/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_page.dart b/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_page.dart index dd738063b88..86af75172b3 100644 --- a/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_page.dart +++ b/dev/integration_tests/android_semantics_testing/lib/src/tests/text_field_page.dart @@ -17,14 +17,13 @@ class TextFieldPage extends StatefulWidget { class _TextFieldPageState extends State { final TextEditingController _normalController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); - final Key backButtonKey = const ValueKey(backButtonKeyValue); final Key normalTextFieldKey = const ValueKey(normalTextFieldKeyValue); final Key passwordTextFieldKey = const ValueKey(passwordTextFieldKeyValue); @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(leading: BackButton(key: backButtonKey)), + appBar: AppBar(leading: const BackButton(key: ValueKey('back'))), body: Material( child: Column(children: [ TextField( diff --git a/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart b/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart index 2568e24ada2..ef760023fb3 100644 --- a/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart +++ b/dev/integration_tests/android_semantics_testing/test_driver/main_test.dart @@ -63,38 +63,6 @@ void main() { await driver.tap(find.text(textFieldRoute)); // Delay for TalkBack to update focus as of November 2019 with Pixel 3 and Android API 28 await Future.delayed(const Duration(milliseconds: 500)); - - // The text selection menu and related semantics vary depending on if - // the clipboard contents are pasteable. Copy some text into the - // clipboard to make sure these tests always run with pasteable content - // in the clipboard. - // Ideally this should test the case where there is nothing on the - // clipboard as well, but there is no reliable way to clear the - // clipboard on Android devices. - final SerializableFinder normalTextField = find.descendant( - of: find.byValueKey(normalTextFieldKeyValue), - matching: find.byType('Semantics'), - firstMatchOnly: true, - ); - await driver.tap(normalTextField); - await Future.delayed(const Duration(milliseconds: 500)); - await driver.enterText('hello world'); - await Future.delayed(const Duration(milliseconds: 500)); - await driver.tap(normalTextField); - await Future.delayed(const Duration(milliseconds: 50)); - await driver.tap(normalTextField); - await Future.delayed(const Duration(milliseconds: 500)); - await driver.tap(find.text('SELECT ALL')); - await Future.delayed(const Duration(milliseconds: 500)); - await driver.tap(find.text('COPY')); - await Future.delayed(const Duration(milliseconds: 50)); - await driver.enterText(''); - await Future.delayed(const Duration(milliseconds: 500)); - // Go back to previous page and forward again to unfocus the field. - await driver.tap(find.byValueKey(backButtonKeyValue)); - await Future.delayed(const Duration(milliseconds: 500)); - await driver.tap(find.text(textFieldRoute)); - await Future.delayed(const Duration(milliseconds: 500)); }); test('TextField has correct Android semantics', () async { diff --git a/packages/flutter/lib/src/cupertino/text_selection.dart b/packages/flutter/lib/src/cupertino/text_selection.dart index 63e91d51b8a..510a9ef8fb5 100644 --- a/packages/flutter/lib/src/cupertino/text_selection.dart +++ b/packages/flutter/lib/src/cupertino/text_selection.dart @@ -8,7 +8,6 @@ import 'dart:ui' as ui; import 'package:flutter/widgets.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'button.dart'; import 'colors.dart'; @@ -63,151 +62,6 @@ const TextStyle _kToolbarButtonDisabledFontStyle = TextStyle( // Eyeballed value. const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 10.0, horizontal: 18.0); -// Generates the child that's passed into CupertinoTextSelectionToolbar. -class _CupertinoTextSelectionToolbarWrapper extends StatefulWidget { - const _CupertinoTextSelectionToolbarWrapper({ - Key key, - this.arrowTipX, - this.barTopY, - this.clipboardStatus, - this.handleCut, - this.handleCopy, - this.handlePaste, - this.handleSelectAll, - this.isArrowPointingDown, - }) : super(key: key); - - final double arrowTipX; - final double barTopY; - final ClipboardStatusNotifier clipboardStatus; - final VoidCallback handleCut; - final VoidCallback handleCopy; - final VoidCallback handlePaste; - final VoidCallback handleSelectAll; - final bool isArrowPointingDown; - - @override - _CupertinoTextSelectionToolbarWrapperState createState() => _CupertinoTextSelectionToolbarWrapperState(); -} - -class _CupertinoTextSelectionToolbarWrapperState extends State<_CupertinoTextSelectionToolbarWrapper> { - ClipboardStatusNotifier _clipboardStatus; - - void _onChangedClipboardStatus() { - setState(() { - // Inform the widget that the value of clipboardStatus has changed. - }); - } - - @override - void initState() { - super.initState(); - _clipboardStatus = widget.clipboardStatus ?? ClipboardStatusNotifier(); - _clipboardStatus.addListener(_onChangedClipboardStatus); - _clipboardStatus.update(); - } - - @override - void didUpdateWidget(_CupertinoTextSelectionToolbarWrapper oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.clipboardStatus == null && widget.clipboardStatus != null) { - _clipboardStatus.removeListener(_onChangedClipboardStatus); - _clipboardStatus.dispose(); - _clipboardStatus = widget.clipboardStatus; - } else if (oldWidget.clipboardStatus != null) { - if (widget.clipboardStatus == null) { - _clipboardStatus = ClipboardStatusNotifier(); - _clipboardStatus.addListener(_onChangedClipboardStatus); - oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus); - } else if (widget.clipboardStatus != oldWidget.clipboardStatus) { - _clipboardStatus = widget.clipboardStatus; - _clipboardStatus.addListener(_onChangedClipboardStatus); - oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus); - } - } - if (widget.handlePaste != null) { - _clipboardStatus.update(); - } - } - - @override - void dispose() { - super.dispose(); - // When used in an Overlay, this can be disposed after its creator has - // already disposed _clipboardStatus. - if (!_clipboardStatus.disposed) { - _clipboardStatus.removeListener(_onChangedClipboardStatus); - if (widget.clipboardStatus == null) { - _clipboardStatus.dispose(); - } - } - } - - @override - Widget build(BuildContext context) { - // Don't render the menu until the state of the clipboard is known. - if (widget.handlePaste != null - && _clipboardStatus.value == ClipboardStatus.unknown) { - return const SizedBox(width: 0.0, height: 0.0); - } - - final List items = []; - final CupertinoLocalizations localizations = CupertinoLocalizations.of(context); - final EdgeInsets arrowPadding = widget.isArrowPointingDown - ? EdgeInsets.only(bottom: _kToolbarArrowSize.height) - : EdgeInsets.only(top: _kToolbarArrowSize.height); - final Widget onePhysicalPixelVerticalDivider = - SizedBox(width: 1.0 / MediaQuery.of(context).devicePixelRatio); - - void addToolbarButton( - String text, - VoidCallback onPressed, - ) { - if (items.isNotEmpty) { - items.add(onePhysicalPixelVerticalDivider); - } - - items.add(CupertinoButton( - child: Text( - text, - overflow: TextOverflow.ellipsis, - style: _kToolbarButtonFontStyle, - ), - borderRadius: null, - color: _kToolbarBackgroundColor, - minSize: _kToolbarHeight, - onPressed: onPressed, - padding: _kToolbarButtonPadding.add(arrowPadding), - pressedOpacity: 0.7, - )); - } - - if (widget.handleCut != null) { - addToolbarButton(localizations.cutButtonLabel, widget.handleCut); - } - if (widget.handleCopy != null) { - addToolbarButton(localizations.copyButtonLabel, widget.handleCopy); - } - if (widget.handlePaste != null - && _clipboardStatus.value == ClipboardStatus.pasteable) { - addToolbarButton(localizations.pasteButtonLabel, widget.handlePaste); - } - if (widget.handleSelectAll != null) { - addToolbarButton(localizations.selectAllButtonLabel, widget.handleSelectAll); - } - - return CupertinoTextSelectionToolbar._( - barTopY: widget.barTopY, - arrowTipX: widget.arrowTipX, - isArrowPointingDown: widget.isArrowPointingDown, - child: items.isEmpty ? null : _CupertinoTextSelectionToolbarContent( - isArrowPointingDown: widget.isArrowPointingDown, - children: items, - ), - ); - } -} - /// An iOS-style toolbar that appears in response to text selection. /// /// Typically displays buttons for text manipulation, e.g. copying and pasting text. @@ -458,7 +312,6 @@ class _CupertinoTextSelectionControls extends TextSelectionControls { Offset position, List endpoints, TextSelectionDelegate delegate, - ClipboardStatusNotifier clipboardStatus, ) { assert(debugCheckHasMediaQuery(context)); final MediaQueryData mediaQuery = MediaQuery.of(context); @@ -485,15 +338,49 @@ class _CupertinoTextSelectionControls extends TextSelectionControls { ? endpoints.first.point.dy - textLineHeight - _kToolbarContentDistance - _kToolbarHeight : endpoints.last.point.dy + _kToolbarContentDistance; - return _CupertinoTextSelectionToolbarWrapper( - arrowTipX: arrowTipX, + final List items = []; + final CupertinoLocalizations localizations = CupertinoLocalizations.of(context); + final EdgeInsets arrowPadding = isArrowPointingDown + ? EdgeInsets.only(bottom: _kToolbarArrowSize.height) + : EdgeInsets.only(top: _kToolbarArrowSize.height); + + void addToolbarButtonIfNeeded( + String text, + bool Function(TextSelectionDelegate) predicate, + void Function(TextSelectionDelegate) onPressed, + ) { + if (!predicate(delegate)) { + return; + } + + items.add(CupertinoButton( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: _kToolbarButtonFontStyle, + ), + color: _kToolbarBackgroundColor, + minSize: _kToolbarHeight, + padding: _kToolbarButtonPadding.add(arrowPadding), + borderRadius: null, + pressedOpacity: 0.7, + onPressed: () => onPressed(delegate), + )); + } + + addToolbarButtonIfNeeded(localizations.cutButtonLabel, canCut, handleCut); + addToolbarButtonIfNeeded(localizations.copyButtonLabel, canCopy, handleCopy); + addToolbarButtonIfNeeded(localizations.pasteButtonLabel, canPaste, handlePaste); + addToolbarButtonIfNeeded(localizations.selectAllButtonLabel, canSelectAll, handleSelectAll); + + return CupertinoTextSelectionToolbar._( barTopY: localBarTopY + globalEditableRegion.top, - clipboardStatus: clipboardStatus, - handleCut: canCut(delegate) ? () => handleCut(delegate) : null, - handleCopy: canCopy(delegate) ? () => handleCopy(delegate, clipboardStatus) : null, - handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null, - handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null, + arrowTipX: arrowTipX, isArrowPointingDown: isArrowPointingDown, + child: items.isEmpty ? null : _CupertinoTextSelectionToolbarContent( + isArrowPointingDown: isArrowPointingDown, + children: items, + ), ); } diff --git a/packages/flutter/lib/src/material/text_selection.dart b/packages/flutter/lib/src/material/text_selection.dart index f590ea6566b..b9c3a4520fc 100644 --- a/packages/flutter/lib/src/material/text_selection.dart +++ b/packages/flutter/lib/src/material/text_selection.dart @@ -6,7 +6,6 @@ import 'dart:math' as math; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'debug.dart'; @@ -30,7 +29,6 @@ const double _kToolbarContentDistance = 8.0; /// Manages a copy/paste text selection toolbar. class _TextSelectionToolbar extends StatefulWidget { const _TextSelectionToolbar({ - this.clipboardStatus, Key key, this.handleCut, this.handleCopy, @@ -39,7 +37,6 @@ class _TextSelectionToolbar extends StatefulWidget { this.isAbove, }) : super(key: key); - final ClipboardStatusNotifier clipboardStatus; final VoidCallback handleCut; final VoidCallback handleCopy; final VoidCallback handlePaste; @@ -53,8 +50,6 @@ class _TextSelectionToolbar extends StatefulWidget { } class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with TickerProviderStateMixin { - ClipboardStatusNotifier _clipboardStatus; - // Whether or not the overflow menu is open. When it is closed, the menu // items that don't overflow are shown. When it is open, only the overflowing // menu items are shown. @@ -71,93 +66,33 @@ class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with Ticke ); } - // Close the menu and reset layout calculations, as in when the menu has - // changed and saved values are no longer relevant. This should be called in - // setState or another context where a rebuild is happening. - void _reset() { - // Change _TextSelectionToolbarContainer's key when the menu changes in - // order to cause it to rebuild. This lets it recalculate its - // saved width for the new set of children, and it prevents AnimatedSize - // from animating the size change. - _containerKey = UniqueKey(); - // If the menu items change, make sure the overflow menu is closed. This - // prevents an empty overflow menu. - _overflowOpen = false; - } - - void _onChangedClipboardStatus() { - setState(() { - // Inform the widget that the value of clipboardStatus has changed. - }); - } - - @override - void initState() { - super.initState(); - _clipboardStatus = widget.clipboardStatus ?? ClipboardStatusNotifier(); - _clipboardStatus.addListener(_onChangedClipboardStatus); - _clipboardStatus.update(); - } - @override void didUpdateWidget(_TextSelectionToolbar oldWidget) { - super.didUpdateWidget(oldWidget); - // If the children are changing, the current page should be reset. if (((widget.handleCut == null) != (oldWidget.handleCut == null)) || ((widget.handleCopy == null) != (oldWidget.handleCopy == null)) || ((widget.handlePaste == null) != (oldWidget.handlePaste == null)) || ((widget.handleSelectAll == null) != (oldWidget.handleSelectAll == null))) { - _reset(); - } - if (oldWidget.clipboardStatus == null && widget.clipboardStatus != null) { - _clipboardStatus.removeListener(_onChangedClipboardStatus); - _clipboardStatus.dispose(); - _clipboardStatus = widget.clipboardStatus; - } else if (oldWidget.clipboardStatus != null) { - if (widget.clipboardStatus == null) { - _clipboardStatus = ClipboardStatusNotifier(); - _clipboardStatus.addListener(_onChangedClipboardStatus); - oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus); - } else if (widget.clipboardStatus != oldWidget.clipboardStatus) { - _clipboardStatus = widget.clipboardStatus; - _clipboardStatus.addListener(_onChangedClipboardStatus); - oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus); - } - } - if (widget.handlePaste != null) { - _clipboardStatus.update(); - } - } - - @override - void dispose() { - super.dispose(); - // When used in an Overlay, this can be disposed after its creator has - // already disposed _clipboardStatus. - if (!_clipboardStatus.disposed) { - _clipboardStatus.removeListener(_onChangedClipboardStatus); - if (widget.clipboardStatus == null) { - _clipboardStatus.dispose(); - } + // Change _TextSelectionToolbarContainer's key when the menu changes in + // order to cause it to rebuild. This lets it recalculate its + // saved width for the new set of children, and it prevents AnimatedSize + // from animating the size change. + _containerKey = UniqueKey(); + // If the menu items change, make sure the overflow menu is closed. This + // prevents an empty overflow menu. + _overflowOpen = false; } + super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { - // Don't render the menu until the state of the clipboard is known. - if (widget.handlePaste != null - && _clipboardStatus.value == ClipboardStatus.unknown) { - return const SizedBox(width: 0.0, height: 0.0); - } - final MaterialLocalizations localizations = MaterialLocalizations.of(context); final List items = [ if (widget.handleCut != null) _getItem(widget.handleCut, localizations.cutButtonLabel), if (widget.handleCopy != null) _getItem(widget.handleCopy, localizations.copyButtonLabel), - if (widget.handlePaste != null - && _clipboardStatus.value == ClipboardStatus.pasteable) + if (widget.handlePaste != null) _getItem(widget.handlePaste, localizations.pasteButtonLabel), if (widget.handleSelectAll != null) _getItem(widget.handleSelectAll, localizations.selectAllButtonLabel), @@ -168,6 +103,7 @@ class _TextSelectionToolbarState extends State<_TextSelectionToolbar> with Ticke return const SizedBox(width: 0.0, height: 0.0); } + return _TextSelectionToolbarContainer( key: _containerKey, overflowOpen: _overflowOpen, @@ -591,18 +527,6 @@ class _TextSelectionToolbarItemsRenderBox extends RenderBox with ContainerRender } return false; } - - // Visit only the children that should be painted. - @override - void visitChildrenForSemantics(RenderObjectVisitor visitor) { - visitChildren((RenderObject renderObjectChild) { - final RenderBox child = renderObjectChild as RenderBox; - final ToolbarItemsParentData childParentData = child.parentData as ToolbarItemsParentData; - if (childParentData.shouldPaint) { - visitor(renderObjectChild); - } - }); - } } /// Centers the toolbar around the given anchor, ensuring that it remains on @@ -706,7 +630,6 @@ class _MaterialTextSelectionControls extends TextSelectionControls { Offset selectionMidpoint, List endpoints, TextSelectionDelegate delegate, - ClipboardStatusNotifier clipboardStatus, ) { assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMaterialLocalizations(context)); @@ -742,9 +665,8 @@ class _MaterialTextSelectionControls extends TextSelectionControls { fitsAbove, ), child: _TextSelectionToolbar( - clipboardStatus: clipboardStatus, handleCut: canCut(delegate) ? () => handleCut(delegate) : null, - handleCopy: canCopy(delegate) ? () => handleCopy(delegate, clipboardStatus) : null, + handleCopy: canCopy(delegate) ? () => handleCopy(delegate) : null, handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null, handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null, isAbove: fitsAbove, diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 03e403fb71d..ef0d5b14e19 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -1144,7 +1144,6 @@ class EditableTextState extends State with AutomaticKeepAliveClien bool _targetCursorVisibility = false; final ValueNotifier _cursorVisibilityNotifier = ValueNotifier(true); final GlobalKey _editableKey = GlobalKey(); - final ClipboardStatusNotifier _clipboardStatus = ClipboardStatusNotifier(); TextInputConnection _textInputConnection; TextSelectionOverlay _selectionOverlay; @@ -1191,18 +1190,11 @@ class EditableTextState extends State with AutomaticKeepAliveClien @override bool get selectAllEnabled => widget.toolbarOptions.selectAll; - void _onChangedClipboardStatus() { - setState(() { - // Inform the widget that the value of clipboardStatus has changed. - }); - } - // State lifecycle: @override void initState() { super.initState(); - _clipboardStatus.addListener(_onChangedClipboardStatus); widget.controller.addListener(_didChangeTextEditingValue); _focusAttachment = widget.focusNode.attach(context); widget.focusNode.addListener(_handleFocusChanged); @@ -1276,9 +1268,6 @@ class EditableTextState extends State with AutomaticKeepAliveClien ); } } - if (widget.selectionEnabled && pasteEnabled && widget.selectionControls?.canPaste(this) == true) { - _clipboardStatus.update(); - } } @override @@ -1295,9 +1284,6 @@ class EditableTextState extends State with AutomaticKeepAliveClien _selectionOverlay = null; _focusAttachment.detach(); widget.focusNode.removeListener(_handleFocusChanged); - WidgetsBinding.instance.removeObserver(this); - _clipboardStatus.removeListener(_onChangedClipboardStatus); - _clipboardStatus.dispose(); super.dispose(); } @@ -1624,7 +1610,6 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (widget.selectionControls != null) { _selectionOverlay = TextSelectionOverlay( - clipboardStatus: _clipboardStatus, context: context, value: _value, debugRequiredFor: widget, @@ -1995,7 +1980,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien VoidCallback _semanticsOnCopy(TextSelectionControls controls) { return widget.selectionEnabled && copyEnabled && _hasFocus && controls?.canCopy(this) == true - ? () => controls.handleCopy(this, _clipboardStatus) + ? () => controls.handleCopy(this) : null; } @@ -2006,7 +1991,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien } VoidCallback _semanticsOnPaste(TextSelectionControls controls) { - return widget.selectionEnabled && pasteEnabled && _hasFocus && controls?.canPaste(this) == true && _clipboardStatus.value == ClipboardStatus.pasteable + return widget.selectionEnabled && pasteEnabled &&_hasFocus && controls?.canPaste(this) == true ? () => controls.handlePaste(this) : null; } diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 754e92dbdd9..8d5bce0e3b2 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -12,7 +12,6 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'basic.dart'; -import 'binding.dart'; import 'constants.dart'; import 'container.dart'; import 'editable_text.dart'; @@ -138,7 +137,6 @@ abstract class TextSelectionControls { Offset position, List endpoints, TextSelectionDelegate delegate, - ClipboardStatusNotifier clipboardStatus, ); /// Returns the size of the selection handle. @@ -167,16 +165,13 @@ abstract class TextSelectionControls { return delegate.copyEnabled && !delegate.textEditingValue.selection.isCollapsed; } - /// Whether the text field managed by the given `delegate` supports pasting - /// from the clipboard. + /// Whether the current [Clipboard] content can be pasted into the text field + /// managed by the given `delegate`. /// /// Subclasses can use this to decide if they should expose the paste /// functionality to the user. - /// - /// This does not consider the contents of the clipboard. Subclasses may want - /// to, for example, disallow pasting when the clipboard contains an empty - /// string. bool canPaste(TextSelectionDelegate delegate) { + // TODO(goderbauer): return false when clipboard is empty, https://github.com/flutter/flutter/issues/11254 return delegate.pasteEnabled; } @@ -218,12 +213,11 @@ abstract class TextSelectionControls { /// /// This is called by subclasses when their copy affordance is activated by /// the user. - void handleCopy(TextSelectionDelegate delegate, ClipboardStatusNotifier clipboardStatus) { + void handleCopy(TextSelectionDelegate delegate) { final TextEditingValue value = delegate.textEditingValue; Clipboard.setData(ClipboardData( text: value.selection.textInside(value.text), )); - clipboardStatus?.update(); delegate.textEditingValue = TextEditingValue( text: value.text, selection: TextSelection.collapsed(offset: value.selection.end), @@ -300,7 +294,6 @@ class TextSelectionOverlay { this.selectionDelegate, this.dragStartBehavior = DragStartBehavior.start, this.onSelectionHandleTapped, - this.clipboardStatus, }) : assert(value != null), assert(context != null), assert(handlesVisible != null), @@ -372,13 +365,6 @@ class TextSelectionOverlay { /// {@endtemplate} final VoidCallback onSelectionHandleTapped; - /// Maintains the status of the clipboard for determining if its contents can - /// be pasted or not. - /// - /// Useful because the actual value of the clipboard can only be checked - /// asynchronously (see [Clipboard.getData]). - final ClipboardStatusNotifier clipboardStatus; - /// Controls the fade-in and fade-out animations for the toolbar and handles. static const Duration fadeDuration = Duration(milliseconds: 150); @@ -590,7 +576,6 @@ class TextSelectionOverlay { midpoint, endpoints, selectionDelegate, - clipboardStatus, ), ), ); @@ -1501,86 +1486,3 @@ class _TransparentTapGestureRecognizer extends TapGestureRecognizer { } } } - -/// A [ValueNotifier] whose [value] indicates whether the current contents of -/// the clipboard can be pasted. -/// -/// The contents of the clipboard can only be read asynchronously, via -/// [Clipboard.getData], so this maintains a value that can be used -/// synchronously. Call [update] to asynchronously update value if needed. -class ClipboardStatusNotifier extends ValueNotifier with WidgetsBindingObserver { - /// Create a new ClipboardStatusNotifier. - ClipboardStatusNotifier({ - ClipboardStatus value = ClipboardStatus.unknown, - }) : super(value); - - bool _disposed = false; - /// True iff this instance has been disposed. - bool get disposed => _disposed; - - /// Check the [Clipboard] and update [value] if needed. - void update() { - Clipboard.getData(Clipboard.kTextPlain).then((ClipboardData data) { - final ClipboardStatus clipboardStatus = data != null && data.text != null && data.text.isNotEmpty - ? ClipboardStatus.pasteable - : ClipboardStatus.notPasteable; - if (_disposed || clipboardStatus == value) { - return; - } - value = clipboardStatus; - }); - } - - @override - void addListener(VoidCallback listener) { - if (!hasListeners) { - WidgetsBinding.instance.addObserver(this); - } - if (value == ClipboardStatus.unknown) { - update(); - } - super.addListener(listener); - } - - @override - void removeListener(VoidCallback listener) { - super.removeListener(listener); - if (!hasListeners) { - WidgetsBinding.instance.removeObserver(this); - } - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - switch (state) { - case AppLifecycleState.resumed: - update(); - break; - case AppLifecycleState.detached: - case AppLifecycleState.inactive: - case AppLifecycleState.paused: - // Nothing to do. - } - } - - @override - void dispose() { - super.dispose(); - WidgetsBinding.instance.removeObserver(this); - _disposed = true; - } -} - -/// An enumeration of the status of the content on the user's clipboard. -enum ClipboardStatus { - /// The clipboard content can be pasted, such as a String of nonzero length. - pasteable, - - /// The status of the clipboard is unknown. Since getting clipboard data is - /// asynchronous (see [Clipboard.getData]), this status often exists while - /// waiting to receive the clipboard contents for the first time. - unknown, - - /// The content on the clipboard is not pasteable, such as when it is empty. - notPasteable, -} diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 07819634320..49fb244ea8f 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -171,11 +171,8 @@ void main() { Offset textOffsetToPosition(WidgetTester tester, int offset) => textOffsetToBottomLeftPosition(tester, offset) + const Offset(0, -2); - setUp(() async { + setUp(() { EditableText.debugDeterministicCursor = false; - // Fill the clipboard so that the PASTE option is available in the text - // selection menu. - await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); }); testWidgets( @@ -1548,7 +1545,7 @@ void main() { await tester.tapAt(textOffsetToPosition(tester, index)); await tester.pump(const Duration(milliseconds: 50)); await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pumpAndSettle(); + await tester.pump(); expect( controller.selection, const TextSelection(baseOffset: 0, extentOffset: 7), @@ -1588,7 +1585,7 @@ void main() { const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); - await tester.pumpAndSettle(); + await tester.pump(); // Second tap selects the word around the cursor. expect( @@ -1624,7 +1621,7 @@ void main() { final TestGesture gesture = await tester.startGesture(textfieldStart + const Offset(150.0, 5.0)); // Hold the press. - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 500)); expect( controller.selection, @@ -1763,7 +1760,7 @@ void main() { final TestGesture gesture = await tester.startGesture(textfieldStart + const Offset(150.0, 5.0)); // Hold the press. - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 500)); // The obscured text is treated as one word, should select all expect( @@ -1849,7 +1846,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(CupertinoTextField)); await tester.longPressAt(textfieldStart + const Offset(50.0, 5.0)); - await tester.pumpAndSettle(); + await tester.pump(); // Collapsed cursor for iOS long press. expect( @@ -1949,7 +1946,7 @@ void main() { expect(find.byType(CupertinoButton), findsNothing); await gesture.up(); - await tester.pumpAndSettle(); + await tester.pump(); // The selection isn't affected by the gesture lift. expect( @@ -2024,7 +2021,7 @@ void main() { expect(find.byType(CupertinoButton), findsNothing); await gesture.up(); - await tester.pumpAndSettle(); + await tester.pump(); // The selection isn't affected by the gesture lift. expect( @@ -2079,7 +2076,7 @@ void main() { await tester.pump(const Duration(milliseconds: 500)); await tester.longPressAt(textfieldStart + const Offset(100.0, 5.0)); - await tester.pumpAndSettle(); + await tester.pump(); // Plain collapsed selection at the exact tap position. expect( @@ -2121,7 +2118,7 @@ void main() { const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); - await tester.pumpAndSettle(); + await tester.pump(); // Double tap selection. expect( @@ -2158,7 +2155,7 @@ void main() { const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream), ); await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, const TextSelection(baseOffset: 0, extentOffset: 7), @@ -2174,7 +2171,7 @@ void main() { const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream), ); await tester.tapAt(textfieldStart + const Offset(100.0, 5.0)); - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, const TextSelection(baseOffset: 0, extentOffset: 7), @@ -2189,7 +2186,7 @@ void main() { const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, const TextSelection(baseOffset: 8, extentOffset: 12), @@ -2233,7 +2230,7 @@ void main() { ); await gesture.up(); - await tester.pumpAndSettle(); + await tester.pump(); // Shows toolbar. expect(find.byType(CupertinoButton), findsNWidgets(3)); }); @@ -3845,7 +3842,7 @@ void main() { // Long press shows the selection menu. await tester.longPressAt(textOffsetToPosition(tester, 0)); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('Paste'), findsOneWidget); }, ); diff --git a/packages/flutter/test/cupertino/text_selection_test.dart b/packages/flutter/test/cupertino/text_selection_test.dart index 5f7288a04ce..379a1cf5110 100644 --- a/packages/flutter/test/cupertino/text_selection_test.dart +++ b/packages/flutter/test/cupertino/text_selection_test.dart @@ -8,26 +8,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import '../widgets/text.dart' show 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; - break; - } - } -} - class _LongCupertinoLocalizationsDelegate extends LocalizationsDelegate { const _LongCupertinoLocalizationsDelegate(); @@ -66,9 +49,6 @@ class _LongCupertinoLocalizations extends DefaultCupertinoLocalizations { const _LongCupertinoLocalizations longLocalizations = _LongCupertinoLocalizations(); void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final MockClipboard mockClipboard = MockClipboard(); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); // Returns true iff the button is visually enabled. bool appearsEnabled(WidgetTester tester, String text) { @@ -174,55 +154,6 @@ void main() { }); }); - testWidgets('Paste only appears when clipboard has contents', (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 selet 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(); - - // No Paste yet, because nothing has been copied. - expect(find.text('Paste'), findsNothing); - 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 now shows. - expect(find.text('Paste'), findsOneWidget); - expect(find.text('Copy'), findsOneWidget); - expect(find.text('Cut'), findsOneWidget); - }, skip: isBrowser, 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'); @@ -250,7 +181,7 @@ void main() { // Long press on an empty space to show the selection menu. await tester.longPressAt(textOffsetToPosition(tester, 4)); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('Cut'), findsNothing); expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsOneWidget); @@ -474,7 +405,7 @@ void main() { // Long press on an empty space to show the selection menu, with only the // paste button visible. await tester.longPressAt(textOffsetToPosition(tester, 4)); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text(longLocalizations.cutButtonLabel), findsNothing); expect(find.text(longLocalizations.copyButtonLabel), findsNothing); expect(find.text(longLocalizations.pasteButtonLabel), findsOneWidget); diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index dc47a0d24ed..947f5446176 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -3,31 +3,12 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; import 'feedback_tester.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; - break; - } - } -} - void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final MockClipboard mockClipboard = MockClipboard(); DateTime firstDate; DateTime lastDate; @@ -54,7 +35,7 @@ void main() { return tester.widget(find.byType(TextField)); } - setUp(() async { + setUp(() { firstDate = DateTime(2001, DateTime.january, 1); lastDate = DateTime(2031, DateTime.december, 31); initialDate = DateTime(2016, DateTime.january, 15); @@ -70,15 +51,6 @@ void main() { fieldHintText = null; fieldLabelText = null; helpText = null; - - // Fill the clipboard so that the PASTE option is available in the text - // selection menu. - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); - await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); - }); - - tearDown(() { - SystemChannels.platform.setMockMethodCallHandler(null); }); Future prepareDatePicker(WidgetTester tester, Future callback(Future date)) async { @@ -1103,6 +1075,7 @@ void main() { semantics.dispose(); }); + }); group('Screen configurations', () { diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index 446120f8f12..d1c6e2dcce9 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -9,37 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.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; - break; - } - } -} - void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final MockClipboard mockClipboard = MockClipboard(); - - setUp(() async { - // Fill the clipboard so that the PASTE option is available in the text - // selection menu. - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); - await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); - }); - - tearDown(() { - SystemChannels.platform.setMockMethodCallHandler(null); - }); - testWidgets('Can open and close search', (WidgetTester tester) async { final _TestSearchDelegate delegate = _TestSearchDelegate(); final List selectedResults = []; diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index b2497e884cc..2425222f7fc 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -142,11 +142,8 @@ void main() { kThreeLines + "\nFourth line won't display and ends at"; - setUp(() async { + setUp(() { debugResetSemanticsIdCounter(); - // Fill the clipboard so that the PASTE option is available in the text - // selection menu. - await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); }); final Key textFieldKey = UniqueKey(); @@ -1048,7 +1045,7 @@ void main() { const int dIndex = 3; final Offset dPos = textOffsetToPosition(tester, dIndex); await tester.longPressAt(dPos); - await tester.pumpAndSettle(); + await tester.pump(); // Context menu should not have paste and cut. expect(find.text('COPY'), findsOneWidget); @@ -1786,8 +1783,6 @@ void main() { renderEditable, ); await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0)); - // Pump an extra frame to allow the selection menu to read the clipboard. - await tester.pump(); await tester.pump(); // Toolbar should fade in. Starting at 0% opacity. @@ -1898,7 +1893,7 @@ void main() { // Long press to select text. final Offset bPos = textOffsetToPosition(tester, 1); await tester.longPressAt(bPos, pointer: 7); - await tester.pumpAndSettle(); + await tester.pump(); // Should only have paste option when whole obscure text is selected. expect(find.text('PASTE'), findsOneWidget); @@ -1910,7 +1905,7 @@ void main() { final Offset iPos = textOffsetToPosition(tester, 10); final Offset slightRight = iPos + const Offset(30.0, 0.0); await tester.longPressAt(slightRight, pointer: 7); - await tester.pumpAndSettle(); + await tester.pump(); // Should have paste and select all options when collapse. expect(find.text('PASTE'), findsOneWidget); @@ -5042,78 +5037,6 @@ void main() { semantics.dispose(); }); - testWidgets('When clipboard empty, no semantics paste option', (WidgetTester tester) async { - const String textInTextField = 'Hello'; - - final SemanticsTester semantics = SemanticsTester(tester); - final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner; - final TextEditingController controller = TextEditingController() - ..text = textInTextField; - final Key key = UniqueKey(); - - // Clear the clipboard. - await Clipboard.setData(const ClipboardData(text: '')); - - await tester.pumpWidget( - overlay( - child: TextField( - key: key, - controller: controller, - ), - ), - ); - - const int inputFieldId = 1; - - expect(semantics, hasSemantics( - TestSemantics.root( - children: [ - TestSemantics( - id: inputFieldId, - flags: [SemanticsFlag.isTextField], - actions: [SemanticsAction.tap], - value: textInTextField, - textDirection: TextDirection.ltr, - ), - ], - ), - ignoreRect: true, ignoreTransform: true, - )); - - semanticsOwner.performAction(inputFieldId, SemanticsAction.tap); - await tester.pump(); - - expect(semantics, hasSemantics( - TestSemantics.root( - children: [ - TestSemantics( - id: inputFieldId, - flags: [ - SemanticsFlag.isTextField, - SemanticsFlag.isFocused, - ], - actions: [ - SemanticsAction.tap, - SemanticsAction.moveCursorBackwardByCharacter, - SemanticsAction.moveCursorBackwardByWord, - SemanticsAction.setSelection, - // No paste option. - ], - value: textInTextField, - textDirection: TextDirection.ltr, - textSelection: const TextSelection( - baseOffset: textInTextField.length, - extentOffset: textInTextField.length, - ), - ), - ], - ), - ignoreRect: true, ignoreTransform: true, - )); - - semantics.dispose(); - }); - testWidgets('TextField throws when not descended from a Material widget', (WidgetTester tester) async { const Widget textField = TextField(); await tester.pumpWidget(textField); @@ -5875,7 +5798,7 @@ void main() { const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); - await tester.pumpAndSettle(); + await tester.pump(); // Second tap selects the word around the cursor. expect( @@ -5920,7 +5843,7 @@ void main() { const TextSelection.collapsed(offset: 9), ); await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); - await tester.pumpAndSettle(); + await tester.pump(); // Second tap selects the word around the cursor. expect( @@ -5971,7 +5894,7 @@ void main() { // Second tap selects the word around the cursor. await tester.tapAt(textOffsetToPosition(tester, index)); - await tester.pumpAndSettle(); + await tester.pump(); expect( controller.selection, const TextSelection(baseOffset: 0, extentOffset: 7), @@ -6003,14 +5926,14 @@ void main() { await tester.tapAt(textOffsetToPosition(tester, 0)); await tester.pump(const Duration(milliseconds: 50)); await tester.tapAt(textOffsetToPosition(tester, 0)); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('PASTE'), findsOneWidget); // Double tap again keeps the selection menu visible. await tester.tapAt(textOffsetToPosition(tester, 0)); await tester.pump(const Duration(milliseconds: 50)); await tester.tapAt(textOffsetToPosition(tester, 0)); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('PASTE'), findsOneWidget); }, ); @@ -6035,7 +5958,7 @@ void main() { // Long press shows the selection menu. await tester.longPressAt(textOffsetToPosition(tester, 0)); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('PASTE'), findsOneWidget); // Long press again keeps the selection menu visible. @@ -6065,7 +5988,7 @@ void main() { // Long press shows the selection menu. await tester.longPress(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('PASTE'), findsOneWidget); // Tap hides the selection menu. @@ -6100,7 +6023,7 @@ void main() { // Long press shows the selection menu. expect(find.text('PASTE'), findsNothing); await tester.longPress(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('PASTE'), findsOneWidget); }, ); @@ -6130,7 +6053,7 @@ void main() { final TestGesture gesture = await tester.startGesture(textfieldStart + const Offset(150.0, 9.0)); // Hold the press. - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 500)); expect( controller.selection, @@ -6219,7 +6142,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); await tester.longPressAt(textfieldStart + const Offset(50.0, 9.0)); - await tester.pumpAndSettle(); + await tester.pump(); // Collapsed cursor for iOS long press. expect( @@ -6252,7 +6175,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); await tester.longPressAt(textfieldStart + const Offset(50.0, 9.0)); - await tester.pumpAndSettle(); + await tester.pump(); expect( controller.selection, @@ -6355,7 +6278,7 @@ void main() { expect(find.byType(CupertinoButton), findsNothing); await gesture.up(); - await tester.pumpAndSettle(); + await tester.pump(); // The selection isn't affected by the gesture lift. expect( @@ -6429,7 +6352,7 @@ void main() { expect(find.byType(CupertinoButton), findsNothing); await gesture.up(); - await tester.pumpAndSettle(); + await tester.pump(); // The selection isn't affected by the gesture lift. expect( @@ -6486,7 +6409,7 @@ void main() { await tester.pump(const Duration(milliseconds: 500)); await tester.longPressAt(textfieldStart + const Offset(100.0, 9.0)); - await tester.pumpAndSettle(); + await tester.pump(); // Plain collapsed selection at the exact tap position. expect( @@ -6529,7 +6452,7 @@ void main() { const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); - await tester.pumpAndSettle(); + await tester.pump(); // Double tap selection. expect( @@ -6564,7 +6487,7 @@ void main() { const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream), ); await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, const TextSelection(baseOffset: 0, extentOffset: 7), @@ -6580,7 +6503,7 @@ void main() { const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream), ); await tester.tapAt(textfieldStart + const Offset(100.0, 9.0)); - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, const TextSelection(baseOffset: 0, extentOffset: 7), @@ -6595,7 +6518,7 @@ void main() { const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, const TextSelection(baseOffset: 8, extentOffset: 12), @@ -6687,7 +6610,7 @@ void main() { ); await gesture.up(); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.byType(CupertinoButton), findsNWidgets(3)); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); diff --git a/packages/flutter/test/material/text_selection_test.dart b/packages/flutter/test/material/text_selection_test.dart index fe8a8a29409..34a0fa59662 100644 --- a/packages/flutter/test/material/text_selection_test.dart +++ b/packages/flutter/test/material/text_selection_test.dart @@ -5,35 +5,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import '../widgets/text.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; - break; - } - } -} - void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final MockClipboard mockClipboard = MockClipboard(); - SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); - - setUp(() async { - await Clipboard.setData(const ClipboardData(text: 'clipboard data')); - }); - group('canSelectAll', () { Widget createEditableText({ Key key, @@ -129,7 +104,7 @@ void main() { expect(endpoints.length, 1); final Offset handlePos = endpoints[0].point + const Offset(0.0, 1.0); await tester.tapAt(handlePos, pointer: 7); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('CUT'), findsNothing); expect(find.text('COPY'), findsNothing); expect(find.text('PASTE'), findsOneWidget); @@ -260,7 +235,7 @@ void main() { // Long press to show the menu. final Offset textOffset = textOffsetToPosition(tester, 1); await tester.longPressAt(textOffset); - await tester.pumpAndSettle(); + await tester.pump(); // The last two buttons are missing, and a more button is shown. expect(find.text('CUT'), findsOneWidget); @@ -326,7 +301,7 @@ void main() { // Long press to show the menu. final Offset textOffset = textOffsetToPosition(tester, 1); await tester.longPressAt(textOffset); - await tester.pumpAndSettle(); + await tester.pump(); // The last button is missing, and a more button is shown. expect(find.text('CUT'), findsOneWidget); @@ -438,7 +413,7 @@ void main() { // Long press to show the menu. await tester.longPressAt(textOffsetToPosition(tester, 1)); - await tester.pumpAndSettle(); + await tester.pump(); // The last button is missing, and a more button is shown. expect(find.text('CUT'), findsOneWidget); @@ -513,7 +488,7 @@ void main() { expect(endpoints.length, 1); final Offset handlePos = endpoints[0].point + const Offset(0.0, 1.0); await tester.tapAt(handlePos, pointer: 7); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('CUT'), findsNothing); expect(find.text('COPY'), findsNothing); expect(find.text('PASTE'), findsOneWidget); @@ -581,58 +556,4 @@ void main() { ); }); }); - - testWidgets('Paste only appears when clipboard has contents', (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 to start. - await Clipboard.setData(const ClipboardData(text: '')); - - // Double tap to selet 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(); - - // No Paste yet, because nothing has been copied. - expect(find.text('PASTE'), findsNothing); - expect(find.text('COPY'), findsOneWidget); - expect(find.text('CUT'), findsOneWidget); - expect(find.text('SELECT ALL'), 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); - expect(find.text('SELECT ALL'), 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 now shows. - expect(find.text('COPY'), findsOneWidget); - expect(find.text('CUT'), findsOneWidget); - expect(find.text('PASTE'), findsOneWidget); - expect(find.text('SELECT ALL'), findsOneWidget); - }, skip: isBrowser); } diff --git a/packages/flutter/test/widgets/editable_text_cursor_test.dart b/packages/flutter/test/widgets/editable_text_cursor_test.dart index 45a1c21a277..2e1144a91c1 100644 --- a/packages/flutter/test/widgets/editable_text_cursor_test.dart +++ b/packages/flutter/test/widgets/editable_text_cursor_test.dart @@ -19,12 +19,6 @@ const TextStyle textStyle = TextStyle(); const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00); void main() { - setUp(() async { - // Fill the clipboard so that the PASTE option is available in the text - // selection menu. - await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); - }); - testWidgets('cursor has expected width and radius', (WidgetTester tester) async { await tester.pumpWidget( MediaQuery(data: const MediaQueryData(devicePixelRatio: 1.0), @@ -45,6 +39,7 @@ void main() { expect(editableText.cursorRadius.x, 2.0); }); + testWidgets('cursor layout has correct width', (WidgetTester tester) async { final GlobalKey editableTextKey = GlobalKey(); @@ -137,7 +132,7 @@ void main() { final Finder textFinder = find.byKey(editableTextKey); await tester.longPress(textFinder); tester.state(textFinder).showToolbar(); - await tester.pumpAndSettle(); + await tester.pump(); await tester.tap(find.text('PASTE')); await tester.pump(); diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 1093db82df0..409c46098bc 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -48,12 +48,9 @@ void main() { final MockClipboard mockClipboard = MockClipboard(); SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); - setUp(() async { + setUp(() { debugResetSemanticsIdCounter(); controller = TextEditingController(); - // Fill the clipboard so that the PASTE option is available in the text - // selection menu. - await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); }); tearDown(() { @@ -964,7 +961,7 @@ void main() { // Can't show the toolbar when there's no focus. expect(state.showToolbar(), false); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('PASTE'), findsNothing); // Can show the toolbar when focused even though there's no text. @@ -974,7 +971,7 @@ void main() { ); await tester.pump(); expect(state.showToolbar(), true); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('PASTE'), findsOneWidget); // Hide the menu again. @@ -986,7 +983,7 @@ void main() { controller.text = 'blah'; await tester.pump(); expect(state.showToolbar(), true); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('PASTE'), findsOneWidget); }, skip: isBrowser); @@ -1026,7 +1023,7 @@ void main() { // Should be able to show the toolbar. expect(state.showToolbar(), true); - await tester.pumpAndSettle(); + await tester.pump(); expect(find.text('PASTE'), findsOneWidget); }); @@ -1195,7 +1192,7 @@ void main() { final Finder textFinder = find.byType(EditableText); await tester.longPress(textFinder); tester.state(textFinder).showToolbar(); - await tester.pumpAndSettle(); + await tester.pump(); await tester.tap(find.text('PASTE')); await tester.pump(); @@ -2415,13 +2412,12 @@ void main() { controls = MockTextSelectionControls(); when(controls.buildHandle(any, any, any)).thenReturn(Container()); - when(controls.buildToolbar(any, any, any, any, any, any, any)) + when(controls.buildToolbar(any, any, any, any, any, any)) .thenReturn(Container()); }); testWidgets('are exposed', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); - addTearDown(semantics.dispose); when(controls.canCopy(any)).thenReturn(false); when(controls.canCut(any)).thenReturn(false); @@ -2461,7 +2457,6 @@ void main() { when(controls.canCopy(any)).thenReturn(false); when(controls.canPaste(any)).thenReturn(true); await _buildApp(controls, tester); - await tester.pumpAndSettle(); expect( semantics, includesNodeWith( @@ -2509,6 +2504,8 @@ void main() { ], ), ); + + semantics.dispose(); }); testWidgets('can copy/cut/paste with a11y', (WidgetTester tester) async { @@ -2567,7 +2564,7 @@ void main() { ); owner.performAction(expectedNodeId, SemanticsAction.copy); - verify(controls.handleCopy(any, any)).called(1); + verify(controls.handleCopy(any)).called(1); owner.performAction(expectedNodeId, SemanticsAction.cut); verify(controls.handleCut(any)).called(1);