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
4220e00f
Unverified
Commit
4220e00f
authored
Dec 29, 2020
by
LongCatIsLooong
Committed by
GitHub
Dec 29, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "squash commits (#68166)" (#73067)
parent
337290e6
Changes
4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
28 additions
and
725 deletions
+28
-725
text_formatter.dart
packages/flutter/lib/src/services/text_formatter.dart
+14
-211
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+14
-134
text_formatter_test.dart
packages/flutter/test/services/text_formatter_test.dart
+0
-231
editable_text_didUpdateWidget_test.dart
...tter/test/widgets/editable_text_didUpdateWidget_test.dart
+0
-149
No files found.
packages/flutter/lib/src/services/text_formatter.dart
View file @
4220e00f
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/widgets/editable_text.dart
View file @
4220e00f
...
...
@@ -34,9 +34,6 @@ 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
);
...
...
@@ -228,7 +225,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 range is cleared.
/// range, the composing
composing
range is cleared.
set
selection
(
TextSelection
newSelection
)
{
if
(!
isSelectionWithinTextBounds
(
newSelection
))
throw
FlutterError
(
'invalid text selection:
$newSelection
'
);
...
...
@@ -275,49 +272,6 @@ 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].
...
...
@@ -571,7 +525,7 @@ class EditableText extends StatefulWidget {
inputFormatters
=
maxLines
==
1
?
<
TextInputFormatter
>[
FilteringTextInputFormatter
.
singleLineFormatter
,
...
?
inputFormatters
,
...
inputFormatters
??
const
Iterable
<
TextInputFormatter
>.
empty
()
,
]
:
inputFormatters
,
showCursor
=
showCursor
??
!
readOnly
,
...
...
@@ -1104,76 +1058,9 @@ class EditableText extends StatefulWidget {
/// {@template flutter.widgets.editableText.inputFormatters}
/// Optional input validation and formatting overrides.
///
/// 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}
///
/// 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.
/// {@endtemplate}
final
List
<
TextInputFormatter
>?
inputFormatters
;
...
...
@@ -1663,7 +1550,6 @@ 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
);
...
...
@@ -1700,11 +1586,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
);
...
...
@@ -1750,11 +1636,6 @@ 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
...
...
@@ -2344,13 +2225,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_lastBottomViewInset
=
WidgetsBinding
.
instance
!.
window
.
viewInsets
.
bottom
;
}
_WhitespaceDirectionalityFormatter
?
_lastUsedWhitespaceFormatter
;
_WhitespaceDirectionalityFormatter
get
_whitespaceFormatter
{
final
_WhitespaceDirectionalityFormatter
?
lastUsed
=
_lastUsedWhitespaceFormatter
;
if
(
lastUsed
!=
null
&&
lastUsed
.
_baseDirection
==
_textDirection
)
return
lastUsed
;
return
_lastUsedWhitespaceFormatter
=
_WhitespaceDirectionalityFormatter
(
textDirection:
_textDirection
);
}
late
final
_WhitespaceDirectionalityFormatter
_whitespaceFormatter
=
_WhitespaceDirectionalityFormatter
(
textDirection:
_textDirection
);
void
_formatAndSetValue
(
TextEditingValue
value
)
{
// Only apply input formatters if the text has changed (including uncommited
...
...
@@ -2366,13 +2241,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
bool
selectionChanged
=
_value
.
selection
!=
value
.
selection
;
if
(
textChanged
)
{
final
TextEditingValue
formatted
=
widget
.
inputFormatters
?.
fold
<
TextEditingValue
>(
value
=
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.
value
=
_whitespaceFormatter
.
formatEditUpdate
(
_value
,
formatted
);
// 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
);
}
// Put all optional user callback invocations in a batch edit to prevent
...
...
packages/flutter/test/services/text_formatter_test.dart
View file @
4220e00f
...
...
@@ -628,235 +628,4 @@ 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
deleted
100644 → 0
View file @
337290e6
// 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