Fix iOS selectWordEdge doesn't account for affinity (#115849)
* Fix iOS selectWordEdge doesn't account for affinity * fix test * update
This commit is contained in:
parent
511c5341bd
commit
224fae5063
@ -2149,7 +2149,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
|||||||
final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition! - _paintOffset));
|
final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition! - _paintOffset));
|
||||||
final TextRange word = _textPainter.getWordBoundary(position);
|
final TextRange word = _textPainter.getWordBoundary(position);
|
||||||
late TextSelection newSelection;
|
late TextSelection newSelection;
|
||||||
if (position.offset - word.start <= 1) {
|
if (position.offset <= word.start) {
|
||||||
newSelection = TextSelection.collapsed(offset: word.start);
|
newSelection = TextSelection.collapsed(offset: word.start);
|
||||||
} else {
|
} else {
|
||||||
newSelection = TextSelection.collapsed(offset: word.end, affinity: TextAffinity.upstream);
|
newSelection = TextSelection.collapsed(offset: word.end, affinity: TextAffinity.upstream);
|
||||||
|
@ -2173,7 +2173,7 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 50));
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
// First tap moved the cursor.
|
// First tap moved the cursor.
|
||||||
expect(controller.selection.isCollapsed, isTrue);
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 8 : 9);
|
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 12 : 9);
|
||||||
|
|
||||||
await tester.tapAt(pPos);
|
await tester.tapAt(pPos);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -2275,7 +2275,7 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 50));
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
// First tap moved the cursor.
|
// First tap moved the cursor.
|
||||||
expect(controller.selection.isCollapsed, isTrue);
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 8 : 9);
|
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 12 : 9);
|
||||||
|
|
||||||
await tester.tapAt(pPos);
|
await tester.tapAt(pPos);
|
||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
@ -3134,7 +3134,7 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 50));
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
// First tap moved the cursor to the beginning of the second word.
|
// First tap moved the cursor to the beginning of the second word.
|
||||||
expect(controller.selection.isCollapsed, isTrue);
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 8 : 9);
|
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 12 : 9);
|
||||||
await tester.tapAt(pPos);
|
await tester.tapAt(pPos);
|
||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
@ -3201,7 +3201,7 @@ void main() {
|
|||||||
// First tap moved the cursor.
|
// First tap moved the cursor.
|
||||||
expect(find.byType(CupertinoButton), findsNothing);
|
expect(find.byType(CupertinoButton), findsNothing);
|
||||||
expect(controller.selection.isCollapsed, isTrue);
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 8 : 9);
|
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 12 : 9);
|
||||||
|
|
||||||
await tester.tapAt(pPos);
|
await tester.tapAt(pPos);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -3270,7 +3270,7 @@ void main() {
|
|||||||
// First tap moved the cursor and hides the toolbar.
|
// First tap moved the cursor and hides the toolbar.
|
||||||
expect(
|
expect(
|
||||||
controller.selection,
|
controller.selection,
|
||||||
const TextSelection.collapsed(offset: 8),
|
const TextSelection.collapsed(offset: 12, affinity: TextAffinity.upstream),
|
||||||
);
|
);
|
||||||
expect(find.byType(CupertinoButton), findsNothing);
|
expect(find.byType(CupertinoButton), findsNothing);
|
||||||
await tester.tapAt(textFieldStart + const Offset(150.0, 5.0));
|
await tester.tapAt(textFieldStart + const Offset(150.0, 5.0));
|
||||||
@ -3375,7 +3375,7 @@ void main() {
|
|||||||
// Fall back to a single tap which selects the edge of the word on iOS, and
|
// Fall back to a single tap which selects the edge of the word on iOS, and
|
||||||
// a precise position on macOS.
|
// a precise position on macOS.
|
||||||
expect(controller.selection.isCollapsed, isTrue);
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 8 : 9);
|
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 12 : 9);
|
||||||
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
// Falling back to a single tap doesn't trigger a toolbar.
|
// Falling back to a single tap doesn't trigger a toolbar.
|
||||||
@ -3410,7 +3410,7 @@ void main() {
|
|||||||
await tester.tapAt(ePos, pointer: 7);
|
await tester.tapAt(ePos, pointer: 7);
|
||||||
await tester.pump(const Duration(milliseconds: 50));
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
expect(controller.selection.isCollapsed, isTrue);
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 4 : 5);
|
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 7 : 5);
|
||||||
await tester.tapAt(ePos, pointer: 7);
|
await tester.tapAt(ePos, pointer: 7);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(controller.selection.baseOffset, 4);
|
expect(controller.selection.baseOffset, 4);
|
||||||
@ -3911,14 +3911,14 @@ void main() {
|
|||||||
await touchGesture.up();
|
await touchGesture.up();
|
||||||
await tester.pumpAndSettle(kDoubleTapTimeout);
|
await tester.pumpAndSettle(kDoubleTapTimeout);
|
||||||
// On iOS, a tap to select, selects the word edge instead of the exact tap position.
|
// On iOS, a tap to select, selects the word edge instead of the exact tap position.
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformApple ? 4 : 5);
|
expect(controller.selection.baseOffset, isTargetPlatformApple ? 7 : 5);
|
||||||
expect(controller.selection.extentOffset, isTargetPlatformApple ? 4 : 5);
|
expect(controller.selection.extentOffset, isTargetPlatformApple ? 7 : 5);
|
||||||
|
|
||||||
// Selection should stay the same since it is set on tap up for mobile platforms.
|
// Selection should stay the same since it is set on tap up for mobile platforms.
|
||||||
await touchGesture.down(gPos);
|
await touchGesture.down(gPos);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformApple ? 4 : 5);
|
expect(controller.selection.baseOffset, isTargetPlatformApple ? 7 : 5);
|
||||||
expect(controller.selection.extentOffset, isTargetPlatformApple ? 4 : 5);
|
expect(controller.selection.extentOffset, isTargetPlatformApple ? 7 : 5);
|
||||||
|
|
||||||
await touchGesture.up();
|
await touchGesture.up();
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -7219,7 +7219,7 @@ void main() {
|
|||||||
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
|
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
|
||||||
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||||
expect(controller.selection.isCollapsed, true);
|
expect(controller.selection.isCollapsed, true);
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformAndroid ? 5 : 4);
|
expect(controller.selection.baseOffset, isTargetPlatformAndroid ? 5 : 7);
|
||||||
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
||||||
|
|
||||||
// Long press the 'e' to move the cursor in front of the 'e' and show the magnifier.
|
// Long press the 'e' to move the cursor in front of the 'e' and show the magnifier.
|
||||||
|
@ -1017,7 +1017,6 @@ void main() {
|
|||||||
);
|
);
|
||||||
// On iOS/iPadOS, during a tap we select the edge of the word closest to the tap.
|
// On iOS/iPadOS, during a tap we select the edge of the word closest to the tap.
|
||||||
// On macOS, we select the precise position of the tap.
|
// On macOS, we select the precise position of the tap.
|
||||||
final bool isTargetPlatformMobile = defaultTargetPlatform == TargetPlatform.iOS;
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
home: Material(
|
home: Material(
|
||||||
@ -1031,21 +1030,19 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final Offset textfieldStart = tester.getTopLeft(find.byType(TextField));
|
|
||||||
|
|
||||||
// This tap just puts the cursor somewhere different than where the double
|
// This tap just puts the cursor somewhere different than where the double
|
||||||
// tap will occur to test that the double tap moves the existing cursor first.
|
// tap will occur to test that the double tap moves the existing cursor first.
|
||||||
await tester.tapAt(textfieldStart + const Offset(50.0, 9.0));
|
await tester.tapAt(textOffsetToPosition(tester, 3));
|
||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
|
await tester.tapAt(textOffsetToPosition(tester, 8));
|
||||||
await tester.pump(const Duration(milliseconds: 50));
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
// First tap moved the cursor.
|
// First tap moved the cursor.
|
||||||
expect(
|
expect(
|
||||||
controller.selection,
|
controller.selection,
|
||||||
TextSelection.collapsed(offset: isTargetPlatformMobile ? 8 : 9),
|
const TextSelection.collapsed(offset: 8),
|
||||||
);
|
);
|
||||||
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
|
await tester.tapAt(textOffsetToPosition(tester, 8));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Second tap selects the word around the cursor.
|
// Second tap selects the word around the cursor.
|
||||||
@ -2088,14 +2085,14 @@ void main() {
|
|||||||
await touchGesture.up();
|
await touchGesture.up();
|
||||||
await tester.pumpAndSettle(kDoubleTapTimeout);
|
await tester.pumpAndSettle(kDoubleTapTimeout);
|
||||||
// On iOS a tap to select, selects the word edge instead of the exact tap position.
|
// On iOS a tap to select, selects the word edge instead of the exact tap position.
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformApple ? 4 : 5);
|
expect(controller.selection.baseOffset, isTargetPlatformApple ? 7 : 5);
|
||||||
expect(controller.selection.extentOffset, isTargetPlatformApple ? 4 : 5);
|
expect(controller.selection.extentOffset, isTargetPlatformApple ? 7 : 5);
|
||||||
|
|
||||||
// Selection should stay the same since it is set on tap up for mobile platforms.
|
// Selection should stay the same since it is set on tap up for mobile platforms.
|
||||||
await touchGesture.down(gPos);
|
await touchGesture.down(gPos);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformApple ? 4 : 5);
|
expect(controller.selection.baseOffset, isTargetPlatformApple ? 7 : 5);
|
||||||
expect(controller.selection.extentOffset, isTargetPlatformApple ? 4 : 5);
|
expect(controller.selection.extentOffset, isTargetPlatformApple ? 7 : 5);
|
||||||
|
|
||||||
await touchGesture.up();
|
await touchGesture.up();
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -8414,14 +8411,11 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'double tap selects word and first tap of double tap moves cursor',
|
'double tap selects word and first tap of double tap moves cursor (iOS)',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController(
|
final TextEditingController controller = TextEditingController(
|
||||||
text: 'Atwater Peel Sherbrooke Bonaventure',
|
text: 'Atwater Peel Sherbrooke Bonaventure',
|
||||||
);
|
);
|
||||||
// On iOS/iPadOS, during a tap we select the edge of the word closest to the tap.
|
|
||||||
// On macOS, we select the precise position of the tap.
|
|
||||||
final bool isTargetPlatformMobile = defaultTargetPlatform == TargetPlatform.iOS;
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
home: Material(
|
home: Material(
|
||||||
@ -8447,7 +8441,7 @@ void main() {
|
|||||||
// First tap moved the cursor.
|
// First tap moved the cursor.
|
||||||
expect(
|
expect(
|
||||||
controller.selection,
|
controller.selection,
|
||||||
TextSelection.collapsed(offset: isTargetPlatformMobile ? 8 : 9),
|
const TextSelection.collapsed(offset: 12, affinity: TextAffinity.upstream),
|
||||||
);
|
);
|
||||||
await tester.tapAt(pPos);
|
await tester.tapAt(pPos);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -8464,6 +8458,37 @@ void main() {
|
|||||||
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
testWidgets('iOS selectWordEdge works correctly', (WidgetTester tester) async {
|
||||||
|
final TextEditingController controller = TextEditingController(
|
||||||
|
text: 'blah1 blah2',
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initially, the menu is not shown and there is no selection.
|
||||||
|
expect(controller.selection, const TextSelection(baseOffset: -1, extentOffset: -1));
|
||||||
|
final Offset pos1 = textOffsetToPosition(tester, 1);
|
||||||
|
TestGesture gesture = await tester.startGesture(pos1);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(controller.selection, const TextSelection.collapsed(offset: 5, affinity: TextAffinity.upstream));
|
||||||
|
|
||||||
|
final Offset pos0 = textOffsetToPosition(tester, 0);
|
||||||
|
gesture = await tester.startGesture(pos0);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(controller.selection, const TextSelection.collapsed(offset: 0));
|
||||||
|
}, variant: TargetPlatformVariant.only(TargetPlatform.iOS));
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'double tap does not select word on read-only obscured field',
|
'double tap does not select word on read-only obscured field',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
@ -8952,7 +8977,7 @@ void main() {
|
|||||||
// First tap moved the cursor.
|
// First tap moved the cursor.
|
||||||
expect(
|
expect(
|
||||||
controller.selection,
|
controller.selection,
|
||||||
TextSelection.collapsed(offset: isTargetPlatformMobile ? 8 : 9),
|
isTargetPlatformMobile ? const TextSelection.collapsed(offset: 12, affinity: TextAffinity.upstream) : const TextSelection.collapsed(offset: 9),
|
||||||
);
|
);
|
||||||
await tester.tapAt(pPos);
|
await tester.tapAt(pPos);
|
||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
@ -9813,7 +9838,7 @@ void main() {
|
|||||||
// First tap moved the cursor to the beginning of the second word.
|
// First tap moved the cursor to the beginning of the second word.
|
||||||
expect(
|
expect(
|
||||||
controller.selection,
|
controller.selection,
|
||||||
TextSelection.collapsed(offset: isTargetPlatformMobile ? 8 : 9),
|
isTargetPlatformMobile ? const TextSelection.collapsed(offset: 12, affinity: TextAffinity.upstream) : const TextSelection.collapsed(offset: 9),
|
||||||
);
|
);
|
||||||
await tester.tapAt(pPos);
|
await tester.tapAt(pPos);
|
||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
@ -9875,7 +9900,7 @@ void main() {
|
|||||||
// First tap moved the cursor.
|
// First tap moved the cursor.
|
||||||
expect(
|
expect(
|
||||||
controller.selection,
|
controller.selection,
|
||||||
TextSelection.collapsed(offset: isTargetPlatformMobile ? 8 : 9),
|
isTargetPlatformMobile ? const TextSelection.collapsed(offset: 12, affinity: TextAffinity.upstream) : const TextSelection.collapsed(offset: 9),
|
||||||
);
|
);
|
||||||
await tester.tapAt(pPos);
|
await tester.tapAt(pPos);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -10006,7 +10031,7 @@ void main() {
|
|||||||
// First tap moved the cursor and hid the toolbar.
|
// First tap moved the cursor and hid the toolbar.
|
||||||
expect(
|
expect(
|
||||||
controller.selection,
|
controller.selection,
|
||||||
const TextSelection.collapsed(offset: 8),
|
const TextSelection.collapsed(offset: 12, affinity: TextAffinity.upstream)
|
||||||
);
|
);
|
||||||
expect(find.byType(CupertinoButton), findsNothing);
|
expect(find.byType(CupertinoButton), findsNothing);
|
||||||
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
|
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
|
||||||
@ -10441,7 +10466,7 @@ void main() {
|
|||||||
// Single taps selects the edge of the word.
|
// Single taps selects the edge of the word.
|
||||||
expect(
|
expect(
|
||||||
controller.selection,
|
controller.selection,
|
||||||
const TextSelection.collapsed(offset: 8),
|
const TextSelection.collapsed(offset: 12, affinity: TextAffinity.upstream),
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -13418,7 +13443,7 @@ void main() {
|
|||||||
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
|
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
|
||||||
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||||
expect(controller.selection.isCollapsed, true);
|
expect(controller.selection.isCollapsed, true);
|
||||||
expect(controller.selection.baseOffset, isTargetPlatformAndroid ? 5 : 4);
|
expect(controller.selection.baseOffset, isTargetPlatformAndroid ? 5 : 7);
|
||||||
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
expect(find.byKey(fakeMagnifier.key!), findsNothing);
|
||||||
|
|
||||||
// Long press the 'e' to select 'def' on Android and show magnifier.
|
// Long press the 'e' to select 'def' on Android and show magnifier.
|
||||||
|
@ -3885,7 +3885,6 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final Offset selectableTextStart = tester.getTopLeft(find.byType(SelectableText));
|
final Offset selectableTextStart = tester.getTopLeft(find.byType(SelectableText));
|
||||||
|
|
||||||
await tester.tapAt(selectableTextStart + const Offset(50.0, 5.0));
|
await tester.tapAt(selectableTextStart + const Offset(50.0, 5.0));
|
||||||
@ -3912,7 +3911,7 @@ void main() {
|
|||||||
// First tap moved the cursor.
|
// First tap moved the cursor.
|
||||||
expect(
|
expect(
|
||||||
controller.selection,
|
controller.selection,
|
||||||
const TextSelection.collapsed(offset: 0),
|
const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream),
|
||||||
);
|
);
|
||||||
await tester.tapAt(selectableTextStart + const Offset(10.0, 5.0));
|
await tester.tapAt(selectableTextStart + const Offset(10.0, 5.0));
|
||||||
await tester.pump(const Duration(milliseconds: 50));
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user