diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 9c922a98ba..915b26f02f 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -486,7 +486,7 @@ class TextPainter { final double caretEnd = box.end; final double dx = box.direction == TextDirection.rtl ? caretEnd - caretPrototype.width : caretEnd; - return Offset(dx, box.top); + return Offset(min(dx, width), box.top); } return null; } @@ -526,7 +526,7 @@ class TextPainter { final TextBox box = boxes.last; final double caretStart = box.start; final double dx = box.direction == TextDirection.rtl ? caretStart - caretPrototype.width : caretStart; - return Offset(dx, box.top); + return Offset(min(dx, width), box.top); } return null; } diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index d6f47fd8ba..a8811bfe2a 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -426,6 +426,82 @@ void main() { }, skip: !Platform.isLinux); */ + testWidgets('Overflowing a line with spaces stops the cursor at the end', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + overlay( + child: TextField( + key: textFieldKey, + controller: controller, + maxLines: null, + ), + ) + ); + expect(controller.selection.baseOffset, -1); + expect(controller.selection.extentOffset, -1); + + const String testValueOneLine = 'enough text to be exactly at the end of the line.'; + await tester.enterText(find.byType(TextField), testValueOneLine); + await skipPastScrollingAnimation(tester); + + RenderBox findInputBox() => tester.renderObject(find.byKey(textFieldKey)); + + RenderBox inputBox = findInputBox(); + final Size oneLineInputSize = inputBox.size; + + await tester.tapAt(textOffsetToPosition(tester, testValueOneLine.length)); + await tester.pump(); + + const String testValueTwoLines = 'enough text to overflow the first line and go to the second'; + await tester.enterText(find.byType(TextField), testValueTwoLines); + await skipPastScrollingAnimation(tester); + + expect(inputBox, findInputBox()); + inputBox = findInputBox(); + expect(inputBox.size.height, greaterThan(oneLineInputSize.height)); + final Size twoLineInputSize = inputBox.size; + + // Enter a string with the same number of characters as testValueTwoLines, + // but where the overflowing part is all spaces. Assert that it only renders + // on one line. + const String testValueSpaces = testValueOneLine + ' '; + expect(testValueSpaces.length, testValueTwoLines.length); + await tester.enterText(find.byType(TextField), testValueSpaces); + await skipPastScrollingAnimation(tester); + + expect(inputBox, findInputBox()); + inputBox = findInputBox(); + expect(inputBox.size.height, oneLineInputSize.height); + + // Swapping the final space for a letter causes it to wrap to 2 lines. + const String testValueSpacesOverflow = testValueOneLine + ' a'; + expect(testValueSpacesOverflow.length, testValueTwoLines.length); + await tester.enterText(find.byType(TextField), testValueSpacesOverflow); + await skipPastScrollingAnimation(tester); + + expect(inputBox, findInputBox()); + inputBox = findInputBox(); + expect(inputBox.size.height, twoLineInputSize.height); + + // Positioning the cursor at the end of a line overflowing with spaces puts + // it inside the input still. + await tester.enterText(find.byType(TextField), testValueSpaces); + await skipPastScrollingAnimation(tester); + await tester.tapAt(textOffsetToPosition(tester, testValueSpaces.length)); + await tester.pump(); + + final double inputWidth = findRenderEditable(tester).size.width; + final Offset cursorOffsetSpaces = findRenderEditable(tester).getLocalRectForCaret( + const TextPosition(offset: testValueSpaces.length), + ).bottomRight; + + // Gap between caret and edge of input, defined in editable.dart. + const int _kCaretGap = 1; + + expect(cursorOffsetSpaces.dx, inputWidth - _kCaretGap); + }); + testWidgets('obscureText control test', (WidgetTester tester) async { await tester.pumpWidget( overlay( diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart index e5f65a90ef..80d0c2228a 100644 --- a/packages/flutter/test/painting/text_painter_test.dart +++ b/packages/flutter/test/painting/text_painter_test.dart @@ -133,7 +133,9 @@ void main() { Offset caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0), ui.Rect.zero); expect(caretOffset.dx, 21); caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: text.length), ui.Rect.zero); - expect(caretOffset.dx, 441); + // The end of the line is 441, but the width is only 420, so the cursor is + // stopped there without overflowing. + expect(caretOffset.dx, painter.width); caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1), ui.Rect.zero); expect(caretOffset.dx, 35);