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
ce0ec01f
Unverified
Commit
ce0ec01f
authored
Dec 28, 2020
by
LongCatIsLooong
Committed by
GitHub
Dec 28, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
squash commits (#68166)
parent
780752e8
Changes
4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
725 additions
and
28 deletions
+725
-28
text_formatter.dart
packages/flutter/lib/src/services/text_formatter.dart
+211
-14
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+134
-14
text_formatter_test.dart
packages/flutter/test/services/text_formatter_test.dart
+231
-0
editable_text_didUpdateWidget_test.dart
...tter/test/widgets/editable_text_didUpdateWidget_test.dart
+149
-0
No files found.
packages/flutter/lib/src/services/text_formatter.dart
View file @
ce0ec01f
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/widgets/editable_text.dart
View file @
ce0ec01f
...
...
@@ -34,6 +34,9 @@ import 'ticker_provider.dart';
export
'package:flutter/rendering.dart'
show
SelectionChangedCause
;
export
'package:flutter/services.dart'
show
TextEditingValue
,
TextSelection
,
TextInputType
,
SmartQuotesType
,
SmartDashesType
;
// Examples can assume:
// late TextInputFormatter usPhoneNumberFormatter;
/// Signature for the callback that reports when the user changes the selection
/// (including the cursor location).
typedef
SelectionChangedCallback
=
void
Function
(
TextSelection
selection
,
SelectionChangedCause
?
cause
);
...
...
@@ -225,7 +228,7 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
/// change the controller's [value].
///
/// If the new selection if of non-zero length, or is outside the composing
/// range, the composing
composing
range is cleared.
/// range, the composing range is cleared.
set
selection
(
TextSelection
newSelection
)
{
if
(!
isSelectionWithinTextBounds
(
newSelection
))
throw
FlutterError
(
'invalid text selection:
$newSelection
'
);
...
...
@@ -272,6 +275,49 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
bool
_isSelectionWithinComposingRange
(
TextSelection
selection
)
{
return
selection
.
start
>=
value
.
composing
.
start
&&
selection
.
end
<=
value
.
composing
.
end
;
}
List
<
TextInputFormatter
>?
_inputFormatters
;
void
_setInputFormatters
(
List
<
TextInputFormatter
>
newValue
)
{
// The setter does not take null values: if currentValue is null that means
// this is the first formatter list ever set, and we should not reformat.
final
List
<
TextInputFormatter
>?
currentValue
=
_inputFormatters
;
_inputFormatters
=
newValue
;
if
(
newValue
==
currentValue
||
currentValue
==
null
)
{
return
;
}
final
Iterator
<
TextInputFormatter
>
oldFormatters
=
currentValue
.
iterator
;
final
Iterator
<
TextInputFormatter
>
newFormatters
=
newValue
.
iterator
;
// Determining how many new input formatters need to be rerun:
//
// * The entire `newValue` list needs to be rerun if it has less formatters
// than the current list, or any of the new input formatter requests
// reformatting.
// * Otherwise, only apply the new input formatters whose index is larger
// than newValue.length.
bool
needsReformat
=
currentValue
.
length
>
newValue
.
length
;
while
(!
needsReformat
&&
oldFormatters
.
moveNext
()
&&
newFormatters
.
moveNext
())
{
if
(
newFormatters
.
current
.
shouldReformat
(
oldFormatters
.
current
))
{
needsReformat
=
true
;
}
}
TextEditingValue
formatted
=
value
;
if
(
needsReformat
||
oldFormatters
.
moveNext
())
{
formatted
=
newValue
.
fold
(
formatted
,
(
TextEditingValue
v
,
TextInputFormatter
formatter
)
=>
formatter
.
format
(
v
),
);
}
else
{
while
(
newFormatters
.
moveNext
())
{
formatted
=
newFormatters
.
current
.
format
(
formatted
);
}
}
value
=
formatted
;
}
}
/// Toolbar configuration for [EditableText].
...
...
@@ -525,7 +571,7 @@ class EditableText extends StatefulWidget {
inputFormatters
=
maxLines
==
1
?
<
TextInputFormatter
>[
FilteringTextInputFormatter
.
singleLineFormatter
,
...
inputFormatters
??
const
Iterable
<
TextInputFormatter
>.
empty
()
,
...
?
inputFormatters
,
]
:
inputFormatters
,
showCursor
=
showCursor
??
!
readOnly
,
...
...
@@ -1058,9 +1104,76 @@ class EditableText extends StatefulWidget {
/// {@template flutter.widgets.editableText.inputFormatters}
/// Optional input validation and formatting overrides.
///
/// Formatters are run in the provided order when the text input changes. When
/// this parameter changes, the new formatters will not be applied until the
/// next time the user inserts or deletes text.
/// Formatters are run in the provided order when the user changes the text
/// contained in the widget. They're not applied when the changes are
/// selection only, or not initiated by the user.
///
/// When this widget rebuilds, each input formatter in the new widget's
/// [inputFormatters] list checks the configuration of the input formatter
/// from the same location in the old [inputFormatters], to determine if the
/// new formatters need to be re-applied to the current [TextEditingValue] of
/// this widget.
///
/// {@tool snippet}
///
/// The following code uses a combination of 2 [TextInputFormatter]s and a
/// `UsPhoneNumberFormatter` (which simply adds parentheses and hypens), to
/// turn user input into a valid United States telephone number (for example,
/// (123)456-7890).
///
/// The combined effect of the 3 formatters is idempotent, meaning applying
/// them together to an already formatted value is a no-op. The
/// `UsPhoneNumberFormatter` is not idempotent, thus should not be used by
/// itself.
///
/// ```dart
/// class UsPhoneNumberFormatter extends TextInputFormatter {
/// const UsPhoneNumberFormatter();
///
/// @override
/// TextEditingValue format(TextEditingValue value) {
/// final int inputLength = value.text.length;
/// if (inputLength <= 3)
/// return value;
///
/// final StringBuffer newText = StringBuffer();
///
/// newText.write('(');
/// newText.write(value.text.substring(0, 3));
/// newText.write(')');
/// newText.write(value.text.substring(3, math.min(6, inputLength)));
///
/// if (inputLength > 6) {
/// newText.write('-');
/// newText.write(value.text.substring(6));
/// }
///
/// final int selectionOffset = value.selection.end <= 3 ? 1 : value.selection.end <= 6 ? 2 : 3;
/// return TextEditingValue(
/// text: newText.toString(),
/// selection: TextSelection.collapsed(offset: value.selection.end + selectionOffset),
/// );
/// }
///
/// @override
/// TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) => format(newValue);
///
/// @override
/// bool shouldReformat(TextInputFormatter oldFormatter) => oldFormatter is! UsPhoneNumberFormatter;
/// }
/// ```
///
/// ```dart
/// TextField(
/// inputFormatters: <TextInputFormatter>[
/// FilteringTextInputFormatter.digitsOnly,
/// LengthLimitingTextInputFormatter(10),
/// usPhoneNumberFormatter,
/// ],
/// )
/// ```
/// {@end-tool}
///
/// {@endtemplate}
final
List
<
TextInputFormatter
>?
inputFormatters
;
...
...
@@ -1550,6 +1663,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
void
initState
()
{
super
.
initState
();
_clipboardStatus
?.
addListener
(
_onChangedClipboardStatus
);
widget
.
controller
.
_setInputFormatters
(
widget
.
inputFormatters
??
const
<
TextInputFormatter
>[]);
widget
.
controller
.
addListener
(
_didChangeTextEditingValue
);
_focusAttachment
=
widget
.
focusNode
.
attach
(
context
);
widget
.
focusNode
.
addListener
(
_handleFocusChanged
);
...
...
@@ -1586,11 +1700,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void
didUpdateWidget
(
EditableText
oldWidget
)
{
beginBatchEdit
();
super
.
didUpdateWidget
(
oldWidget
);
if
(
widget
.
controller
!=
oldWidget
.
controller
)
{
oldWidget
.
controller
.
removeListener
(
_didChangeTextEditingValue
);
widget
.
controller
.
addListener
(
_didChangeTextEditingValue
);
_updateRemoteEditingValueIfNeeded
();
}
if
(
widget
.
controller
.
selection
!=
oldWidget
.
controller
.
selection
)
{
_selectionOverlay
?.
update
(
_value
);
...
...
@@ -1636,6 +1750,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
widget
.
selectionEnabled
&&
pasteEnabled
&&
widget
.
selectionControls
?.
canPaste
(
this
)
==
true
)
{
_clipboardStatus
?.
update
();
}
widget
.
controller
.
_setInputFormatters
(
widget
.
inputFormatters
??
const
<
TextInputFormatter
>[]
);
endBatchEdit
();
}
@override
...
...
@@ -2225,7 +2344,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_lastBottomViewInset
=
WidgetsBinding
.
instance
!.
window
.
viewInsets
.
bottom
;
}
late
final
_WhitespaceDirectionalityFormatter
_whitespaceFormatter
=
_WhitespaceDirectionalityFormatter
(
textDirection:
_textDirection
);
_WhitespaceDirectionalityFormatter
?
_lastUsedWhitespaceFormatter
;
_WhitespaceDirectionalityFormatter
get
_whitespaceFormatter
{
final
_WhitespaceDirectionalityFormatter
?
lastUsed
=
_lastUsedWhitespaceFormatter
;
if
(
lastUsed
!=
null
&&
lastUsed
.
_baseDirection
==
_textDirection
)
return
lastUsed
;
return
_lastUsedWhitespaceFormatter
=
_WhitespaceDirectionalityFormatter
(
textDirection:
_textDirection
);
}
void
_formatAndSetValue
(
TextEditingValue
value
)
{
// Only apply input formatters if the text has changed (including uncommited
...
...
@@ -2241,18 +2366,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
bool
selectionChanged
=
_value
.
selection
!=
value
.
selection
;
if
(
textChanged
)
{
value
=
widget
.
inputFormatters
?.
fold
<
TextEditingValue
>(
final
TextEditingValue
formatted
=
widget
.
inputFormatters
?.
fold
<
TextEditingValue
>(
value
,
(
TextEditingValue
newValue
,
TextInputFormatter
formatter
)
=>
formatter
.
formatEditUpdate
(
_value
,
newValue
),
)
??
value
;
// Always pass the text through the whitespace directionality formatter to
// maintain expected behavior with carets on trailing whitespace.
// TODO(LongCatIsLooong): The if statement here is for retaining the
// previous behavior. The input formatter logic will be updated in an
// upcoming PR.
if
(
widget
.
inputFormatters
?.
isNotEmpty
??
false
)
value
=
_whitespaceFormatter
.
formatEditUpdate
(
_value
,
value
);
value
=
_whitespaceFormatter
.
formatEditUpdate
(
_value
,
formatted
);
}
// Put all optional user callback invocations in a batch edit to prevent
...
...
packages/flutter/test/services/text_formatter_test.dart
View file @
ce0ec01f
...
...
@@ -628,4 +628,235 @@ void main() {
// cursor must be now at fourth position (right after the number 9)
expect
(
formatted
.
selection
.
baseOffset
,
equals
(
4
));
});
group
(
'provided formatters implement shouldReformat correctly'
,
()
{
test
(
'length limiting formatter'
,
()
{
expect
(
LengthLimitingTextInputFormatter
(-
1
).
shouldReformat
(
LengthLimitingTextInputFormatter
(
null
)),
isFalse
,
);
expect
(
LengthLimitingTextInputFormatter
(
null
).
shouldReformat
(
LengthLimitingTextInputFormatter
(-
1
)),
isFalse
,
);
expect
(
LengthLimitingTextInputFormatter
(
null
).
shouldReformat
(
LengthLimitingTextInputFormatter
(
null
)),
isFalse
,
);
expect
(
LengthLimitingTextInputFormatter
(
3
).
shouldReformat
(
LengthLimitingTextInputFormatter
(
3
)),
isFalse
,
);
// We're relaxing the length constraint. No reformatting needed.
expect
(
LengthLimitingTextInputFormatter
(-
1
).
shouldReformat
(
LengthLimitingTextInputFormatter
(
3
)),
isFalse
,
);
// We're relaxing the length constraint. No reformatting needed.
expect
(
LengthLimitingTextInputFormatter
(
4
).
shouldReformat
(
LengthLimitingTextInputFormatter
(
3
)),
isFalse
,
);
expect
(
LengthLimitingTextInputFormatter
(
3
).
shouldReformat
(
LengthLimitingTextInputFormatter
(
4
)),
isTrue
,
);
expect
(
LengthLimitingTextInputFormatter
(
3
).
shouldReformat
(
LengthLimitingTextInputFormatter
(
null
)),
isTrue
,
);
expect
(
LengthLimitingTextInputFormatter
(
3
).
shouldReformat
(
LengthLimitingTextInputFormatter
(-
1
)),
isTrue
,
);
});
test
(
'FliteringTextInputFormatter'
,
()
{
expect
(
FilteringTextInputFormatter
(
'a'
,
allow:
true
,
replacementString:
'b'
).
shouldReformat
(
FilteringTextInputFormatter
(
'a'
,
allow:
true
,
replacementString:
'b'
),
),
isFalse
,
);
expect
(
FilteringTextInputFormatter
(
'a'
,
allow:
true
,
replacementString:
'b'
).
shouldReformat
(
FilteringTextInputFormatter
(
'a'
,
allow:
true
,
replacementString:
'c'
),
),
isTrue
,
);
expect
(
FilteringTextInputFormatter
(
'a'
,
allow:
true
,
replacementString:
'b'
).
shouldReformat
(
FilteringTextInputFormatter
(
'a'
,
allow:
false
,
replacementString:
'b'
),
),
isTrue
,
);
expect
(
FilteringTextInputFormatter
(
'a'
,
allow:
true
,
replacementString:
'b'
).
shouldReformat
(
FilteringTextInputFormatter
(
'c'
,
allow:
true
,
replacementString:
'b'
),
),
isTrue
,
);
expect
(
FilteringTextInputFormatter
(
'a'
,
allow:
true
,
replacementString:
'b'
).
shouldReformat
(
FilteringTextInputFormatter
(
'c'
,
allow:
true
),
),
isTrue
,
);
});
});
group
(
'provided formatters do not further modify a formatted value'
,
()
{
// Framework-provided TextInputFormatters must be idempotent in order to be
// used alone.
void
verifyFormatterIdempotency
(
TextInputFormatter
formatter
,
TextEditingValue
input
,
)
{
final
TextEditingValue
formatted
=
formatter
.
format
(
input
);
expect
(
formatter
.
format
(
formatted
),
formatted
);
}
setUp
(()
{
// a1b(2c3
// d4)e5f6
// where the parentheses are the selection range.
testNewValue
=
const
TextEditingValue
(
text:
'a1b2c3
\n
d4e5f6'
,
selection:
TextSelection
(
baseOffset:
3
,
extentOffset:
9
,
),
);
});
test
(
'FliteringTextInputFormatter with replacementString'
,
()
{
const
TextEditingValue
selectedIntoTheWoods
=
TextEditingValue
(
text:
'Into the Woods'
,
selection:
TextSelection
(
baseOffset:
11
,
extentOffset:
14
),
);
for
(
final
Pattern
p
in
<
Pattern
>[
'o'
,
RegExp
(
'o+'
)])
{
verifyFormatterIdempotency
(
FilteringTextInputFormatter
(
p
,
allow:
true
,
replacementString:
'*'
),
selectedIntoTheWoods
,
);
verifyFormatterIdempotency
(
FilteringTextInputFormatter
(
p
,
allow:
false
,
replacementString:
'*'
),
selectedIntoTheWoods
,
);
}
});
test
(
'single line formatter'
,
()
{
verifyFormatterIdempotency
(
FilteringTextInputFormatter
.
singleLineFormatter
,
testNewValue
,
);
});
test
(
'digits only formatter'
,
()
{
verifyFormatterIdempotency
(
FilteringTextInputFormatter
.
digitsOnly
,
testNewValue
,
);
});
test
(
'length limiting formatter'
,
()
{
verifyFormatterIdempotency
(
LengthLimitingTextInputFormatter
(
5
),
testNewValue
,
);
});
});
group
(
'CompositeTextInputFormatter'
,
()
{
test
(
'combine effects, in provided order'
,
()
{
final
CompositeTextInputFormatter
formatter
=
CompositeTextInputFormatter
(
<
TextInputFormatter
>[
FilteringTextInputFormatter
.
allow
(
RegExp
(
r'[a\*]'
),
replacementString:
'**'
),
LengthLimitingTextInputFormatter
(
3
),
]
);
expect
(
formatter
.
format
(
const
TextEditingValue
(
text:
'aab'
)).
text
,
'aab'
);
expect
(
formatter
.
formatEditUpdate
(
const
TextEditingValue
(
text:
'aaa'
),
const
TextEditingValue
(
text:
'aab'
)).
text
,
'aaa'
,
);
});
test
(
'anyChildNeedsReformat'
,
()
{
final
CompositeTextInputFormatter
oldFormatter
=
CompositeTextInputFormatter
(
<
TextInputFormatter
>[
FilteringTextInputFormatter
.
allow
(
RegExp
(
r'[a\*]'
),
replacementString:
'**'
),
LengthLimitingTextInputFormatter
(
3
),
]
);
final
CompositeTextInputFormatter
newFormatter
=
CompositeTextInputFormatter
(
<
TextInputFormatter
>[
FilteringTextInputFormatter
.
allow
(
RegExp
(
r'[a\*]'
),
replacementString:
'**'
),
LengthLimitingTextInputFormatter
(
1
),
]
);
expect
(
newFormatter
.
shouldReformat
(
newFormatter
),
isFalse
);
expect
(
oldFormatter
.
shouldReformat
(
oldFormatter
),
isFalse
);
expect
(
newFormatter
.
shouldReformat
(
oldFormatter
),
isTrue
);
});
test
(
'neverReformat'
,
()
{
final
CompositeTextInputFormatter
oldFormatter
=
CompositeTextInputFormatter
(
<
TextInputFormatter
>[
FilteringTextInputFormatter
.
allow
(
RegExp
(
r'[a\*]'
),
replacementString:
'**'
),
LengthLimitingTextInputFormatter
(
3
),
]
);
final
CompositeTextInputFormatter
newFormatter
=
CompositeTextInputFormatter
(
<
TextInputFormatter
>[
FilteringTextInputFormatter
.
allow
(
RegExp
(
r'[a\*]'
),
replacementString:
'**'
),
LengthLimitingTextInputFormatter
(
1
),
],
shouldReformatPredicate:
CompositeTextInputFormatter
.
neverReformat
,
);
expect
(
newFormatter
.
shouldReformat
(
newFormatter
),
isFalse
);
expect
(
oldFormatter
.
shouldReformat
(
oldFormatter
),
isFalse
);
expect
(
newFormatter
.
shouldReformat
(
oldFormatter
),
isFalse
);
});
test
(
'alwaysReformat'
,
()
{
final
CompositeTextInputFormatter
oldFormatter
=
CompositeTextInputFormatter
(
<
TextInputFormatter
>[
FilteringTextInputFormatter
.
allow
(
RegExp
(
r'[a\*]'
),
replacementString:
'**'
),
LengthLimitingTextInputFormatter
(
3
),
]
);
final
CompositeTextInputFormatter
newFormatter
=
CompositeTextInputFormatter
(
<
TextInputFormatter
>[
FilteringTextInputFormatter
.
allow
(
RegExp
(
r'[a\*]'
),
replacementString:
'**'
),
LengthLimitingTextInputFormatter
(
999
),
],
shouldReformatPredicate:
CompositeTextInputFormatter
.
alwaysReformat
,
);
expect
(
newFormatter
.
shouldReformat
(
newFormatter
),
isTrue
);
expect
(
oldFormatter
.
shouldReformat
(
oldFormatter
),
isFalse
);
expect
(
newFormatter
.
shouldReformat
(
oldFormatter
),
isTrue
);
});
});
}
packages/flutter/test/widgets/editable_text_didUpdateWidget_test.dart
0 → 100644
View file @
ce0ec01f
// 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_test/flutter_test.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/services.dart'
;
void
main
(
)
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'EditableText Node'
);
const
TextStyle
textStyle
=
TextStyle
();
const
Color
cursorColor
=
Color
.
fromARGB
(
0xFF
,
0xFF
,
0x00
,
0x00
);
const
Color
backgroundColor
=
Color
.
fromARGB
(
0xFF
,
0xFF
,
0x00
,
0x00
);
late
TextEditingController
defaultController
;
group
(
'didUpdateWidget'
,
()
{
final
_AppendingFormatter
appendingFormatter
=
_AppendingFormatter
();
Widget
build
({
TextDirection
textDirection
=
TextDirection
.
ltr
,
List
<
TextInputFormatter
>?
formatters
,
TextEditingController
?
controller
,
})
{
return
MediaQuery
(
data:
const
MediaQueryData
(
devicePixelRatio:
1.0
),
child:
Directionality
(
textDirection:
textDirection
,
child:
EditableText
(
backgroundCursorColor:
backgroundColor
,
controller:
controller
??
defaultController
,
maxLines:
null
,
// Remove the builtin newline formatter.
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
cursorColor
,
inputFormatters:
formatters
,
),
),
);
}
testWidgets
(
'EditableText only reformats when needed'
,
(
WidgetTester
tester
)
async
{
appendingFormatter
.
needsReformat
=
false
;
defaultController
=
TextEditingController
(
text:
'initialText'
);
String
previousText
=
defaultController
.
text
;
// Initial build, do not apply formatters.
await
tester
.
pumpWidget
(
build
());
expect
(
defaultController
.
text
,
previousText
);
await
tester
.
pumpWidget
(
build
(
formatters:
<
TextInputFormatter
>[
LengthLimitingTextInputFormatter
(
null
),
appendingFormatter
,
]));
expect
(
defaultController
.
text
,
contains
(
previousText
+
'a'
));
previousText
=
defaultController
.
text
;
// Change the first formatter.
await
tester
.
pumpWidget
(
build
(
formatters:
<
TextInputFormatter
>[
LengthLimitingTextInputFormatter
(
1000
),
appendingFormatter
,
]));
// Reformat since the length formatter changed and it becomes more
// strict (null -> 1000).
expect
(
defaultController
.
text
,
contains
(
previousText
+
'a'
));
previousText
=
defaultController
.
text
;
await
tester
.
pumpWidget
(
build
(
formatters:
<
TextInputFormatter
>[
LengthLimitingTextInputFormatter
(
2000
),
appendingFormatter
,
]));
// No reformat needed since the length formatter relaxed its constraint
// (1000 -> 2000).
expect
(
defaultController
.
text
,
previousText
);
await
tester
.
pumpWidget
(
build
(
formatters:
<
TextInputFormatter
>[
appendingFormatter
,
]));
// Reformat since we reduced the number of new formatters.
expect
(
defaultController
.
text
,
previousText
+
'a'
);
previousText
=
defaultController
.
text
;
// Now the the appending formatter always requests a reformat when
// didUpdateWidget is called.
appendingFormatter
.
needsReformat
=
true
;
await
tester
.
pumpWidget
(
build
(
formatters:
<
TextInputFormatter
>[
appendingFormatter
,
]));
// Reformat since appendingFormatter now always requests a rerun.
expect
(
defaultController
.
text
,
contains
(
previousText
+
'a'
));
previousText
=
defaultController
.
text
;
});
testWidgets
(
'Changing the controller along with the formatter does not reformat'
,
(
WidgetTester
tester
)
async
{
// This test verifies that the `shouldReformat` predicate is run against
// the previous formatter associated with the *TextEditingController*,
// instead of the one associated with the widget, to avoid unnecessary
// rebuilds.
final
TextEditingController
controller1
=
TextEditingController
(
text:
'shorttxt'
);
final
TextEditingController
controller2
=
TextEditingController
(
text:
'looooong text'
);
final
Widget
editableText1
=
build
(
controller:
controller1
,
formatters:
<
TextInputFormatter
>[
LengthLimitingTextInputFormatter
(
controller1
.
text
.
length
)],
);
final
Widget
editableText2
=
build
(
controller:
controller2
,
formatters:
<
TextInputFormatter
>[
LengthLimitingTextInputFormatter
(
controller2
.
text
.
length
)],
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Column
(
children:
<
Widget
>[
editableText1
,
editableText2
]),
));
// The 2 input fields swap places. The input formatters should not rerun.
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Column
(
children:
<
Widget
>[
editableText2
,
editableText1
]),
));
expect
(
controller1
.
text
,
'shorttxt'
);
expect
(
controller2
.
text
,
'looooong text'
);
});
});
}
// A TextInputFormatter that appends 'a' to the current editing value every time
// it runs.
class
_AppendingFormatter
extends
TextInputFormatter
{
bool
needsReformat
=
true
;
@override
TextEditingValue
formatEditUpdate
(
TextEditingValue
oldValue
,
TextEditingValue
newValue
)
{
return
newValue
.
copyWith
(
text:
newValue
.
text
+
'a'
);
}
@override
bool
shouldReformat
(
TextInputFormatter
oldFormatter
)
=>
needsReformat
;
}
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