diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 840f490dd1..7af291079a 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'colors.dart'; +import 'constants.dart'; import 'input_border.dart'; import 'theme.dart'; @@ -496,6 +497,7 @@ class _Decoration { this.counter, this.container, this.alignLabelWithHint, + this.isDense, }) : assert(contentPadding != null), assert(isCollapsed != null), assert(floatingLabelHeight != null), @@ -508,6 +510,7 @@ class _Decoration { final InputBorder border; final _InputBorderGap borderGap; final bool alignLabelWithHint; + final bool isDense; final Widget icon; final Widget input; final Widget label; @@ -1037,10 +1040,19 @@ class _RenderDecoration extends RenderBox { + fixBelowInput + contentPadding.bottom, ); + final double minContainerHeight = decoration.isDense || expands + ? 0.0 + : kMinInteractiveDimension; final double maxContainerHeight = boxConstraints.maxHeight - bottomHeight; final double containerHeight = expands ? maxContainerHeight - : math.min(contentHeight, maxContainerHeight); + : math.min(math.max(contentHeight, minContainerHeight), maxContainerHeight); + + // Ensure the text is vertically centered in cases where the content is + // shorter than kMinInteractiveDimension. + final double interactiveAdjustment = minContainerHeight > contentHeight + ? (minContainerHeight - contentHeight) / 2.0 + : 0.0; // Try to consider the prefix/suffix as part of the text when aligning it. // If the prefix/suffix overflows however, allow it to extend outside of the @@ -1058,7 +1070,8 @@ class _RenderDecoration extends RenderBox { final double topInputBaseline = contentPadding.top + topHeight + inputInternalBaseline - + baselineAdjustment; + + baselineAdjustment + + interactiveAdjustment; final double maxContentHeight = containerHeight - contentPadding.top - topHeight @@ -2156,7 +2169,10 @@ class _InputDecoratorState extends State with TickerProviderStat widthFactor: 1.0, heightFactor: 1.0, child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0), + constraints: const BoxConstraints( + minWidth: kMinInteractiveDimension, + minHeight: kMinInteractiveDimension, + ), child: IconTheme.merge( data: IconThemeData( color: iconColor, @@ -2172,7 +2188,10 @@ class _InputDecoratorState extends State with TickerProviderStat widthFactor: 1.0, heightFactor: 1.0, child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0), + constraints: const BoxConstraints( + minWidth: kMinInteractiveDimension, + minHeight: kMinInteractiveDimension, + ), child: IconTheme.merge( data: IconThemeData( color: iconColor, @@ -2253,6 +2272,7 @@ class _InputDecoratorState extends State with TickerProviderStat input: widget.child, label: label, alignLabelWithHint: decoration.alignLabelWithHint, + isDense: decoration.isDense, hint: hint, prefix: prefix, suffix: suffix, diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 9d5e78bd7e..c704192e3d 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -509,12 +509,12 @@ void main() { ), ); - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 40.0)); - expect(tester.getTopLeft(find.text('text')).dy, 12.0); - expect(tester.getBottomLeft(find.text('text')).dy, 28.0); - expect(tester.getTopLeft(find.text('hint')).dy, 12.0); - expect(tester.getBottomLeft(find.text('hint')).dy, 28.0); - expect(getBorderBottom(tester), 40.0); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); + expect(tester.getTopLeft(find.text('text')).dy, 16.0); + expect(tester.getBottomLeft(find.text('text')).dy, 32.0); + expect(tester.getTopLeft(find.text('hint')).dy, 16.0); + expect(tester.getBottomLeft(find.text('hint')).dy, 32.0); + expect(getBorderBottom(tester), 48.0); expect(getBorderWeight(tester), 1.0); expect(tester.getSize(find.text('hint')).width, tester.getSize(find.text('text')).width); @@ -1144,14 +1144,14 @@ void main() { // The prefix and suffix wrap the input text and are left and right justified // respectively. They should have the same height as the input text (16). - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 40.0)); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); expect(tester.getSize(find.text('text')).height, 16.0); expect(tester.getSize(find.text('p')).height, 16.0); expect(tester.getSize(find.text('s')).height, 16.0); - expect(tester.getTopLeft(find.text('text')).dy, 12.0); - expect(tester.getTopLeft(find.text('p')).dy, 12.0); + expect(tester.getTopLeft(find.text('text')).dy, 16.0); + expect(tester.getTopLeft(find.text('p')).dy, 16.0); expect(tester.getTopLeft(find.text('p')).dx, 12.0); - expect(tester.getTopLeft(find.text('s')).dy, 12.0); + expect(tester.getTopLeft(find.text('s')).dy, 16.0); expect(tester.getTopRight(find.text('s')).dx, 788.0); // layout is a row: [p text s] @@ -1179,18 +1179,18 @@ void main() { // 16 - input text (ahem font size 16dps) // 12 - bottom padding - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 40.0)); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); expect(tester.getSize(find.text('text')).height, 16.0); expect(tester.getSize(find.text('p')).height, 16.0); expect(tester.getSize(find.text('s')).height, 16.0); - expect(tester.getTopLeft(find.text('text')).dy, 12.0); - expect(tester.getTopLeft(find.text('p')).dy, 12.0); - expect(tester.getTopLeft(find.text('s')).dy, 12.0); + expect(tester.getTopLeft(find.text('text')).dy, 16.0); + expect(tester.getTopLeft(find.text('p')).dy, 16.0); + expect(tester.getTopLeft(find.text('s')).dy, 16.0); expect(tester.getTopRight(find.text('s')).dx, 788.0); expect(tester.getSize(find.byType(Icon)).height, 24.0); // The 24dps high icon is centered on the 16dps high input line - expect(tester.getTopLeft(find.byType(Icon)).dy, 8.0); + expect(tester.getTopLeft(find.byType(Icon)).dy, 12.0); // layout is a row: [icon, p text s] expect(tester.getTopLeft(find.byType(Icon)).dx, 0.0); @@ -1939,10 +1939,10 @@ void main() { ), ); - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 44.0)); - expect(tester.getTopLeft(find.text('text')).dy, 13.0); - expect(tester.getBottomLeft(find.text('text')).dy, 29.0); - expect(getBorderBottom(tester), 44.0); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); + expect(tester.getTopLeft(find.text('text')).dy, 15.0); + expect(tester.getBottomLeft(find.text('text')).dy, 31.0); + expect(getBorderBottom(tester), 48.0); expect(getBorderWeight(tester), 1.0); }); @@ -1960,10 +1960,10 @@ void main() { ), ); - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 44.0)); - expect(tester.getTopLeft(find.text('text')).dy, 13.0); - expect(tester.getBottomLeft(find.text('text')).dy, 29.0); - expect(getBorderBottom(tester), 44.0); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); + expect(tester.getTopLeft(find.text('text')).dy, 15.0); + expect(tester.getBottomLeft(find.text('text')).dy, 31.0); + expect(getBorderBottom(tester), 48.0); expect(getBorderWeight(tester), 1.0); }); @@ -2098,7 +2098,7 @@ void main() { // Margin for text decoration is 12 when filled // (dx) - 12 = (text offset)x. - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 60.0)); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 68.0)); final double dx = tester.getRect(find.byType(InputDecorator)).right; expect(tester.getRect(find.text('test')).right, dx - 12.0); }); @@ -2118,7 +2118,7 @@ void main() { // Margin for text decoration is 12 when filled and top left offset is (0, 0) // 0 + 12 = 12. - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 60.0)); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 68.0)); expect(tester.getRect(find.text('test')).left, 12.0); }); @@ -2236,13 +2236,13 @@ void main() { // 16 - input text (ahem font size 16dps) // 12 - bottom padding - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 40.0)); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); // 40 bumped up to minimum. expect(tester.getSize(find.text('text')).height, 16.0); expect(tester.getSize(find.text('p')).height, 16.0); expect(tester.getSize(find.text('s')).height, 16.0); - expect(tester.getTopLeft(find.text('text')).dy, 12.0); - expect(tester.getTopLeft(find.text('p')).dy, 12.0); - expect(tester.getTopLeft(find.text('s')).dy, 12.0); + expect(tester.getTopLeft(find.text('text')).dy, 16.0); + expect(tester.getTopLeft(find.text('p')).dy, 16.0); + expect(tester.getTopLeft(find.text('s')).dy, 16.0); // layout is a row: [s text p] expect(tester.getTopLeft(find.text('s')).dx, 12.0); @@ -2339,10 +2339,10 @@ void main() { // 16 - input text (ahem font size 16dps) // 12 - bottom padding - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 40.0)); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); // 40 bumped up to minimum. expect(tester.getSize(find.text('text')).height, 16.0); - expect(tester.getTopLeft(find.text('text')).dy, 12.0); - expect(getBorderBottom(tester), 40.0); + expect(tester.getTopLeft(find.text('text')).dy, 16.0); + expect(getBorderBottom(tester), kMinInteractiveDimension); // 40 bumped up to minimum. expect(getBorderWeight(tester), 1.0); }, skip: isBrowser); @@ -2360,9 +2360,9 @@ void main() { // Overall height for this InputDecorator is 16dps: // 16 - input text (ahem font size 16dps) - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 16.0)); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); // 16 bumped up to minimum. expect(tester.getSize(find.text('text')).height, 16.0); - expect(tester.getTopLeft(find.text('text')).dy, 0.0); + expect(tester.getTopLeft(find.text('text')).dy, 16.0); expect(getOpacity(tester, 'hint'), 0.0); expect(getBorderWeight(tester), 0.0); @@ -2378,11 +2378,11 @@ void main() { ); await tester.pumpAndSettle(); - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 16.0)); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); expect(tester.getSize(find.text('text')).height, 16.0); - expect(tester.getTopLeft(find.text('text')).dy, 0.0); + expect(tester.getTopLeft(find.text('text')).dy, 16.0); expect(tester.getSize(find.text('hint')).height, 16.0); - expect(tester.getTopLeft(find.text('hint')).dy, 0.0); + expect(tester.getTopLeft(find.text('hint')).dy, 16.0); expect(getBorderWeight(tester), 0.0); }, skip: isBrowser); @@ -2418,13 +2418,13 @@ void main() { // 10 - label (ahem font size 10dps) // 17.75 - bottom padding (empty input text still appears here) - expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 45.5)); + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); // 45.5 bumped up to minimum. expect(tester.getSize(find.text('hint')).height, 10.0); expect(tester.getSize(find.text('label')).height, 10.0); expect(tester.getSize(find.text('text')).height, 10.0); - expect(tester.getTopLeft(find.text('hint')).dy, 23.5); - expect(tester.getTopLeft(find.text('label')).dy, 17.75); - expect(tester.getTopLeft(find.text('text')).dy, 23.5); + expect(tester.getTopLeft(find.text('hint')).dy, 24.75); + expect(tester.getTopLeft(find.text('label')).dy, 19.0); + expect(tester.getTopLeft(find.text('text')).dy, 24.75); }, skip: isBrowser); testWidgets('InputDecorator with empty style overrides', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 368690e2e4..625ddb718c 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -519,10 +519,10 @@ void main() { // This tap just puts the cursor somewhere different than where the double // tap will occur to test that the double tap moves the existing cursor first. - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(const Duration(milliseconds: 500)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor. expect( @@ -530,7 +530,7 @@ void main() { const TextSelection.collapsed( offset: 8, affinity: TextAffinity.downstream), ); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(); // Second tap selects the word around the cursor. @@ -566,10 +566,10 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(); // Selected text shows 'COPY', and not 'PASTE', 'CUT', 'SELECT ALL'. @@ -2870,7 +2870,7 @@ void main() { ), ); - expect(tester.getTopLeft(find.text('hint')), equals(tester.getTopLeft(find.byType(TextField)))); + expect(tester.getTopLeft(find.text('hint')), equals(tester.getTopLeft(find.byType(EditableText)))); }); testWidgets('Can align to center', (WidgetTester tester) async { @@ -5523,7 +5523,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(); // We moved the cursor. @@ -5557,7 +5557,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(); // We moved the cursor. @@ -5592,9 +5592,9 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(const Duration(milliseconds: 500)); - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(); // Plain collapsed selection. @@ -5631,17 +5631,17 @@ void main() { // This tap just puts the cursor somewhere different than where the double // tap will occur to test that the double tap moves the existing cursor first. - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(const Duration(milliseconds: 500)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor. expect( controller.selection, const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(); // Second tap selects the word around the cursor. @@ -5677,17 +5677,17 @@ void main() { // This tap just puts the cursor somewhere different than where the double // tap will occur to test that the double tap moves the existing cursor first. - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(const Duration(milliseconds: 500)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor. expect( controller.selection, const TextSelection.collapsed(offset: 9), ); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(); // Second tap selects the word around the cursor. @@ -5896,10 +5896,10 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); final TestGesture gesture = - await tester.startGesture(textfieldStart + const Offset(150.0, 5.0)); + await tester.startGesture(textfieldStart + const Offset(150.0, 9.0)); // Hold the press. await tester.pump(const Duration(milliseconds: 500)); @@ -5945,17 +5945,17 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor. expect( controller.selection, const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 500)); - await tester.tapAt(textfieldStart + const Offset(100.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(100.0, 9.0)); await tester.pump(); // Plain collapsed selection at the edge of first word. In iOS 12, the @@ -5993,7 +5993,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.longPressAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.longPressAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(); // Collapsed cursor for iOS long press. @@ -6027,7 +6027,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.longPressAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.longPressAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(); expect( @@ -6061,10 +6061,10 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.longPressAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.longPressAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(); // We ended up moving the cursor to the edge of the same word and dismissed @@ -6101,7 +6101,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); final TestGesture gesture = - await tester.startGesture(textfieldStart + const Offset(50.0, 5.0)); + await tester.startGesture(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(const Duration(milliseconds: 500)); // Long press on iOS shows collapsed selection cursor. @@ -6258,17 +6258,17 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor to the beginning of the second word. expect( controller.selection, const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 500)); - await tester.longPressAt(textfieldStart + const Offset(100.0, 5.0)); + await tester.longPressAt(textfieldStart + const Offset(100.0, 9.0)); await tester.pump(); // Plain collapsed selection at the exact tap position. @@ -6303,17 +6303,17 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.longPressAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.longPressAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor. expect( controller.selection, const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(); // Double tap selection. @@ -6346,13 +6346,13 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream), ); - await tester.tapAt(textfieldStart + const Offset(50.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(50.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, @@ -6361,14 +6361,14 @@ void main() { expect(find.byType(CupertinoButton), findsNWidgets(3)); // Double tap selecting the same word somewhere else is fine. - await tester.tapAt(textfieldStart + const Offset(100.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(100.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor. expect( controller.selection, const TextSelection.collapsed(offset: 7, affinity: TextAffinity.upstream), ); - await tester.tapAt(textfieldStart + const Offset(100.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(100.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, @@ -6376,14 +6376,14 @@ void main() { ); expect(find.byType(CupertinoButton), findsNWidgets(3)); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); // First tap moved the cursor. expect( controller.selection, const TextSelection.collapsed(offset: 8, affinity: TextAffinity.downstream), ); - await tester.tapAt(textfieldStart + const Offset(150.0, 5.0)); + await tester.tapAt(textfieldStart + const Offset(150.0, 9.0)); await tester.pump(const Duration(milliseconds: 50)); expect( controller.selection, @@ -6407,7 +6407,7 @@ void main() { ), ); - final Offset offset = tester.getTopLeft(find.byType(TextField)) + const Offset(150.0, 5.0); + final Offset offset = tester.getTopLeft(find.byType(TextField)) + const Offset(150.0, 9.0); const int pointerValue = 1; final TestGesture gesture = await tester.createGesture(); @@ -6421,7 +6421,7 @@ void main() { pressureMin: 0.0, ), ); - await gesture.updateWithCustomEvent(PointerMoveEvent(pointer: pointerValue, position: offset + const Offset(150.0, 5.0), pressure: 0.5, pressureMin: 0, pressureMax: 1)); + await gesture.updateWithCustomEvent(PointerMoveEvent(pointer: pointerValue, position: offset + const Offset(150.0, 9.0), pressure: 0.5, pressureMin: 0, pressureMax: 1)); // We don't want this gesture to select any word on Android. expect(controller.selection, const TextSelection.collapsed(offset: -1)); @@ -6449,7 +6449,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); const int pointerValue = 1; - final Offset offset = textfieldStart + const Offset(150.0, 5.0); + final Offset offset = textfieldStart + const Offset(150.0, 9.0); final TestGesture gesture = await tester.createGesture(); await gesture.downWithCustomEvent( offset, @@ -6462,7 +6462,7 @@ void main() { ), ); - await gesture.updateWithCustomEvent(PointerMoveEvent(pointer: pointerValue, position: textfieldStart + const Offset(150.0, 5.0), pressure: 0.5, pressureMin: 0, pressureMax: 1)); + await gesture.updateWithCustomEvent(PointerMoveEvent(pointer: pointerValue, position: textfieldStart + const Offset(150.0, 9.0), pressure: 0.5, pressureMin: 0, pressureMax: 1)); // We expect the force press to select a word at the given location. expect( controller.selection, @@ -6492,7 +6492,7 @@ void main() { final Offset textfieldStart = tester.getTopLeft(find.byType(TextField)); const int pointerValue = 1; - final Offset offset = textfieldStart + const Offset(150.0, 5.0); + final Offset offset = textfieldStart + const Offset(150.0, 9.0); final TestGesture gesture = await tester.createGesture(); await gesture.downWithCustomEvent( offset, @@ -6506,7 +6506,7 @@ void main() { ), ); - await gesture.updateWithCustomEvent(PointerMoveEvent(pointer: pointerValue, position: textfieldStart + const Offset(150.0, 5.0), pressure: 0.5, pressureMin: 0, pressureMax: 1)); + await gesture.updateWithCustomEvent(PointerMoveEvent(pointer: pointerValue, position: textfieldStart + const Offset(150.0, 9.0), pressure: 0.5, pressureMin: 0, pressureMax: 1)); await gesture.up(); // The event should fallback to a normal tap and move the cursor. // Single taps selects the edge of the word. @@ -6602,9 +6602,11 @@ void main() { expect( tester.getSize(find.byType(TextField)), - // This is the height of the decoration (24) plus the metrics from the default - // TextStyle of the theme (16). - const Size(800, 40), + // The TextField will be as tall as the decoration (24) plus the metrics + // from the default TextStyle of the theme (16), or 40 altogether. + // Because this is less than the kMinInteractiveDimension, it will be + // increased to that value (48). + const Size(800, kMinInteractiveDimension), ); }, ); @@ -6629,7 +6631,7 @@ void main() { tester.getSize(find.byType(TextField)), // Strut should inherit the TextStyle.fontSize by default and produce the // same height as if it were disabled. - const Size(800, 44), + const Size(800, kMinInteractiveDimension), // Because 44 < 48. ); await tester.pumpWidget( @@ -6649,7 +6651,7 @@ void main() { expect( tester.getSize(find.byType(TextField)), // The height here should match the previous version with strut enabled. - const Size(800, 44), + const Size(800, kMinInteractiveDimension), // Because 44 < 48. ); }, ); @@ -7235,7 +7237,6 @@ void main() { testWidgets('when TextField would be blocked by keyboard, it is shown with enough space for the selection handle', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); - final TextEditingController controller = TextEditingController(); await tester.pumpWidget(MaterialApp( theme: ThemeData(), @@ -7245,7 +7246,7 @@ void main() { controller: scrollController, children: [ Container(height: 579), // Push field almost off screen. - TextField(controller: controller), + const TextField(), Container(height: 1000), ], ), @@ -7255,12 +7256,62 @@ void main() { // Tap the TextField to put the cursor into it and bring it into view. expect(scrollController.offset, 0.0); - await tester.tap(find.byType(TextField)); + await tester.tapAt(tester.getTopLeft(find.byType(TextField))); await tester.pumpAndSettle(); // The ListView has scrolled to keep the TextField and cursor handle // visible. - expect(scrollController.offset, 44.0); + expect(scrollController.offset, 48.0); + }); + + group('height', () { + testWidgets('By default, TextField is at least kMinInteractiveDimension high', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(), + home: const Scaffold( + body: Center( + child: TextField(), + ), + ), + )); + + final RenderBox renderBox = tester.renderObject(find.byType(TextField)); + expect(renderBox.size.height, greaterThanOrEqualTo(kMinInteractiveDimension)); + }); + + testWidgets('When text is very small, TextField still doesn\'t go below kMinInteractiveDimension height', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(), + home: const Scaffold( + body: Center( + child: TextField( + style: TextStyle(fontSize: 2.0), + ), + ), + ), + )); + + final RenderBox renderBox = tester.renderObject(find.byType(TextField)); + expect(renderBox.size.height, kMinInteractiveDimension); + }); + + testWidgets('When isDense, TextField can go below kMinInteractiveDimension height', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(), + home: const Scaffold( + body: Center( + child: TextField( + decoration: InputDecoration( + isDense: true, + ), + ), + ), + ), + )); + + final RenderBox renderBox = tester.renderObject(find.byType(TextField)); + expect(renderBox.size.height, lessThan(kMinInteractiveDimension)); + }); }); testWidgets("Arrow keys don't move input focus", (WidgetTester tester) async { final TextEditingController controller1 = TextEditingController();