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
bf66cc2e
Unverified
Commit
bf66cc2e
authored
Sep 16, 2021
by
Renzo Olivares
Committed by
GitHub
Sep 16, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Framework can receive TextEditingDeltas from engine (#88477)
parent
65d8dd98
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
979 additions
and
1 deletion
+979
-1
services.dart
packages/flutter/lib/services.dart
+1
-0
text_editing_delta.dart
packages/flutter/lib/src/services/text_editing_delta.dart
+386
-0
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+50
-1
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+9
-0
autofill_test.dart
packages/flutter/test/services/autofill_test.dart
+13
-0
text_editing_delta_test.dart
packages/flutter/test/services/text_editing_delta_test.dart
+217
-0
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+6
-0
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+297
-0
No files found.
packages/flutter/lib/services.dart
View file @
bf66cc2e
...
...
@@ -42,6 +42,7 @@ export 'src/services/system_chrome.dart';
export
'src/services/system_navigator.dart'
;
export
'src/services/system_sound.dart'
;
export
'src/services/text_editing.dart'
;
export
'src/services/text_editing_delta.dart'
;
export
'src/services/text_formatter.dart'
;
export
'src/services/text_input.dart'
;
export
'src/services/text_layout_metrics.dart'
;
packages/flutter/lib/src/services/text_editing_delta.dart
0 → 100644
View file @
bf66cc2e
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/foundation.dart'
;
import
'text_editing.dart'
;
import
'text_input.dart'
show
TextEditingValue
;
TextAffinity
?
_toTextAffinity
(
String
?
affinity
)
{
switch
(
affinity
)
{
case
'TextAffinity.downstream'
:
return
TextAffinity
.
downstream
;
case
'TextAffinity.upstream'
:
return
TextAffinity
.
upstream
;
}
return
null
;
}
/// Replaces a range of text in the original string with the text given in the
/// replacement string.
String
_replace
(
String
originalText
,
String
replacementText
,
int
start
,
int
end
)
{
final
String
textStart
=
originalText
.
substring
(
0
,
start
);
final
String
textEnd
=
originalText
.
substring
(
end
,
originalText
.
length
);
final
String
newText
=
textStart
+
replacementText
+
textEnd
;
return
newText
;
}
/// A structure representing a granular change that has occurred to the editing
/// state as a result of text editing.
///
/// See also:
///
/// * [TextEditingDeltaInsertion], a delta representing an insertion.
/// * [TextEditingDeltaDeletion], a delta representing a deletion.
/// * [TextEditingDeltaReplacement], a delta representing a replacement.
/// * [TextEditingDeltaNonTextUpdate], a delta representing an update to the
/// selection and/or composing region.
/// * [TextInputConfiguration], to opt-in your [TextInputClient] to receive
/// [TextEditingDelta]'s you must set [TextInputConfiguration.enableDeltaModel]
/// to true.
abstract
class
TextEditingDelta
{
/// Creates a delta for a given change to the editing state.
///
/// {@template flutter.services.TextEditingDelta}
/// The [oldText], [selection], and [composing] arguments must not be null.
/// {@endtemplate}
const
TextEditingDelta
({
required
this
.
oldText
,
required
this
.
selection
,
required
this
.
composing
,
})
:
assert
(
oldText
!=
null
),
assert
(
selection
!=
null
),
assert
(
composing
!=
null
);
/// Creates an instance of this class from a JSON object by inferring the
/// type of delta based on values sent from the engine.
factory
TextEditingDelta
.
fromJSON
(
Map
<
String
,
dynamic
>
encoded
)
{
// An insertion delta is one where replacement destination is collapsed.
//
// A deletion delta is one where the replacement source is empty.
//
// An insertion/deletion can still occur when the replacement destination is not
// collapsed, or the replacement source is not empty.
//
// On native platforms when composing text, the entire composing region is
// replaced on input, rather than reporting character by character
// insertion/deletion. In these cases we can detect if there was an
// insertion/deletion by checking if the text inside the original composing
// region was modified by the replacement. If the text is the same then we have
// an insertion/deletion. If the text is different then we can say we have
// a replacement.
//
// For example say we are currently composing the word: 'world'.
// Our current state is 'worl|' with the cursor at the end of 'l'. If we
// input the character 'd', the platform will tell us 'worl' was replaced
// with 'world' at range (0,4). Here we can check if the text found in the
// composing region (0,4) has been modified. We see that it hasn't because
// 'worl' == 'worl', so this means that the text in
// 'world'{replacementDestinationEnd, replacementDestinationStart + replacementSourceEnd}
// can be considered an insertion. In this case we inserted 'd'.
//
// Similarly for a a deletion, say we are currently composing the word: 'worl'.
// Our current state is 'world|' with the cursor at the end of 'd'. If we
// press backspace to delete the character 'd', the platform will tell us 'world'
// was replaced with 'worl' at range (0,5). Here we can check if the text found
// in the new composing region, is the same as the replacement text. We can do this
// by using oldText{replacementDestinationStart, replacementDestinationStart + replacementSourceEnd}
// which in this case is 'worl'. We then compare 'worl' with 'worl' and
// verify that they are the same. This means that the text in
// 'world'{replacementDestinationEnd, replacementDestinationStart + replacementSourceEnd} was deleted.
// In this case the character 'd' was deleted.
//
// A replacement delta occurs when the original composing region has been
// modified.
//
// A non text update delta occurs when the selection and/or composing region
// has been changed by the platform, and there have been no changes to the
// text value.
final
String
oldText
=
encoded
[
'oldText'
]
as
String
;
final
int
replacementDestinationStart
=
encoded
[
'deltaStart'
]
as
int
;
final
int
replacementDestinationEnd
=
encoded
[
'deltaEnd'
]
as
int
;
final
String
replacementSource
=
encoded
[
'deltaText'
]
as
String
;
const
int
replacementSourceStart
=
0
;
final
int
replacementSourceEnd
=
replacementSource
.
length
;
// This delta is explicitly a non text update.
final
bool
isNonTextUpdate
=
replacementDestinationStart
==
-
1
&&
replacementDestinationStart
==
replacementDestinationEnd
;
final
TextRange
newComposing
=
TextRange
(
start:
encoded
[
'composingBase'
]
as
int
?
??
-
1
,
end:
encoded
[
'composingExtent'
]
as
int
?
??
-
1
,
);
final
TextSelection
newSelection
=
TextSelection
(
baseOffset:
encoded
[
'selectionBase'
]
as
int
?
??
-
1
,
extentOffset:
encoded
[
'selectionExtent'
]
as
int
?
??
-
1
,
affinity:
_toTextAffinity
(
encoded
[
'selectionAffinity'
]
as
String
?)
??
TextAffinity
.
downstream
,
isDirectional:
encoded
[
'selectionIsDirectional'
]
as
bool
?
??
false
,
);
if
(
isNonTextUpdate
)
{
return
TextEditingDeltaNonTextUpdate
(
oldText:
oldText
,
selection:
newSelection
,
composing:
newComposing
,
);
}
final
String
newText
=
_replace
(
oldText
,
replacementSource
,
replacementDestinationStart
,
replacementDestinationEnd
);
final
bool
isEqual
=
oldText
==
newText
;
final
bool
isDeletionGreaterThanOne
=
(
replacementDestinationEnd
-
replacementDestinationStart
)
-
(
replacementSourceEnd
-
replacementSourceStart
)
>
1
;
final
bool
isDeletingByReplacingWithEmpty
=
replacementSource
.
isEmpty
&&
replacementSourceStart
==
0
&&
replacementSourceStart
==
replacementSourceEnd
;
final
bool
isReplacedByShorter
=
isDeletionGreaterThanOne
&&
(
replacementSourceEnd
-
replacementSourceStart
<
replacementDestinationEnd
-
replacementDestinationStart
);
final
bool
isReplacedByLonger
=
replacementSourceEnd
-
replacementSourceStart
>
replacementDestinationEnd
-
replacementDestinationStart
;
final
bool
isReplacedBySame
=
replacementSourceEnd
-
replacementSourceStart
==
replacementDestinationEnd
-
replacementDestinationStart
;
final
bool
isInsertingInsideComposingRegion
=
replacementDestinationStart
+
replacementSourceEnd
>
replacementDestinationEnd
;
final
bool
isDeletingInsideComposingRegion
=
!
isReplacedByShorter
&&
!
isDeletingByReplacingWithEmpty
&&
replacementDestinationStart
+
replacementSourceEnd
<
replacementDestinationEnd
;
String
newComposingText
;
String
originalComposingText
;
if
(
isDeletingByReplacingWithEmpty
||
isDeletingInsideComposingRegion
||
isReplacedByShorter
)
{
newComposingText
=
replacementSource
.
substring
(
replacementSourceStart
,
replacementSourceEnd
);
originalComposingText
=
oldText
.
substring
(
replacementDestinationStart
,
replacementDestinationStart
+
replacementSourceEnd
);
}
else
{
newComposingText
=
replacementSource
.
substring
(
replacementSourceStart
,
replacementSourceStart
+
(
replacementDestinationEnd
-
replacementDestinationStart
));
originalComposingText
=
oldText
.
substring
(
replacementDestinationStart
,
replacementDestinationEnd
);
}
final
bool
isOriginalComposingRegionTextChanged
=
!(
originalComposingText
==
newComposingText
);
final
bool
isReplaced
=
isOriginalComposingRegionTextChanged
||
(
isReplacedByLonger
||
isReplacedByShorter
||
isReplacedBySame
);
if
(
isEqual
)
{
return
TextEditingDeltaNonTextUpdate
(
oldText:
oldText
,
selection:
newSelection
,
composing:
newComposing
,
);
}
else
if
((
isDeletingByReplacingWithEmpty
||
isDeletingInsideComposingRegion
)
&&
!
isOriginalComposingRegionTextChanged
)
{
// Deletion.
int
actualStart
=
replacementDestinationStart
;
if
(!
isDeletionGreaterThanOne
)
{
actualStart
=
replacementDestinationEnd
-
1
;
}
return
TextEditingDeltaDeletion
(
oldText:
oldText
,
deletedRange:
TextRange
(
start:
actualStart
,
end:
replacementDestinationEnd
,
),
selection:
newSelection
,
composing:
newComposing
,
);
}
else
if
((
replacementDestinationStart
==
replacementDestinationEnd
||
isInsertingInsideComposingRegion
)
&&
!
isOriginalComposingRegionTextChanged
)
{
// Insertion.
return
TextEditingDeltaInsertion
(
oldText:
oldText
,
textInserted:
replacementSource
.
substring
(
replacementDestinationEnd
-
replacementDestinationStart
,
(
replacementDestinationEnd
-
replacementDestinationStart
)
+
(
replacementSource
.
length
-
(
replacementDestinationEnd
-
replacementDestinationStart
))),
insertionOffset:
replacementDestinationEnd
,
selection:
newSelection
,
composing:
newComposing
,
);
}
else
if
(
isReplaced
)
{
// Replacement.
return
TextEditingDeltaReplacement
(
oldText:
oldText
,
replacementText:
replacementSource
,
replacedRange:
TextRange
(
start:
replacementDestinationStart
,
end:
replacementDestinationEnd
,
),
selection:
newSelection
,
composing:
newComposing
,
);
}
assert
(
false
);
return
TextEditingDeltaNonTextUpdate
(
oldText:
oldText
,
selection:
newSelection
,
composing:
newComposing
,
);
}
/// The old text state before the delta has occurred.
final
String
oldText
;
/// The range of text that is currently selected after the delta has been
/// applied.
final
TextSelection
selection
;
/// The range of text that is still being composed after the delta has been
/// applied.
final
TextRange
composing
;
/// This method will take the given [TextEditingValue] and return a new
/// [TextEditingValue] with that instance of [TextEditingDelta] applied to it.
TextEditingValue
apply
(
TextEditingValue
value
);
}
/// A structure representing an insertion of a single/or contigous sequence of
/// characters at some offset of an editing state.
@immutable
class
TextEditingDeltaInsertion
extends
TextEditingDelta
{
/// Creates an insertion delta for a given change to the editing state.
///
/// {@macro flutter.services.TextEditingDelta}
///
/// {@template flutter.services.TextEditingDelta.optIn}
/// See also:
///
/// * [TextInputConfiguration], to opt-in your [TextInputClient] to receive
/// [TextEditingDelta]'s you must set [TextInputConfiguration.enableDeltaModel]
/// to true.
/// {@endtemplate}
const
TextEditingDeltaInsertion
({
required
String
oldText
,
required
this
.
textInserted
,
required
this
.
insertionOffset
,
required
TextSelection
selection
,
required
TextRange
composing
,
})
:
super
(
oldText:
oldText
,
selection:
selection
,
composing:
composing
,
);
/// The text that is being inserted into [oldText].
final
String
textInserted
;
/// The offset in the [oldText] where the insertion begins.
final
int
insertionOffset
;
@override
TextEditingValue
apply
(
TextEditingValue
value
)
{
// To stay inline with the plain text model we should follow a last write wins
// policy and apply the delta to the oldText. This is due to the asyncronous
// nature of the connection between the framework and platform text input plugins.
String
newText
=
oldText
;
newText
=
_replace
(
newText
,
textInserted
,
insertionOffset
,
insertionOffset
);
return
value
.
copyWith
(
text:
newText
,
selection:
selection
,
composing:
composing
);
}
}
/// A structure representing the deletion of a single/or contiguous sequence of
/// characters in an editing state.
@immutable
class
TextEditingDeltaDeletion
extends
TextEditingDelta
{
/// Creates a deletion delta for a given change to the editing state.
///
/// {@macro flutter.services.TextEditingDelta}
///
/// {@macro flutter.services.TextEditingDelta.optIn}
const
TextEditingDeltaDeletion
({
required
String
oldText
,
required
this
.
deletedRange
,
required
TextSelection
selection
,
required
TextRange
composing
,
})
:
super
(
oldText:
oldText
,
selection:
selection
,
composing:
composing
,
);
/// The range in [oldText] that is being deleted.
final
TextRange
deletedRange
;
/// The text from [oldText] that is being deleted.
String
get
textDeleted
=>
oldText
.
substring
(
deletedRange
.
start
,
deletedRange
.
end
);
@override
TextEditingValue
apply
(
TextEditingValue
value
)
{
// To stay inline with the plain text model we should follow a last write wins
// policy and apply the delta to the oldText. This is due to the asyncronous
// nature of the connection between the framework and platform text input plugins.
String
newText
=
oldText
;
newText
=
_replace
(
newText
,
''
,
deletedRange
.
start
,
deletedRange
.
end
);
return
value
.
copyWith
(
text:
newText
,
selection:
selection
,
composing:
composing
);
}
}
/// A structure representing a replacement of a range of characters with a
/// new sequence of text.
@immutable
class
TextEditingDeltaReplacement
extends
TextEditingDelta
{
/// Creates a replacement delta for a given change to the editing state.
///
/// The range that is being replaced can either grow or shrink based on the
/// given replacement text.
///
/// A replacement can occur in cases such as auto-correct, suggestions, and
/// when a selection is replaced by a single character.
///
/// {@macro flutter.services.TextEditingDelta}
///
/// {@macro flutter.services.TextEditingDelta.optIn}
const
TextEditingDeltaReplacement
({
required
String
oldText
,
required
this
.
replacementText
,
required
this
.
replacedRange
,
required
TextSelection
selection
,
required
TextRange
composing
,
})
:
super
(
oldText:
oldText
,
selection:
selection
,
composing:
composing
,
);
/// The new text that is replacing [replacedRange] in [oldText].
final
String
replacementText
;
/// The range in [oldText] that is being replaced.
final
TextRange
replacedRange
;
/// The original text that is being replaced in [oldText].
String
get
textReplaced
=>
oldText
.
substring
(
replacedRange
.
start
,
replacedRange
.
end
);
@override
TextEditingValue
apply
(
TextEditingValue
value
)
{
// To stay inline with the plain text model we should follow a last write wins
// policy and apply the delta to the oldText. This is due to the asyncronous
// nature of the connection between the framework and platform text input plugins.
String
newText
=
oldText
;
newText
=
_replace
(
newText
,
replacementText
,
replacedRange
.
start
,
replacedRange
.
end
);
return
value
.
copyWith
(
text:
newText
,
selection:
selection
,
composing:
composing
);
}
}
/// A structure representing changes to the selection and/or composing regions
/// of an editing state and no changes to the text value.
@immutable
class
TextEditingDeltaNonTextUpdate
extends
TextEditingDelta
{
/// Creates a delta representing no updates to the text value of the current
/// editing state. This delta includes updates to the selection and/or composing
/// regions.
///
/// A situation where this delta would be created is when dragging the selection
/// handles. There are no changes to the text, but there are updates to the selection
/// and potentially the composing region as well.
///
/// {@macro flutter.services.TextEditingDelta}
///
/// {@macro flutter.services.TextEditingDelta.optIn}
const
TextEditingDeltaNonTextUpdate
({
required
String
oldText
,
required
TextSelection
selection
,
required
TextRange
composing
,
})
:
super
(
oldText:
oldText
,
selection:
selection
,
composing:
composing
,
);
@override
TextEditingValue
apply
(
TextEditingValue
value
)
{
// To stay inline with the plain text model we should follow a last write wins
// policy and apply the delta to the oldText. This is due to the asyncronous
// nature of the connection between the framework and platform text input plugins.
return
TextEditingValue
(
text:
oldText
,
selection:
selection
,
composing:
composing
);
}
}
packages/flutter/lib/src/services/text_input.dart
View file @
bf66cc2e
...
...
@@ -23,6 +23,7 @@ import 'platform_channel.dart';
import
'system_channels.dart'
;
import
'system_chrome.dart'
;
import
'text_editing.dart'
;
import
'text_editing_delta.dart'
;
export
'dart:ui'
show
TextAffinity
;
...
...
@@ -467,6 +468,7 @@ class TextInputConfiguration {
this
.
textCapitalization
=
TextCapitalization
.
none
,
this
.
autofillConfiguration
=
AutofillConfiguration
.
disabled
,
this
.
enableIMEPersonalizedLearning
=
true
,
this
.
enableDeltaModel
=
false
,
})
:
assert
(
inputType
!=
null
),
assert
(
obscureText
!=
null
),
smartDashesType
=
smartDashesType
??
(
obscureText
?
SmartDashesType
.
disabled
:
SmartDashesType
.
enabled
),
...
...
@@ -476,7 +478,8 @@ class TextInputConfiguration {
assert
(
keyboardAppearance
!=
null
),
assert
(
inputAction
!=
null
),
assert
(
textCapitalization
!=
null
),
assert
(
enableIMEPersonalizedLearning
!=
null
);
assert
(
enableIMEPersonalizedLearning
!=
null
),
assert
(
enableDeltaModel
!=
null
);
/// The type of information for which to optimize the text input control.
final
TextInputType
inputType
;
...
...
@@ -622,6 +625,7 @@ class TextInputConfiguration {
TextCapitalization
?
textCapitalization
,
bool
?
enableIMEPersonalizedLearning
,
AutofillConfiguration
?
autofillConfiguration
,
bool
?
enableDeltaModel
,
})
{
return
TextInputConfiguration
(
inputType:
inputType
??
this
.
inputType
,
...
...
@@ -636,8 +640,30 @@ class TextInputConfiguration {
keyboardAppearance:
keyboardAppearance
??
this
.
keyboardAppearance
,
enableIMEPersonalizedLearning:
enableIMEPersonalizedLearning
??
this
.
enableIMEPersonalizedLearning
,
autofillConfiguration:
autofillConfiguration
??
this
.
autofillConfiguration
,
enableDeltaModel:
enableDeltaModel
??
this
.
enableDeltaModel
,
);
}
/// Whether to enable that the engine sends text input updates to the
/// framework as [TextEditingDelta]'s or as one [TextEditingValue].
///
/// When this is enabled platform text input updates will
/// come through [TextInputClient.updateEditingValueWithDeltas].
///
/// When this is disabled platform text input updates will come through
/// [TextInputClient.updateEditingValue].
///
/// Enabling this flag results in granular text updates being received from the
/// platforms text input control rather than a single new bulk editing state
/// given by [TextInputClient.updateEditingValue].
///
/// If the platform does not currently support the delta model then updates
/// for the editing state will continue to come through the
/// [TextInputClient.updateEditingValue] channel.
///
/// Defaults to false. Cannot be null.
final
bool
enableDeltaModel
;
/// Returns a representation of this object as a JSON object.
Map
<
String
,
dynamic
>
toJson
()
{
final
Map
<
String
,
dynamic
>?
autofill
=
autofillConfiguration
.
toJson
();
...
...
@@ -655,6 +681,7 @@ class TextInputConfiguration {
'keyboardAppearance'
:
keyboardAppearance
.
toString
(),
'enableIMEPersonalizedLearning'
:
enableIMEPersonalizedLearning
,
if
(
autofill
!=
null
)
'autofill'
:
autofill
,
'enableDeltaModel'
:
enableDeltaModel
,
};
}
}
...
...
@@ -956,6 +983,16 @@ abstract class TextInputClient {
/// formatting.
void
updateEditingValue
(
TextEditingValue
value
);
/// Requests that this client update its editing state by applying the deltas
/// received from the engine.
///
/// The list of [TextEditingDelta]'s are treated as changes that will be applied
/// to the client's editing state. A change is any mutation to the raw text
/// value, or any updates to the selection and/or composing region.
///
/// {@macro flutter.services.TextEditingDelta.optIn}
void
updateEditingValueWithDeltas
(
List
<
TextEditingDelta
>
textEditingDeltas
);
/// Requests that this client perform the given action.
void
performAction
(
TextInputAction
action
);
...
...
@@ -1447,6 +1484,18 @@ class TextInput {
case
'TextInputClient.updateEditingState'
:
_currentConnection
!.
_client
.
updateEditingValue
(
TextEditingValue
.
fromJSON
(
args
[
1
]
as
Map
<
String
,
dynamic
>));
break
;
case
'TextInputClient.updateEditingStateWithDeltas'
:
final
List
<
TextEditingDelta
>
deltas
=
<
TextEditingDelta
>[];
final
Map
<
String
,
dynamic
>
encoded
=
args
[
1
]
as
Map
<
String
,
dynamic
>;
for
(
final
dynamic
encodedDelta
in
encoded
[
'deltas'
])
{
final
TextEditingDelta
delta
=
TextEditingDelta
.
fromJSON
(
encodedDelta
as
Map
<
String
,
dynamic
>);
deltas
.
add
(
delta
);
}
_currentConnection
!.
_client
.
updateEditingValueWithDeltas
(
deltas
);
break
;
case
'TextInputClient.performAction'
:
_currentConnection
!.
_client
.
performAction
(
_toTextInputAction
(
args
[
1
]
as
String
));
break
;
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
bf66cc2e
...
...
@@ -1795,6 +1795,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
TextEditingValue
get
currentTextEditingValue
=>
_value
;
@override
void
updateEditingValueWithDeltas
(
List
<
TextEditingDelta
>
textEditingDeltas
)
{
TextEditingValue
value
=
_value
;
for
(
final
TextEditingDelta
delta
in
textEditingDeltas
)
{
value
=
delta
.
apply
(
value
);
}
updateEditingValue
(
value
);
}
@override
void
updateEditingValue
(
TextEditingValue
value
)
{
// This method handles text editing state updates from the platform text
...
...
packages/flutter/test/services/autofill_test.dart
View file @
bf66cc2e
...
...
@@ -106,6 +106,19 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
latestMethodCall
=
'updateEditingValue'
;
}
@override
void
updateEditingValueWithDeltas
(
List
<
TextEditingDelta
>
textEditingDeltas
)
{
TextEditingValue
newEditingValue
=
currentTextEditingValue
;
for
(
final
TextEditingDelta
delta
in
textEditingDeltas
)
{
newEditingValue
=
delta
.
apply
(
newEditingValue
);
}
currentTextEditingValue
=
newEditingValue
;
latestMethodCall
=
'updateEditingValueWithDeltas'
;
}
@override
AutofillScope
?
currentAutofillScope
;
...
...
packages/flutter/test/services/text_editing_delta_test.dart
0 → 100644
View file @
bf66cc2e
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:convert'
show
jsonDecode
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
group
(
'TextEditingDeltaInsertion'
,
()
{
test
(
'Verify creation of insertion delta when inserting at a collapsed selection.'
,
()
{
const
String
jsonInsertionDelta
=
'{'
'"oldText": "",'
' "deltaText": "let there be text",'
' "deltaStart": 0,'
' "deltaEnd": 0,'
' "selectionBase": 17,'
' "selectionExtent": 17,'
' "selectionAffinity" : "TextAffinity.downstream" ,'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
final
TextEditingDeltaInsertion
delta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonInsertionDelta
)
as
Map
<
String
,
dynamic
>)
as
TextEditingDeltaInsertion
;
const
TextRange
expectedComposing
=
TextRange
.
empty
;
const
int
expectedInsertionOffset
=
0
;
const
TextSelection
expectedSelection
=
TextSelection
.
collapsed
(
offset:
17
);
expect
(
delta
.
oldText
,
''
);
expect
(
delta
.
textInserted
,
'let there be text'
);
expect
(
delta
.
insertionOffset
,
expectedInsertionOffset
);
expect
(
delta
.
selection
,
expectedSelection
);
expect
(
delta
.
composing
,
expectedComposing
);
});
test
(
'Verify creation of insertion delta when inserting at end of composing region.'
,
()
{
const
String
jsonInsertionDelta
=
'{'
'"oldText": "hello worl",'
' "deltaText": "world",'
' "deltaStart": 6,'
' "deltaEnd": 10,'
' "selectionBase": 11,'
' "selectionExtent": 11,'
' "selectionAffinity" : "TextAffinity.downstream",'
' "selectionIsDirectional": false,'
' "composingBase": 6,'
' "composingExtent": 11}'
;
final
TextEditingDeltaInsertion
delta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonInsertionDelta
)
as
Map
<
String
,
dynamic
>)
as
TextEditingDeltaInsertion
;
const
TextRange
expectedComposing
=
TextRange
(
start:
6
,
end:
11
);
const
int
expectedInsertionOffset
=
10
;
const
TextSelection
expectedSelection
=
TextSelection
.
collapsed
(
offset:
11
);
expect
(
delta
.
oldText
,
'hello worl'
);
expect
(
delta
.
textInserted
,
'd'
);
expect
(
delta
.
insertionOffset
,
expectedInsertionOffset
);
expect
(
delta
.
selection
,
expectedSelection
);
expect
(
delta
.
composing
,
expectedComposing
);
});
});
group
(
'TextEditingDeltaDeletion'
,
()
{
test
(
'Verify creation of deletion delta when deleting.'
,
()
{
const
String
jsonDeletionDelta
=
'{'
'"oldText": "let there be text.",'
' "deltaText": "",'
' "deltaStart": 1,'
' "deltaEnd": 2,'
' "selectionBase": 1,'
' "selectionExtent": 1,'
' "selectionAffinity" : "TextAffinity.downstream" ,'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
final
TextEditingDeltaDeletion
delta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonDeletionDelta
)
as
Map
<
String
,
dynamic
>)
as
TextEditingDeltaDeletion
;
const
TextRange
expectedComposing
=
TextRange
.
empty
;
const
TextRange
expectedDeletedRange
=
TextRange
(
start:
1
,
end:
2
);
const
TextSelection
expectedSelection
=
TextSelection
.
collapsed
(
offset:
1
);
expect
(
delta
.
oldText
,
'let there be text.'
);
expect
(
delta
.
textDeleted
,
'e'
);
expect
(
delta
.
deletedRange
,
expectedDeletedRange
);
expect
(
delta
.
selection
,
expectedSelection
);
expect
(
delta
.
composing
,
expectedComposing
);
});
test
(
'Verify creation of deletion delta when deleting at end of composing region.'
,
()
{
const
String
jsonDeletionDelta
=
'{'
'"oldText": "hello world",'
' "deltaText": "worl",'
' "deltaStart": 6,'
' "deltaEnd": 11,'
' "selectionBase": 10,'
' "selectionExtent": 10,'
' "selectionAffinity" : "TextAffinity.downstream",'
' "selectionIsDirectional": false,'
' "composingBase": 6,'
' "composingExtent": 10}'
;
final
TextEditingDeltaDeletion
delta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonDeletionDelta
)
as
Map
<
String
,
dynamic
>)
as
TextEditingDeltaDeletion
;
const
TextRange
expectedComposing
=
TextRange
(
start:
6
,
end:
10
);
const
TextRange
expectedDeletedRange
=
TextRange
(
start:
10
,
end:
11
);
const
TextSelection
expectedSelection
=
TextSelection
.
collapsed
(
offset:
10
);
expect
(
delta
.
oldText
,
'hello world'
);
expect
(
delta
.
textDeleted
,
'd'
);
expect
(
delta
.
deletedRange
,
expectedDeletedRange
);
expect
(
delta
.
selection
,
expectedSelection
);
expect
(
delta
.
composing
,
expectedComposing
);
});
});
group
(
'TextEditingDeltaReplacement'
,
()
{
test
(
'Verify creation of replacement delta when replacing with longer.'
,
()
{
const
String
jsonReplacementDelta
=
'{'
'"oldText": "hello worfi",'
' "deltaText": "working",'
' "deltaStart": 6,'
' "deltaEnd": 11,'
' "selectionBase": 13,'
' "selectionExtent": 13,'
' "selectionAffinity" : "TextAffinity.downstream",'
' "selectionIsDirectional": false,'
' "composingBase": 6,'
' "composingExtent": 13}'
;
final
TextEditingDeltaReplacement
delta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonReplacementDelta
)
as
Map
<
String
,
dynamic
>)
as
TextEditingDeltaReplacement
;
const
TextRange
expectedComposing
=
TextRange
(
start:
6
,
end:
13
);
const
TextRange
expectedReplacedRange
=
TextRange
(
start:
6
,
end:
11
);
const
TextSelection
expectedSelection
=
TextSelection
.
collapsed
(
offset:
13
);
expect
(
delta
.
oldText
,
'hello worfi'
);
expect
(
delta
.
textReplaced
,
'worfi'
);
expect
(
delta
.
replacementText
,
'working'
);
expect
(
delta
.
replacedRange
,
expectedReplacedRange
);
expect
(
delta
.
selection
,
expectedSelection
);
expect
(
delta
.
composing
,
expectedComposing
);
});
test
(
'Verify creation of replacement delta when replacing with shorter.'
,
()
{
const
String
jsonReplacementDelta
=
'{'
'"oldText": "hello world",'
' "deltaText": "h",'
' "deltaStart": 6,'
' "deltaEnd": 11,'
' "selectionBase": 7,'
' "selectionExtent": 7,'
' "selectionAffinity" : "TextAffinity.downstream",'
' "selectionIsDirectional": false,'
' "composingBase": 6,'
' "composingExtent": 7}'
;
final
TextEditingDeltaReplacement
delta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonReplacementDelta
)
as
Map
<
String
,
dynamic
>)
as
TextEditingDeltaReplacement
;
const
TextRange
expectedComposing
=
TextRange
(
start:
6
,
end:
7
);
const
TextRange
expectedReplacedRange
=
TextRange
(
start:
6
,
end:
11
);
const
TextSelection
expectedSelection
=
TextSelection
.
collapsed
(
offset:
7
);
expect
(
delta
.
oldText
,
'hello world'
);
expect
(
delta
.
textReplaced
,
'world'
);
expect
(
delta
.
replacementText
,
'h'
);
expect
(
delta
.
replacedRange
,
expectedReplacedRange
);
expect
(
delta
.
selection
,
expectedSelection
);
expect
(
delta
.
composing
,
expectedComposing
);
});
test
(
'Verify creation of replacement delta when replacing with same.'
,
()
{
const
String
jsonReplacementDelta
=
'{'
'"oldText": "hello world",'
' "deltaText": "words",'
' "deltaStart": 6,'
' "deltaEnd": 11,'
' "selectionBase": 11,'
' "selectionExtent": 11,'
' "selectionAffinity" : "TextAffinity.downstream",'
' "selectionIsDirectional": false,'
' "composingBase": 6,'
' "composingExtent": 11}'
;
final
TextEditingDeltaReplacement
delta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonReplacementDelta
)
as
Map
<
String
,
dynamic
>)
as
TextEditingDeltaReplacement
;
const
TextRange
expectedComposing
=
TextRange
(
start:
6
,
end:
11
);
const
TextRange
expectedReplacedRange
=
TextRange
(
start:
6
,
end:
11
);
const
TextSelection
expectedSelection
=
TextSelection
.
collapsed
(
offset:
11
);
expect
(
delta
.
oldText
,
'hello world'
);
expect
(
delta
.
textReplaced
,
'world'
);
expect
(
delta
.
replacementText
,
'words'
);
expect
(
delta
.
replacedRange
,
expectedReplacedRange
);
expect
(
delta
.
selection
,
expectedSelection
);
expect
(
delta
.
composing
,
expectedComposing
);
});
});
group
(
'TextEditingDeltaNonTextUpdate'
,
()
{
test
(
'Verify non text update delta created.'
,
()
{
const
String
jsonNonTextUpdateDelta
=
'{'
'"oldText": "hello world",'
' "deltaText": "",'
' "deltaStart": -1,'
' "deltaEnd": -1,'
' "selectionBase": 10,'
' "selectionExtent": 10,'
' "selectionAffinity" : "TextAffinity.downstream",'
' "selectionIsDirectional": false,'
' "composingBase": 6,'
' "composingExtent": 11}'
;
final
TextEditingDeltaNonTextUpdate
delta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonNonTextUpdateDelta
)
as
Map
<
String
,
dynamic
>)
as
TextEditingDeltaNonTextUpdate
;
const
TextRange
expectedComposing
=
TextRange
(
start:
6
,
end:
11
);
const
TextSelection
expectedSelection
=
TextSelection
.
collapsed
(
offset:
10
);
expect
(
delta
.
oldText
,
'hello world'
);
expect
(
delta
.
selection
,
expectedSelection
);
expect
(
delta
.
composing
,
expectedComposing
);
});
});
}
packages/flutter/test/services/text_input_test.dart
View file @
bf66cc2e
...
...
@@ -119,6 +119,7 @@ void main() {
expect
(
configuration
.
inputType
,
TextInputType
.
text
);
expect
(
configuration
.
readOnly
,
false
);
expect
(
configuration
.
obscureText
,
false
);
expect
(
configuration
.
enableDeltaModel
,
false
);
expect
(
configuration
.
autocorrect
,
true
);
expect
(
configuration
.
actionLabel
,
null
);
expect
(
configuration
.
textCapitalization
,
TextCapitalization
.
none
);
...
...
@@ -450,6 +451,11 @@ class FakeTextInputClient implements TextInputClient {
latestMethodCall
=
'updateEditingValue'
;
}
@override
void
updateEditingValueWithDeltas
(
List
<
TextEditingDelta
>
textEditingDeltas
)
{
latestMethodCall
=
'updateEditingValueWithDeltas'
;
}
@override
void
updateFloatingCursor
(
RawFloatingCursorPoint
point
)
{
latestMethodCall
=
'updateFloatingCursor'
;
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
bf66cc2e
...
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:convert'
show
jsonDecode
;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
...
...
@@ -6618,6 +6619,302 @@ void main() {
expect
(
focusNode
.
hasFocus
,
false
);
});
group
(
'TextEditingDelta'
,
()
{
testWidgets
(
'TextEditingDeltaInsertion verification'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
MediaQuery
(
data:
const
MediaQueryData
(
devicePixelRatio:
1.0
),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
Material
(
child:
EditableText
(
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
Colors
.
red
,
backgroundCursorColor:
Colors
.
red
,
keyboardType:
TextInputType
.
multiline
,
onChanged:
(
String
value
)
{
},
),
),
),
),
),
),
);
final
EditableTextState
state
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
const
String
jsonDelta
=
'{'
'"oldText": "",'
' "deltaText": "let there be text",'
' "deltaStart": 0,'
' "deltaEnd": 0,'
' "selectionBase": 17,'
' "selectionExtent": 17,'
' "selectionAffinity" : "TextAffinity.downstream" ,'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
final
Map
<
String
,
dynamic
>
test
=
jsonDecode
(
jsonDelta
)
as
Map
<
String
,
dynamic
>;
final
TextEditingDelta
delta
=
TextEditingDelta
.
fromJSON
(
test
);
expect
(
delta
.
runtimeType
,
TextEditingDeltaInsertion
);
state
.
updateEditingValueWithDeltas
(<
TextEditingDelta
>[
delta
]);
await
tester
.
pump
();
expect
(
controller
.
text
,
'let there be text'
);
expect
(
controller
.
selection
,
delta
.
selection
);
expect
(
state
.
currentTextEditingValue
.
composing
,
delta
.
composing
);
});
testWidgets
(
'TextEditingDeltaDeletion verification'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'let there be text'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
MediaQuery
(
data:
const
MediaQueryData
(
devicePixelRatio:
1.0
),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
Material
(
child:
EditableText
(
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
Colors
.
red
,
backgroundCursorColor:
Colors
.
red
,
keyboardType:
TextInputType
.
multiline
,
onChanged:
(
String
value
)
{
},
),
),
),
),
),
),
);
final
EditableTextState
state
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
const
String
jsonDelta
=
'{'
'"oldText": "let there be text",'
' "deltaText": "",'
' "deltaStart": 0,'
' "deltaEnd": 17,'
' "selectionBase": 0,'
' "selectionExtent": 0,'
' "selectionAffinity" : "TextAffinity.downstream" ,'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
final
Map
<
String
,
dynamic
>
test
=
jsonDecode
(
jsonDelta
)
as
Map
<
String
,
dynamic
>;
final
TextEditingDelta
delta
=
TextEditingDelta
.
fromJSON
(
test
);
expect
(
delta
.
runtimeType
,
TextEditingDeltaDeletion
);
state
.
updateEditingValueWithDeltas
(<
TextEditingDelta
>[
delta
]);
await
tester
.
pump
();
expect
(
controller
.
text
,
''
);
expect
(
controller
.
selection
,
delta
.
selection
);
expect
(
state
.
currentTextEditingValue
.
composing
,
delta
.
composing
);
});
testWidgets
(
'TextEditingDeltaReplacement verification'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'let there be text'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
MediaQuery
(
data:
const
MediaQueryData
(
devicePixelRatio:
1.0
),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
Material
(
child:
EditableText
(
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
Colors
.
red
,
backgroundCursorColor:
Colors
.
red
,
keyboardType:
TextInputType
.
multiline
,
onChanged:
(
String
value
)
{
},
),
),
),
),
),
),
);
final
EditableTextState
state
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
const
String
jsonDelta
=
'{'
'"oldText": "let there be text",'
' "deltaText": "this is your replacement text",'
' "deltaStart": 0,'
' "deltaEnd": 17,'
' "selectionBase": 0,'
' "selectionExtent": 0,'
' "selectionAffinity" : "TextAffinity.downstream",'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
final
Map
<
String
,
dynamic
>
test
=
jsonDecode
(
jsonDelta
)
as
Map
<
String
,
dynamic
>;
final
TextEditingDelta
delta
=
TextEditingDelta
.
fromJSON
(
test
);
expect
(
delta
.
runtimeType
,
TextEditingDeltaReplacement
);
state
.
updateEditingValueWithDeltas
(<
TextEditingDelta
>[
delta
]);
await
tester
.
pump
();
expect
(
controller
.
text
,
'this is your replacement text'
);
expect
(
controller
.
selection
,
delta
.
selection
);
expect
(
state
.
currentTextEditingValue
.
composing
,
delta
.
composing
);
});
testWidgets
(
'TextEditingDeltaNonTextUpdate verification'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'let there be text'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
MediaQuery
(
data:
const
MediaQueryData
(
devicePixelRatio:
1.0
),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
Material
(
child:
EditableText
(
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
Colors
.
red
,
backgroundCursorColor:
Colors
.
red
,
keyboardType:
TextInputType
.
multiline
,
onChanged:
(
String
value
)
{
},
),
),
),
),
),
),
);
final
EditableTextState
state
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
const
String
jsonDelta
=
'{'
'"oldText": "let there be text",'
' "deltaText": "",'
' "deltaStart": -1,'
' "deltaEnd": -1,'
' "selectionBase": 17,'
' "selectionExtent": 17,'
' "selectionAffinity" : "TextAffinity.downstream",'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
final
Map
<
String
,
dynamic
>
test
=
jsonDecode
(
jsonDelta
)
as
Map
<
String
,
dynamic
>;
final
TextEditingDelta
delta
=
TextEditingDelta
.
fromJSON
(
test
);
expect
(
delta
.
runtimeType
,
TextEditingDeltaNonTextUpdate
);
state
.
updateEditingValueWithDeltas
(<
TextEditingDelta
>[
delta
]);
await
tester
.
pump
();
expect
(
controller
.
text
,
'let there be text'
);
expect
(
controller
.
selection
,
delta
.
selection
);
expect
(
state
.
currentTextEditingValue
.
composing
,
delta
.
composing
);
});
testWidgets
(
'TextEditingDelta verify batch deltas apply'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
MediaQuery
(
data:
const
MediaQueryData
(
devicePixelRatio:
1.0
),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
Material
(
child:
EditableText
(
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
Colors
.
red
,
backgroundCursorColor:
Colors
.
red
,
keyboardType:
TextInputType
.
multiline
,
onChanged:
(
String
value
)
{
},
),
),
),
),
),
),
);
final
EditableTextState
state
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
const
String
jsonInsertionDelta
=
'{'
'"oldText": "",'
' "deltaText": "let there be text",'
' "deltaStart": 0,'
' "deltaEnd": 0,'
' "selectionBase": 17,'
' "selectionExtent": 17,'
' "selectionAffinity" : "TextAffinity.downstream" ,'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
const
String
jsonDeletionDelta
=
'{'
'"oldText": "let there be text",'
' "deltaText": "",'
' "deltaStart": 12,'
' "deltaEnd": 17,'
' "selectionBase": 12,'
' "selectionExtent": 12,'
' "selectionAffinity" : "TextAffinity.downstream" ,'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
const
String
jsonReplacementDelta
=
'{'
'"oldText": "let there be",'
' "deltaText": "b light",'
' "deltaStart": 10,'
' "deltaEnd": 12,'
' "selectionBase": 17,'
' "selectionExtent": 17,'
' "selectionAffinity" : "TextAffinity.downstream" ,'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
const
String
jsonNonTextUpdateDelta
=
'{'
'"oldText": "let there b light",'
' "deltaText": "",'
' "deltaStart": -1,'
' "deltaEnd": -1,'
' "selectionBase": 17,'
' "selectionExtent": 17,'
' "selectionAffinity" : "TextAffinity.downstream",'
' "selectionIsDirectional": false,'
' "composingBase": -1,'
' "composingExtent": -1}'
;
final
TextEditingDelta
insertionDelta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonInsertionDelta
)
as
Map
<
String
,
dynamic
>);
final
TextEditingDelta
deletionDelta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonDeletionDelta
)
as
Map
<
String
,
dynamic
>);
final
TextEditingDelta
replacementDelta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonReplacementDelta
)
as
Map
<
String
,
dynamic
>);
final
TextEditingDelta
nonTextUpdateDelta
=
TextEditingDelta
.
fromJSON
(
jsonDecode
(
jsonNonTextUpdateDelta
)
as
Map
<
String
,
dynamic
>);
expect
(
insertionDelta
.
runtimeType
,
TextEditingDeltaInsertion
);
expect
(
deletionDelta
.
runtimeType
,
TextEditingDeltaDeletion
);
expect
(
replacementDelta
.
runtimeType
,
TextEditingDeltaReplacement
);
expect
(
nonTextUpdateDelta
.
runtimeType
,
TextEditingDeltaNonTextUpdate
);
state
.
updateEditingValueWithDeltas
(<
TextEditingDelta
>[
insertionDelta
,
deletionDelta
,
replacementDelta
,
nonTextUpdateDelta
]);
await
tester
.
pump
();
expect
(
controller
.
text
,
'let there b light'
);
expect
(
controller
.
selection
,
nonTextUpdateDelta
.
selection
);
expect
(
state
.
currentTextEditingValue
.
composing
,
nonTextUpdateDelta
.
composing
);
});
});
group
(
'TextEditingController'
,
()
{
testWidgets
(
'TextEditingController.text set to empty string clears field'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
...
...
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