Unverified Commit a21a1f41 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

iOS selection handles are invisible (#31332)

Fix a bug where text selection handles were invisible in iOS
parent 6b191841
......@@ -654,4 +654,4 @@ class TextPainter {
final List<int> indices = _paragraph.getWordBoundary(position.offset);
return TextRange(start: indices[0], end: indices[1]);
}
}
\ No newline at end of file
}
......@@ -302,15 +302,25 @@ class RenderEditable extends RenderBox {
TextPosition(offset: _selection.start, affinity: _selection.affinity),
Rect.zero,
);
_selectionStartInViewport.value = visibleRegion.contains(startOffset + effectiveOffset);
// TODO(justinmc): https://github.com/flutter/flutter/issues/31495
// Check if the selection is visible with an approximation because a
// difference between rounded and unrounded values causes the caret to be
// reported as having a slightly (< 0.5) negative y offset. This rounding
// happens in paragraph.cc's layout and TextPainer's
// _applyFloatingPointHack. Ideally, the rounding mismatch will be fixed and
// this can be changed to be a strict check instead of an approximation.
const double visibleRegionSlop = 0.5;
_selectionStartInViewport.value = visibleRegion
.inflate(visibleRegionSlop)
.contains(startOffset + effectiveOffset);
final Offset endOffset = _textPainter.getOffsetForCaret(
TextPosition(offset: _selection.end, affinity: _selection.affinity),
Rect.zero,
);
_selectionEndInViewport.value = visibleRegion.contains(endOffset + effectiveOffset);
_selectionEndInViewport.value = visibleRegion
.inflate(visibleRegionSlop)
.contains(endOffset + effectiveOffset);
}
static const int _kLeftArrowCode = 21;
......
......@@ -612,7 +612,6 @@ class _TextSelectionHandleOverlayState
point.dy.clamp(0.0, viewport.height),
);
return CompositedTransformFollower(
link: widget.layerLink,
showWhenUnlinked: false,
......
......@@ -2091,4 +2091,39 @@ void main() {
final EditableText editableText = tester.firstWidget(find.byType(EditableText));
expect(editableText.cursorColor, const Color(0xFFF44336));
});
testWidgets('iOS shows selection handles', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
const String testText = 'lorem ipsum';
final TextEditingController controller = TextEditingController(text: testText);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(),
home: Center(
child: CupertinoTextField(
controller: controller,
),
),
),
);
final RenderEditable renderEditable =
tester.state<EditableTextState>(find.byType(EditableText)).renderEditable;
await tester.tapAt(textOffsetToPosition(tester, 5));
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pumpAndSettle();
final List<Widget> transitions =
find.byType(FadeTransition).evaluate().map((Element e) => e.widget).toList();
expect(transitions.length, 2);
final FadeTransition left = transitions[0];
final FadeTransition right = transitions[1];
expect(left.opacity.value, equals(1.0));
expect(right.opacity.value, equals(1.0));
debugDefaultTargetPlatformOverride = null;
});
}
......@@ -5676,4 +5676,71 @@ void main() {
);
expect(topLeft.dx, equals(383)); // Should be same as equivalent in 'Caret center position'
});
testWidgets('selection handles are rendered and not faded away', (WidgetTester tester) async {
const String testText = 'lorem ipsum';
final TextEditingController controller = TextEditingController(text: testText);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: TextField(
controller: controller,
),
),
),
);
final RenderEditable renderEditable =
tester.state<EditableTextState>(find.byType(EditableText)).renderEditable;
await tester.tapAt(const Offset(20, 10));
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pumpAndSettle();
final List<Widget> transitions =
find.byType(FadeTransition).evaluate().map((Element e) => e.widget).toList();
// On Android, an empty app contains a single FadeTransition. The following
// two are the left and right text selection handles, respectively.
expect(transitions.length, 3);
final FadeTransition left = transitions[1];
final FadeTransition right = transitions[2];
expect(left.opacity.value, equals(1.0));
expect(right.opacity.value, equals(1.0));
});
testWidgets('iOS selection handles are rendered and not faded away', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
const String testText = 'lorem ipsum';
final TextEditingController controller = TextEditingController(text: testText);
await tester.pumpWidget(
MaterialApp(
home: Material(
child: TextField(
controller: controller,
),
),
),
);
final RenderEditable renderEditable =
tester.state<EditableTextState>(find.byType(EditableText)).renderEditable;
await tester.tapAt(const Offset(20, 10));
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pumpAndSettle();
final List<Widget> transitions =
find.byType(FadeTransition).evaluate().map((Element e) => e.widget).toList();
expect(transitions.length, 2);
final FadeTransition left = transitions[0];
final FadeTransition right = transitions[1];
expect(left.opacity.value, equals(1.0));
expect(right.opacity.value, equals(1.0));
debugDefaultTargetPlatformOverride = null;
});
}
......@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart';
import 'package:mockito/mockito.dart';
......@@ -21,6 +22,10 @@ final FocusScopeNode focusScopeNode = FocusScopeNode();
const TextStyle textStyle = TextStyle();
const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00);
enum HandlePositionInViewport {
leftEdge, rightEdge, within,
}
void main() {
setUp(() {
debugResetSemanticsIdCounter();
......@@ -469,14 +474,10 @@ void main() {
});
testWidgets('Fires onChanged when text changes via TextSelectionOverlay', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
String changedValue;
final Widget widget = MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
key: editableTextKey,
controller: TextEditingController(),
focusNode: FocusNode(),
style: Typography(platform: TargetPlatform.android).black.subhead,
......@@ -500,7 +501,7 @@ void main() {
});
// Long-press to bring up the text editing controls.
final Finder textFinder = find.byKey(editableTextKey);
final Finder textFinder = find.byType(EditableText);
await tester.longPress(textFinder);
tester.state<EditableTextState>(textFinder).showToolbar();
await tester.pump();
......@@ -512,14 +513,11 @@ void main() {
});
testWidgets('Does not lose focus by default when "next" action is pressed', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
final FocusNode focusNode = FocusNode();
final Widget widget = MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
key: editableTextKey,
controller: TextEditingController(),
focusNode: focusNode,
style: Typography(platform: TargetPlatform.android).black.subhead,
......@@ -531,7 +529,7 @@ void main() {
await tester.pumpWidget(widget);
// Select EditableText to give it focus.
final Finder textFinder = find.byKey(editableTextKey);
final Finder textFinder = find.byType(EditableText);
await tester.tap(textFinder);
await tester.pump();
......@@ -545,14 +543,11 @@ void main() {
});
testWidgets('Does not lose focus by default when "done" action is pressed and onEditingComplete is provided', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
final FocusNode focusNode = FocusNode();
final Widget widget = MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
key: editableTextKey,
controller: TextEditingController(),
focusNode: focusNode,
style: Typography(platform: TargetPlatform.android).black.subhead,
......@@ -567,7 +562,7 @@ void main() {
await tester.pumpWidget(widget);
// Select EditableText to give it focus.
final Finder textFinder = find.byKey(editableTextKey);
final Finder textFinder = find.byType(EditableText);
await tester.tap(textFinder);
await tester.pump();
......@@ -582,8 +577,6 @@ void main() {
});
testWidgets('When "done" is pressed callbacks are invoked: onEditingComplete > onSubmitted', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
final FocusNode focusNode = FocusNode();
bool onEditingCompleteCalled = false;
......@@ -592,7 +585,6 @@ void main() {
final Widget widget = MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
key: editableTextKey,
controller: TextEditingController(),
focusNode: focusNode,
style: Typography(platform: TargetPlatform.android).black.subhead,
......@@ -610,7 +602,7 @@ void main() {
await tester.pumpWidget(widget);
// Select EditableText to give it focus.
final Finder textFinder = find.byKey(editableTextKey);
final Finder textFinder = find.byType(EditableText);
await tester.tap(textFinder);
await tester.pump();
......@@ -625,8 +617,6 @@ void main() {
});
testWidgets('When "next" is pressed callbacks are invoked: onEditingComplete > onSubmitted', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
final FocusNode focusNode = FocusNode();
bool onEditingCompleteCalled = false;
......@@ -635,7 +625,6 @@ void main() {
final Widget widget = MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
key: editableTextKey,
controller: TextEditingController(),
focusNode: focusNode,
style: Typography(platform: TargetPlatform.android).black.subhead,
......@@ -653,7 +642,7 @@ void main() {
await tester.pumpWidget(widget);
// Select EditableText to give it focus.
final Finder textFinder = find.byKey(editableTextKey);
final Finder textFinder = find.byType(EditableText);
await tester.tap(textFinder);
await tester.pump();
......@@ -668,8 +657,6 @@ void main() {
});
testWidgets('When "newline" action is called on a Editable text with maxLines == 1 callbacks are invoked: onEditingComplete > onSubmitted', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
final FocusNode focusNode = FocusNode();
bool onEditingCompleteCalled = false;
......@@ -678,7 +665,6 @@ void main() {
final Widget widget = MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
key: editableTextKey,
controller: TextEditingController(),
focusNode: focusNode,
style: Typography(platform: TargetPlatform.android).black.subhead,
......@@ -697,7 +683,7 @@ void main() {
await tester.pumpWidget(widget);
// Select EditableText to give it focus.
final Finder textFinder = find.byKey(editableTextKey);
final Finder textFinder = find.byType(EditableText);
await tester.tap(textFinder);
await tester.pump();
......@@ -711,8 +697,6 @@ void main() {
});
testWidgets('When "newline" action is called on a Editable text with maxLines != 1, onEditingComplete and onSubmitted callbacks are not invoked.', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
final FocusNode focusNode = FocusNode();
bool onEditingCompleteCalled = false;
......@@ -721,7 +705,6 @@ void main() {
final Widget widget = MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
key: editableTextKey,
controller: TextEditingController(),
focusNode: focusNode,
style: Typography(platform: TargetPlatform.android).black.subhead,
......@@ -738,7 +721,7 @@ void main() {
await tester.pumpWidget(widget);
// Select EditableText to give it focus.
final Finder textFinder = find.byKey(editableTextKey);
final Finder textFinder = find.byType(EditableText);
await tester.tap(textFinder);
await tester.pump();
......@@ -754,8 +737,6 @@ void main() {
});
testWidgets('Changing controller updates EditableText', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
final TextEditingController controller1 =
TextEditingController(text: 'Wibble');
final TextEditingController controller2 =
......@@ -775,7 +756,6 @@ void main() {
child: Material(
child: EditableText(
backgroundCursorColor: Colors.grey,
key: editableTextKey,
controller: currentController,
focusNode: FocusNode(),
style: Typography(platform: TargetPlatform.android)
......@@ -1904,15 +1884,12 @@ void main() {
composing: TextRange(start: 5, end: 14),
),
);
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(MaterialApp( // So we can show overlays.
home: EditableText(
autofocus: true,
backgroundCursorColor: Colors.grey,
key: editableTextKey,
controller: controller,
focusNode: focusNode,
style: textStyle,
......@@ -1943,19 +1920,16 @@ void main() {
});
testWidgets('text selection handle visibility', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
// Text with two separate words to select.
const String testText = 'XXXXX XXXXX';
final TextEditingController controller = TextEditingController(text: testText);
final Widget widget = MaterialApp(
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 100,
child: EditableText(
key: editableTextKey,
controller: controller,
focusNode: FocusNode(),
style: Typography(platform: TargetPlatform.android).black.subhead,
......@@ -1966,77 +1940,83 @@ void main() {
),
),
),
);
await tester.pumpWidget(widget);
));
final EditableTextState state =
tester.state<EditableTextState>(find.byType(EditableText));
final RenderEditable renderEditable = state.renderEditable;
final Scrollable scrollable = tester.widget<Scrollable>(find.byType(Scrollable));
bool leftVisibleBefore = false;
bool rightVisibleBefore = false;
bool expectedLeftVisibleBefore = false;
bool expectedRightVisibleBefore = false;
Future<void> verifyVisibility(
bool leftVisible,
Symbol leftPosition,
bool rightVisible,
Symbol rightPosition,
HandlePositionInViewport leftPosition,
bool expectedLeftVisible,
HandlePositionInViewport rightPosition,
bool expectedRightVisible,
) async {
await tester.pump();
// Check the signal from RenderEditable about whether they're within the
// viewport.
expect(renderEditable.selectionStartInViewport.value, equals(leftVisible));
expect(renderEditable.selectionEndInViewport.value, equals(rightVisible));
expect(renderEditable.selectionStartInViewport.value, equals(expectedLeftVisible));
expect(renderEditable.selectionEndInViewport.value, equals(expectedRightVisible));
// Check that the animations are functional and going in the right
// direction.
final List<Widget> transitions =
find.byType(FadeTransition).evaluate().map((Element e) => e.widget).toList();
// On Android, an empty app contains a single FadeTransition. The following
// two are the left and right text selection handles, respectively.
final FadeTransition left = transitions[1];
final FadeTransition right = transitions[2];
if (leftVisibleBefore)
if (expectedLeftVisibleBefore)
expect(left.opacity.value, equals(1.0));
if (rightVisibleBefore)
if (expectedRightVisibleBefore)
expect(right.opacity.value, equals(1.0));
await tester.pump(TextSelectionOverlay.fadeDuration ~/ 2);
if (leftVisible != leftVisibleBefore)
if (expectedLeftVisible != expectedLeftVisibleBefore)
expect(left.opacity.value, equals(0.5));
if (rightVisible != rightVisibleBefore)
if (expectedRightVisible != expectedRightVisibleBefore)
expect(right.opacity.value, equals(0.5));
await tester.pump(TextSelectionOverlay.fadeDuration ~/ 2);
if (leftVisible)
if (expectedLeftVisible)
expect(left.opacity.value, equals(1.0));
if (rightVisible)
if (expectedRightVisible)
expect(right.opacity.value, equals(1.0));
leftVisibleBefore = leftVisible;
rightVisibleBefore = rightVisible;
expectedLeftVisibleBefore = expectedLeftVisible;
expectedRightVisibleBefore = expectedRightVisible;
// Check that the handles' positions are correct (clamped within the
// viewport but not stuck).
// Check that the handles' positions are correct.
final List<Positioned> positioned =
find.byType(Positioned).evaluate().map((Element e) => e.widget).cast<Positioned>().toList();
final Size viewport = renderEditable.size;
void testPosition(double pos, Symbol expected) {
if (expected == #left)
expect(pos, equals(0.0));
if (expected == #right)
expect(pos, equals(viewport.width));
if (expected == #middle)
expect(pos, inExclusiveRange(0.0, viewport.width));
void testPosition(double pos, HandlePositionInViewport expected) {
switch (expected) {
case HandlePositionInViewport.leftEdge:
expect(pos, equals(0.0));
break;
case HandlePositionInViewport.rightEdge:
expect(pos, equals(viewport.width));
break;
case HandlePositionInViewport.within:
expect(pos, inExclusiveRange(0.0, viewport.width));
break;
default:
throw TestFailure('HandlePositionInViewport can\'t be null.');
}
}
testPosition(positioned[0].left, leftPosition);
......@@ -2047,38 +2027,224 @@ void main() {
await tester.tapAt(const Offset(20, 10));
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pump();
await verifyVisibility(true, #left, true, #middle);
await verifyVisibility(HandlePositionInViewport.leftEdge, true, HandlePositionInViewport.within, true);
// Drag the text slightly so the first word is partially visible. Only the
// right handle should be visible.
scrollable.controller.jumpTo(20.0);
await verifyVisibility(HandlePositionInViewport.leftEdge, false, HandlePositionInViewport.within, true);
// Drag the text all the way to the left so the first word is not visible at
// all (and the second word is fully visible). Both handles should be
// invisible now.
scrollable.controller.jumpTo(200.0);
await verifyVisibility(HandlePositionInViewport.leftEdge, false, HandlePositionInViewport.leftEdge, false);
// Tap to unselect.
await tester.tap(find.byType(EditableText));
await tester.pump();
// Now that the second word has been dragged fully into view, select it.
await tester.tapAt(const Offset(80, 10));
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pump();
await verifyVisibility(HandlePositionInViewport.within, true, HandlePositionInViewport.within, true);
// Drag the text slightly to the right. Only the left handle should be
// visible.
scrollable.controller.jumpTo(150);
await verifyVisibility(HandlePositionInViewport.within, true, HandlePositionInViewport.rightEdge, false);
// Drag the text all the way to the right, so the second word is not visible
// at all. Again, both handles should be invisible.
scrollable.controller.jumpTo(0);
await verifyVisibility(HandlePositionInViewport.rightEdge, false, HandlePositionInViewport.rightEdge, false);
});
testWidgets('text selection handle visibility RTL', (WidgetTester tester) async {
// Text with two separate words to select.
const String testText = 'XXXXX XXXXX';
final TextEditingController controller = TextEditingController(text: testText);
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 100,
child: EditableText(
controller: controller,
focusNode: FocusNode(),
style: Typography(platform: TargetPlatform.android).black.subhead,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
textAlign: TextAlign.right,
),
),
),
));
final EditableTextState state =
tester.state<EditableTextState>(find.byType(EditableText));
// Select the first word. Both handles should be visible.
await tester.tapAt(const Offset(20, 10));
state.renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pump();
final List<Positioned> positioned =
find.byType(Positioned).evaluate().map((Element e) => e.widget).cast<Positioned>().toList();
expect(positioned[0].left, 0.0);
expect(positioned[1].left, 70.0);
expect(controller.selection.base.offset, 0);
expect(controller.selection.extent.offset, 5);
});
// Regression test for https://github.com/flutter/flutter/issues/31287
testWidgets('iOS text selection handle visibility', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
// Text with two separate words to select.
const String testText = 'XXXXX XXXXX';
final TextEditingController controller = TextEditingController(text: testText);
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: Container(
child: SizedBox(
width: 100,
child: EditableText(
controller: controller,
focusNode: FocusNode(),
style: Typography(platform: TargetPlatform.iOS).black.subhead,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
selectionControls: cupertinoTextSelectionControls,
keyboardType: TextInputType.text,
),
),
),
),
));
final EditableTextState state =
tester.state<EditableTextState>(find.byType(EditableText));
final RenderEditable renderEditable = state.renderEditable;
final Scrollable scrollable = tester.widget<Scrollable>(find.byType(Scrollable));
bool expectedLeftVisibleBefore = false;
bool expectedRightVisibleBefore = false;
Future<void> verifyVisibility(
HandlePositionInViewport leftPosition,
bool expectedLeftVisible,
HandlePositionInViewport rightPosition,
bool expectedRightVisible,
) async {
await tester.pump();
// Check the signal from RenderEditable about whether they're within the
// viewport.
expect(renderEditable.selectionStartInViewport.value, equals(expectedLeftVisible));
expect(renderEditable.selectionEndInViewport.value, equals(expectedRightVisible));
// Check that the animations are functional and going in the right
// direction.
final List<Widget> transitions =
find.byType(FadeTransition).evaluate().map((Element e) => e.widget).toList();
final FadeTransition left = transitions[0];
final FadeTransition right = transitions[1];
if (expectedLeftVisibleBefore)
expect(left.opacity.value, equals(1.0));
if (expectedRightVisibleBefore)
expect(right.opacity.value, equals(1.0));
await tester.pump(TextSelectionOverlay.fadeDuration ~/ 2);
if (expectedLeftVisible != expectedLeftVisibleBefore)
expect(left.opacity.value, equals(0.5));
if (expectedRightVisible != expectedRightVisibleBefore)
expect(right.opacity.value, equals(0.5));
await tester.pump(TextSelectionOverlay.fadeDuration ~/ 2);
if (expectedLeftVisible)
expect(left.opacity.value, equals(1.0));
if (expectedRightVisible)
expect(right.opacity.value, equals(1.0));
expectedLeftVisibleBefore = expectedLeftVisible;
expectedRightVisibleBefore = expectedRightVisible;
// Check that the handles' positions are correct.
final List<Positioned> positioned =
find.byType(Positioned).evaluate().map((Element e) => e.widget).cast<Positioned>().toList();
final Size viewport = renderEditable.size;
void testPosition(double pos, HandlePositionInViewport expected) {
switch (expected) {
case HandlePositionInViewport.leftEdge:
expect(pos, equals(0.0));
break;
case HandlePositionInViewport.rightEdge:
expect(pos, equals(viewport.width));
break;
case HandlePositionInViewport.within:
expect(pos, inExclusiveRange(0.0, viewport.width));
break;
default:
throw TestFailure('HandlePositionInViewport can\'t be null.');
}
}
testPosition(positioned[1].left, leftPosition);
testPosition(positioned[2].left, rightPosition);
}
// Select the first word. Both handles should be visible.
await tester.tapAt(const Offset(20, 10));
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pump();
await verifyVisibility(HandlePositionInViewport.leftEdge, true, HandlePositionInViewport.within, true);
// Drag the text slightly so the first word is partially visible. Only the
// right handle should be visible.
scrollable.controller.jumpTo(20.0);
await verifyVisibility(false, #left, true, #middle);
await verifyVisibility(HandlePositionInViewport.leftEdge, false, HandlePositionInViewport.within, true);
// Drag the text all the way to the left so the first word is not visible at
// all (and the second word is fully visible). Both handles should be
// invisible now.
scrollable.controller.jumpTo(200.0);
await verifyVisibility(false, #left, false, #left);
await verifyVisibility(HandlePositionInViewport.leftEdge, false, HandlePositionInViewport.leftEdge, false);
// Tap to unselect.
await tester.tap(find.byKey(editableTextKey));
await tester.tap(find.byType(EditableText));
await tester.pump();
// Now that the second word has been dragged fully into view, select it.
await tester.tapAt(const Offset(80, 10));
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pump();
await verifyVisibility(true, #middle, true, #middle);
await verifyVisibility(HandlePositionInViewport.within, true, HandlePositionInViewport.within, true);
// Drag the text slightly to the right. Only the left handle should be
// visible.
scrollable.controller.jumpTo(150);
await verifyVisibility(true, #middle, false, #right);
await verifyVisibility(HandlePositionInViewport.within, true, HandlePositionInViewport.rightEdge, false);
// Drag the text all the way to the right, so the second word is not visible
// at all. Again, both handles should be invisible.
scrollable.controller.jumpTo(0);
await verifyVisibility(false, #right, false, #right);
await verifyVisibility(HandlePositionInViewport.rightEdge, false, HandlePositionInViewport.rightEdge, false);
debugDefaultTargetPlatformOverride = null;
});
}
......
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