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
a46139a2
Unverified
Commit
a46139a2
authored
Feb 19, 2021
by
chunhtai
Committed by
GitHub
Feb 19, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fixes TextInputFormatter gets wrong old value of a selection (#75541)
parent
b7d48062
Changes
13
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
306 additions
and
199 deletions
+306
-199
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+1
-0
selectable_text.dart
packages/flutter/lib/src/material/selectable_text.dart
+6
-2
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+59
-92
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+52
-4
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+55
-59
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+41
-23
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+0
-2
text_field_focus_test.dart
packages/flutter/test/material/text_field_focus_test.dart
+2
-0
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+53
-2
editable_test.dart
packages/flutter/test/rendering/editable_test.dart
+3
-0
editable_text_cursor_test.dart
packages/flutter/test/widgets/editable_text_cursor_test.dart
+9
-6
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+24
-9
text_selection_test.dart
packages/flutter/test/widgets/text_selection_test.dart
+1
-0
No files found.
packages/flutter/lib/src/cupertino/text_field.dart
View file @
a46139a2
...
...
@@ -102,6 +102,7 @@ class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGe
@override
void
onSingleTapUp
(
TapUpDetails
details
)
{
editableText
.
hideToolbar
();
// Because TextSelectionGestureDetector listens to taps that happen on
// widgets in front of it, tapping the clear button will also trigger
// this handler. If the clear button widget recognizes the up event,
...
...
packages/flutter/lib/src/material/selectable_text.dart
View file @
a46139a2
...
...
@@ -494,6 +494,8 @@ class _SelectableTextState extends State<SelectableText> with AutomaticKeepAlive
});
}
TextSelection
?
_lastSeenTextSelection
;
void
_handleSelectionChanged
(
TextSelection
selection
,
SelectionChangedCause
?
cause
)
{
final
bool
willShowSelectionHandles
=
_shouldShowSelectionHandles
(
cause
);
if
(
willShowSelectionHandles
!=
_showSelectionHandles
)
{
...
...
@@ -501,10 +503,12 @@ class _SelectableTextState extends State<SelectableText> with AutomaticKeepAlive
_showSelectionHandles
=
willShowSelectionHandles
;
});
}
if
(
widget
.
onSelectionChanged
!=
null
)
{
// TODO(chunhtai): The selection may be the same. We should remove this
// check once this is fixed https://github.com/flutter/flutter/issues/76349.
if
(
widget
.
onSelectionChanged
!=
null
&&
_lastSeenTextSelection
!=
selection
)
{
widget
.
onSelectionChanged
!(
selection
,
cause
);
}
_lastSeenTextSelection
=
selection
;
switch
(
Theme
.
of
(
context
).
platform
)
{
case
TargetPlatform
.
iOS
:
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
a46139a2
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/services/text_input.dart
View file @
a46139a2
...
...
@@ -754,12 +754,60 @@ class TextEditingValue {
);
}
/// Indicates what triggered the change in selected text (including changes to
/// the cursor location).
enum
SelectionChangedCause
{
/// The user tapped on the text and that caused the selection (or the location
/// of the cursor) to change.
tap
,
/// The user tapped twice in quick succession on the text and that caused
/// the selection (or the location of the cursor) to change.
doubleTap
,
/// The user long-pressed the text and that caused the selection (or the
/// location of the cursor) to change.
longPress
,
/// The user force-pressed the text and that caused the selection (or the
/// location of the cursor) to change.
forcePress
,
/// The user used the keyboard to change the selection or the location of the
/// cursor.
///
/// Keyboard-triggered selection changes may be caused by the IME as well as
/// by accessibility tools (e.g. TalkBack on Android).
keyboard
,
/// The user used the selection toolbar to change the selection or the
/// location of the cursor.
///
/// An example is when the user taps on select all in the tool bar.
toolBar
,
/// The user used the mouse to change the selection by dragging over a piece
/// of text.
drag
,
}
/// An interface for manipulating the selection, to be used by the implementor
/// of the toolbar widget.
abstract
class
TextSelectionDelegate
{
/// Gets the current text input.
TextEditingValue
get
textEditingValue
;
/// Indicates that the user has requested the delegate to replace its current
/// text editing state with [value].
///
/// The new [value] is treated as user input and thus may subject to input
/// formatting.
@Deprecated
(
'Use the userUpdateTextEditingValue instead. '
'This feature was deprecated after v1.26.0-17.2.pre.'
)
set
textEditingValue
(
TextEditingValue
value
)
{}
/// Indicates that the user has requested the delegate to replace its current
/// text editing state with [value].
///
...
...
@@ -768,10 +816,10 @@ abstract class TextSelectionDelegate {
///
/// See also:
///
/// * [EditableTextState.
textEditingValue]: an implementation that applies
/// a
dditional pre-processing to the specified [value], before updating th
e
/// text editing state.
set
textEditingValue
(
TextEditingValue
valu
e
);
/// * [EditableTextState.
userUpdateTextEditingValue]: an implementation that
/// a
pplies additional pre-processing to the specified [value], befor
e
///
updating the
text editing state.
void
userUpdateTextEditingValue
(
TextEditingValue
value
,
SelectionChangedCause
caus
e
);
/// Hides the text selection toolbar.
void
hideToolbar
();
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
a46139a2
...
...
@@ -32,8 +32,7 @@ import 'text.dart';
import
'text_selection.dart'
;
import
'ticker_provider.dart'
;
export
'package:flutter/rendering.dart'
show
SelectionChangedCause
;
export
'package:flutter/services.dart'
show
TextEditingValue
,
TextSelection
,
TextInputType
,
SmartQuotesType
,
SmartDashesType
;
export
'package:flutter/services.dart'
show
SelectionChangedCause
,
TextEditingValue
,
TextSelection
,
TextInputType
,
SmartQuotesType
,
SmartDashesType
;
/// Signature for the callback that reports when the user changes the selection
/// (including the cursor location).
...
...
@@ -1480,7 +1479,7 @@ class EditableText extends StatefulWidget {
}
/// State for a [EditableText].
class
EditableTextState
extends
State
<
EditableText
>
with
AutomaticKeepAliveClientMixin
<
EditableText
>,
WidgetsBindingObserver
,
TickerProviderStateMixin
<
EditableText
>
implements
TextSelectionDelegate
,
TextInputClient
,
AutofillClient
{
class
EditableTextState
extends
State
<
EditableText
>
with
AutomaticKeepAliveClientMixin
<
EditableText
>,
WidgetsBindingObserver
,
TickerProviderStateMixin
<
EditableText
>
,
TextSelectionDelegate
implements
TextInputClient
,
AutofillClient
{
Timer
?
_cursorTimer
;
bool
_targetCursorVisibility
=
false
;
final
ValueNotifier
<
bool
>
_cursorVisibilityNotifier
=
ValueNotifier
<
bool
>(
true
);
...
...
@@ -1715,7 +1714,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
value
.
text
==
_value
.
text
&&
value
.
composing
==
_value
.
composing
)
{
// `selection` is the only change.
_handleSelectionChanged
(
value
.
selection
,
renderEditable
,
SelectionChangedCause
.
keyboard
);
_handleSelectionChanged
(
value
.
selection
,
SelectionChangedCause
.
keyboard
);
}
else
{
hideToolbar
();
_currentPromptRectRange
=
null
;
...
...
@@ -1728,7 +1727,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
_formatAndSetValue
(
value
);
_formatAndSetValue
(
value
,
SelectionChangedCause
.
keyboard
);
}
if
(
_hasInputConnection
)
{
...
...
@@ -1836,7 +1835,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
renderEditable
.
setFloatingCursor
(
FloatingCursorDragState
.
End
,
finalPosition
,
_lastTextPosition
!);
if
(
_lastTextPosition
!.
offset
!=
renderEditable
.
selection
!.
baseOffset
)
// The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same.
_handleSelectionChanged
(
TextSelection
.
collapsed
(
offset:
_lastTextPosition
!.
offset
),
renderEditable
,
SelectionChangedCause
.
forcePress
);
_handleSelectionChanged
(
TextSelection
.
collapsed
(
offset:
_lastTextPosition
!.
offset
),
SelectionChangedCause
.
forcePress
);
_startCaretRect
=
null
;
_lastTextPosition
=
null
;
_pointOffsetOrigin
=
null
;
...
...
@@ -2102,7 +2101,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
void
_handleSelectionChanged
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
?
cause
)
{
void
_handleSelectionChanged
(
TextSelection
selection
,
SelectionChangedCause
?
cause
)
{
// We return early if the selection is not valid. This can happen when the
// text of [EditableText] is updated at the same time as the selection is
// changed by a gesture event.
...
...
@@ -2114,11 +2113,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// This will show the keyboard for all selection changes on the
// EditableWidget, not just changes triggered by user gestures.
requestKeyboard
();
if
(
widget
.
selectionControls
==
null
)
{
_selectionOverlay
?.
hide
();
_selectionOverlay
=
null
;
if
(
widget
.
selectionControls
!
=
null
)
{
}
else
{
if
(
_selectionOverlay
=
=
null
)
{
_selectionOverlay
=
TextSelectionOverlay
(
clipboardStatus:
_clipboardStatus
,
context:
context
,
...
...
@@ -2127,14 +2126,21 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
toolbarLayerLink:
_toolbarLayerLink
,
startHandleLayerLink:
_startHandleLayerLink
,
endHandleLayerLink:
_endHandleLayerLink
,
renderObject:
renderObject
,
renderObject:
renderEditable
,
selectionControls:
widget
.
selectionControls
,
selectionDelegate:
this
,
dragStartBehavior:
widget
.
dragStartBehavior
,
onSelectionHandleTapped:
widget
.
onSelectionHandleTapped
,
);
}
else
{
_selectionOverlay
!.
update
(
_value
);
}
_selectionOverlay
!.
handlesVisible
=
widget
.
showSelectionHandles
;
_selectionOverlay
!.
showHandles
();
}
// TODO(chunhtai): we should make sure selection actually changed before
// we call the onSelectionChanged.
// https://github.com/flutter/flutter/issues/76349.
try
{
widget
.
onSelectionChanged
?.
call
(
selection
,
cause
);
}
catch
(
exception
,
stack
)
{
...
...
@@ -2145,7 +2151,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
context:
ErrorDescription
(
'while calling onSelectionChanged for
$cause
'
),
));
}
}
// To keep the cursor from blinking while it moves, restart the timer here.
if
(
_cursorTimer
!=
null
)
{
...
...
@@ -2239,7 +2244,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
late
final
_WhitespaceDirectionalityFormatter
_whitespaceFormatter
=
_WhitespaceDirectionalityFormatter
(
textDirection:
_textDirection
);
void
_formatAndSetValue
(
TextEditingValue
value
)
{
void
_formatAndSetValue
(
TextEditingValue
value
,
SelectionChangedCause
?
cause
,
{
bool
userInteraction
=
false
}
)
{
// Only apply input formatters if the text has changed (including uncommited
// text in the composing region), or when the user committed the composing
// text.
...
...
@@ -2271,6 +2276,16 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// sending multiple `TextInput.updateEditingValue` messages.
beginBatchEdit
();
_value
=
value
;
// Changes made by the keyboard can sometimes be "out of band" for listening
// components, so always send those events, even if we didn't think it
// changed. Also, the user long pressing should always send a selection change
// as well.
if
(
selectionChanged
||
(
userInteraction
&&
(
cause
==
SelectionChangedCause
.
longPress
||
cause
==
SelectionChangedCause
.
keyboard
)))
{
_handleSelectionChanged
(
value
.
selection
,
cause
);
}
if
(
textChanged
)
{
try
{
widget
.
onChanged
?.
call
(
value
.
text
);
...
...
@@ -2284,19 +2299,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
if
(
selectionChanged
)
{
try
{
widget
.
onSelectionChanged
?.
call
(
value
.
selection
,
null
);
}
catch
(
exception
,
stack
)
{
FlutterError
.
reportError
(
FlutterErrorDetails
(
exception:
exception
,
stack:
stack
,
library
:
'widgets'
,
context:
ErrorDescription
(
'while calling onSelectionChanged'
),
));
}
}
endBatchEdit
();
}
...
...
@@ -2407,7 +2409,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_showCaretOnScreen
();
if
(!
_value
.
selection
.
isValid
)
{
// Place cursor at the end if the selection is invalid when we receive focus.
_handleSelectionChanged
(
TextSelection
.
collapsed
(
offset:
_value
.
text
.
length
),
renderEditable
,
null
);
_handleSelectionChanged
(
TextSelection
.
collapsed
(
offset:
_value
.
text
.
length
),
null
);
}
}
else
{
WidgetsBinding
.
instance
!.
removeObserver
(
this
);
...
...
@@ -2469,9 +2471,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
double
get
_devicePixelRatio
=>
MediaQuery
.
of
(
context
).
devicePixelRatio
;
@override
set
textEditingValue
(
TextEditingValue
value
)
{
_selectionOverlay
?.
update
(
value
);
_formatAndSetValue
(
value
);
void
userUpdateTextEditingValue
(
TextEditingValue
value
,
SelectionChangedCause
?
cause
)
{
_formatAndSetValue
(
value
,
cause
,
userInteraction:
true
);
}
@override
...
...
@@ -2634,7 +2635,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
smartQuotesType:
widget
.
smartQuotesType
,
enableSuggestions:
widget
.
enableSuggestions
,
offset:
offset
,
onSelectionChanged:
_handleSelectionChanged
,
onCaretChanged:
_handleCaretChanged
,
rendererIgnoresPointer:
widget
.
rendererIgnoresPointer
,
cursorWidth:
widget
.
cursorWidth
,
...
...
@@ -2718,7 +2718,6 @@ class _Editable extends LeafRenderObjectWidget {
required
this
.
smartQuotesType
,
required
this
.
enableSuggestions
,
required
this
.
offset
,
this
.
onSelectionChanged
,
this
.
onCaretChanged
,
this
.
rendererIgnoresPointer
=
false
,
required
this
.
cursorWidth
,
...
...
@@ -2766,7 +2765,6 @@ class _Editable extends LeafRenderObjectWidget {
final
SmartQuotesType
smartQuotesType
;
final
bool
enableSuggestions
;
final
ViewportOffset
offset
;
final
SelectionChangedHandler
?
onSelectionChanged
;
final
CaretChangedHandler
?
onCaretChanged
;
final
bool
rendererIgnoresPointer
;
final
double
cursorWidth
;
...
...
@@ -2806,7 +2804,6 @@ class _Editable extends LeafRenderObjectWidget {
locale:
locale
??
Localizations
.
maybeLocaleOf
(
context
),
selection:
value
.
selection
,
offset:
offset
,
onSelectionChanged:
onSelectionChanged
,
onCaretChanged:
onCaretChanged
,
ignorePointer:
rendererIgnoresPointer
,
obscuringCharacter:
obscuringCharacter
,
...
...
@@ -2851,7 +2848,6 @@ class _Editable extends LeafRenderObjectWidget {
..
locale
=
locale
??
Localizations
.
maybeLocaleOf
(
context
)
..
selection
=
value
.
selection
..
offset
=
offset
..
onSelectionChanged
=
onSelectionChanged
..
onCaretChanged
=
onCaretChanged
..
ignorePointer
=
rendererIgnoresPointer
..
textHeightBehavior
=
textHeightBehavior
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
a46139a2
...
...
@@ -205,12 +205,15 @@ abstract class TextSelectionControls {
Clipboard
.
setData
(
ClipboardData
(
text:
value
.
selection
.
textInside
(
value
.
text
),
));
delegate
.
textEditingValue
=
TextEditingValue
(
delegate
.
userUpdateTextEditingValue
(
TextEditingValue
(
text:
value
.
selection
.
textBefore
(
value
.
text
)
+
value
.
selection
.
textAfter
(
value
.
text
),
selection:
TextSelection
.
collapsed
(
offset:
value
.
selection
.
start
)
),
SelectionChangedCause
.
toolBar
,
);
delegate
.
bringIntoView
(
delegate
.
textEditingValue
.
selection
.
extent
);
delegate
.
hideToolbar
();
...
...
@@ -228,9 +231,12 @@ abstract class TextSelectionControls {
text:
value
.
selection
.
textInside
(
value
.
text
),
));
clipboardStatus
?.
update
();
delegate
.
textEditingValue
=
TextEditingValue
(
delegate
.
userUpdateTextEditingValue
(
TextEditingValue
(
text:
value
.
text
,
selection:
TextSelection
.
collapsed
(
offset:
value
.
selection
.
end
),
),
SelectionChangedCause
.
toolBar
,
);
delegate
.
bringIntoView
(
delegate
.
textEditingValue
.
selection
.
extent
);
delegate
.
hideToolbar
();
...
...
@@ -251,13 +257,16 @@ abstract class TextSelectionControls {
final
TextEditingValue
value
=
delegate
.
textEditingValue
;
// Snapshot the input before using `await`.
final
ClipboardData
?
data
=
await
Clipboard
.
getData
(
Clipboard
.
kTextPlain
);
if
(
data
!=
null
)
{
delegate
.
textEditingValue
=
TextEditingValue
(
delegate
.
userUpdateTextEditingValue
(
TextEditingValue
(
text:
value
.
selection
.
textBefore
(
value
.
text
)
+
data
.
text
!
+
value
.
selection
.
textAfter
(
value
.
text
),
selection:
TextSelection
.
collapsed
(
offset:
value
.
selection
.
start
+
data
.
text
!.
length
),
),
SelectionChangedCause
.
toolBar
,
);
}
delegate
.
bringIntoView
(
delegate
.
textEditingValue
.
selection
.
extent
);
...
...
@@ -272,12 +281,15 @@ abstract class TextSelectionControls {
/// This is called by subclasses when their select-all affordance is activated
/// by the user.
void
handleSelectAll
(
TextSelectionDelegate
delegate
)
{
delegate
.
textEditingValue
=
TextEditingValue
(
delegate
.
userUpdateTextEditingValue
(
TextEditingValue
(
text:
delegate
.
textEditingValue
.
text
,
selection:
TextSelection
(
baseOffset:
0
,
extentOffset:
delegate
.
textEditingValue
.
text
.
length
,
),
),
SelectionChangedCause
.
toolBar
,
);
delegate
.
bringIntoView
(
delegate
.
textEditingValue
.
selection
.
extent
);
}
...
...
@@ -436,13 +448,16 @@ class TextSelectionOverlay {
/// Builds the handles by inserting them into the [context]'s overlay.
void
showHandles
()
{
assert
(
_handles
==
null
);
if
(
_handles
!=
null
)
return
;
_handles
=
<
OverlayEntry
>[
OverlayEntry
(
builder:
(
BuildContext
context
)
=>
_buildHandle
(
context
,
_TextSelectionHandlePosition
.
start
)),
OverlayEntry
(
builder:
(
BuildContext
context
)
=>
_buildHandle
(
context
,
_TextSelectionHandlePosition
.
end
)),
];
Overlay
.
of
(
context
,
rootOverlay:
true
,
debugRequiredFor:
debugRequiredFor
)!.
insertAll
(
_handles
!);
Overlay
.
of
(
context
,
rootOverlay:
true
,
debugRequiredFor:
debugRequiredFor
)!
.
insertAll
(
_handles
!);
}
/// Destroys the handles by removing them from overlay.
...
...
@@ -613,10 +628,13 @@ class TextSelectionOverlay {
textPosition
=
newSelection
.
base
;
break
;
case
_TextSelectionHandlePosition
.
end
:
textPosition
=
newSelection
.
extent
;
textPosition
=
newSelection
.
extent
;
break
;
}
selectionDelegate
!.
textEditingValue
=
_value
.
copyWith
(
selection:
newSelection
,
composing:
TextRange
.
empty
);
selectionDelegate
!.
userUpdateTextEditingValue
(
_value
.
copyWith
(
selection:
newSelection
,
composing:
TextRange
.
empty
),
SelectionChangedCause
.
drag
,
);
selectionDelegate
!.
bringIntoView
(
textPosition
);
}
}
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
a46139a2
...
...
@@ -3475,7 +3475,6 @@ void main() {
from:
tester
.
getTopRight
(
find
.
byType
(
CupertinoApp
)),
cause:
SelectionChangedCause
.
tap
,
);
expect
(
state
.
showToolbar
(),
true
);
await
tester
.
pumpAndSettle
();
// -1 because we want to reach the end of the line, not the start of a new line.
...
...
@@ -3536,7 +3535,6 @@ void main() {
from:
tester
.
getCenter
(
find
.
byType
(
EditableText
)),
cause:
SelectionChangedCause
.
tap
,
);
expect
(
state
.
showToolbar
(),
true
);
await
tester
.
pumpAndSettle
();
bottomLeftSelectionPosition
=
textOffsetToBottomLeftPosition
(
tester
,
state
.
renderEditable
.
selection
!.
baseOffset
);
...
...
packages/flutter/test/material/text_field_focus_test.dart
View file @
a46139a2
...
...
@@ -119,6 +119,8 @@ void main() {
expect
(
tester
.
testTextInput
.
isVisible
,
isTrue
);
tester
.
testTextInput
.
hide
();
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
state
.
connectionClosed
();
expect
(
tester
.
testTextInput
.
isVisible
,
isFalse
);
...
...
packages/flutter/test/material/text_field_test.dart
View file @
a46139a2
...
...
@@ -18,6 +18,8 @@ import '../widgets/editable_text_utils.dart' show findRenderEditable, globalize,
import
'../widgets/semantics_tester.dart'
;
import
'feedback_tester.dart'
;
typedef
FormatEditUpdateCallback
=
void
Function
(
TextEditingValue
,
TextEditingValue
);
class
MockClipboard
{
Object
_clipboardData
=
<
String
,
dynamic
>{
'text'
:
null
,
...
...
@@ -127,6 +129,16 @@ double getOpacity(WidgetTester tester, Finder finder) {
).
opacity
.
value
;
}
class
TestFormatter
extends
TextInputFormatter
{
TestFormatter
(
this
.
onFormatEditUpdate
);
FormatEditUpdateCallback
onFormatEditUpdate
;
@override
TextEditingValue
formatEditUpdate
(
TextEditingValue
oldValue
,
TextEditingValue
newValue
)
{
onFormatEditUpdate
(
oldValue
,
newValue
);
return
newValue
;
}
}
void
main
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
final
MockClipboard
mockClipboard
=
MockClipboard
();
...
...
@@ -474,6 +486,47 @@ void main() {
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
testWidgets
(
'TextInputFormatter gets correct selection value'
,
(
WidgetTester
tester
)
async
{
late
TextEditingValue
actualOldValue
;
late
TextEditingValue
actualNewValue
;
final
FormatEditUpdateCallback
callBack
=
(
TextEditingValue
oldValue
,
TextEditingValue
newValue
)
{
actualOldValue
=
oldValue
;
actualNewValue
=
newValue
;
};
final
FocusNode
focusNode
=
FocusNode
();
final
TextEditingController
controller
=
TextEditingController
(
text:
'123'
);
await
tester
.
pumpWidget
(
boilerplate
(
child:
TextField
(
controller:
controller
,
focusNode:
focusNode
,
inputFormatters:
<
TextInputFormatter
>[
TestFormatter
(
callBack
)],
),
),
);
await
tester
.
tap
(
find
.
byType
(
TextField
));
await
tester
.
pumpAndSettle
();
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
backspace
);
await
tester
.
pumpAndSettle
();
expect
(
actualOldValue
,
const
TextEditingValue
(
text:
'123'
,
selection:
TextSelection
.
collapsed
(
offset:
3
,
affinity:
TextAffinity
.
upstream
),
),
);
expect
(
actualNewValue
,
const
TextEditingValue
(
text:
'12'
,
selection:
TextSelection
.
collapsed
(
offset:
2
),
),
);
});
testWidgets
(
'text field selection toolbar renders correctly inside opacity'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
...
...
@@ -1071,11 +1124,9 @@ void main() {
));
expect
(
find
.
text
(
'Paste'
),
findsNothing
);
final
Offset
emptyPos
=
textOffsetToPosition
(
tester
,
0
);
await
tester
.
longPressAt
(
emptyPos
,
pointer:
7
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Paste'
),
findsOneWidget
);
});
...
...
packages/flutter/test/rendering/editable_test.dart
View file @
a46139a2
...
...
@@ -18,6 +18,9 @@ class FakeEditableTextState with TextSelectionDelegate {
@override
TextEditingValue
textEditingValue
=
TextEditingValue
.
empty
;
@override
void
userUpdateTextEditingValue
(
TextEditingValue
value
,
SelectionChangedCause
cause
)
{
}
@override
void
hideToolbar
()
{
}
...
...
packages/flutter/test/widgets/editable_text_cursor_test.dart
View file @
a46139a2
...
...
@@ -48,6 +48,7 @@ void main() {
});
testWidgets
(
'cursor layout has correct width'
,
(
WidgetTester
tester
)
async
{
EditableText
.
debugDeterministicCursor
=
true
;
final
GlobalKey
<
EditableTextState
>
editableTextKey
=
GlobalKey
<
EditableTextState
>();
late
String
changedValue
;
...
...
@@ -87,8 +88,7 @@ void main() {
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
text
(
'Paste'
));
// Wait for cursor to appear.
await
tester
.
pump
(
const
Duration
(
milliseconds:
600
));
await
tester
.
pump
();
expect
(
changedValue
,
clipboardContent
);
...
...
@@ -96,6 +96,7 @@ void main() {
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'editable_text_test.0.png'
),
);
EditableText
.
debugDeterministicCursor
=
false
;
});
testWidgets
(
'cursor layout has correct radius'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -787,6 +788,7 @@ void main() {
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
testWidgets
(
'cursor layout'
,
(
WidgetTester
tester
)
async
{
EditableText
.
debugDeterministicCursor
=
true
;
final
GlobalKey
<
EditableTextState
>
editableTextKey
=
GlobalKey
<
EditableTextState
>();
late
String
changedValue
;
...
...
@@ -831,8 +833,7 @@ void main() {
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
text
(
'Paste'
));
// Wait for cursor to appear.
await
tester
.
pump
(
const
Duration
(
milliseconds:
600
));
await
tester
.
pump
();
expect
(
changedValue
,
clipboardContent
);
...
...
@@ -840,9 +841,11 @@ void main() {
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'editable_text_test.2.png'
),
);
EditableText
.
debugDeterministicCursor
=
false
;
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
testWidgets
(
'cursor layout has correct height'
,
(
WidgetTester
tester
)
async
{
EditableText
.
debugDeterministicCursor
=
true
;
final
GlobalKey
<
EditableTextState
>
editableTextKey
=
GlobalKey
<
EditableTextState
>();
late
String
changedValue
;
...
...
@@ -888,8 +891,7 @@ void main() {
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
text
(
'Paste'
));
// Wait for cursor to appear.
await
tester
.
pump
(
const
Duration
(
milliseconds:
600
));
await
tester
.
pump
();
expect
(
changedValue
,
clipboardContent
);
...
...
@@ -897,5 +899,6 @@ void main() {
find
.
byKey
(
const
ValueKey
<
int
>(
1
)),
matchesGoldenFile
(
'editable_text_test.3.png'
),
);
EditableText
.
debugDeterministicCursor
=
false
;
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
}
packages/flutter/test/widgets/editable_text_test.dart
View file @
a46139a2
...
...
@@ -5199,7 +5199,7 @@ void main() {
tester
.
testTextInput
.
log
.
clear
();
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byWidget
(
editableText
));
state
.
textEditingValue
=
const
TextEditingValue
(
text:
'remoteremoteremote'
);
state
.
userUpdateTextEditingValue
(
const
TextEditingValue
(
text:
'remoteremoteremote'
),
SelectionChangedCause
.
keyboard
);
// Apply in order: length formatter -> listener -> onChanged -> listener.
expect
(
controller
.
text
,
'remote listener onChanged listener'
);
...
...
@@ -5355,6 +5355,7 @@ void main() {
'TextInput.setEditingState'
,
'TextInput.setEditingState'
,
'TextInput.show'
,
'TextInput.show'
,
];
expect
(
tester
.
testTextInput
.
log
.
length
,
logOrder
.
length
);
int
index
=
0
;
...
...
@@ -5469,16 +5470,18 @@ void main() {
log
.
clear
();
final
EditableTextState
state
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
// setEditingState is not called when only the remote changes
state
.
updateEditingValue
(
const
TextEditingValue
(
state
.
updateEditingValue
(
TextEditingValue
(
text:
'a'
,
selection:
controller
.
selection
,
));
expect
(
log
.
length
,
0
);
// setEditingState is called when remote value modified by the formatter.
state
.
updateEditingValue
(
const
TextEditingValue
(
state
.
updateEditingValue
(
TextEditingValue
(
text:
'I will be modified by the formatter.'
,
selection:
controller
.
selection
,
));
expect
(
log
.
length
,
1
);
MethodCall
methodCall
=
log
[
0
];
...
...
@@ -5592,8 +5595,9 @@ void main() {
final
EditableTextState
state
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
// setEditingState is called when remote value modified by the formatter.
state
.
updateEditingValue
(
const
TextEditingValue
(
state
.
updateEditingValue
(
TextEditingValue
(
text:
'I will be modified by the formatter.'
,
selection:
controller
.
selection
,
));
expect
(
log
.
length
,
1
);
expect
(
log
,
contains
(
matchesMethodCall
(
...
...
@@ -5665,8 +5669,9 @@ void main() {
final
EditableTextState
state
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
state
.
updateEditingValue
(
TextEditingValue
(
text:
'a'
,
selection:
controller
.
selection
,
));
await
tester
.
pump
();
...
...
@@ -5689,8 +5694,9 @@ void main() {
log
.
clear
();
// Send repeat value from the engine.
state
.
updateEditingValue
(
const
TextEditingValue
(
state
.
updateEditingValue
(
TextEditingValue
(
text:
'a'
,
selection:
controller
.
selection
,
));
await
tester
.
pump
();
...
...
@@ -5784,8 +5790,9 @@ void main() {
final
EditableTextState
state
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
state
.
updateEditingValue
(
TextEditingValue
(
text:
'a'
,
selection:
controller
.
selection
,
));
await
tester
.
pump
();
...
...
@@ -6579,6 +6586,7 @@ void main() {
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'foo composing bar'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
composing:
TextRange
(
start:
4
,
end:
12
),
));
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
2
);
...
...
@@ -6587,6 +6595,7 @@ void main() {
// Reset the composing range.
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'foo composing bar'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
composing:
TextRange
(
start:
4
,
end:
12
),
));
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
4
,
end:
12
));
...
...
@@ -6594,13 +6603,14 @@ void main() {
// Positioning cursor after the composing range should clear the composing range.
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'foo composing bar'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
composing:
TextRange
(
start:
4
,
end:
12
),
));
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
14
);
expect
(
state
.
currentTextEditingValue
.
composing
,
TextRange
.
empty
);
});
testWidgets
(
'Clears composing range if cursor moves outside that range'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Clears composing range if cursor moves outside that range
- case two
'
,
(
WidgetTester
tester
)
async
{
final
Widget
widget
=
MaterialApp
(
home:
EditableText
(
backgroundCursorColor:
Colors
.
grey
,
...
...
@@ -6617,6 +6627,7 @@ void main() {
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'foo composing bar'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
composing:
TextRange
(
start:
4
,
end:
12
),
));
controller
.
selection
=
const
TextSelection
(
baseOffset:
1
,
extentOffset:
2
);
...
...
@@ -6625,6 +6636,7 @@ void main() {
// Reset the composing range.
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'foo composing bar'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
composing:
TextRange
(
start:
4
,
end:
12
),
));
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
4
,
end:
12
));
...
...
@@ -6632,6 +6644,7 @@ void main() {
// Setting a selection within the composing range clears the composing range.
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'foo composing bar'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
composing:
TextRange
(
start:
4
,
end:
12
),
));
controller
.
selection
=
const
TextSelection
(
baseOffset:
5
,
extentOffset:
7
);
...
...
@@ -6640,6 +6653,7 @@ void main() {
// Reset the composing range.
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'foo composing bar'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
composing:
TextRange
(
start:
4
,
end:
12
),
));
expect
(
state
.
currentTextEditingValue
.
composing
,
const
TextRange
(
start:
4
,
end:
12
));
...
...
@@ -6647,6 +6661,7 @@ void main() {
// Setting a selection after the composing range clears the composing range.
state
.
updateEditingValue
(
const
TextEditingValue
(
text:
'foo composing bar'
,
selection:
TextSelection
.
collapsed
(
offset:
4
),
composing:
TextRange
(
start:
4
,
end:
12
),
));
controller
.
selection
=
const
TextSelection
(
baseOffset:
13
,
extentOffset:
15
);
...
...
packages/flutter/test/widgets/text_selection_test.dart
View file @
a46139a2
...
...
@@ -797,6 +797,7 @@ class FakeRenderEditable extends RenderEditable {
),
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
ignorePointer:
true
,
textAlign:
TextAlign
.
start
,
textDirection:
TextDirection
.
ltr
,
locale:
const
Locale
(
'en'
,
'US'
),
...
...
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