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

Handle invalid selection in TextEditingActionTarget (#90826)

Prevents bugs related to invalid (-1,-1) selection in keyboard shortcuts
parent 2367a177
......@@ -322,7 +322,7 @@ abstract class TextEditingActionTarget {
///
/// If the selection is not collapsed, deletes the selection.
///
/// If [readOnly] is true, does nothing.
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// {@template flutter.widgets.TextEditingActionTarget.cause}
/// The given [SelectionChangedCause] indicates the cause of this change and
......@@ -336,6 +336,9 @@ abstract class TextEditingActionTarget {
if (readOnly) {
return;
}
if (!textEditingValue.selection.isValid) {
return;
}
// `delete` does not depend on the text layout, and the boundary analysis is
// done using the `previousCharacter` method instead of ICU, we can keep
......@@ -356,7 +359,7 @@ abstract class TextEditingActionTarget {
///
/// If the selection is not collapsed, deletes the selection.
///
/// If [readOnly] is true, does nothing.
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// If [obscureText] is true, it treats the whole text content as a single
/// word.
......@@ -377,6 +380,9 @@ abstract class TextEditingActionTarget {
if (readOnly) {
return;
}
if (!textEditingValue.selection.isValid) {
return;
}
if (obscureText) {
// When the text is obscured, the whole thing is treated as one big line.
......@@ -400,7 +406,7 @@ abstract class TextEditingActionTarget {
/// If [obscureText] is true, it treats the whole text content as
/// a single word.
///
/// If [readOnly] is true, does nothing.
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
......@@ -411,6 +417,9 @@ abstract class TextEditingActionTarget {
if (readOnly) {
return;
}
if (!textEditingValue.selection.isValid) {
return;
}
// When there is a line break, line delete shouldn't do anything
final String textBefore = textEditingValue.selection.textBefore(textEditingValue.text);
......@@ -439,7 +448,7 @@ abstract class TextEditingActionTarget {
///
/// If the selection is not collapsed, deletes the selection.
///
/// If [readOnly] is true, does nothing.
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
......@@ -450,6 +459,9 @@ abstract class TextEditingActionTarget {
if (readOnly) {
return;
}
if (!textEditingValue.selection.isValid) {
return;
}
final String textAfter = textEditingValue.selection.textAfter(textEditingValue.text);
final int characterBoundary = nextCharacter(0, textAfter);
......@@ -462,7 +474,7 @@ abstract class TextEditingActionTarget {
///
/// If the selection is not collapsed, deletes the selection.
///
/// If [readOnly] is true, does nothing.
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// If [obscureText] is true, it treats the whole text content as
/// a single word.
......@@ -479,6 +491,9 @@ abstract class TextEditingActionTarget {
if (readOnly) {
return;
}
if (!textEditingValue.selection.isValid) {
return;
}
if (obscureText) {
// When the text is obscured, the whole thing is treated as one big word.
......@@ -498,7 +513,7 @@ abstract class TextEditingActionTarget {
///
/// If the selection is not collapsed, deletes the selection.
///
/// If [readOnly] is true, does nothing.
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// If [obscureText] is true, it treats the whole text content as
/// a single word.
......@@ -512,6 +527,9 @@ abstract class TextEditingActionTarget {
if (readOnly) {
return;
}
if (!textEditingValue.selection.isValid) {
return;
}
if (obscureText) {
// When the text is obscured, the whole thing is treated as one big line.
......@@ -539,10 +557,18 @@ abstract class TextEditingActionTarget {
/// The given SelectionChangedCause indicates the cause of this change and
/// will be passed to setSelection.
///
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// See also:
/// * [deleteToStart]
void deleteToEnd(SelectionChangedCause cause) {
assert(textEditingValue.selection.isCollapsed);
if (readOnly) {
return;
}
if (!textEditingValue.selection.isValid) {
return;
}
setTextEditingValue(_deleteTo(TextPosition(offset: textEditingValue.text.length)), cause);
}
......@@ -552,10 +578,18 @@ abstract class TextEditingActionTarget {
/// The given SelectionChangedCause indicates the cause of this change and
/// will be passed to setSelection.
///
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// See also:
/// * [deleteToEnd]
void deleteToStart(SelectionChangedCause cause) {
assert(textEditingValue.selection.isCollapsed);
if (readOnly) {
return;
}
if (!textEditingValue.selection.isValid) {
return;
}
setTextEditingValue(_deleteTo(const TextPosition(offset: 0)), cause);
}
......@@ -569,12 +603,17 @@ abstract class TextEditingActionTarget {
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
/// to the end.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
///
/// * [expandSelectionToStart], which is same but in the opposite direction.
void expandSelectionToEnd(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionToEnd(cause);
}
......@@ -595,6 +634,8 @@ abstract class TextEditingActionTarget {
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
/// to the start.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
......@@ -602,6 +643,9 @@ abstract class TextEditingActionTarget {
/// * [expandSelectionToEnd], which is the same but in the opposite
/// direction.
void expandSelectionToStart(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionToStart(cause);
}
......@@ -623,6 +667,8 @@ abstract class TextEditingActionTarget {
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
/// left by line.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
......@@ -630,6 +676,9 @@ abstract class TextEditingActionTarget {
/// * [expandSelectionRightByLine], which is the same but in the opposite
/// direction.
void expandSelectionLeftByLine(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionLeftByLine(cause);
}
......@@ -666,6 +715,8 @@ abstract class TextEditingActionTarget {
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
/// right by line.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
......@@ -673,6 +724,9 @@ abstract class TextEditingActionTarget {
/// * [expandSelectionLeftByLine], which is the same but in the opposite
/// direction.
void expandSelectionRightByLine(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionRightByLine(cause);
}
......@@ -707,12 +761,17 @@ abstract class TextEditingActionTarget {
/// If selectionEnabled is false, keeps the selection collapsed and just
/// moves it down.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
///
/// * [extendSelectionUp], which is same but in the opposite direction.
void extendSelectionDown(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionDown(cause);
}
......@@ -747,12 +806,17 @@ abstract class TextEditingActionTarget {
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
/// left.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
///
/// * [extendSelectionRight], which is same but in the opposite direction.
void extendSelectionLeft(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionLeft(cause);
}
......@@ -782,6 +846,8 @@ abstract class TextEditingActionTarget {
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
/// left by line.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
......@@ -791,6 +857,9 @@ abstract class TextEditingActionTarget {
/// * [expandSelectionRightByLine], which strictly grows the selection
/// regardless of the order.
void extendSelectionLeftByLine(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionLeftByLine(cause);
}
......@@ -828,12 +897,17 @@ abstract class TextEditingActionTarget {
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
/// right.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
///
/// * [extendSelectionLeft], which is same but in the opposite direction.
void extendSelectionRight(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionRight(cause);
}
......@@ -860,6 +934,8 @@ abstract class TextEditingActionTarget {
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
/// right by line.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
......@@ -869,6 +945,9 @@ abstract class TextEditingActionTarget {
/// * [expandSelectionRightByLine], which strictly grows the selection
/// regardless of the order.
void extendSelectionRightByLine(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionRightByLine(cause);
}
......@@ -898,6 +977,8 @@ abstract class TextEditingActionTarget {
/// Extend the current selection to the previous start of a word.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// {@macro flutter.widgets.TextEditingActionTarget.whiteSpace}
......@@ -915,6 +996,9 @@ abstract class TextEditingActionTarget {
/// direction.
void extendSelectionLeftByWord(SelectionChangedCause cause,
[bool includeWhitespace = true, bool stopAtReversal = false]) {
if (!textEditingValue.selection.isValid) {
return;
}
// When the text is obscured, the whole thing is treated as one big word.
if (obscureText) {
return _extendSelectionToStart(cause);
......@@ -946,6 +1030,8 @@ abstract class TextEditingActionTarget {
/// Extend the current selection to the next end of a word.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// {@macro flutter.widgets.TextEditingActionTarget.whiteSpace}
......@@ -958,6 +1044,9 @@ abstract class TextEditingActionTarget {
/// direction.
void extendSelectionRightByWord(SelectionChangedCause cause,
[bool includeWhitespace = true, bool stopAtReversal = false]) {
if (!textEditingValue.selection.isValid) {
return;
}
debugAssertLayoutUpToDate();
// When the text is obscured, the whole thing is treated as one big word.
if (obscureText) {
......@@ -997,6 +1086,8 @@ abstract class TextEditingActionTarget {
/// If [selectionEnabled] is false, keeps the selection collapsed and moves it
/// up.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
......@@ -1004,6 +1095,9 @@ abstract class TextEditingActionTarget {
/// * [extendSelectionDown], which is the same but in the opposite
/// direction.
void extendSelectionUp(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
if (!selectionEnabled) {
return moveSelectionUp(cause);
}
......@@ -1043,6 +1137,8 @@ abstract class TextEditingActionTarget {
/// Move the current selection to the leftmost point of the current line.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
......@@ -1050,6 +1146,9 @@ abstract class TextEditingActionTarget {
/// * [moveSelectionRightByLine], which is the same but in the opposite
/// direction.
void moveSelectionLeftByLine(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
// If already at the left edge of the line, do nothing.
final TextSelection currentLine = textLayoutMetrics.getLineAtOffset(
textEditingValue.selection.extent,
......@@ -1079,12 +1178,17 @@ abstract class TextEditingActionTarget {
///
/// Move the current selection to the next line.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
///
/// * [moveSelectionUp], which is the same but in the opposite direction.
void moveSelectionDown(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
// If the selection is collapsed at the end of the field already, then
// nothing happens.
if (textEditingValue.selection.isCollapsed &&
......@@ -1118,12 +1222,17 @@ abstract class TextEditingActionTarget {
///
/// If it can't be moved left, do nothing.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
///
/// * [moveSelectionRight], which is the same but in the opposite direction.
void moveSelectionLeft(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
// If the selection is already all the way left, there is nothing to do.
if (textEditingValue.selection.isCollapsed && textEditingValue.selection.extentOffset <= 0) {
return;
......@@ -1156,6 +1265,8 @@ abstract class TextEditingActionTarget {
/// A TextSelection that isn't collapsed will be collapsed and moved from the
/// extentOffset.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// {@macro flutter.widgets.TextEditingActionTarget.whiteSpace}
......@@ -1166,6 +1277,9 @@ abstract class TextEditingActionTarget {
/// direction.
void moveSelectionLeftByWord(SelectionChangedCause cause,
[bool includeWhitespace = true]) {
if (!textEditingValue.selection.isValid) {
return;
}
// When the text is obscured, the whole thing is treated as one big word.
if (obscureText) {
return moveSelectionToStart(cause);
......@@ -1189,7 +1303,7 @@ abstract class TextEditingActionTarget {
/// Move the current selection to the right by one character.
///
/// If it can't be moved right, do nothing.
/// If the selection is invalid or it can't be moved right, do nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
......@@ -1197,6 +1311,9 @@ abstract class TextEditingActionTarget {
///
/// * [moveSelectionLeft], which is the same but in the opposite direction.
void moveSelectionRight(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
// If the selection is already all the way right, there is nothing to do.
if (textEditingValue.selection.isCollapsed &&
textEditingValue.selection.extentOffset >= textEditingValue.text.length) {
......@@ -1224,6 +1341,8 @@ abstract class TextEditingActionTarget {
///
/// Move the current selection to the rightmost point of the current line.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
......@@ -1231,6 +1350,9 @@ abstract class TextEditingActionTarget {
/// * [moveSelectionLeftByLine], which is the same but in the opposite
/// direction.
void moveSelectionRightByLine(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
// If already at the right edge of the line, do nothing.
final TextSelection currentLine = textLayoutMetrics.getLineAtOffset(
textEditingValue.selection.extent,
......@@ -1263,6 +1385,8 @@ abstract class TextEditingActionTarget {
/// A TextSelection that isn't collapsed will be collapsed and moved from the
/// extentOffset.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// {@macro flutter.widgets.TextEditingActionTarget.whiteSpace}
......@@ -1273,6 +1397,9 @@ abstract class TextEditingActionTarget {
/// direction.
void moveSelectionRightByWord(SelectionChangedCause cause,
[bool includeWhitespace = true]) {
if (!textEditingValue.selection.isValid) {
return;
}
// When the text is obscured, the whole thing is treated as one big word.
if (obscureText) {
return moveSelectionToEnd(cause);
......@@ -1328,12 +1455,17 @@ abstract class TextEditingActionTarget {
/// Move the current selection up by one line.
///
/// If the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
///
/// See also:
///
/// * [moveSelectionDown], which is the same but in the opposite direction.
void moveSelectionUp(SelectionChangedCause cause) {
if (!textEditingValue.selection.isValid) {
return;
}
final int nextIndex =
textLayoutMetrics.getTextPositionAbove(textEditingValue.selection.extent).offset;
......@@ -1363,12 +1495,14 @@ abstract class TextEditingActionTarget {
/// Copy current selection to [Clipboard].
/// {@endtemplate}
///
/// If the selection is collapsed or invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
void copySelection(SelectionChangedCause cause) {
final TextSelection selection = textEditingValue.selection;
final String text = textEditingValue.text;
assert(selection != null);
if (selection.isCollapsed) {
if (selection.isCollapsed || !selection.isValid) {
return;
}
Clipboard.setData(ClipboardData(text: selection.textInside(text)));
......@@ -1378,12 +1512,14 @@ abstract class TextEditingActionTarget {
/// Cut current selection to Clipboard.
/// {@endtemplate}
///
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
void cutSelection(SelectionChangedCause cause) {
if (readOnly) {
final TextSelection selection = textEditingValue.selection;
if (readOnly || !selection.isValid) {
return;
}
final TextSelection selection = textEditingValue.selection;
final String text = textEditingValue.text;
assert(selection != null);
if (selection.isCollapsed) {
......@@ -1408,12 +1544,14 @@ abstract class TextEditingActionTarget {
///
/// If there is currently a selection, it will be replaced.
///
/// If [readOnly] is true or the selection is invalid, does nothing.
///
/// {@macro flutter.widgets.TextEditingActionTarget.cause}
Future<void> pasteText(SelectionChangedCause cause) async {
if (readOnly) {
final TextSelection selection = textEditingValue.selection;
if (readOnly || !selection.isValid) {
return;
}
final TextSelection selection = textEditingValue.selection;
final String text = textEditingValue.text;
assert(selection != null);
if (!selection.isValid) {
......
......@@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/rendering_tester.dart';
import 'clipboard_utils.dart';
class _FakeEditableTextState with TextSelectionDelegate, TextEditingActionTarget {
_FakeEditableTextState({
......@@ -93,9 +94,24 @@ class _FakeEditableTextState with TextSelectionDelegate, TextEditingActionTarget
}
void main() {
final MockClipboard mockClipboard = MockClipboard();
// Ensure that all TestRenderingFlutterBinding bindings are initialized.
renderer;
setUp(() async {
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(
SystemChannels.platform,
mockClipboard.handleMethodCall,
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(
SystemChannels.platform,
null,
);
});
test('moveSelectionLeft/RightByLine stays on the current line', () async {
const String text = 'one two three\n\nfour five six';
final _FakeEditableTextState editableTextState = _FakeEditableTextState(
......@@ -1712,6 +1728,134 @@ void main() {
});
});
test("When a selection is needed but it's invalid, nothing is changed", () async {
const String text = 'one two three\n\nfour five six';
final _FakeEditableTextState editableTextState = _FakeEditableTextState(
textSpan: const TextSpan(
text: text,
style: TextStyle(
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
),
),
textEditingValue: const TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: -1),
),
);
final RenderEditable editable = editableTextState.renderEditable;
layout(editable);
editable.hasFocus = true;
editableTextState.delete(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
expect(editableTextState.textEditingValue.text, text);
editableTextState.deleteByWord(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
expect(editableTextState.textEditingValue.text, text);
editableTextState.deleteByLine(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
expect(editableTextState.textEditingValue.text, text);
editableTextState.deleteForward(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
expect(editableTextState.textEditingValue.text, text);
editableTextState.deleteForwardByWord(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
expect(editableTextState.textEditingValue.text, text);
editableTextState.deleteForwardByLine(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
expect(editableTextState.textEditingValue.text, text);
editableTextState.deleteToEnd(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
expect(editableTextState.textEditingValue.text, text);
editableTextState.deleteToStart(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
expect(editableTextState.textEditingValue.text, text);
editableTextState.expandSelectionToEnd(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.expandSelectionToStart(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.expandSelectionLeftByLine(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.expandSelectionRightByLine(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.extendSelectionDown(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.extendSelectionLeft(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.extendSelectionLeft(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.extendSelectionLeftByLine(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.extendSelectionRight(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.extendSelectionRightByLine(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.extendSelectionLeftByWord(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.extendSelectionRightByWord(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.extendSelectionUp(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.moveSelectionLeftByLine(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.moveSelectionDown(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.moveSelectionLeft(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.moveSelectionLeftByWord(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.moveSelectionRight(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.moveSelectionRightByLine(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.moveSelectionRightByWord(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.moveSelectionUp(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
editableTextState.copySelection(SelectionChangedCause.keyboard);
ClipboardData? clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
expect(clipboardData?.text, null);
editableTextState.cutSelection(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
expect(clipboardData?.text, null);
editableTextState.pasteText(SelectionChangedCause.keyboard);
expect(editableTextState.textEditingValue.selection.isValid, false);
expect(editableTextState.textEditingValue.text, text);
});
group('nextCharacter', () {
test('handles normal strings correctly', () {
expect(TextEditingActionTarget.nextCharacter(0, '01234567'), 1);
......
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