diff --git a/packages/flutter/lib/src/services/text_formatter.dart b/packages/flutter/lib/src/services/text_formatter.dart index f967cf6d5b..b9420e012d 100644 --- a/packages/flutter/lib/src/services/text_formatter.dart +++ b/packages/flutter/lib/src/services/text_formatter.dart @@ -502,7 +502,7 @@ class LengthLimitingTextInputFormatter extends TextInputFormatter { case MaxLengthEnforcement.enforced: // If already at the maximum and tried to enter even more, and has no // selection, keep the old value. - if (oldValue.text.characters.length == maxLength && !oldValue.selection.isValid) { + if (oldValue.text.characters.length == maxLength && oldValue.selection.isCollapsed) { return oldValue; } diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 857ddd03d8..d8ad879516 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -3864,6 +3864,90 @@ void main() { expect(textController.text, testValue); }); + testWidgets( + 'maxLength limits input in the center of a maxed-out field, with collapsed selection', + (WidgetTester tester) async { + final TextEditingController textController = TextEditingController(); + const String testValue = '0123456789'; + + await tester.pumpWidget(boilerplate( + child: TextField( + controller: textController, + maxLength: 10, + ), + )); + + // Max out the character limit in the field. + await tester.showKeyboard(find.byType(TextField)); + tester.testTextInput.updateEditingValue(const TextEditingValue( + text: testValue, + selection: TextSelection.collapsed(offset: 10), + composing: TextRange.empty, + )); + await tester.pump(); + expect(textController.text, testValue); + + // Entering more characters at the end does nothing. + await tester.showKeyboard(find.byType(TextField)); + tester.testTextInput.updateEditingValue(const TextEditingValue( + text: testValue + '9999999', + selection: TextSelection.collapsed(offset: 10 + 7), + composing: TextRange.empty, + )); + await tester.pump(); + + expect(textController.text, testValue); + + // Entering text in the middle of the field also does nothing. + // Entering more characters at the end does nothing. + await tester.showKeyboard(find.byType(TextField)); + tester.testTextInput.updateEditingValue(const TextEditingValue( + text: '0123455555555556789', + selection: TextSelection.collapsed(offset: 19), + composing: TextRange.empty, + )); + await tester.pump(); + + expect(textController.text, testValue); + }, + ); + + testWidgets( + 'maxLength limits input in the center of a maxed-out field, with non-collapsed selection', + (WidgetTester tester) async { + final TextEditingController textController = TextEditingController(); + const String testValue = '0123456789'; + + await tester.pumpWidget(boilerplate( + child: TextField( + controller: textController, + maxLength: 10, + ), + )); + + // Max out the character limit in the field. + await tester.showKeyboard(find.byType(TextField)); + tester.testTextInput.updateEditingValue(const TextEditingValue( + text: testValue, + selection: TextSelection(baseOffset: 8, extentOffset: 10), + composing: TextRange.empty, + )); + await tester.pump(); + expect(textController.text, testValue); + + // Entering more characters at the end does nothing. + await tester.showKeyboard(find.byType(TextField)); + tester.testTextInput.updateEditingValue(const TextEditingValue( + text: '01234569999999', + selection: TextSelection.collapsed(offset: 14), + composing: TextRange.empty, + )); + await tester.pump(); + + expect(textController.text, '0123456999'); + }, + ); + testWidgets('maxLength limits input length even if decoration is null.', (WidgetTester tester) async { final TextEditingController textController = TextEditingController();