diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 96785c0b0f..c374cc4735 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -511,44 +511,43 @@ class EditableTextState extends State with AutomaticKeepAliveClien void performAction(TextInputAction action) { switch (action) { case TextInputAction.newline: - // Do nothing for a "newline" action: the newline is already inserted. + // If this is a multiline EditableText, do nothing for a "newline" + // action; The newline is already inserted. Otherwise, finalize + // editing. + if (widget.maxLines == 1) + _finalizeEditing(true); break; case TextInputAction.done: case TextInputAction.go: case TextInputAction.send: case TextInputAction.search: - // Take any actions necessary now that the user has completed editing. - if (widget.onEditingComplete != null) { - widget.onEditingComplete(); - } else { - // Default behavior if the developer did not provide an - // onEditingComplete callback: Finalize editing and remove focus. - widget.controller.clearComposing(); - widget.focusNode.unfocus(); - } - - // Invoke optional callback with the user's submitted content. - if (widget.onSubmitted != null) - widget.onSubmitted(_value.text); + _finalizeEditing(true); break; default: - if (widget.onEditingComplete != null) { - widget.onEditingComplete(); - } else { - // Default behavior if the developer did not provide an - // onEditingComplete callback: Finalize editing, but don't give up - // focus because this keyboard action does not imply the user is done - // inputting information. - widget.controller.clearComposing(); - } - - // Invoke optional callback with the user's submitted content. - if (widget.onSubmitted != null) - widget.onSubmitted(_value.text); + // Finalize editing, but don't give up focus because this keyboard + // action does not imply the user is done inputting information. + _finalizeEditing(false); break; } } + void _finalizeEditing(bool shouldUnfocus) { + // Take any actions necessary now that the user has completed editing. + if (widget.onEditingComplete != null) { + widget.onEditingComplete(); + } else { + // Default behavior if the developer did not provide an + // onEditingComplete callback: Finalize editing and remove focus. + widget.controller.clearComposing(); + if (shouldUnfocus) + widget.focusNode.unfocus(); + } + + // Invoke optional callback with the user's submitted content. + if (widget.onSubmitted != null) + widget.onSubmitted(_value.text); + } + void _updateRemoteEditingValueIfNeeded() { if (!_hasInputConnection) return; diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 8b502d893d..f4a1b86f55 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -745,6 +745,94 @@ void main() { // and onSubmission callbacks. }); + testWidgets( + 'When "newline" action is called on a Editable text with maxLines == 1 callbacks are invoked: onEditingComplete > onSubmitted', + (WidgetTester tester) async { + final GlobalKey editableTextKey = + GlobalKey(); + final FocusNode focusNode = FocusNode(); + + bool onEditingCompleteCalled = false; + bool onSubmittedCalled = false; + + final Widget widget = MaterialApp( + home: EditableText( + key: editableTextKey, + controller: TextEditingController(), + focusNode: focusNode, + style: Typography(platform: TargetPlatform.android).black.subhead, + cursorColor: Colors.blue, + maxLines: 1, + onEditingComplete: () { + onEditingCompleteCalled = true; + assert(!onSubmittedCalled); + }, + onSubmitted: (String value) { + onSubmittedCalled = true; + assert(onEditingCompleteCalled); + }, + ), + ); + await tester.pumpWidget(widget); + + // Select EditableText to give it focus. + final Finder textFinder = find.byKey(editableTextKey); + await tester.tap(textFinder); + await tester.pump(); + + assert(focusNode.hasFocus); + + // The execution path starting with receiveAction() will trigger the + // onEditingComplete and onSubmission callbacks. + await tester.testTextInput.receiveAction(TextInputAction.newline); + // The expectations we care about are up above in the onEditingComplete + // and onSubmission callbacks. + }); + +testWidgets( + 'When "newline" action is called on a Editable text with maxLines != 1, onEditingComplete and onSubmitted callbacks are not invoked.', + (WidgetTester tester) async { + final GlobalKey editableTextKey = + GlobalKey(); + final FocusNode focusNode = FocusNode(); + + bool onEditingCompleteCalled = false; + bool onSubmittedCalled = false; + + final Widget widget = MaterialApp( + home: EditableText( + key: editableTextKey, + controller: TextEditingController(), + focusNode: focusNode, + style: Typography(platform: TargetPlatform.android).black.subhead, + cursorColor: Colors.blue, + maxLines: 3, + onEditingComplete: () { + onEditingCompleteCalled = true; + }, + onSubmitted: (String value) { + onSubmittedCalled = true; + }, + ), + ); + await tester.pumpWidget(widget); + + // Select EditableText to give it focus. + final Finder textFinder = find.byKey(editableTextKey); + await tester.tap(textFinder); + await tester.pump(); + + assert(focusNode.hasFocus); + + // The execution path starting with receiveAction() will trigger the + // onEditingComplete and onSubmission callbacks. + await tester.testTextInput.receiveAction(TextInputAction.newline); + + // These callbacks shouldn't have been triggered. + assert(!onSubmittedCalled); + assert(!onEditingCompleteCalled); + }); + testWidgets('Changing controller updates EditableText', (WidgetTester tester) async { final GlobalKey editableTextKey =