Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
0ba2f6a8
Unverified
Commit
0ba2f6a8
authored
Apr 07, 2021
by
Ricardo Canastro
Committed by
GitHub
Apr 07, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support block delete with word and line modifiers (#79695)
Support for keyboard backspace/delete shortcuts
parent
e5f75dbe
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
1828 additions
and
143 deletions
+1828
-143
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+374
-54
default_text_editing_actions.dart
...flutter/lib/src/widgets/default_text_editing_actions.dart
+48
-0
default_text_editing_shortcuts.dart
...utter/lib/src/widgets/default_text_editing_shortcuts.dart
+56
-0
text_editing_intents.dart
packages/flutter/lib/src/widgets/text_editing_intents.dart
+48
-0
editable_test.dart
packages/flutter/test/rendering/editable_test.dart
+1302
-89
No files found.
packages/flutter/lib/src/rendering/editable.dart
View file @
0ba2f6a8
...
...
@@ -670,10 +670,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
// _handleShortcuts depends on being started in the same stack invocation
// as the _handleKeyEvent method
_handleShortcuts
(
key
);
}
else
if
(
key
==
LogicalKeyboardKey
.
delete
)
{
_handleDelete
(
forward:
true
);
}
else
if
(
key
==
LogicalKeyboardKey
.
backspace
)
{
_handleDelete
(
forward:
false
);
}
}
...
...
@@ -1015,11 +1011,90 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
return
_getTextPositionVertical
(
offset
,
verticalOffset
);
}
/// Keeping [selection]'s [TextSelection.baseOffset] fixed, move the
/// [TextSelection.extentOffset] down by one line.
// Deletes the current uncollapsed selection.
void
_deleteSelection
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
assert
(
selection
.
isCollapsed
==
false
);
if
(
_readOnly
||
!
selection
.
isValid
||
selection
.
isCollapsed
)
{
return
;
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
final
String
textBefore
=
selection
.
textBefore
(
text
);
final
String
textAfter
=
selection
.
textAfter
(
text
);
final
int
cursorPosition
=
math
.
min
(
selection
.
start
,
selection
.
end
);
final
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
cursorPosition
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
newSelection
),
cause
,
);
}
// Deletes the from the current collapsed selection to the start of the field.
//
// The given SelectionChangedCause indicates the cause of this change and
// will be passed to onSelectionChanged.
//
// See also:
// * _deleteToEnd
void
_deleteToStart
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
assert
(
selection
.
isCollapsed
);
if
(
_readOnly
||
!
selection
.
isValid
)
{
return
;
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
final
String
textBefore
=
selection
.
textBefore
(
text
);
if
(
textBefore
.
isEmpty
)
{
return
;
}
final
String
textAfter
=
selection
.
textAfter
(
text
);
const
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
0
);
_setTextEditingValue
(
TextEditingValue
(
text:
textAfter
,
selection:
newSelection
),
cause
,
);
}
// Deletes the from the current collapsed selection to the end of the field.
//
// The given SelectionChangedCause indicates the cause of this change and
// will be passed to onSelectionChanged.
//
// See also:
// * _deleteToStart
void
_deleteToEnd
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
assert
(
selection
.
isCollapsed
);
if
(
_readOnly
||
!
selection
.
isValid
)
{
return
;
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
final
String
textAfter
=
selection
.
textAfter
(
text
);
if
(
textAfter
.
isEmpty
)
{
return
;
}
final
String
textBefore
=
selection
.
textBefore
(
text
);
final
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
textBefore
.
length
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
,
selection:
newSelection
),
cause
,
);
}
/// Deletes backwards from the current selection.
///
/// If [selectionEnabled] is false, keeps the selection collapsed and just
/// moves it down.
/// If the [selection] is collapsed, deletes a single character before the
/// cursor.
///
/// If the [selection] is not collapsed, deletes the selection.
///
/// {@template flutter.rendering.RenderEditable.cause}
/// The given [SelectionChangedCause] indicates the cause of this change and
...
...
@@ -1028,6 +1103,292 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
///
/// See also:
///
/// * [deleteForward], which is same but in the opposite direction.
void
delete
(
SelectionChangedCause
cause
)
{
assert
(
_selection
!=
null
);
if
(
_readOnly
||
!
_selection
!.
isValid
)
{
return
;
}
if
(!
_selection
!.
isCollapsed
)
{
return
_deleteSelection
(
_selection
!,
cause
);
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
String
textBefore
=
_selection
!.
textBefore
(
text
);
if
(
textBefore
.
isEmpty
)
{
return
;
}
final
int
characterBoundary
=
previousCharacter
(
textBefore
.
length
,
textBefore
);
textBefore
=
textBefore
.
substring
(
0
,
characterBoundary
);
final
String
textAfter
=
_selection
!.
textAfter
(
text
);
final
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
characterBoundary
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
newSelection
),
cause
,
);
}
/// Deletes a word backwards from the current selection.
///
/// If the [selection] is collapsed, deletes a word before the cursor.
///
/// If the [selection] is not collapsed, deletes the selection.
///
/// If [obscureText] is true, it treats the whole text content as
/// a single word.
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// {@template flutter.rendering.RenderEditable.whiteSpace}
/// By default, includeWhitespace is set to true, meaning that whitespace can
/// be considered a word in itself. If set to false, the selection will be
/// extended past any whitespace and the first word following the whitespace.
/// {@endtemplate}
///
/// See also:
///
/// * [deleteForwardByWord], which is same but in the opposite direction.
void
deleteByWord
(
SelectionChangedCause
cause
,
[
bool
includeWhitespace
=
true
])
{
assert
(
_selection
!=
null
);
if
(
_readOnly
||
!
_selection
!.
isValid
)
{
return
;
}
if
(!
_selection
!.
isCollapsed
)
{
return
_deleteSelection
(
_selection
!,
cause
);
}
// When the text is obscured, the whole thing is treated as one big line.
if
(
obscureText
)
{
return
_deleteToStart
(
_selection
!,
cause
);
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
String
textBefore
=
_selection
!.
textBefore
(
text
);
if
(
textBefore
.
isEmpty
)
{
return
;
}
final
int
characterBoundary
=
_getLeftByWord
(
_textPainter
,
textBefore
.
length
,
includeWhitespace
);
textBefore
=
textBefore
.
trimRight
().
substring
(
0
,
characterBoundary
);
final
String
textAfter
=
_selection
!.
textAfter
(
text
);
final
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
characterBoundary
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
newSelection
),
cause
,
);
}
/// Deletes a line backwards from the current selection.
///
/// If the [selection] is collapsed, deletes a line before the cursor.
///
/// If the [selection] is not collapsed, deletes the selection.
///
/// If [obscureText] is true, it treats the whole text content as
/// a single word.
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// See also:
///
/// * [deleteForwardByLine], which is same but in the opposite direction.
void
deleteByLine
(
SelectionChangedCause
cause
)
{
assert
(
_selection
!=
null
);
if
(
_readOnly
||
!
_selection
!.
isValid
)
{
return
;
}
if
(!
_selection
!.
isCollapsed
)
{
return
_deleteSelection
(
_selection
!,
cause
);
}
// When the text is obscured, the whole thing is treated as one big line.
if
(
obscureText
)
{
return
_deleteToStart
(
_selection
!,
cause
);
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
String
textBefore
=
_selection
!.
textBefore
(
text
);
if
(
textBefore
.
isEmpty
)
{
return
;
}
// When there is a line break, line delete shouldn't do anything
final
bool
isPreviousCharacterBreakLine
=
textBefore
.
codeUnitAt
(
textBefore
.
length
-
1
)
==
0x0A
;
if
(
isPreviousCharacterBreakLine
)
{
return
;
}
final
TextSelection
line
=
_getLineAtOffset
(
TextPosition
(
offset:
textBefore
.
length
-
1
));
textBefore
=
textBefore
.
substring
(
0
,
line
.
start
);
final
String
textAfter
=
_selection
!.
textAfter
(
text
);
final
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
textBefore
.
length
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
newSelection
),
cause
,
);
}
/// Deletes in the foward direction from the current selection.
///
/// If the [selection] is collapsed, deletes a single character after the
/// cursor.
///
/// If the [selection] is not collapsed, deletes the selection.
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// See also:
///
/// * [delete], which is same but in the opposite direction.
void
deleteForward
(
SelectionChangedCause
cause
)
{
assert
(
_selection
!=
null
);
if
(
_readOnly
||
!
_selection
!.
isValid
)
{
return
;
}
if
(!
_selection
!.
isCollapsed
)
{
return
_deleteSelection
(
_selection
!,
cause
);
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
final
String
textBefore
=
_selection
!.
textBefore
(
text
);
String
textAfter
=
_selection
!.
textAfter
(
text
);
if
(
textAfter
.
isEmpty
)
{
return
;
}
final
int
deleteCount
=
nextCharacter
(
0
,
textAfter
);
textAfter
=
textAfter
.
substring
(
deleteCount
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
_selection
!),
cause
,
);
}
/// Deletes a word in the foward direction from the current selection.
///
/// If the [selection] is collapsed, deletes a word after the cursor.
///
/// If the [selection] is not collapsed, deletes the selection.
///
/// If [obscureText] is true, it treats the whole text content as
/// a single word.
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// {@macro flutter.rendering.RenderEditable.whiteSpace}
///
/// See also:
///
/// * [deleteByWord], which is same but in the opposite direction.
void
deleteForwardByWord
(
SelectionChangedCause
cause
,
[
bool
includeWhitespace
=
true
])
{
assert
(
_selection
!=
null
);
if
(
_readOnly
||
!
_selection
!.
isValid
)
{
return
;
}
if
(!
_selection
!.
isCollapsed
)
{
return
_deleteSelection
(
_selection
!,
cause
);
}
// When the text is obscured, the whole thing is treated as one big word.
if
(
obscureText
)
{
return
_deleteToEnd
(
_selection
!,
cause
);
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
String
textAfter
=
_selection
!.
textAfter
(
text
);
if
(
textAfter
.
isEmpty
)
{
return
;
}
final
String
textBefore
=
_selection
!.
textBefore
(
text
);
final
int
characterBoundary
=
_getRightByWord
(
_textPainter
,
textBefore
.
length
,
includeWhitespace
);
textAfter
=
textAfter
.
substring
(
characterBoundary
-
textBefore
.
length
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
_selection
!),
cause
,
);
}
/// Deletes a line in the foward direction from the current selection.
///
/// If the [selection] is collapsed, deletes a line after the cursor.
///
/// If the [selection] is not collapsed, deletes the selection.
///
/// If [obscureText] is true, it treats the whole text content as
/// a single word.
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// See also:
///
/// * [deleteByLine], which is same but in the opposite direction.
void
deleteForwardByLine
(
SelectionChangedCause
cause
)
{
assert
(
_selection
!=
null
);
if
(
_readOnly
||
!
_selection
!.
isValid
)
{
return
;
}
if
(!
_selection
!.
isCollapsed
)
{
return
_deleteSelection
(
_selection
!,
cause
);
}
// When the text is obscured, the whole thing is treated as one big line.
if
(
obscureText
)
{
return
_deleteToEnd
(
_selection
!,
cause
);
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
String
textAfter
=
_selection
!.
textAfter
(
text
);
if
(
textAfter
.
isEmpty
)
{
return
;
}
// When there is a line break, it shouldn't do anything.
final
bool
isNextCharacterBreakLine
=
textAfter
.
codeUnitAt
(
0
)
==
0x0A
;
if
(
isNextCharacterBreakLine
)
{
return
;
}
final
String
textBefore
=
_selection
!.
textBefore
(
text
);
final
TextSelection
line
=
_getLineAtOffset
(
TextPosition
(
offset:
textBefore
.
length
));
textAfter
=
textAfter
.
substring
(
line
.
end
-
textBefore
.
length
,
textAfter
.
length
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
_selection
!),
cause
,
);
}
/// Keeping [selection]'s [TextSelection.baseOffset] fixed, move the
/// [TextSelection.extentOffset] down by one line.
///
/// If [selectionEnabled] is false, keeps the selection collapsed and just
/// moves it down.
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// See also:
///
/// * [extendSelectionUp], which is same but in the opposite direction.
void
extendSelectionDown
(
SelectionChangedCause
cause
)
{
assert
(
selection
!=
null
);
...
...
@@ -1375,10 +1736,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// By default, `includeWhitespace` is set to true, meaning that whitespace
/// can be considered a word in itself. If set to false, the selection will
/// be extended past any whitespace and the first word following the
/// whitespace.
/// {@macro flutter.rendering.RenderEditable.whiteSpace}
///
/// {@template flutter.rendering.RenderEditable.stopAtReversal}
/// The `stopAtReversal` parameter is false by default, meaning that it's
...
...
@@ -1420,13 +1778,11 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// By default, `includeWhitespace` is set to true, meaning that whitespace
/// can be considered a word in itself. If set to false, the selection will
/// be extended past any whitespace and the first word following the
/// whitespace.
/// {@macro flutter.rendering.RenderEditable.whiteSpace}
///
/// {@macro flutter.rendering.RenderEditable.stopAtReversal}
///
///
/// See also:
///
/// * [extendSelectionLeftByWord], which is the same but in the opposite
...
...
@@ -1585,9 +1941,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// By default, includeWhitespace is set to true, meaning that whitespace can
/// be considered a word in itself. If set to false, the selection will be
/// moved past any whitespace and the first word following the whitespace.
/// {@macro flutter.rendering.RenderEditable.whiteSpace}
///
/// See also:
///
...
...
@@ -1673,9 +2027,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
/// By default, includeWhitespace is set to true, meaning that whitespace can
/// be considered a word in itself. If set to false, the selection will be
/// moved past any whitespace and the first word following the whitespace.
/// {@macro flutter.rendering.RenderEditable.whiteSpace}
///
/// See also:
///
...
...
@@ -1825,38 +2177,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
}
}
void
_handleDelete
({
required
bool
forward
})
{
final
TextSelection
selection
=
textSelectionDelegate
.
textEditingValue
.
selection
;
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
assert
(
_selection
!=
null
);
if
(
_readOnly
||
!
selection
.
isValid
)
{
return
;
}
String
textBefore
=
selection
.
textBefore
(
text
);
String
textAfter
=
selection
.
textAfter
(
text
);
int
cursorPosition
=
math
.
min
(
selection
.
start
,
selection
.
end
);
// If not deleting a selection, delete the next/previous character.
if
(
selection
.
isCollapsed
)
{
if
(!
forward
&&
textBefore
.
isNotEmpty
)
{
final
int
characterBoundary
=
previousCharacter
(
textBefore
.
length
,
textBefore
);
textBefore
=
textBefore
.
substring
(
0
,
characterBoundary
);
cursorPosition
=
characterBoundary
;
}
if
(
forward
&&
textAfter
.
isNotEmpty
)
{
final
int
deleteCount
=
nextCharacter
(
0
,
textAfter
);
textAfter
=
textAfter
.
substring
(
deleteCount
);
}
}
final
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
cursorPosition
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
newSelection
,
),
SelectionChangedCause
.
keyboard
,
);
}
@override
void
markNeedsPaint
()
{
super
.
markNeedsPaint
();
...
...
packages/flutter/lib/src/widgets/default_text_editing_actions.dart
View file @
0ba2f6a8
...
...
@@ -36,6 +36,12 @@ class DefaultTextEditingActions extends Actions{
// are called on which platform.
static
final
Map
<
Type
,
Action
<
Intent
>>
_shortcutsActions
=
<
Type
,
Action
<
Intent
>>{
DoNothingAndStopPropagationTextIntent:
_DoNothingAndStopPropagationTextAction
(),
DeleteTextIntent:
_DeleteTextAction
(),
DeleteByWordTextIntent:
_DeleteByWordTextAction
(),
DeleteByLineTextIntent:
_DeleteByLineTextAction
(),
DeleteForwardTextIntent:
_DeleteForwardTextAction
(),
DeleteForwardByWordTextIntent:
_DeleteForwardByWordTextAction
(),
DeleteForwardByLineTextIntent:
_DeleteForwardByLineTextAction
(),
ExtendSelectionDownTextIntent:
_ExtendSelectionDownTextAction
(),
ExtendSelectionLeftByLineTextIntent:
_ExtendSelectionLeftByLineTextAction
(),
ExtendSelectionLeftByWordTextIntent:
_ExtendSelectionLeftByWordTextAction
(),
...
...
@@ -76,6 +82,48 @@ class _DoNothingAndStopPropagationTextAction extends TextEditingAction<DoNothing
void
invoke
(
DoNothingAndStopPropagationTextIntent
intent
,
[
BuildContext
?
context
])
{}
}
class
_DeleteTextAction
extends
TextEditingAction
<
DeleteTextIntent
>
{
@override
Object
?
invoke
(
DeleteTextIntent
intent
,
[
BuildContext
?
context
])
{
textEditingActionTarget
!.
renderEditable
.
delete
(
SelectionChangedCause
.
keyboard
);
}
}
class
_DeleteByWordTextAction
extends
TextEditingAction
<
DeleteByWordTextIntent
>
{
@override
Object
?
invoke
(
DeleteByWordTextIntent
intent
,
[
BuildContext
?
context
])
{
textEditingActionTarget
!.
renderEditable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
,
false
);
}
}
class
_DeleteByLineTextAction
extends
TextEditingAction
<
DeleteByLineTextIntent
>
{
@override
Object
?
invoke
(
DeleteByLineTextIntent
intent
,
[
BuildContext
?
context
])
{
textEditingActionTarget
!.
renderEditable
.
deleteByLine
(
SelectionChangedCause
.
keyboard
);
}
}
class
_DeleteForwardTextAction
extends
TextEditingAction
<
DeleteForwardTextIntent
>
{
@override
Object
?
invoke
(
DeleteForwardTextIntent
intent
,
[
BuildContext
?
context
])
{
textEditingActionTarget
!.
renderEditable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
}
}
class
_DeleteForwardByWordTextAction
extends
TextEditingAction
<
DeleteForwardByWordTextIntent
>
{
@override
Object
?
invoke
(
DeleteForwardByWordTextIntent
intent
,
[
BuildContext
?
context
])
{
textEditingActionTarget
!.
renderEditable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
,
false
);
}
}
class
_DeleteForwardByLineTextAction
extends
TextEditingAction
<
DeleteForwardByLineTextIntent
>
{
@override
Object
?
invoke
(
DeleteForwardByLineTextIntent
intent
,
[
BuildContext
?
context
])
{
textEditingActionTarget
!.
renderEditable
.
deleteForwardByLine
(
SelectionChangedCause
.
keyboard
);
}
}
class
_ExpandSelectionLeftByLineTextAction
extends
TextEditingAction
<
ExpandSelectionLeftByLineTextIntent
>
{
@override
Object
?
invoke
(
ExpandSelectionLeftByLineTextIntent
intent
,
[
BuildContext
?
context
])
{
...
...
packages/flutter/lib/src/widgets/default_text_editing_shortcuts.dart
View file @
0ba2f6a8
...
...
@@ -161,6 +161,12 @@ class DefaultTextEditingShortcuts extends Shortcuts {
);
static
final
Map
<
LogicalKeySet
,
Intent
>
_androidShortcuts
=
<
LogicalKeySet
,
Intent
>{
LogicalKeySet
(
LogicalKeyboardKey
.
backspace
):
const
DeleteTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
delete
):
const
DeleteForwardTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowDown
):
const
MoveSelectionToEndTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowLeft
):
const
MoveSelectionLeftByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowRight
):
const
MoveSelectionRightByLineTextIntent
(),
...
...
@@ -195,9 +201,17 @@ class DefaultTextEditingShortcuts extends Shortcuts {
// * Meta + shift + arrow up
// * Shift + end
// * Shift + home
// * Meta + delete
// * Meta + backspace
};
static
final
Map
<
LogicalKeySet
,
Intent
>
_fuchsiaShortcuts
=
<
LogicalKeySet
,
Intent
>{
LogicalKeySet
(
LogicalKeyboardKey
.
backspace
):
const
DeleteTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
delete
):
const
DeleteForwardTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowDown
):
const
MoveSelectionToEndTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowLeft
):
const
MoveSelectionLeftByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowRight
):
const
MoveSelectionRightByLineTextIntent
(),
...
...
@@ -232,9 +246,17 @@ class DefaultTextEditingShortcuts extends Shortcuts {
// * Meta + shift + arrow up
// * Shift + end
// * Shift + home
// * Meta + delete
// * Meta + backspace
};
static
final
Map
<
LogicalKeySet
,
Intent
>
_iOSShortcuts
=
<
LogicalKeySet
,
Intent
>{
LogicalKeySet
(
LogicalKeyboardKey
.
backspace
):
const
DeleteTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
delete
):
const
DeleteForwardTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowDown
):
const
MoveSelectionToEndTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowLeft
):
const
MoveSelectionLeftByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowRight
):
const
MoveSelectionRightByLineTextIntent
(),
...
...
@@ -269,9 +291,17 @@ class DefaultTextEditingShortcuts extends Shortcuts {
// * Meta + shift + arrow up
// * Shift + end
// * Shift + home
// * Meta + delete
// * Meta + backspace
};
static
final
Map
<
LogicalKeySet
,
Intent
>
_linuxShortcuts
=
<
LogicalKeySet
,
Intent
>{
LogicalKeySet
(
LogicalKeyboardKey
.
backspace
):
const
DeleteTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
delete
):
const
DeleteForwardTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowDown
):
const
MoveSelectionToEndTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowLeft
):
const
MoveSelectionLeftByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowRight
):
const
MoveSelectionRightByLineTextIntent
(),
...
...
@@ -306,9 +336,17 @@ class DefaultTextEditingShortcuts extends Shortcuts {
// * Meta + shift + arrow up
// * Shift + end
// * Shift + home
// * Meta + delete
// * Meta + backspace
};
static
final
Map
<
LogicalKeySet
,
Intent
>
_macShortcuts
=
<
LogicalKeySet
,
Intent
>{
LogicalKeySet
(
LogicalKeyboardKey
.
backspace
):
const
DeleteTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
meta
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
delete
):
const
DeleteForwardTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
meta
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowDown
):
const
MoveSelectionRightByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowLeft
):
const
MoveSelectionLeftByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowRight
):
const
MoveSelectionRightByWordTextIntent
(),
...
...
@@ -343,9 +381,17 @@ class DefaultTextEditingShortcuts extends Shortcuts {
// * Home
// * Shift + end
// * Shift + home
// * Control + delete
// * Control + backspace
};
static
final
Map
<
LogicalKeySet
,
Intent
>
_windowsShortcuts
=
<
LogicalKeySet
,
Intent
>{
LogicalKeySet
(
LogicalKeyboardKey
.
backspace
):
const
DeleteTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
backspace
):
const
DeleteByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
delete
):
const
DeleteForwardTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByWordTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
delete
):
const
DeleteForwardByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowDown
):
const
MoveSelectionToEndTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowLeft
):
const
MoveSelectionLeftByLineTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowRight
):
const
MoveSelectionRightByLineTextIntent
(),
...
...
@@ -380,11 +426,21 @@ class DefaultTextEditingShortcuts extends Shortcuts {
// * Meta + shift + arrow left
// * Meta + shift + arrow right
// * Meta + shift + arrow up
// * Meta + delete
// * Meta + backspace
};
// Web handles its text selection natively and doesn't use any of these
// shortcuts in Flutter.
static
final
Map
<
LogicalKeySet
,
Intent
>
_webShortcuts
=
<
LogicalKeySet
,
Intent
>{
LogicalKeySet
(
LogicalKeyboardKey
.
backspace
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
delete
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
backspace
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
delete
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
backspace
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
control
,
LogicalKeyboardKey
.
delete
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
meta
,
LogicalKeyboardKey
.
backspace
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
meta
,
LogicalKeyboardKey
.
delete
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowDown
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowLeft
):
const
DoNothingAndStopPropagationTextIntent
(),
LogicalKeySet
(
LogicalKeyboardKey
.
alt
,
LogicalKeyboardKey
.
arrowRight
):
const
DoNothingAndStopPropagationTextIntent
(),
...
...
packages/flutter/lib/src/widgets/text_editing_intents.dart
View file @
0ba2f6a8
...
...
@@ -4,6 +4,54 @@
import
'actions.dart'
;
/// An [Intent] to delete a character in the backwards direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class
DeleteTextIntent
extends
Intent
{
/// Creates an instance of DeleteTextIntent.
const
DeleteTextIntent
();
}
/// An [Intent] to delete a word in the backwards direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class
DeleteByWordTextIntent
extends
Intent
{
/// Creates an instance of DeleteByWordTextIntent.
const
DeleteByWordTextIntent
();
}
/// An [Intent] to delete a line in the backwards direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class
DeleteByLineTextIntent
extends
Intent
{
/// Creates an instance of DeleteByLineTextIntent.
const
DeleteByLineTextIntent
();
}
/// An [Intent] to delete in the forward direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class
DeleteForwardTextIntent
extends
Intent
{
/// Creates an instance of DeleteForwardTextIntent.
const
DeleteForwardTextIntent
();
}
/// An [Intent] to delete a word in the forward direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class
DeleteForwardByWordTextIntent
extends
Intent
{
/// Creates an instance of DeleteByWordTextIntent.
const
DeleteForwardByWordTextIntent
();
}
/// An [Intent] to delete a line in the forward direction.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class
DeleteForwardByLineTextIntent
extends
Intent
{
/// Creates an instance of DeleteByLineTextIntent.
const
DeleteForwardByLineTextIntent
();
}
/// An [Intent] to send the event straight to the engine, but only if a
/// TextEditingTarget is focused.
///
...
...
packages/flutter/test/rendering/editable_test.dart
View file @
0ba2f6a8
...
...
@@ -962,8 +962,7 @@ void main() {
expect
(
currentSelection
.
isCollapsed
,
true
);
expect
(
currentSelection
.
baseOffset
,
0
);
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'est'
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
...
...
@@ -1013,8 +1012,7 @@ void main() {
expect
(
currentSelection
.
isCollapsed
,
true
);
expect
(
currentSelection
.
baseOffset
,
4
);
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'01236789'
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
...
...
@@ -1064,8 +1062,7 @@ void main() {
expect
(
currentSelection
.
isCollapsed
,
true
);
expect
(
currentSelection
.
baseOffset
,
4
);
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'01232345'
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
...
...
@@ -1114,8 +1111,7 @@ void main() {
expect
(
currentSelection
.
isCollapsed
,
true
);
expect
(
currentSelection
.
baseOffset
,
0
);
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
''
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
...
...
@@ -1170,8 +1166,7 @@ void main() {
expect
(
editable
.
selection
?.
isCollapsed
,
true
);
expect
(
editable
.
selection
?.
baseOffset
,
3
);
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'W Sczebrzeszynie chrząszcz brzmi w trzcinie'
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
...
...
@@ -1401,7 +1396,7 @@ void main() {
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/58068
group
(
'delete'
,
()
{
test
(
'
handles
selection'
,
()
async
{
test
(
'
when as a non-collapsed selection, it should delete a
selection'
,
()
async
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
...
...
@@ -1431,18 +1426,17 @@ void main() {
editable
.
hasFocus
=
true
;
pumpFrame
();
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
editable
.
delete
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'tt'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
1
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
test
(
'
is a no-op at the end of the tex
t'
,
()
async
{
test
(
'
when as simple text, it should delete the character to the lef
t'
,
()
async
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
selection:
TextSelection
.
collapsed
(
offset:
3
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
...
...
@@ -1461,25 +1455,132 @@ void main() {
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
4
),
selection:
const
TextSelection
.
collapsed
(
offset:
3
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
expect
(
delegate
.
textEditingValue
.
text
,
'test'
);
editable
.
delete
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'tet'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
2
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
test
(
'when has surrogate pairs, it should delete the pair'
,
()
async
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'
\
u{1F44D}'
,
selection:
TextSelection
.
collapsed
(
offset:
2
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'
\
u{1F44D}'
,
// Thumbs up
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
2
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
delete
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
''
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
test
(
'when has grapheme clusters, it should delete the grapheme cluster'
,
()
async
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'0123👨👩👦2345'
,
selection:
TextSelection
.
collapsed
(
offset:
12
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'0123👨👩👦2345'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
12
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
delete
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'01232345'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
4
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
test
(
'handles obscured text'
,
()
async
{
test
(
'when is at the start of the text, it should be a no-op'
,
()
async
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
.
collapsed
(
offset:
0
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'test'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
0
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
delete
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'test'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
test
(
'when input has obscured text, it should delete the character to the left'
,
()
async
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
.
collapsed
(
offset:
0
),
selection:
TextSelection
.
collapsed
(
offset:
4
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
...
...
@@ -1500,28 +1601,104 @@ void main() {
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
0
),
selection:
const
TextSelection
.
collapsed
(
offset:
4
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
delete
,
platform:
'android'
);
editable
.
delete
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'tes'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
3
);
},
skip:
isBrowser
);
expect
(
delegate
.
textEditingValue
.
text
,
'est'
);
test
(
'when using cjk characters'
,
()
async
{
const
String
text
=
'用多個塊測試'
;
const
int
offset
=
4
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
delete
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'用多個測試'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
3
);
},
skip:
isBrowser
);
test
(
'when using rtl'
,
()
async
{
const
String
text
=
'برنامج أهلا بالعالم'
;
const
int
offset
=
text
.
length
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
rtl
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
delete
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'برنامج أهلا بالعال'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
text
.
length
-
1
);
},
skip:
isBrowser
);
});
group
(
'backspace'
,
()
{
test
(
'handles selection'
,
()
async
{
group
(
'deleteByWord'
,
()
{
test
(
'when cursor is on the middle of a word, it should delete the left part of the word'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
8
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
(
baseOffset:
1
,
extentOffset:
3
),
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
...
...
@@ -1535,30 +1712,31 @@ void main() {
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'test'
,
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
(
baseOffset:
1
,
extentOffset:
3
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
expect
(
delegate
.
textEditingValue
.
text
,
'tt'
);
editable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test h multiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
1
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
5
);
},
skip:
isBrowser
);
test
(
'handles simple text'
,
()
async
{
test
(
'when includeWhiteSpace is true, it should treat a whiteSpace as a single word'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
10
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
.
collapsed
(
offset:
3
),
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
...
...
@@ -1572,30 +1750,31 @@ void main() {
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'test'
,
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
3
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
expect
(
delegate
.
textEditingValue
.
text
,
'tet'
);
editable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'test withmultiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
2
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
9
);
},
skip:
isBrowser
);
test
(
'handles surrogate pairs'
,
()
async
{
test
(
'when cursor is after a word, it should delete the whole word'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
9
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'
\
u{1F44D}'
,
selection:
TextSelection
.
collapsed
(
offset:
2
),
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
...
...
@@ -1609,30 +1788,31 @@ void main() {
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'
\
u{1F44D}'
,
// Thumbs up
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
2
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
expect
(
delegate
.
textEditingValue
.
text
,
''
);
editable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test multiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
5
);
},
skip:
isBrowser
);
test
(
'handles grapheme clusters'
,
()
async
{
test
(
'when cursor is preceeded by white spaces, it should delete the spaces and the next word to the left'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
12
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'0123👨👩👦2345'
,
selection:
TextSelection
.
collapsed
(
offset:
12
),
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
...
...
@@ -1646,30 +1826,31 @@ void main() {
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'0123👨👩👦2345'
,
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
12
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
expect
(
delegate
.
textEditingValue
.
text
,
'01232345'
);
editable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test multiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
4
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
5
);
},
skip:
isBrowser
);
test
(
'is a no-op at the start of the text'
,
()
async
{
test
(
'when cursor is preceeded by tabs spaces'
,
()
async
{
const
String
text
=
'test with
\t\t\t
multiple blocks'
;
const
int
offset
=
12
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
.
collapsed
(
offset:
0
),
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
...
...
@@ -1683,32 +1864,32 @@ void main() {
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'test'
,
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
0
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
expect
(
delegate
.
textEditingValue
.
text
,
'test'
);
editable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test multiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
5
);
},
skip:
isBrowser
);
test
(
'handles obscured text'
,
()
async
{
test
(
'when cursor is preceeded by break line, it should delete the breaking line and the word right before it'
,
()
async
{
const
String
text
=
'test with
\n
multiple blocks'
;
const
int
offset
=
10
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
);
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
...
...
@@ -1717,29 +1898,1061 @@ void main() {
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
obscureText:
true
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'****'
,
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
4
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
await
simulateKeyDownEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
await
simulateKeyUpEvent
(
LogicalKeyboardKey
.
backspace
,
platform:
'android'
);
editable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test multiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
5
);
},
skip:
isBrowser
);
expect
(
delegate
.
textEditingValue
.
text
,
'tes'
);
test
(
'when using cjk characters'
,
()
async
{
const
String
text
=
'用多個塊測試'
;
const
int
offset
=
4
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'用多個測試'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
3
);
},
skip:
isBrowser
);
test
(
'when using rtl'
,
()
async
{
const
String
text
=
'برنامج أهلا بالعالم'
;
const
int
offset
=
text
.
length
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
rtl
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'برنامج أهلا '
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
3
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
12
);
},
skip:
isBrowser
);
test
(
'when input has obscured text, it should delete everything before the selection'
,
()
async
{
const
int
offset
=
21
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test with multiple
\n\n
words'
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
obscureText:
true
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'****'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'words'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
});
group
(
'deleteByLine'
,
()
{
test
(
'when cursor is on last character of a line, it should delete everything to the left'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
text
.
length
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
''
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
test
(
'when cursor is on the middle of a word, it should delete delete everything to the left'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
8
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'h multiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
test
(
'when previous character is a breakline, it should preserve it'
,
()
async
{
const
String
text
=
'test with
\n
multiple blocks'
;
const
int
offset
=
10
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
text
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when text is multiline, it should delete until the first line break it finds'
,
()
async
{
const
String
text
=
'test with
\n\n
More stuff right here.
\n
multiple blocks'
;
const
int
offset
=
22
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'test with
\n\n
right here.
\n
multiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
11
);
},
skip:
isBrowser
);
test
(
'when input has obscured text, it should delete everything before the selection'
,
()
async
{
const
int
offset
=
21
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test with multiple
\n\n
words'
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
obscureText:
true
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'****'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'words'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
});
group
(
'deleteForward'
,
()
{
test
(
'when as a non-collapsed selection, it should delete a selection'
,
()
async
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
(
baseOffset:
1
,
extentOffset:
3
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'test'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
(
baseOffset:
1
,
extentOffset:
3
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'tt'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
1
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
test
(
'when includeWhiteSpace is true, it should treat a whiteSpace as a single word'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
9
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'test withmultiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
9
);
},
skip:
isBrowser
);
test
(
'when at the end of a text, it should be a no-op'
,
()
async
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'test'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
4
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'test'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
4
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/61021
test
(
'when the input has obscured text, it should delete the forward character'
,
()
async
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test'
,
selection:
TextSelection
.
collapsed
(
offset:
0
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
obscureText:
true
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'****'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
0
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'est'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
test
(
'when using cjk characters'
,
()
async
{
const
String
text
=
'用多個塊測試'
;
const
int
offset
=
0
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'多個塊測試'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
test
(
'when using rtl'
,
()
async
{
const
String
text
=
'برنامج أهلا بالعالم'
;
const
int
offset
=
0
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
rtl
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForward
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'رنامج أهلا بالعالم'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
0
);
},
skip:
isBrowser
);
});
group
(
'deleteForwardByWord'
,
()
{
test
(
'when cursor is on the middle of a word, it should delete the next part of the word'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
6
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test w multiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when cursor is before a word, it should delete the whole word'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
10
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test with blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when cursor is preceeded by white spaces, it should delete the spaces and the next word'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
9
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test with blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when cursor is before tabs, it should delete the tabs and the next word'
,
()
async
{
const
String
text
=
'test with
\t\t\t
multiple blocks'
;
const
int
offset
=
9
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test with blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when cursor is followed by break line, it should delete the next word'
,
()
async
{
const
String
text
=
'test with
\n\n\n
multiple blocks'
;
const
int
offset
=
9
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test with blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when using cjk characters'
,
()
async
{
const
String
text
=
'用多個塊測試'
;
const
int
offset
=
0
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'多個塊測試'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when using rtl'
,
()
async
{
const
String
text
=
'برنامج أهلا بالعالم'
;
const
int
offset
=
0
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
rtl
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
' أهلا بالعالم'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when input has obscured text, it should delete everything after the selection'
,
()
async
{
const
int
offset
=
4
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test with multiple
\n\n
words'
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
obscureText:
true
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'****'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByWord
(
SelectionChangedCause
.
keyboard
,
false
);
expect
(
delegate
.
textEditingValue
.
text
,
'test'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
});
group
(
'deleteForwardByLine'
,
()
{
test
(
'when cursor is on first character of a line, it should delete everything that follows'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
4
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'test'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when cursor is on the middle of a word, it should delete delete everything that follows'
,
()
async
{
const
String
text
=
'test with multiple blocks'
;
const
int
offset
=
8
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'test wit'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when next character is a breakline, it should preserve it'
,
()
async
{
const
String
text
=
'test with
\n\n\n
multiple blocks'
;
const
int
offset
=
9
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
text
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when text is multiline, it should delete until the first line break it finds'
,
()
async
{
const
String
text
=
'test with
\n\n
More stuff right here.
\n
multiple blocks'
;
const
int
offset
=
2
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
text
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'te
\n\n
More stuff right here.
\n
multiple blocks'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
test
(
'when input has obscured text, it should delete everything after the selection'
,
()
async
{
const
int
offset
=
4
;
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
()
..
textEditingValue
=
const
TextEditingValue
(
text:
'test with multiple
\n\n
words'
,
selection:
TextSelection
.
collapsed
(
offset:
offset
),
);
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
textSelectionDelegate:
delegate
,
obscureText:
true
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{},
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
text:
const
TextSpan
(
text:
'****'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
selection:
const
TextSelection
.
collapsed
(
offset:
offset
),
);
layout
(
editable
);
editable
.
hasFocus
=
true
;
pumpFrame
();
editable
.
deleteForwardByLine
(
SelectionChangedCause
.
keyboard
);
expect
(
delegate
.
textEditingValue
.
text
,
'test'
);
expect
(
delegate
.
textEditingValue
.
selection
.
isCollapsed
,
true
);
expect
(
delegate
.
textEditingValue
.
selection
.
baseOffset
,
offset
);
},
skip:
isBrowser
);
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment