From 02e87334ddb9f64a2ff12c0dc201f3e410e74be7 Mon Sep 17 00:00:00 2001 From: Niko Yuwono <1751188+NikoYuwono@users.noreply.github.com> Date: Thu, 20 Sep 2018 05:35:33 +0900 Subject: [PATCH] Implement Double Tap Handling in TextField and Editable (#21264) Thanks @NikoYuwono for getting this working! We appreciate your help! * Implement Double Tap Handling in TextField and Editable * Fix test broken by the change and add test for double tap * Fix affected tests * Remove unnecessary new * Fix test --- .../flutter/lib/src/material/text_field.dart | 6 ++ .../flutter/lib/src/rendering/editable.dart | 29 +++++- .../test/cupertino/tab_scaffold_test.dart | 2 + .../flutter/test/material/search_test.dart | 12 +-- .../test/material/text_field_focus_test.dart | 14 +-- .../test/material/text_field_splash_test.dart | 10 +- .../test/material/text_field_test.dart | 99 +++++++++++++------ .../test/widgets/editable_text_test.dart | 23 +++-- 8 files changed, 138 insertions(+), 57 deletions(-) diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 044011a45d..f7e503ed4b 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -496,6 +496,11 @@ class _TextFieldState extends State with AutomaticKeepAliveClientMixi _cancelCurrentSplash(); } + void _handleDoubleTap() { + _renderEditable.handleDoubleTap(); + _confirmCurrentSplash(); + } + void _handleLongPress() { _renderEditable.handleLongPress(); _confirmCurrentSplash(); @@ -614,6 +619,7 @@ class _TextFieldState extends State with AutomaticKeepAliveClientMixi onTapDown: _handleTapDown, onTap: _handleTap, onTapCancel: _handleTapCancel, + onDoubleTap: _handleDoubleTap, onLongPress: _handleLongPress, excludeFromSemantics: true, child: child, diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index 7c3d8410e4..b038da0df0 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -32,6 +32,10 @@ enum SelectionChangedCause { /// of the cursor) to change. tap, + /// The user tapped twice in quick succession on the text and that caused + /// the selection (or the location of the cursor) to change. + doubleTap, + /// The user long-pressed the text and that caused the selection (or the /// location of the cursor) to change. longPress, @@ -168,6 +172,8 @@ class RenderEditable extends RenderBox { _tap = TapGestureRecognizer(debugOwner: this) ..onTapDown = _handleTapDown ..onTap = _handleTap; + _doubleTap = DoubleTapGestureRecognizer(debugOwner: this) + ..onDoubleTap = _handleDoubleTap; _longPress = LongPressGestureRecognizer(debugOwner: this) ..onLongPress = _handleLongPress; } @@ -185,7 +191,7 @@ class RenderEditable extends RenderBox { /// If true [handleEvent] does nothing and it's assumed that this /// renderer will be notified of input gestures via [handleTapDown], - /// [handleTap], and [handleLongPress]. + /// [handleTap], [handleDoubleTap], and [handleLongPress]. /// /// The default value of this property is false. bool ignorePointer; @@ -1022,6 +1028,7 @@ class RenderEditable extends RenderBox { bool hitTestSelf(Offset position) => true; TapGestureRecognizer _tap; + DoubleTapGestureRecognizer _doubleTap; LongPressGestureRecognizer _longPress; @override @@ -1031,6 +1038,7 @@ class RenderEditable extends RenderBox { assert(debugHandleEvent(event, entry)); if (event is PointerDownEvent && onSelectionChanged != null) { _tap.addPointer(event); + _doubleTap.addPointer(event); _longPress.addPointer(event); } } @@ -1070,6 +1078,25 @@ class RenderEditable extends RenderBox { handleTap(); } + /// If [ignorePointer] is false (the default) then this method is called by + /// the internal gesture recognizer's [DoubleTapGestureRecognizer.onDoubleTap] + /// callback. + /// + /// When [ignorePointer] is true, an ancestor widget must respond to long + /// press events by calling this method. + void handleDoubleTap() { + _layoutText(constraints.maxWidth); + assert(_lastTapDownPosition != null); + if (onSelectionChanged != null) { + final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition)); + onSelectionChanged(_selectWordAtOffset(position), this, SelectionChangedCause.doubleTap); + } + } + void _handleDoubleTap() { + assert(!ignorePointer); + handleDoubleTap(); + } + /// If [ignorePointer] is false (the default) then this method is called by /// the internal gesture recognizer's [LongPressRecognizer.onLongPress] /// callback. diff --git a/packages/flutter/test/cupertino/tab_scaffold_test.dart b/packages/flutter/test/cupertino/tab_scaffold_test.dart index cfecd3b105..47485bad22 100644 --- a/packages/flutter/test/cupertino/tab_scaffold_test.dart +++ b/packages/flutter/test/cupertino/tab_scaffold_test.dart @@ -179,6 +179,7 @@ void main() { ); await tester.tap(find.widgetWithText(TextField, 'TextField 2')); + await tester.pump(const Duration(milliseconds: 300)); expect( focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)), @@ -189,6 +190,7 @@ void main() { await tester.pump(); await tester.tap(find.widgetWithText(TextField, 'TextField 1')); + await tester.pump(const Duration(milliseconds: 300)); expect( focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)), diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index 9f2454be34..b337a40ccf 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -164,7 +164,7 @@ void main() { delegate: delegate, )); await tester.tap(find.byTooltip('Search')); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); // Showing suggestions expect(find.text('Suggestions'), findsOneWidget); @@ -173,14 +173,14 @@ void main() { // Typing query Wow delegate.querysForSuggestions.clear(); await tester.enterText(find.byType(TextField), 'Wow'); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); expect(delegate.query, 'Wow'); expect(delegate.querysForSuggestions, ['Wow']); expect(delegate.querysForResults, hasLength(0)); await tester.tap(find.text('Suggestions')); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); // Showing Results expect(find.text('Suggestions'), findsNothing); @@ -195,7 +195,7 @@ void main() { // Taping search field to go back to suggestions await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); textField = tester.widget(find.byType(TextField)); expect(textField.focusNode.hasFocus, isTrue); @@ -206,7 +206,7 @@ void main() { expect(delegate.querysForResults, ['Wow']); await tester.enterText(find.byType(TextField), 'Foo'); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); expect(delegate.query, 'Foo'); expect(delegate.querysForSuggestions, ['Wow', 'Wow', 'Foo']); @@ -214,7 +214,7 @@ void main() { // Go to results again await tester.tap(find.text('Suggestions')); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); expect(find.text('Suggestions'), findsNothing); expect(find.text('Results'), findsOneWidget); diff --git a/packages/flutter/test/material/text_field_focus_test.dart b/packages/flutter/test/material/text_field_focus_test.dart index 4bb79d6c4c..b8219bc116 100644 --- a/packages/flutter/test/material/text_field_focus_test.dart +++ b/packages/flutter/test/material/text_field_focus_test.dart @@ -6,6 +6,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; void main() { + const Duration doubleTapTimeout = Duration(milliseconds: 300); + testWidgets('Request focus shows keyboard', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); @@ -71,7 +73,7 @@ void main() { expect(tester.testTextInput.isVisible, isFalse); await tester.tap(find.byType(TextField)); - await tester.idle(); + await tester.pump(doubleTapTimeout); expect(tester.testTextInput.isVisible, isTrue); @@ -80,7 +82,7 @@ void main() { expect(tester.testTextInput.isVisible, isFalse); await tester.tap(find.byType(TextField)); - await tester.idle(); + await tester.pump(doubleTapTimeout); expect(tester.testTextInput.isVisible, isTrue); @@ -113,17 +115,17 @@ void main() { builder: (BuildContext context) => const SimpleDialog(title: Text('Dialog')), ); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect(tester.testTextInput.isVisible, isFalse); Navigator.of(tester.element(find.text('Dialog'))).pop(); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect(tester.testTextInput.isVisible, isFalse); await tester.tap(find.byType(TextField)); - await tester.idle(); + await tester.pump(doubleTapTimeout); expect(tester.testTextInput.isVisible, isTrue); @@ -224,7 +226,7 @@ void main() { expect(tester.testTextInput.isVisible, isFalse); await tester.tap(find.byType(TextField)); - await tester.idle(); + await tester.pump(doubleTapTimeout); expect(tester.testTextInput.isVisible, isTrue); }); } diff --git a/packages/flutter/test/material/text_field_splash_test.dart b/packages/flutter/test/material/text_field_splash_test.dart index 811a2b8227..55a6a3ac85 100644 --- a/packages/flutter/test/material/text_field_splash_test.dart +++ b/packages/flutter/test/material/text_field_splash_test.dart @@ -121,32 +121,32 @@ void main() { cancelCount = 0; await tester.tap(find.byKey(textField1)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(Duration(milliseconds: 300)); expect(confirmCount, 1); expect(cancelCount, 0); // textField1 already has the focus, no new splash await tester.tap(find.byKey(textField1)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(Duration(milliseconds: 300)); expect(confirmCount, 1); expect(cancelCount, 0); // textField2 gets the focus and a splash await tester.tap(find.byKey(textField2)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(Duration(milliseconds: 300)); expect(confirmCount, 2); expect(cancelCount, 0); // Tap outside of textField1's editable. It still gets focus and splash. await tester.tapAt(tester.getTopLeft(find.byKey(textField1))); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(Duration(milliseconds: 300)); expect(confirmCount, 3); expect(cancelCount, 0); // Tap in the center of textField2's editable. It still gets the focus // and the splash. There is no splash cancel. await tester.tap(find.byKey(textField2)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(Duration(milliseconds: 300)); expect(confirmCount, 4); expect(cancelCount, 0); }); diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 5a1d8d6d3a..bf156e2960 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -118,6 +118,7 @@ void main() { final MockClipboard mockClipboard = MockClipboard(); SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall); + const Duration doubleTapTimeout = Duration(milliseconds: 300); const String kThreeLines = 'First line of text is ' 'Second line goes until ' @@ -398,13 +399,13 @@ void main() { final int tapIndex = testValue.indexOf('e'); final Offset ePos = textOffsetToPosition(tester, tapIndex); await tester.tapAt(ePos); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect(controller.selection.baseOffset, tapIndex); expect(controller.selection.extentOffset, tapIndex); }); - testWidgets('Can long press to select', (WidgetTester tester) async { + testWidgets('Can long press to select a word', (WidgetTester tester) async { final TextEditingController controller = TextEditingController(); await tester.pumpWidget( @@ -434,6 +435,40 @@ void main() { expect(controller.selection.extentOffset, testValue.indexOf('f')+1); }); + testWidgets('Can double tap to select a word', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + overlay( + child: TextField( + controller: controller, + ), + ) + ); + + const String testValue = 'abc def ghi'; + await tester.enterText(find.byType(TextField), testValue); + expect(controller.value.text, testValue); + await skipPastScrollingAnimation(tester); + + expect(controller.selection.isCollapsed, true); + + // Long press the 'e' to select 'def'. + final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e')); + final TestGesture firstTapGesture = await tester.startGesture(ePos); + await tester.pump(const Duration(milliseconds: 100)); + await firstTapGesture.up(); + await tester.pump(); + final TestGesture secondTapGesture = await tester.startGesture(ePos); + await tester.pump(); + await secondTapGesture.up(); + await tester.pump(); + + // 'def' is selected. + expect(controller.selection.baseOffset, testValue.indexOf('d')); + expect(controller.selection.extentOffset, testValue.indexOf('f')+1); + }); + testWidgets('Can drag handles to change selection', (WidgetTester tester) async { final TextEditingController controller = TextEditingController(); @@ -512,7 +547,7 @@ void main() { // Tap the selection handle to bring up the "paste / select all" menu. await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e'))); - await tester.pump(); + await tester.pump(doubleTapTimeout); await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero RenderEditable renderEditable = findRenderEditable(tester); List endpoints = globalize( @@ -520,7 +555,7 @@ void main() { renderEditable, ); await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0)); - await tester.pump(); + await tester.pump(doubleTapTimeout); await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero // SELECT ALL should select all the text. @@ -536,7 +571,7 @@ void main() { // Tap again to bring back the menu. await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e'))); - await tester.pump(); + await tester.pump(doubleTapTimeout); await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero renderEditable = findRenderEditable(tester); endpoints = globalize( @@ -544,7 +579,7 @@ void main() { renderEditable, ); await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0)); - await tester.pump(); + await tester.pump(doubleTapTimeout); await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero // PASTE right before the 'e'. @@ -570,7 +605,7 @@ void main() { // Tap the selection handle to bring up the "paste / select all" menu. await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e'))); - await tester.pump(); + await tester.pump(doubleTapTimeout); await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero final RenderEditable renderEditable = findRenderEditable(tester); final List endpoints = globalize( @@ -578,7 +613,7 @@ void main() { renderEditable, ); await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0)); - await tester.pump(); + await tester.pump(doubleTapTimeout); // Toolbar should fade in. Starting at 0% opacity. final Element target = tester.element(find.text('SELECT ALL')); @@ -1062,7 +1097,7 @@ void main() { // Focus the Input. The prefix should still display. await tester.tap(find.byKey(secondKey)); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect(find.text('Prefix'), findsOneWidget); expect(find.text('Suffix'), findsOneWidget); @@ -1114,7 +1149,7 @@ void main() { expect(getOpacity(tester, find.text('Hint')), 1.0); await tester.tap(find.byKey(secondKey)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); // Focus the Input. The hint, prefix, and suffix should appear expect(getOpacity(tester, find.text('Prefix')), 1.0); @@ -1180,7 +1215,7 @@ void main() { // Focus the input. The label, prefix, and suffix should appear. await tester.tap(find.byKey(secondKey)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); expect(getOpacity(tester, find.text('Prefix')), 1.0); expect(getOpacity(tester, find.text('Suffix')), 1.0); @@ -1229,7 +1264,7 @@ void main() { // Focus the Input. The label should start animating upwards. await tester.tap(find.byKey(secondKey)); await tester.idle(); - await tester.pump(); + await tester.pump(doubleTapTimeout); await tester.pump(const Duration(milliseconds: 50)); Offset newPos = tester.getTopLeft(find.text('Second')); @@ -1364,7 +1399,7 @@ void main() { // Initial state with null controller. await tester.tap(find.byType(TextField)); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect(tester.testTextInput.editingState['text'], isEmpty); // Update the controller from null to controller1. @@ -1502,7 +1537,7 @@ void main() { await skipPastScrollingAnimation(tester); await tester.tapAt(textOffsetToPosition(tester, '123'.indexOf('2'))); - await tester.pump(); + await tester.pump(doubleTapTimeout); await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero final RenderEditable renderEditable = findRenderEditable(tester); final List endpoints = globalize( @@ -1510,7 +1545,7 @@ void main() { renderEditable, ); await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0)); - await tester.pump(); + await tester.pump(doubleTapTimeout); await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero Clipboard.setData(const ClipboardData(text: '一4二\n5三6')); @@ -1606,7 +1641,7 @@ void main() { expect(controller1.selection, isNot(equals(TextRange.empty))); await tester.tap(find.byKey(key2)); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect(controller1.selection, equals(TextRange.empty)); }); @@ -1823,7 +1858,7 @@ void main() { await tester.idle(); await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown, SHIFT_ON expect(controller.selection.extentOffset - controller.selection.baseOffset, 1); @@ -1836,7 +1871,7 @@ void main() { await tester.idle(); await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); await tester.pumpAndSettle(); sendKeyEventWithCode(22, true, true, true); // RIGHT_ARROW keydown SHIFT_ON, CONTROL_ON @@ -1852,7 +1887,7 @@ void main() { await tester.idle(); await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown await tester.pumpAndSettle(); @@ -1875,7 +1910,7 @@ void main() { await tester.idle(); await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); for (int i = 0; i < 5; i += 1) { sendKeyEventWithCode(22, true, false, false); // RIGHT_ARROW keydown @@ -1962,7 +1997,7 @@ void main() { await tester.idle(); await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); // Select the first 5 characters for (int i = 0; i < 5; i += 1) { @@ -2032,7 +2067,7 @@ void main() { await tester.idle(); await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); // Select the first 5 characters for (int i = 0; i < 5; i += 1) { @@ -2095,7 +2130,7 @@ void main() { await tester.idle(); await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); // Select All sendKeyEventWithCode(_kAKeyCode, true, false, true); // keydown control A @@ -2141,7 +2176,7 @@ void main() { await tester.idle(); await tester.tap(find.byType(TextField)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); // Delete for (int i = 0; i < 6; i += 1) { @@ -2211,7 +2246,7 @@ void main() { await tester.idle(); await tester.tap(find.byType(TextField).first); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); for (int i = 0; i < 5; i += 1) { sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown @@ -2298,7 +2333,7 @@ void main() { const String testValue = 'a big house'; await tester.enterText(find.byType(TextField).first, testValue); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); for (int i = 0; i < 5; i += 1) { sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown @@ -2313,7 +2348,7 @@ void main() { await tester.enterText(find.byType(TextField).last, testValue); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(doubleTapTimeout); for (int i = 0; i < 5; i += 1) { sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown @@ -2343,7 +2378,7 @@ void main() { // Tap the selection handle to bring up the "paste / select all" menu. await tester.tapAt(textOffsetToPosition(tester, 0)); - await tester.pump(); + await tester.pump(doubleTapTimeout); await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is // Confirm that the selection was updated. @@ -2450,7 +2485,7 @@ void main() { ), ignoreTransform: true, ignoreRect: true)); await tester.tap(find.byKey(key)); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect(semantics, hasSemantics(TestSemantics.root( children: [ @@ -2563,7 +2598,7 @@ void main() { // Focus the text field await tester.tap(find.byKey(key)); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect(semantics, hasSemantics(TestSemantics.root( children: [ @@ -2637,7 +2672,7 @@ void main() { // Focus the text field await tester.tap(find.byKey(key)); - await tester.pump(); + await tester.pump(doubleTapTimeout); const int inputFieldId = 1; @@ -2869,7 +2904,7 @@ void main() { ), ignoreTransform: true, ignoreRect: true)); await tester.tap(find.byType(TextField)); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect(semantics, hasSemantics(TestSemantics.root( children: [ diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 2dba2fe4d4..8591cb9127 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -20,6 +20,7 @@ void main() { final FocusScopeNode focusScopeNode = FocusScopeNode(); const TextStyle textStyle = TextStyle(); const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00); + const Duration doubleTapTimeout = Duration(milliseconds: 300); setUp(() { debugResetSemanticsIdCounter(); @@ -53,6 +54,7 @@ void main() { ); await tester.tap(find.byType(EditableText)); + await tester.pump(doubleTapTimeout); await tester.showKeyboard(find.byType(EditableText)); controller.text = 'test'; await tester.idle(); @@ -119,6 +121,7 @@ void main() { ), ); await tester.tap(find.byType(EditableText)); + await tester.pump(doubleTapTimeout); await tester.showKeyboard(find.byType(EditableText)); controller.text = 'test'; await tester.idle(); @@ -282,6 +285,7 @@ void main() { ); await tester.tap(find.byType(EditableText)); + await tester.pump(doubleTapTimeout); await tester.showKeyboard(find.byType(EditableText)); controller.text = 'test'; await tester.idle(); @@ -311,6 +315,7 @@ void main() { ); await tester.tap(find.byType(EditableText)); + await tester.pump(doubleTapTimeout); await tester.showKeyboard(find.byType(EditableText)); controller.text = 'test'; await tester.idle(); @@ -341,6 +346,7 @@ void main() { ); await tester.tap(find.byType(EditableText)); + await tester.pump(doubleTapTimeout); await tester.showKeyboard(find.byType(EditableText)); controller.text = 'test'; await tester.idle(); @@ -373,6 +379,7 @@ void main() { ); await tester.tap(find.byType(EditableText)); + await tester.pump(doubleTapTimeout); await tester.showKeyboard(find.byType(EditableText)); controller.text = 'test'; await tester.idle(); @@ -403,6 +410,7 @@ void main() { ); await tester.tap(find.byType(EditableText)); + await tester.pump(doubleTapTimeout); await tester.showKeyboard(find.byType(EditableText)); controller.text = 'test'; await tester.idle(); @@ -433,6 +441,7 @@ void main() { ); await tester.tap(find.byType(EditableText)); + await tester.pump(doubleTapTimeout); await tester.showKeyboard(find.byType(EditableText)); controller.text = 'test'; await tester.idle(); @@ -608,7 +617,7 @@ void main() { // Select EditableText to give it focus. final Finder textFinder = find.byKey(editableTextKey); await tester.tap(textFinder); - await tester.pump(); + await tester.pump(doubleTapTimeout); assert(focusNode.hasFocus); @@ -645,7 +654,7 @@ void main() { // Select EditableText to give it focus. final Finder textFinder = find.byKey(editableTextKey); await tester.tap(textFinder); - await tester.pump(); + await tester.pump(doubleTapTimeout); assert(focusNode.hasFocus); @@ -689,7 +698,7 @@ void main() { // Select EditableText to give it focus. final Finder textFinder = find.byKey(editableTextKey); await tester.tap(textFinder); - await tester.pump(); + await tester.pump(doubleTapTimeout); assert(focusNode.hasFocus); @@ -733,7 +742,7 @@ void main() { // Select EditableText to give it focus. final Finder textFinder = find.byKey(editableTextKey); await tester.tap(textFinder); - await tester.pump(); + await tester.pump(doubleTapTimeout); assert(focusNode.hasFocus); @@ -838,7 +847,7 @@ void main() { await tester.tap(find.byType(EditableText)); await tester.idle(); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect( semantics, @@ -1479,7 +1488,7 @@ void main() { await _buildApp(controls, tester); await tester.tap(find.byType(EditableText)); - await tester.pump(); + await tester.pump(doubleTapTimeout); expect( semantics, @@ -1565,7 +1574,7 @@ void main() { when(controls.canPaste(any)).thenReturn(true); await _buildApp(controls, tester); await tester.tap(find.byType(EditableText)); - await tester.pump(); + await tester.pump(doubleTapTimeout); final SemanticsOwner owner = tester.binding.pipelineOwner.semanticsOwner; const int expectedNodeId = 4;