Re-land "Deprecate WhitelistingTextInputFormatter and BlacklistingTextInputFormatter (#59120)" (#59876)

This relands #59120, which was reverted in #59870.
This commit is contained in:
Christopher Fujino 2020-06-19 12:03:38 -07:00 committed by GitHub
parent 0d7ff7a9e7
commit 5cfb16b193
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 662 additions and 158 deletions

View file

@ -29,6 +29,9 @@ analyzer:
missing_return: warning
# allow having TODOs in the code
todo: ignore
# allow self-reference to deprecated members (we do this because otherwise we have
# to annotate every member in every test, assert, etc, when we deprecate something)
deprecated_member_use_from_same_package: ignore
# Ignore analyzer hints for updating pubspecs when using Future or
# Stream and not importing dart:async
# Please see https://github.com/flutter/flutter/pull/24528 for details.

View file

@ -135,7 +135,7 @@ Future<void> run(List<String> arguments) async {
final RegExp _findDeprecationPattern = RegExp(r'@[Dd]eprecated');
final RegExp _deprecationPattern1 = RegExp(r'^( *)@Deprecated\($'); // ignore: flutter_deprecation_syntax (see analyze.dart)
final RegExp _deprecationPattern2 = RegExp(r"^ *'(.+) '$");
final RegExp _deprecationPattern3 = RegExp(r"^ *'This feature was deprecated after v([0-9]+)\.([0-9]+)\.([0-9]+)\.'$");
final RegExp _deprecationPattern3 = RegExp(r"^ *'This feature was deprecated after v([0-9]+)\.([0-9]+)\.([0-9]+)(\-[0-9]+\.[0-9]+\.pre)?\.'$");
final RegExp _deprecationPattern4 = RegExp(r'^ *\)$');
/// Some deprecation notices are special, for example they're used to annotate members that
@ -182,7 +182,7 @@ Future<void> verifyDeprecations(String workingDirectory, { int minimumMatches =
if (message == null) {
final String firstChar = String.fromCharCode(match2[1].runes.first);
if (firstChar.toUpperCase() != firstChar)
throw 'Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide.';
throw 'Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo';
}
message = match2[1];
lineNumber += 1;
@ -190,6 +190,13 @@ Future<void> verifyDeprecations(String workingDirectory, { int minimumMatches =
throw 'Incomplete deprecation notice.';
match3 = _deprecationPattern3.firstMatch(lines[lineNumber]);
} while (match3 == null);
final int v1 = int.parse(match3[1]);
final int v2 = int.parse(match3[2]);
final bool hasV4 = match3[4] != null;
if (v1 > 1 || (v1 == 1 && v2 >= 20)) {
if (!hasV4)
throw 'Deprecation notice does not accurately indicate a dev branch version number; please see https://flutter.dev/docs/development/tools/sdk/releases to find the latest dev build version number.';
}
if (!message.endsWith('.') && !message.endsWith('!') && !message.endsWith('?'))
throw 'Deprecation notice should be a grammatically correct sentence and end with a period.';
if (!lines[lineNumber].startsWith("$indent '"))

View file

@ -55,6 +55,42 @@ void test10() { }
@Deprecated(
'URLs are not required. '
'This feature was deprecated after v2.0.0.'
'This feature was deprecated after v1.0.0.'
)
void test11() { }
@Deprecated(
'Version number test (should fail). '
'This feature was deprecated after v1.19.0.'
)
void test12() { }
@Deprecated(
'Version number test (should fail). '
'This feature was deprecated after v1.20.0.'
)
void test13() { }
@Deprecated(
'Version number test (should fail). '
'This feature was deprecated after v1.21.0.'
)
void test14() { }
@Deprecated(
'Version number test (should fail). '
'This feature was deprecated after v3.1.0.'
)
void test15() { }
@Deprecated(
'Version number test (should be fine). '
'This feature was deprecated after v0.1.0.'
)
void test16() { }
@Deprecated(
'Version number test (should be fine). '
'This feature was deprecated after v1.20.0-1.0.pre.'
)
void test17() { }

View file

@ -41,7 +41,7 @@ void main() {
+
(
'test/analyze-test-input/root/packages/foo/deprecation.dart:12: Deprecation notice does not match required pattern.\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:18: Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide.\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:18: Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: STYLE_GUIDE_URL\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:25: Deprecation notice should be a grammatically correct sentence and end with a period.\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:29: Deprecation notice does not match required pattern.\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:32: Deprecation notice does not match required pattern.\n'
@ -49,7 +49,12 @@ void main() {
'test/analyze-test-input/root/packages/foo/deprecation.dart:41: Deprecation notice does not match required pattern.\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:48: End of deprecation notice does not match required pattern.\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:51: Unexpected deprecation notice indent.\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:70: Deprecation notice does not accurately indicate a dev branch version number; please see RELEASES_URL to find the latest dev build version number.\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:76: Deprecation notice does not accurately indicate a dev branch version number; please see RELEASES_URL to find the latest dev build version number.\n'
'test/analyze-test-input/root/packages/foo/deprecation.dart:82: Deprecation notice does not accurately indicate a dev branch version number; please see RELEASES_URL to find the latest dev build version number.\n'
.replaceAll('/', Platform.isWindows ? r'\' : '/')
.replaceAll('STYLE_GUIDE_URL', 'https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo')
.replaceAll('RELEASES_URL', 'https://flutter.dev/docs/development/tools/sdk/releases')
)
+
'See: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes\n'

View file

@ -217,7 +217,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
validator: _validatePhoneNumber,
// TextInputFormatters are applied in sequence.
inputFormatters: <TextInputFormatter> [
WhitelistingTextInputFormatter.digitsOnly,
FilteringTextInputFormatter.digitsOnly,
// Fit the validating format.
_phoneNumberFormatter,
],

View file

@ -463,7 +463,7 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
);
final Color actionsForegroundColor = CupertinoDynamicColor.resolve(
widget.actionsForegroundColor, // ignore: deprecated_member_use_from_same_package
widget.actionsForegroundColor,
context,
);
if (!widget.transitionBetweenRoutes || !_isTransitionable(context)) {
@ -694,8 +694,8 @@ class _CupertinoSliverNavigationBarState extends State<CupertinoSliverNavigation
@override
Widget build(BuildContext context) {
// Lint ignore to maintain backward compatibility.
final Color actionsForegroundColor = CupertinoDynamicColor.resolve(widget.actionsForegroundColor, context) // ignore: deprecated_member_use_from_same_package
?? CupertinoTheme.of(context).primaryColor;
final Color actionsForegroundColor = CupertinoDynamicColor.resolve(widget.actionsForegroundColor, context)
?? CupertinoTheme.of(context).primaryColor;
final _NavigationBarStaticComponents components = _NavigationBarStaticComponents(
keys: keys,

View file

@ -4,11 +4,6 @@
// @dart = 2.8
// TODO(shihaohong): remove ignoring deprecated member use analysis
// when AlertDialog.scrollable parameter is removed. See
// https://flutter.dev/go/scrollable-alert-dialog for more details.
// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:async';
import 'package:flutter/foundation.dart';

View file

@ -1927,7 +1927,6 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
final bool labelIsInitiallyFloating = widget.decoration.floatingLabelBehavior == FloatingLabelBehavior.always
|| (widget.decoration.floatingLabelBehavior != FloatingLabelBehavior.never &&
// ignore: deprecated_member_use_from_same_package
widget.decoration.hasFloatingPlaceholder &&
widget._labelShouldWithdraw);
@ -1976,7 +1975,6 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
bool get isHovering => widget.isHovering && decoration.enabled;
bool get isEmpty => widget.isEmpty;
bool get _floatingLabelEnabled {
// ignore: deprecated_member_use_from_same_package
return decoration.hasFloatingPlaceholder && decoration.floatingLabelBehavior != FloatingLabelBehavior.never;
}
@ -1987,7 +1985,6 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
_effectiveDecoration = null;
final bool floatBehaviorChanged = widget.decoration.floatingLabelBehavior != old.decoration.floatingLabelBehavior
// ignore: deprecated_member_use_from_same_package
|| widget.decoration.hasFloatingPlaceholder != old.decoration.hasFloatingPlaceholder;
if (widget._labelShouldWithdraw != old._labelShouldWithdraw || floatBehaviorChanged) {
@ -2520,7 +2517,7 @@ class InputDecoration {
'Use floatingLabelBehavior instead. '
'This feature was deprecated after v1.13.2.'
)
this.hasFloatingPlaceholder = true, // ignore: deprecated_member_use_from_same_package
this.hasFloatingPlaceholder = true,
this.floatingLabelBehavior = FloatingLabelBehavior.auto,
this.isCollapsed = false,
this.isDense,
@ -2566,7 +2563,6 @@ class InputDecoration {
'Use floatingLabelBehavior instead. '
'This feature was deprecated after v1.13.2.'
)
// ignore: deprecated_member_use_from_same_package
this.hasFloatingPlaceholder = true,
this.floatingLabelBehavior = FloatingLabelBehavior.auto,
this.hintStyle,
@ -2577,9 +2573,8 @@ class InputDecoration {
this.border = InputBorder.none,
this.enabled = true,
}) : assert(enabled != null),
// ignore: deprecated_member_use_from_same_package
assert(!(!hasFloatingPlaceholder && identical(floatingLabelBehavior, FloatingLabelBehavior.always)),
'hasFloatingPlaceholder=false conflicts with FloatingLabelBehavior.always'),
'hasFloatingPlaceholder=false conflicts with FloatingLabelBehavior.always'),
icon = null,
labelText = null,
labelStyle = null,
@ -3372,7 +3367,6 @@ class InputDecoration {
errorText: errorText ?? this.errorText,
errorStyle: errorStyle ?? this.errorStyle,
errorMaxLines: errorMaxLines ?? this.errorMaxLines,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: hasFloatingPlaceholder ?? this.hasFloatingPlaceholder,
floatingLabelBehavior: floatingLabelBehavior ?? this.floatingLabelBehavior,
isCollapsed: isCollapsed ?? this.isCollapsed,
@ -3420,7 +3414,6 @@ class InputDecoration {
hintStyle: hintStyle ?? theme.hintStyle,
errorStyle: errorStyle ?? theme.errorStyle,
errorMaxLines: errorMaxLines ?? theme.errorMaxLines,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: hasFloatingPlaceholder ?? theme.hasFloatingPlaceholder,
floatingLabelBehavior: floatingLabelBehavior ?? theme.floatingLabelBehavior,
isCollapsed: isCollapsed ?? theme.isCollapsed,
@ -3462,7 +3455,6 @@ class InputDecoration {
&& other.errorText == errorText
&& other.errorStyle == errorStyle
&& other.errorMaxLines == errorMaxLines
// ignore: deprecated_member_use_from_same_package
&& other.hasFloatingPlaceholder == hasFloatingPlaceholder
&& other.floatingLabelBehavior == floatingLabelBehavior
&& other.isDense == isDense
@ -3511,7 +3503,7 @@ class InputDecoration {
errorText,
errorStyle,
errorMaxLines,
hasFloatingPlaceholder,// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder,
floatingLabelBehavior,
isDense,
contentPadding,
@ -3560,7 +3552,6 @@ class InputDecoration {
if (errorText != null) 'errorText: "$errorText"',
if (errorStyle != null) 'errorStyle: "$errorStyle"',
if (errorMaxLines != null) 'errorMaxLines: "$errorMaxLines"',
// ignore: deprecated_member_use_from_same_package
if (hasFloatingPlaceholder == false) 'hasFloatingPlaceholder: false',
if (floatingLabelBehavior != null) 'floatingLabelBehavior: $floatingLabelBehavior',
if (isDense ?? false) 'isDense: $isDense',
@ -3624,7 +3615,6 @@ class InputDecorationTheme with Diagnosticable {
'Use floatingLabelBehavior instead. '
'This feature was deprecated after v1.13.2.'
)
// ignore: deprecated_member_use_from_same_package
this.hasFloatingPlaceholder = true,
this.floatingLabelBehavior = FloatingLabelBehavior.auto,
this.isDense = false,
@ -3648,7 +3638,6 @@ class InputDecorationTheme with Diagnosticable {
assert(isCollapsed != null),
assert(filled != null),
assert(alignLabelWithHint != null),
// ignore: deprecated_member_use_from_same_package
assert(!(!hasFloatingPlaceholder && identical(floatingLabelBehavior, FloatingLabelBehavior.always)),
'hasFloatingPlaceholder=false conflicts with FloatingLabelBehavior.always');
@ -4001,7 +3990,6 @@ class InputDecorationTheme with Diagnosticable {
hintStyle: hintStyle ?? this.hintStyle,
errorStyle: errorStyle ?? this.errorStyle,
errorMaxLines: errorMaxLines ?? this.errorMaxLines,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: hasFloatingPlaceholder ?? this.hasFloatingPlaceholder,
floatingLabelBehavior: floatingLabelBehavior ?? this.floatingLabelBehavior,
isDense: isDense ?? this.isDense,
@ -4033,7 +4021,6 @@ class InputDecorationTheme with Diagnosticable {
hintStyle,
errorStyle,
errorMaxLines,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder,
floatingLabelBehavior,
isDense,
@ -4100,7 +4087,6 @@ class InputDecorationTheme with Diagnosticable {
properties.add(DiagnosticsProperty<TextStyle>('hintStyle', hintStyle, defaultValue: defaultTheme.hintStyle));
properties.add(DiagnosticsProperty<TextStyle>('errorStyle', errorStyle, defaultValue: defaultTheme.errorStyle));
properties.add(IntProperty('errorMaxLines', errorMaxLines, defaultValue: defaultTheme.errorMaxLines));
// ignore: deprecated_member_use_from_same_package
properties.add(DiagnosticsProperty<bool>('hasFloatingPlaceholder', hasFloatingPlaceholder, defaultValue: defaultTheme.hasFloatingPlaceholder));
properties.add(DiagnosticsProperty<FloatingLabelBehavior>('floatingLabelBehavior', floatingLabelBehavior, defaultValue: defaultTheme.floatingLabelBehavior));
properties.add(DiagnosticsProperty<bool>('isDense', isDense, defaultValue: defaultTheme.isDense));

View file

@ -29,9 +29,9 @@ const double _maxCalendarWidthPortrait = 480.0;
/// Displays a scrollable calendar grid that allows a user to select a range
/// of dates.
///
/// Note: this is not publicly exported (see pickers.dart), as it is an
/// internal component used by [showDateRangePicker].
//
// This is not publicly exported (see pickers.dart), as it is an
// internal component used by [showDateRangePicker].
class CalendarDateRangePicker extends StatefulWidget {
/// Creates a scrollable calendar grid for picking date ranges.
CalendarDateRangePicker({

View file

@ -21,7 +21,7 @@ import '../theme.dart';
import 'date_picker_common.dart';
// NOTE: this is the original implementation for the Material Date Picker.
// This is the original implementation for the Material Date Picker.
// These classes are deprecated and the whole file can be removed after
// this has been on stable for long enough for people to migrate to the new
// CalendarDatePicker (if needed, as showDatePicker has already been migrated
@ -406,7 +406,6 @@ class MonthPicker extends StatefulWidget {
_MonthPickerState createState() => _MonthPickerState();
}
// ignore: deprecated_member_use_from_same_package
class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStateMixin {
static final Animatable<double> _chevronOpacityTween = Tween<double>(begin: 1.0, end: 0.0)
.chain(CurveTween(curve: Curves.easeInOut));
@ -428,7 +427,6 @@ class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStat
}
@override
// ignore: deprecated_member_use_from_same_package
void didUpdateWidget(MonthPicker oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedDate != oldWidget.selectedDate) {
@ -479,7 +477,6 @@ class _MonthPickerState extends State<MonthPicker> with SingleTickerProviderStat
Widget _buildItems(BuildContext context, int index) {
final DateTime month = _addMonthsToMonthDate(widget.firstDate, index);
// ignore: deprecated_member_use_from_same_package
return DayPicker(
key: ValueKey<DateTime>(month),
selectedDate: widget.selectedDate,
@ -675,7 +672,6 @@ class YearPicker extends StatefulWidget {
_YearPickerState createState() => _YearPickerState();
}
// ignore: deprecated_member_use_from_same_package
class _YearPickerState extends State<YearPicker> {
static const double _itemExtent = 50.0;
ScrollController scrollController;

View file

@ -12,7 +12,7 @@ import '../material.dart';
import '../text_theme.dart';
import '../theme.dart';
// NOTE: This is an internal implementation file. Even though there are public
// This is an internal implementation file. Even though there are public
// classes and functions defined here, they are only meant to be used by the
// date picker implementation and are not exported as part of the Material library.
// See pickers.dart for exactly what is considered part of the public API.

View file

@ -4,10 +4,9 @@
// @dart = 2.8
// Common date utility functions used by the date picker implementation
// NOTE: This is an internal implementation file. Even though there are public
// This is an internal implementation file. Even though there are public
// classes and functions defined here, they are only meant to be used by the
// date picker implementation and are not exported as part of the Material library.
// See pickers.dart for exactly what is considered part of the public API.

View file

@ -244,16 +244,16 @@ class _InputDatePickerFormFieldState extends State<InputDatePickerFormField> {
}
/// A `TextInputFormatter` set up to format dates.
///
/// Note: this is not publicly exported (see pickers.dart), as it is
/// just meant for internal use by `InputDatePickerFormField` and
/// `InputDateRangePicker`.
//
// This is not publicly exported (see pickers.dart), as it is
// just meant for internal use by `InputDatePickerFormField` and
// `InputDateRangePicker`.
class DateTextInputFormatter extends TextInputFormatter {
/// Creates a date formatter with the given separator.
DateTextInputFormatter(
this.separator
) : _filterFormatter = WhitelistingTextInputFormatter(RegExp('[\\d$_commonSeparators\\$separator]+'));
) : _filterFormatter = FilteringTextInputFormatter.allow(RegExp('[\\d$_commonSeparators\\$separator]+'));
/// List of common separators that are used in dates. This is used to make
/// sure that if given platform's [TextInputType.datetime] keyboard doesn't
@ -267,7 +267,7 @@ class DateTextInputFormatter extends TextInputFormatter {
// Formatter that will filter out all characters except digits and date
// separators.
final WhitelistingTextInputFormatter _filterFormatter;
final TextInputFormatter _filterFormatter;
@override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {

View file

@ -18,9 +18,9 @@ import 'input_date_picker.dart' show DateTextInputFormatter;
/// Provides a pair of text fields that allow the user to enter the start and
/// end dates that represent a range of dates.
///
/// Note: this is not publicly exported (see pickers.dart), as it is just an
/// internal component used by [showDateRangePicker].
//
// This is not publicly exported (see pickers.dart), as it is just an
// internal component used by [showDateRangePicker].
class InputDateRangePicker extends StatefulWidget {
/// Creates a row with two text fields configured to accept the start and end dates
/// of a date range.

View file

@ -15,3 +15,9 @@ export 'date_picker_deprecated.dart';
export 'date_picker_dialog.dart' show showDatePicker;
export 'date_range_picker_dialog.dart' show showDateRangePicker;
export 'input_date_picker.dart' show InputDatePickerFormField;
// TODO(ianh): Not exporting everything is unusual and we should
// probably change to just exporting everything and making sure it's
// acceptable as a public API, or, worst case, merging the parts
// that really must be public into a single file and make them
// actually private.

View file

@ -2092,7 +2092,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin {
// Backwards compatibility for deprecated resizeToAvoidBottomPadding property
bool get _resizeToAvoidBottomInset {
// ignore: deprecated_member_use_from_same_package
return widget.resizeToAvoidBottomInset ?? widget.resizeToAvoidBottomPadding ?? true;
}

View file

@ -9,10 +9,6 @@ import 'package:flutter/painting.dart';
import 'typography.dart';
// Eventually we'll get rid of the deprecated members, but for now, we have to use them
// in order to implement them.
// ignore_for_file: deprecated_member_use_from_same_package
/// Material design text theme.
///
/// Definitions for the various typographical styles found in Material Design

View file

@ -218,7 +218,7 @@ class PlatformAssetBundle extends CachingAssetBundle {
Future<ByteData> load(String key) async {
final Uint8List encoded = utf8.encoder.convert(Uri(path: Uri.encodeFull(key)).path);
final ByteData asset =
await defaultBinaryMessenger.send('flutter/assets', encoded.buffer.asByteData()); // ignore: deprecated_member_use_from_same_package
await defaultBinaryMessenger.send('flutter/assets', encoded.buffer.asByteData());
if (asset == null)
throw FlutterError('Unable to load asset: $key');
return asset;

View file

@ -48,7 +48,7 @@ class BasicMessageChannel<T> {
final MessageCodec<T> codec;
/// The messenger which sends the bytes for this channel, not null.
BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger; // ignore: deprecated_member_use_from_same_package
BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger;
final BinaryMessenger _binaryMessenger;
/// Sends the specified [message] to the platform plugins on this channel.
@ -142,7 +142,7 @@ class MethodChannel {
/// The messenger used by this channel to send platform messages.
///
/// The messenger may not be null.
BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger; // ignore: deprecated_member_use_from_same_package
BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger;
final BinaryMessenger _binaryMessenger;
@optionalTypeArgs
@ -506,7 +506,7 @@ class EventChannel {
final MethodCodec codec;
/// The messenger used by this channel to send platform messages, not null.
BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger; // ignore: deprecated_member_use_from_same_package
BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger;
final BinaryMessenger _binaryMessenger;
/// Sets up a broadcast stream for receiving events on this channel.

View file

@ -6,7 +6,8 @@
import 'dart:math' as math;
import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:flutter/foundation.dart';
import 'text_editing.dart';
import 'text_input.dart';
@ -17,10 +18,9 @@ import 'text_input.dart';
/// IME and not on text under composition (i.e., only when
/// [TextEditingValue.composing] is collapsed).
///
/// Concrete implementations [BlacklistingTextInputFormatter], which removes
/// blacklisted characters upon edit commit, and
/// [WhitelistingTextInputFormatter], which only allows entries of whitelisted
/// characters, are provided.
/// See also the [FilteringTextInputFormatter], a subclass that
/// removes characters that the user tries to enter if they do, or do
/// not, match a given pattern (as applicable).
///
/// To create custom formatters, extend the [TextInputFormatter] class and
/// implement the [formatEditUpdate] method.
@ -28,9 +28,7 @@ import 'text_input.dart';
/// See also:
///
/// * [EditableText] on which the formatting apply.
/// * [BlacklistingTextInputFormatter], a provided formatter for blacklisting
/// characters.
/// * [WhitelistingTextInputFormatter], a provided formatter for whitelisting
/// * [FilteringTextInputFormatter], a provided formatter for filtering
/// characters.
abstract class TextInputFormatter {
/// Called when text is being typed or cut/copy/pasted in the [EditableText].
@ -77,34 +75,140 @@ class _SimpleTextInputFormatter extends TextInputFormatter {
}
}
/// A [TextInputFormatter] that prevents the insertion of blacklisted
/// characters patterns.
/// A [TextInputFormatter] that prevents the insertion of characters
/// matching (or not matching) a particular pattern.
///
/// Instances of blacklisted characters found in the new [TextEditingValue]s
/// Instances of filtered characters found in the new [TextEditingValue]s
/// will be replaced with the [replacementString] which defaults to the empty
/// string.
///
/// Since this formatter only removes characters from the text, it attempts to
/// preserve the existing [TextEditingValue.selection] to values it would now
/// fall at with the removed characters.
///
/// See also:
///
/// * [WhitelistingTextInputFormatter], which uses a whitelist instead of a
/// blacklist.
class BlacklistingTextInputFormatter extends TextInputFormatter {
/// Creates a formatter that prevents the insertion of blacklisted characters patterns.
class FilteringTextInputFormatter extends TextInputFormatter {
/// Creates a formatter that prevents the insertion of characters
/// based on a filter pattern.
///
/// The [blacklistedPattern] must not be null.
BlacklistingTextInputFormatter(
this.blacklistedPattern, {
/// If [allow] is true, then the filter pattern is an allow list,
/// and characters must match the pattern to be accepted. See also
/// [new FilteringTextInputFormatter.allow].
///
/// If [allow] is false, then the filter pattern is a deny list,
/// and characters that match the pattern are rejected. See also
/// [new FilteringTextInputFormatter.deny].
///
/// The [filterPattern], [allow], and [replacementString] arguments
/// must not be null.
FilteringTextInputFormatter(
this.filterPattern, {
@required this.allow,
this.replacementString = '',
}) : assert(blacklistedPattern != null);
}) : assert(filterPattern != null),
assert(allow != null),
assert(replacementString != null);
/// A [Pattern] to match and replace incoming [TextEditingValue]s.
final Pattern blacklistedPattern;
/// Creates a formatter that only allows characters matching a pattern.
///
/// The [filterPattern] and [replacementString] arguments
/// must not be null.
FilteringTextInputFormatter.allow(
this.filterPattern, {
this.replacementString = '',
}) : assert(filterPattern != null),
assert(replacementString != null),
allow = true;
/// String used to replace found patterns.
/// Creates a formatter that blocks characters matching a pattern.
///
/// The [filterPattern] and [replacementString] arguments
/// must not be null.
FilteringTextInputFormatter.deny(
this.filterPattern, {
this.replacementString = '',
}) : assert(filterPattern != null),
assert(replacementString != null),
allow = false;
/// A [Pattern] to match and replace in incoming [TextEditingValue]s.
///
/// The behaviour of the pattern depends on the [allow] property. If
/// it is true, then this is an allow list, specifying a pattern that
/// characters must match to be accepted. Otherwise, it is a deny list,
/// specifying a pattern that characters must not match to be accepted.
///
/// In general, the pattern should only match one character at a
/// time. See the discussion at [replacementString].
///
/// {@tool snippet}
/// Typically the pattern is a regular expression, as in:
///
/// ```dart
/// var onlyDigits = FilteringTextInputFormatter.allow(RegExp(r'[0-9]'));
/// ```
/// {@end-tool}
///
/// {@tool snippet}
/// If the pattern is a single character, a pattern consisting of a
/// [String] can be used:
///
/// ```dart
/// var noTabs = FilteringTextInputFormatter.deny('\t');
/// ```
/// {@end-tool}
final Pattern filterPattern;
/// Whether the pattern is an allow list or not.
///
/// When true, [filterPattern] denotes an allow list: characters
/// must match the filter to be allowed.
///
/// When false, [filterPattern] denotes a deny list: characters
/// that match the filter are disallowed.
final bool allow;
/// String used to replace banned patterns.
///
/// For deny lists ([allow] is false), each match of the
/// [filterPattern] is replaced with this string. If [filterPattern]
/// can match more than one character at a time, then this can
/// result in multiple characters being replaced by a single
/// instance of this [replacementString].
///
/// For allow lists ([allow] is true), sequences between matches of
/// [filterPattern] are replaced as one, regardless of the number of
/// characters.
///
/// For example, consider a [filterPattern] consisting of just the
/// letter "o", applied to text field whose initial value is the
/// string "Into The Woods", with the [replacementString] set to
/// `*`.
///
/// If [allow] is true, then the result will be "*o*oo*". Each
/// sequence of characters not matching the pattern is replaced by
/// its own single copy of the replacement string, regardless of how
/// many characters are in that sequence.
///
/// If [allow] is false, then the result will be "Int* the W**ds".
/// Every matching sequence is replaced, and each "o" matches the
/// pattern separately.
///
/// If the pattern was the [RegExp] `o+`, the result would be the
/// same in the case where [allow] is true, but in the case where
/// [allow] is false, the result would be "Int* the W*ds" (with the
/// two "o"s replaced by a single occurrence of the replacement
/// string) because both of the "o"s would be matched simultaneously
/// by the pattern.
///
/// Additionally, each segment of the string before, during, and
/// after the current selection in the [TextEditingValue] is handled
/// separately. This means that, in the case of the "Into the Woods"
/// example above, if the selection ended between the two "o"s in
/// "Woods", even if the pattern was `RegExp('o+')`, the result
/// would be "Int* the W**ds", since the two "o"s would be handled
/// in separate passes.
///
/// See also [String.splitMapJoin], which is used to implement this
/// behavior in both cases.
final String replacementString;
@override
@ -115,16 +219,87 @@ class BlacklistingTextInputFormatter extends TextInputFormatter {
return _selectionAwareTextManipulation(
newValue,
(String substring) {
return substring.replaceAll(blacklistedPattern, replacementString);
return substring.splitMapJoin(
filterPattern,
onMatch: !allow ? (Match match) => replacementString : null,
onNonMatch: allow ? (String nonMatch) => nonMatch.isNotEmpty ? replacementString : '' : null,
);
},
);
}
/// A [BlacklistingTextInputFormatter] that forces input to be a single line.
/// A [TextInputFormatter] that forces input to be a single line.
static final TextInputFormatter singleLineFormatter = FilteringTextInputFormatter.deny('\n');
/// A [TextInputFormatter] that takes in digits `[0-9]` only.
static final TextInputFormatter digitsOnly = FilteringTextInputFormatter.allow(RegExp(r'[0-9]'));
}
/// Old name for [FilteringTextInputFormatter.deny].
@Deprecated(
'Use FilteringTextInputFormatter.deny instead. '
'This feature was deprecated after v1.20.0-1.0.pre.'
)
class BlacklistingTextInputFormatter extends FilteringTextInputFormatter {
/// Old name for [FilteringTextInputFormatter.deny].
@Deprecated(
'Use FilteringTextInputFormatter.deny instead. '
'This feature was deprecated after v1.20.0-1.0.pre.'
)
BlacklistingTextInputFormatter(
Pattern blacklistedPattern, {
String replacementString = '',
}) : super.deny(blacklistedPattern, replacementString: replacementString);
/// Old name for [filterPattern].
@Deprecated(
'Use filterPattern instead. '
'This feature was deprecated after v1.20.0-1.0.pre.'
)
Pattern get blacklistedPattern => filterPattern;
/// Old name for [FilteringTextInputFormatter.singleLineFormatter].
@Deprecated(
'Use FilteringTextInputFormatter.singleLineFormatter instead. '
'This feature was deprecated after v1.20.0-1.0.pre.'
)
static final BlacklistingTextInputFormatter singleLineFormatter
= BlacklistingTextInputFormatter(RegExp(r'\n'));
}
/// Old name for [FilteringTextInputFormatter.allow].
// TODO(ianh): Deprecate these once the samples are migrated.
// at-Deprecated(
// 'Use FilteringTextInputFormatter.allow instead. '
// 'This feature was deprecated after v1.20.0-1.0.pre.'
// )
class WhitelistingTextInputFormatter extends FilteringTextInputFormatter {
/// Old name for [FilteringTextInputFormatter.allow].
@Deprecated(
'Use FilteringTextInputFormatter.allow instead. '
'This feature was deprecated after v1.20.0-1.0.pre.'
)
WhitelistingTextInputFormatter(Pattern whitelistedPattern)
: assert(whitelistedPattern != null),
super.allow(whitelistedPattern);
/// Old name for [filterPattern].
@Deprecated(
'Use filterPattern instead. '
'This feature was deprecated after v1.20.0-1.0.pre.'
)
Pattern get whitelistedPattern => filterPattern;
/// Old name for [FilteringTextInputFormatter.digitsOnly].
// TODO(ianh): Deprecate these once the samples are migrated.
// at-Deprecated(
// 'Use FilteringTextInputFormatter.digitsOnly instead. '
// 'This feature was deprecated after v1.20.0-1.0.pre.'
// )
static final WhitelistingTextInputFormatter digitsOnly
= WhitelistingTextInputFormatter(RegExp(r'\d+'));
}
/// A [TextInputFormatter] that prevents the insertion of more characters
/// (currently defined as Unicode scalar values) than allowed.
///
@ -216,50 +391,6 @@ class LengthLimitingTextInputFormatter extends TextInputFormatter {
}
}
/// A [TextInputFormatter] that allows only the insertion of whitelisted
/// characters patterns.
///
/// Since this formatter only removes characters from the text, it attempts to
/// preserve the existing [TextEditingValue.selection] to values it would now
/// fall at with the removed characters.
///
/// See also:
///
/// * [BlacklistingTextInputFormatter], which uses a blacklist instead of a
/// whitelist.
class WhitelistingTextInputFormatter extends TextInputFormatter {
/// Creates a formatter that allows only the insertion of whitelisted characters patterns.
///
/// The [whitelistedPattern] must not be null.
WhitelistingTextInputFormatter(this.whitelistedPattern)
: assert(whitelistedPattern != null);
/// A [Pattern] to extract all instances of allowed characters.
///
/// [RegExp] with multiple groups is not supported.
final Pattern whitelistedPattern;
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, // unused.
TextEditingValue newValue,
) {
return _selectionAwareTextManipulation(
newValue,
(String substring) {
return whitelistedPattern
.allMatches(substring)
.map<String>((Match match) => match.group(0))
.join();
} ,
);
}
/// A [WhitelistingTextInputFormatter] that takes in digits `[0-9]` only.
static final WhitelistingTextInputFormatter digitsOnly
= WhitelistingTextInputFormatter(RegExp(r'\d+'));
}
TextEditingValue _selectionAwareTextManipulation(
TextEditingValue value,
String substringManipulation(String substring),

View file

@ -5709,17 +5709,17 @@ class Listener extends StatelessWidget {
'Use MouseRegion.onEnter instead. See MouseRegion.opaque for behavioral difference. '
'This feature was deprecated after v1.10.14.'
)
this.onPointerEnter, // ignore: deprecated_member_use_from_same_package
this.onPointerEnter,
@Deprecated(
'Use MouseRegion.onExit instead. See MouseRegion.opaque for behavioral difference. '
'This feature was deprecated after v1.10.14.'
)
this.onPointerExit, // ignore: deprecated_member_use_from_same_package
this.onPointerExit,
@Deprecated(
'Use MouseRegion.onHover instead. See MouseRegion.opaque for behavioral difference. '
'This feature was deprecated after v1.10.14.'
)
this.onPointerHover, // ignore: deprecated_member_use_from_same_package
this.onPointerHover,
this.onPointerUp,
this.onPointerCancel,
this.onPointerSignal,

View file

@ -460,7 +460,7 @@ class EditableText extends StatefulWidget {
keyboardType = keyboardType ?? _inferKeyboardType(autofillHints: autofillHints, maxLines: maxLines),
inputFormatters = maxLines == 1
? <TextInputFormatter>[
BlacklistingTextInputFormatter.singleLineFormatter,
FilteringTextInputFormatter.singleLineFormatter,
...inputFormatters ?? const Iterable<TextInputFormatter>.empty(),
]
: inputFormatters,

View file

@ -2846,7 +2846,6 @@ void main() {
isEmpty: true,
decoration: const InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide.none),
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: false,
labelText: 'label',
),
@ -2871,7 +2870,6 @@ void main() {
// isFocused: false (default)
decoration: const InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide.none),
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: false,
labelText: 'label',
),
@ -3941,7 +3939,6 @@ void main() {
helperMaxLines: 6,
hintStyle: TextStyle(),
errorMaxLines: 5,
// ignore: deprecated_member_use_from_same_package
hasFloatingPlaceholder: false,
floatingLabelBehavior: FloatingLabelBehavior.never,
contentPadding: EdgeInsetsDirectional.only(start: 40.0, top: 12.0, bottom: 12.0),

View file

@ -3157,6 +3157,27 @@ void main() {
testWidgets('Injected formatters are chained', (WidgetTester tester) async {
final TextEditingController textController = TextEditingController();
await tester.pumpWidget(boilerplate(
child: TextField(
controller: textController,
decoration: null,
inputFormatters: <TextInputFormatter> [
FilteringTextInputFormatter.deny(
RegExp(r'[a-z]'),
replacementString: '#',
),
],
),
));
await tester.enterText(find.byType(TextField), 'a一b二c三\nd四e五f六');
// The default single line formatter replaces \n with empty string.
expect(textController.text, '#一#二#三#四#五#六');
});
testWidgets('Injected formatters are chained (deprecated names)', (WidgetTester tester) async {
final TextEditingController textController = TextEditingController();
await tester.pumpWidget(boilerplate(
child: TextField(
controller: textController,
@ -3178,6 +3199,33 @@ void main() {
testWidgets('Chained formatters are in sequence', (WidgetTester tester) async {
final TextEditingController textController = TextEditingController();
await tester.pumpWidget(boilerplate(
child: TextField(
controller: textController,
decoration: null,
maxLines: 2,
inputFormatters: <TextInputFormatter> [
FilteringTextInputFormatter.deny(
RegExp(r'[a-z]'),
replacementString: '12\n',
),
FilteringTextInputFormatter.allow(RegExp(r'\n[0-9]')),
],
),
));
await tester.enterText(find.byType(TextField), 'a1b2c3');
// The first formatter turns it into
// 12\n112\n212\n3
// The second formatter turns it into
// \n1\n2\n3
// Multiline is allowed since maxLine != 1.
expect(textController.text, '\n1\n2\n3');
});
testWidgets('Chained formatters are in sequence (deprecated names)', (WidgetTester tester) async {
final TextEditingController textController = TextEditingController();
await tester.pumpWidget(boilerplate(
child: TextField(
controller: textController,
@ -3205,6 +3253,44 @@ void main() {
testWidgets('Pasted values are formatted', (WidgetTester tester) async {
final TextEditingController textController = TextEditingController();
await tester.pumpWidget(
overlay(
child: TextField(
controller: textController,
decoration: null,
inputFormatters: <TextInputFormatter> [
FilteringTextInputFormatter.digitsOnly,
],
),
),
);
await tester.enterText(find.byType(TextField), 'a1b\n2c3');
expect(textController.text, '123');
await skipPastScrollingAnimation(tester);
await tester.tapAt(textOffsetToPosition(tester, '123'.indexOf('2')));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
final RenderEditable renderEditable = findRenderEditable(tester);
final List<TextSelectionPoint> endpoints = globalize(
renderEditable.getEndpointsForSelection(textController.selection),
renderEditable,
);
await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
Clipboard.setData(const ClipboardData(text: '一4二\n5三6'));
await tester.tap(find.text('PASTE'));
await tester.pump();
// Puts 456 before the 2 in 123.
expect(textController.text, '145623');
});
testWidgets('Pasted values are formatted (deprecated names)', (WidgetTester tester) async {
final TextEditingController textController = TextEditingController();
await tester.pumpWidget(
overlay(
child: TextField(
@ -3473,7 +3559,28 @@ void main() {
expect(textController.text, '0123456789');
});
testWidgets('maxLength still works with other formatters.', (WidgetTester tester) async {
testWidgets('maxLength still works with other formatters', (WidgetTester tester) async {
final TextEditingController textController = TextEditingController();
await tester.pumpWidget(boilerplate(
child: TextField(
controller: textController,
maxLength: 10,
inputFormatters: <TextInputFormatter> [
FilteringTextInputFormatter.deny(
RegExp(r'[a-z]'),
replacementString: '#',
),
],
),
));
await tester.enterText(find.byType(TextField), 'a一b二c三\nd四e五f六');
// The default single line formatter replaces \n with empty string.
expect(textController.text, '#一#二#三#四#五');
});
testWidgets('maxLength still works with other formatters (deprecated names)', (WidgetTester tester) async {
final TextEditingController textController = TextEditingController();
await tester.pumpWidget(boilerplate(

View file

@ -86,4 +86,148 @@ void main() {
});
});
});
test('FilteringTextInputFormatter should return the old value if new value contains non-white-listed character', () {
const TextEditingValue oldValue = TextEditingValue(text: '12345');
const TextEditingValue newValue = TextEditingValue(text: '12345@');
final TextInputFormatter formatter = FilteringTextInputFormatter.digitsOnly;
final TextEditingValue formatted = formatter.formatEditUpdate(oldValue, newValue);
// assert that we are passing digits only at the first time
expect(oldValue.text, equals('12345'));
// The new value is always the oldValue plus a non-digit character (user press @)
expect(newValue.text, equals('12345@'));
// we expect that the formatted value returns the oldValue only since the newValue does not
// satisfy the formatter condition (which is, in this case, digitsOnly)
expect(formatted.text, equals('12345'));
});
test('FilteringTextInputFormatter should move the cursor to the right position', () {
TextEditingValue collapsedValue(String text, int offset) =>
TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: offset),
);
TextEditingValue oldValue = collapsedValue('123', 0);
TextEditingValue newValue = collapsedValue('123456', 6);
final TextInputFormatter formatter = FilteringTextInputFormatter.digitsOnly;
TextEditingValue formatted = formatter.formatEditUpdate(oldValue, newValue);
// assert that we are passing digits only at the first time
expect(oldValue.text, equals('123'));
// assert that we are passing digits only at the second time
expect(newValue.text, equals('123456'));
// assert that cursor is at the end of the text
expect(formatted.selection.baseOffset, equals(6));
// move cursor at the middle of the text and then add the number 9.
oldValue = newValue.copyWith(selection: const TextSelection.collapsed(offset: 4));
newValue = oldValue.copyWith(text: '1239456');
formatted = formatter.formatEditUpdate(oldValue, newValue);
// cursor must be now at fourth position (right after the number 9)
expect(formatted.selection.baseOffset, equals(4));
});
test('FilteringTextInputFormatter should remove non-allowed characters', () {
const TextEditingValue oldValue = TextEditingValue(text: '12345');
const TextEditingValue newValue = TextEditingValue(text: '12345@');
final TextInputFormatter formatter = FilteringTextInputFormatter.digitsOnly;
final TextEditingValue formatted = formatter.formatEditUpdate(oldValue, newValue);
// assert that we are passing digits only at the first time
expect(oldValue.text, equals('12345'));
// The new value is always the oldValue plus a non-digit character (user press @)
expect(newValue.text, equals('12345@'));
// we expect that the formatted value returns the oldValue only since the difference
// between the oldValue and the newValue is only material that isn't allowed
expect(formatted.text, equals('12345'));
});
test('WhitelistingTextInputFormatter should return the old value if new value contains non-allowed character', () {
const TextEditingValue oldValue = TextEditingValue(text: '12345');
const TextEditingValue newValue = TextEditingValue(text: '12345@');
final WhitelistingTextInputFormatter formatter = WhitelistingTextInputFormatter.digitsOnly;
final TextEditingValue formatted = formatter.formatEditUpdate(oldValue, newValue);
// assert that we are passing digits only at the first time
expect(oldValue.text, equals('12345'));
// The new value is always the oldValue plus a non-digit character (user press @)
expect(newValue.text, equals('12345@'));
// we expect that the formatted value returns the oldValue only since the newValue does not
// satisfy the formatter condition (which is, in this case, digitsOnly)
expect(formatted.text, equals('12345'));
});
test('FilteringTextInputFormatter should move the cursor to the right position', () {
TextEditingValue collapsedValue(String text, int offset) =>
TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: offset),
);
TextEditingValue oldValue = collapsedValue('123', 0);
TextEditingValue newValue = collapsedValue('123456', 6);
final TextInputFormatter formatter =
FilteringTextInputFormatter.digitsOnly;
TextEditingValue formatted = formatter.formatEditUpdate(oldValue,
newValue);
// assert that we are passing digits only at the first time
expect(oldValue.text, equals('123'));
// assert that we are passing digits only at the second time
expect(newValue.text, equals('123456'));
// assert that cursor is at the end of the text
expect(formatted.selection.baseOffset, equals(6));
// move cursor at the middle of the text and then add the number 9.
oldValue = newValue.copyWith(
selection: const TextSelection.collapsed(offset: 4));
newValue = oldValue.copyWith(text: '1239456');
formatted = formatter.formatEditUpdate(oldValue, newValue);
// cursor must be now at fourth position (right after the number 9)
expect(formatted.selection.baseOffset, equals(4));
});
test('WhitelistingTextInputFormatter should move the cursor to the right position', () {
TextEditingValue collapsedValue(String text, int offset) =>
TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: offset),
);
TextEditingValue oldValue = collapsedValue('123', 0);
TextEditingValue newValue = collapsedValue('123456', 6);
final WhitelistingTextInputFormatter formatter =
WhitelistingTextInputFormatter.digitsOnly;
TextEditingValue formatted = formatter.formatEditUpdate(oldValue,
newValue);
// assert that we are passing digits only at the first time
expect(oldValue.text, equals('123'));
// assert that we are passing digits only at the second time
expect(newValue.text, equals('123456'));
// assert that cursor is at the end of the text
expect(formatted.selection.baseOffset, equals(6));
// move cursor at the middle of the text and then add the number 9.
oldValue = newValue.copyWith(
selection: const TextSelection.collapsed(offset: 4));
newValue = oldValue.copyWith(text: '1239456');
formatted = formatter.formatEditUpdate(oldValue, newValue);
// cursor must be now at fourth position (right after the number 9)
expect(formatted.selection.baseOffset, equals(4));
});
}

View file

@ -13,10 +13,6 @@ import 'package:flutter/gestures.dart';
// The tests in this file are moved from listener_test.dart, which tests several
// deprecated APIs. The file should be removed once these parameters are.
// ignore_for_file: deprecated_member_use_from_same_package
// We have to ignore the lint rule here because we need to use the deprecated
// callbacks in order to test them.
class HoverClient extends StatefulWidget {
const HoverClient({Key key, this.onHover, this.child}) : super(key: key);

View file

@ -9,11 +9,10 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
void main() {
TextEditingValue testOldValue;
const TextEditingValue testOldValue = TextEditingValue();
TextEditingValue testNewValue;
test('withFunction wraps formatting function', () {
testOldValue = const TextEditingValue();
testNewValue = const TextEditingValue();
TextEditingValue calledOldValue;
@ -47,7 +46,62 @@ void main() {
);
});
test('test blacklisting formatter', () {
test('test filtering formatter example', () {
const TextEditingValue intoTheWoods = TextEditingValue(text: 'Into the Woods');
expect(
FilteringTextInputFormatter('o', allow: true, replacementString: '*').formatEditUpdate(testOldValue, intoTheWoods),
const TextEditingValue(text: '*o*oo*'),
);
expect(
FilteringTextInputFormatter('o', allow: false, replacementString: '*').formatEditUpdate(testOldValue, intoTheWoods),
const TextEditingValue(text: 'Int* the W**ds'),
);
expect(
FilteringTextInputFormatter(RegExp('o+'), allow: true, replacementString: '*').formatEditUpdate(testOldValue, intoTheWoods),
const TextEditingValue(text: '*o*oo*'),
);
expect(
FilteringTextInputFormatter(RegExp('o+'), allow: false, replacementString: '*').formatEditUpdate(testOldValue, intoTheWoods),
const TextEditingValue(text: 'Int* the W*ds'),
);
const TextEditingValue selectedIntoTheWoods = TextEditingValue(text: 'Into the Woods', selection: TextSelection(baseOffset: 11, extentOffset: 14));
expect(
FilteringTextInputFormatter('o', allow: true, replacementString: '*').formatEditUpdate(testOldValue, selectedIntoTheWoods),
const TextEditingValue(text: '*o*oo*', selection: TextSelection(baseOffset: 4, extentOffset: 6)),
);
expect(
FilteringTextInputFormatter('o', allow: false, replacementString: '*').formatEditUpdate(testOldValue, selectedIntoTheWoods),
const TextEditingValue(text: 'Int* the W**ds', selection: TextSelection(baseOffset: 11, extentOffset: 14)),
);
expect(
FilteringTextInputFormatter(RegExp('o+'), allow: true, replacementString: '*').formatEditUpdate(testOldValue, selectedIntoTheWoods),
const TextEditingValue(text: '*o*oo*', selection: TextSelection(baseOffset: 4, extentOffset: 6)),
);
expect(
FilteringTextInputFormatter(RegExp('o+'), allow: false, replacementString: '*').formatEditUpdate(testOldValue, selectedIntoTheWoods),
const TextEditingValue(text: 'Int* the W**ds', selection: TextSelection(baseOffset: 11, extentOffset: 14)),
);
});
test('test filtering formatter, deny mode', () {
final TextEditingValue actualValue =
FilteringTextInputFormatter.deny(RegExp(r'[a-z]'))
.formatEditUpdate(testOldValue, testNewValue);
// Expecting
// 1(23
// 4)56
expect(actualValue, const TextEditingValue(
text: '123\n456',
selection: TextSelection(
baseOffset: 1,
extentOffset: 5,
),
));
});
test('test filtering formatter, deny mode (deprecated names)', () {
final TextEditingValue actualValue =
BlacklistingTextInputFormatter(RegExp(r'[a-z]'))
.formatEditUpdate(testOldValue, testNewValue);
@ -65,6 +119,22 @@ void main() {
});
test('test single line formatter', () {
final TextEditingValue actualValue =
FilteringTextInputFormatter.singleLineFormatter
.formatEditUpdate(testOldValue, testNewValue);
// Expecting
// a1b(2c3d4)e5f6
expect(actualValue, const TextEditingValue(
text: 'a1b2c3d4e5f6',
selection: TextSelection(
baseOffset: 3,
extentOffset: 8,
),
));
});
test('test single line formatter (deprecated names)', () {
final TextEditingValue actualValue =
BlacklistingTextInputFormatter.singleLineFormatter
.formatEditUpdate(testOldValue, testNewValue);
@ -80,7 +150,23 @@ void main() {
));
});
test('test whitelisting formatter', () {
test('test filtering formatter, allow mode', () {
final TextEditingValue actualValue =
FilteringTextInputFormatter.allow(RegExp(r'[a-c]'))
.formatEditUpdate(testOldValue, testNewValue);
// Expecting
// ab(c)
expect(actualValue, const TextEditingValue(
text: 'abc',
selection: TextSelection(
baseOffset: 2,
extentOffset: 3,
),
));
});
test('test filtering formatter, allow mode (deprecated names)', () {
final TextEditingValue actualValue =
WhitelistingTextInputFormatter(RegExp(r'[a-c]'))
.formatEditUpdate(testOldValue, testNewValue);
@ -97,6 +183,22 @@ void main() {
});
test('test digits only formatter', () {
final TextEditingValue actualValue =
FilteringTextInputFormatter.digitsOnly
.formatEditUpdate(testOldValue, testNewValue);
// Expecting
// 1(234)56
expect(actualValue, const TextEditingValue(
text: '123456',
selection: TextSelection(
baseOffset: 1,
extentOffset: 4,
),
));
});
test('test digits only formatter (deprecated names)', () {
final TextEditingValue actualValue =
WhitelistingTextInputFormatter.digitsOnly
.formatEditUpdate(testOldValue, testNewValue);
@ -246,6 +348,5 @@ void main() {
),
));
});
});
}

View file

@ -129,9 +129,9 @@ class FlutterDriverExtension {
'waitFor': _waitFor,
'waitForAbsent': _waitForAbsent,
'waitForCondition': _waitForCondition,
'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks, // ignore: deprecated_member_use_from_same_package
'waitUntilNoPendingFrame': _waitUntilNoPendingFrame, // ignore: deprecated_member_use_from_same_package
'waitUntilFirstFrameRasterized': _waitUntilFirstFrameRasterized, // ignore: deprecated_member_use_from_same_package
'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks,
'waitUntilNoPendingFrame': _waitUntilNoPendingFrame,
'waitUntilFirstFrameRasterized': _waitUntilFirstFrameRasterized,
'get_semantics_id': _getSemanticsId,
'get_offset': _getOffset,
'get_diagnostics_tree': _getDiagnosticsTree,
@ -153,9 +153,9 @@ class FlutterDriverExtension {
'waitFor': (Map<String, String> params) => WaitFor.deserialize(params),
'waitForAbsent': (Map<String, String> params) => WaitForAbsent.deserialize(params),
'waitForCondition': (Map<String, String> params) => WaitForCondition.deserialize(params),
'waitUntilNoTransientCallbacks': (Map<String, String> params) => WaitUntilNoTransientCallbacks.deserialize(params), // ignore: deprecated_member_use_from_same_package
'waitUntilNoPendingFrame': (Map<String, String> params) => WaitUntilNoPendingFrame.deserialize(params), // ignore: deprecated_member_use_from_same_package
'waitUntilFirstFrameRasterized': (Map<String, String> params) => WaitUntilFirstFrameRasterized.deserialize(params), // ignore: deprecated_member_use_from_same_package
'waitUntilNoTransientCallbacks': (Map<String, String> params) => WaitUntilNoTransientCallbacks.deserialize(params),
'waitUntilNoPendingFrame': (Map<String, String> params) => WaitUntilNoPendingFrame.deserialize(params),
'waitUntilFirstFrameRasterized': (Map<String, String> params) => WaitUntilFirstFrameRasterized.deserialize(params),
'get_semantics_id': (Map<String, String> params) => GetSemanticsId.deserialize(params),
'get_offset': (Map<String, String> params) => GetOffset.deserialize(params),
'get_diagnostics_tree': (Map<String, String> params) => GetDiagnosticsTree.deserialize(params),

View file

@ -41,7 +41,7 @@ void main() {
});
testWidgets('returns immediately when transient callback queue is empty', (WidgetTester tester) async {
extension.call(const WaitUntilNoTransientCallbacks().serialize()) // ignore: deprecated_member_use_from_same_package
extension.call(const WaitUntilNoTransientCallbacks().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
@ -61,7 +61,7 @@ void main() {
// Intentionally blank. We only care about existence of a callback.
});
extension.call(const WaitUntilNoTransientCallbacks().serialize()) // ignore: deprecated_member_use_from_same_package
extension.call(const WaitUntilNoTransientCallbacks().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
@ -888,7 +888,7 @@ void main() {
testWidgets('returns immediately when frame is synced', (
WidgetTester tester) async {
extension.call(const WaitUntilNoPendingFrame().serialize()) // ignore: deprecated_member_use_from_same_package
extension.call(const WaitUntilNoPendingFrame().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
@ -909,7 +909,7 @@ void main() {
// Intentionally blank. We only care about existence of a callback.
});
extension.call(const WaitUntilNoPendingFrame().serialize()) // ignore: deprecated_member_use_from_same_package
extension.call(const WaitUntilNoPendingFrame().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));
@ -933,7 +933,7 @@ void main() {
'waits until no pending scheduled frame', (WidgetTester tester) async {
SchedulerBinding.instance.scheduleFrame();
extension.call(const WaitUntilNoPendingFrame().serialize()) // ignore: deprecated_member_use_from_same_package
extension.call(const WaitUntilNoPendingFrame().serialize())
.then<void>(expectAsync1((Map<String, dynamic> r) {
result = r;
}));