| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This file is run as part of a reduced test set in CI on Mac and Windows |
| // machines. |
| @Tags(<String>['reduced-test-set']) |
| |
| import 'dart:async'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../rendering/mock_canvas.dart'; |
| |
| Widget buildInputDecorator({ |
| InputDecoration decoration = const InputDecoration(), |
| ThemeData? theme, |
| InputDecorationTheme? inputDecorationTheme, |
| TextDirection textDirection = TextDirection.ltr, |
| bool expands = false, |
| bool isEmpty = false, |
| bool isFocused = false, |
| bool isHovering = false, |
| bool useMaterial3 = false, |
| TextStyle? baseStyle, |
| TextAlignVertical? textAlignVertical, |
| VisualDensity? visualDensity, |
| Widget child = const Text( |
| 'text', |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 16.0), |
| ), |
| }) { |
| return MaterialApp( |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return Theme( |
| data: (theme ?? Theme.of(context)).copyWith( |
| inputDecorationTheme: inputDecorationTheme, |
| visualDensity: visualDensity, |
| useMaterial3: useMaterial3, |
| textTheme: const TextTheme(bodyLarge: TextStyle(fontFamily: 'Ahem', fontSize: 16.0)), |
| ), |
| child: Align( |
| alignment: Alignment.topLeft, |
| child: Directionality( |
| textDirection: textDirection, |
| child: InputDecorator( |
| expands: expands, |
| decoration: decoration, |
| isEmpty: isEmpty, |
| isFocused: isFocused, |
| isHovering: isHovering, |
| baseStyle: baseStyle, |
| textAlignVertical: textAlignVertical, |
| child: child, |
| ), |
| ), |
| ), |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| Finder findBorderPainter() { |
| return find.descendant( |
| of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_BorderContainer'), |
| matching: find.byWidgetPredicate((Widget w) => w is CustomPaint), |
| ); |
| } |
| |
| double getBorderBottom(WidgetTester tester) { |
| final RenderBox box = InputDecorator.containerOf(tester.element(findBorderPainter()))!; |
| return box.size.height; |
| } |
| |
| Finder findLabel() { |
| return find.descendant( |
| of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Shaker'), |
| matching: find.byWidgetPredicate((Widget w) => w is Text), |
| ); |
| } |
| |
| Rect getLabelRect(WidgetTester tester) { |
| return tester.getRect(findLabel()); |
| } |
| |
| TextStyle getLabelStyle(WidgetTester tester) { |
| return tester.firstWidget<AnimatedDefaultTextStyle>( |
| find.ancestor( |
| of: find.text('label'), |
| matching: find.byType(AnimatedDefaultTextStyle), |
| ), |
| ).style; |
| } |
| |
| InputBorder? getBorder(WidgetTester tester) { |
| if (!tester.any(findBorderPainter())) { |
| return null; |
| } |
| final CustomPaint customPaint = tester.widget(findBorderPainter()); |
| final dynamic/*_InputBorderPainter*/ inputBorderPainter = customPaint.foregroundPainter; |
| // ignore: avoid_dynamic_calls |
| final dynamic/*_InputBorderTween*/ inputBorderTween = inputBorderPainter.border; |
| // ignore: avoid_dynamic_calls |
| final Animation<double> animation = inputBorderPainter.borderAnimation as Animation<double>; |
| // ignore: avoid_dynamic_calls |
| final InputBorder border = inputBorderTween.evaluate(animation) as InputBorder; |
| return border; |
| } |
| |
| BorderSide? getBorderSide(WidgetTester tester) { |
| return getBorder(tester)!.borderSide; |
| } |
| |
| BorderRadius? getBorderRadius(WidgetTester tester) { |
| final InputBorder border = getBorder(tester)!; |
| if (border is UnderlineInputBorder) { |
| return border.borderRadius; |
| } |
| return null; |
| } |
| |
| double getBorderWeight(WidgetTester tester) => getBorderSide(tester)!.width; |
| |
| Color getBorderColor(WidgetTester tester) => getBorderSide(tester)!.color; |
| |
| Color getContainerColor(WidgetTester tester) { |
| final CustomPaint customPaint = tester.widget(findBorderPainter()); |
| final dynamic/*_InputBorderPainter*/ inputBorderPainter = customPaint.foregroundPainter; |
| // ignore: avoid_dynamic_calls |
| return inputBorderPainter.blendedColor as Color; |
| } |
| |
| double getOpacity(WidgetTester tester, String textValue) { |
| final FadeTransition opacityWidget = tester.widget<FadeTransition>( |
| find.ancestor( |
| of: find.text(textValue), |
| matching: find.byType(FadeTransition), |
| ).first, |
| ); |
| return opacityWidget.opacity.value; |
| } |
| |
| void main() { |
| for(final bool useMaterial3 in <bool>[true, false]){ |
| testWidgets('InputDecorator input/label text layout', (WidgetTester tester) async { |
| // The label appears above the input text |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| // Overall height for this InputDecorator is 56dps: |
| // 12 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 1.0); |
| |
| // The label appears within the input when there is no text content |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| |
| // The label appears above the input text when there is no content and floatingLabelBehavior is always |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| floatingLabelBehavior: FloatingLabelBehavior.always, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| |
| // The label appears within the input text when there is content and floatingLabelBehavior is never |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| floatingLabelBehavior: FloatingLabelBehavior.never, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| |
| // Overall height for this InputDecorator is 56dps: |
| // 12 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| |
| // isFocused: true increases the border's weight from 1.0 to 2.0 |
| // but does not change the overall height. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| isFocused: true, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 2.0); |
| |
| // isEmpty: true causes the label to be aligned with the input text |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| ), |
| ), |
| ); |
| |
| // The label animates downwards from it's initial position |
| // above the input text. The animation's duration is 167ms. |
| { |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double labelY50ms = tester.getTopLeft(find.text('label')).dy; |
| expect(labelY50ms, inExclusiveRange(12.0, 20.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double labelY100ms = tester.getTopLeft(find.text('label')).dy; |
| expect(labelY100ms, inExclusiveRange(labelY50ms, 20.0)); |
| } |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 36.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 1.0); |
| |
| // isFocused: true causes the label to move back up above the input text. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| isFocused: true, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| ), |
| ), |
| ); |
| |
| // The label animates upwards from it's initial position |
| // above the input text. The animation's duration is 167ms. |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double labelY50ms = tester.getTopLeft(find.text('label')).dy; |
| expect(labelY50ms, inExclusiveRange(12.0, 28.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double labelY100ms = tester.getTopLeft(find.text('label')).dy; |
| expect(labelY100ms, inExclusiveRange(12.0, labelY50ms)); |
| |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 2.0); |
| |
| // enabled: false produces a hairline border if filled: false (the default) |
| // The widget's size and layout is the same as for enabled: true. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| enabled: false, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 36.0); |
| expect(getBorderWeight(tester), useMaterial3 ? 1.0 : 0.0); |
| |
| // enabled: false produces a transparent border if filled: true. |
| // The widget's size and layout is the same as for enabled: true. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| enabled: false, |
| filled: true, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 36.0); |
| final ThemeData theme = ThemeData.from(colorScheme: const ColorScheme.light()); |
| expect(getBorderColor(tester), useMaterial3 ? theme.colorScheme.onSurface.withOpacity(0.38) : Colors.transparent); |
| |
| // alignLabelWithHint: true positions the label at the text baseline, |
| // aligned with the hint. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| alignLabelWithHint: true, |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| if(!useMaterial3) { |
| expect(tester.getTopLeft(find.text('label')).dy, tester.getTopLeft(find.text('hint')).dy); |
| expect(tester.getBottomLeft(find.text('label')).dy, tester.getBottomLeft(find.text('hint')).dy); |
| } |
| }); |
| |
| testWidgets('InputDecorator input/label widget layout', (WidgetTester tester) async { |
| const Key key = Key('l'); |
| |
| // The label appears above the input text. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| // Overall height for this InputDecorator is 56dps: |
| // 12 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.byKey(key)).dy, 12.0); |
| expect(tester.getBottomLeft(find.byKey(key)).dy, 24.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 1.0); |
| |
| // The label appears within the input when there is no text content. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getTopLeft(find.byKey(key)).dy, 20.0); |
| |
| // The label appears above the input text when there is no content and the |
| // floatingLabelBehavior is set to always. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| floatingLabelBehavior: FloatingLabelBehavior.always, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getTopLeft(find.byKey(key)).dy, 12.0); |
| |
| // The label appears within the input text when there is content and |
| // the floatingLabelBehavior is set to never. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| floatingLabelBehavior: FloatingLabelBehavior.never, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.getTopLeft(find.byKey(key)).dy, 20.0); |
| |
| // Overall height for this InputDecorator is 56dps: |
| // 12 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| |
| expect(tester.getTopLeft(find.byKey(key)).dy, 20.0); |
| |
| // isFocused: true increases the border's weight from 1.0 to 2.0 |
| // but does not change the overall height. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| isFocused: true, |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.byKey(key)).dy, 12.0); |
| expect(tester.getBottomLeft(find.byKey(key)).dy, 24.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 2.0); |
| |
| // isEmpty: true causes the label to be aligned with the input text. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| ), |
| ), |
| ); |
| |
| // The label animates downwards from it's initial position |
| // above the input text. The animation's duration is 167ms. |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double labelY50ms = tester.getTopLeft(find.byKey(key)).dy; |
| expect(labelY50ms, inExclusiveRange(12.0, 20.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double labelY100ms = tester.getTopLeft(find.byKey(key)).dy; |
| expect(labelY100ms, inExclusiveRange(labelY50ms, 20.0)); |
| |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.byKey(key)).dy, 20.0); |
| expect(tester.getBottomLeft(find.byKey(key)).dy, 36.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 1.0); |
| |
| // isFocused: true causes the label to move back up above the input text. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| isFocused: true, |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| ), |
| ), |
| ); |
| |
| // The label animates upwards from it's initial position |
| // above the input text. The animation's duration is 167ms. |
| { |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double labelY50ms = tester.getTopLeft(find.byKey(key)).dy; |
| expect(labelY50ms, inExclusiveRange(12.0, 28.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double labelY100ms = tester.getTopLeft(find.byKey(key)).dy; |
| expect(labelY100ms, inExclusiveRange(12.0, labelY50ms)); |
| } |
| |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.byKey(key)).dy, 12.0); |
| expect(tester.getBottomLeft(find.byKey(key)).dy, 24.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 2.0); |
| |
| // enabled: false produces a hairline border if filled: false (the default) |
| // The widget's size and layout is the same as for enabled: true. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| enabled: false, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy,44.0); |
| expect(tester.getTopLeft(find.byKey(key)).dy, 20.0); |
| expect(tester.getBottomLeft(find.byKey(key)).dy, 36.0); |
| expect(getBorderWeight(tester),useMaterial3 ? 1.0 : 0.0); |
| |
| // enabled: false produces a transparent border if filled: true. |
| // The widget's size and layout is the same as for enabled: true. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| enabled: false, |
| filled: true, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.byKey(key)).dy, 20.0); |
| expect(tester.getBottomLeft(find.byKey(key)).dy, 36.0); |
| final ThemeData theme = ThemeData.from(colorScheme: const ColorScheme.light()); |
| expect(getBorderColor(tester), useMaterial3 ? theme.colorScheme.onSurface.withOpacity(0.38) : Colors.transparent); |
| |
| // alignLabelWithHint: true positions the label at the text baseline, |
| // aligned with the hint. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| decoration: const InputDecoration( |
| label: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| TextSpan(text: 'label'), |
| WidgetSpan( |
| child: Text('*', style: TextStyle(color: Colors.red), |
| ), |
| ), |
| ], |
| ), |
| key: key, |
| ), |
| alignLabelWithHint: true, |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| if(!useMaterial3) { |
| expect(tester.getTopLeft(find.byKey(key)).dy, tester.getTopLeft(find.text('hint')).dy); |
| expect(tester.getBottomLeft(find.byKey(key)).dy, tester.getBottomLeft(find.text('hint')).dy); |
| } |
| |
| }); |
| |
| testWidgets('InputDecorator floating label animation duration and curve', (WidgetTester tester) async { |
| Future<void> pumpInputDecorator({ |
| required bool isFocused, |
| }) async { |
| return tester.pumpWidget( |
| buildInputDecorator( |
| isEmpty: true, |
| isFocused: isFocused, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| floatingLabelBehavior: FloatingLabelBehavior.auto, |
| ), |
| ), |
| ); |
| } |
| await pumpInputDecorator(isFocused: false); |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| |
| // The label animates upwards and scales down. |
| // The animation duration is 167ms and the curve is fastOutSlowIn. |
| await pumpInputDecorator(isFocused: true); |
| await tester.pump(const Duration(milliseconds: 42)); |
| expect(tester.getTopLeft(find.text('label')).dy, closeTo(18.06, 0.5)); |
| await tester.pump(const Duration(milliseconds: 42)); |
| expect(tester.getTopLeft(find.text('label')).dy, closeTo(13.78, 0.5)); |
| await tester.pump(const Duration(milliseconds: 42)); |
| expect(tester.getTopLeft(find.text('label')).dy, closeTo(12.31, 0.5)); |
| await tester.pump(const Duration(milliseconds: 41)); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| |
| // If the animation changes direction without first reaching the |
| // AnimationStatus.completed or AnimationStatus.dismissed status, |
| // the CurvedAnimation stays on the same curve in the opposite direction. |
| // The pumpAndSettle is used to prevent this behavior. |
| await tester.pumpAndSettle(); |
| |
| // The label animates downwards and scales up. |
| // The animation duration is 167ms and the curve is fastOutSlowIn. |
| await pumpInputDecorator(isFocused: false); |
| await tester.pump(const Duration(milliseconds: 42)); |
| expect(tester.getTopLeft(find.text('label')).dy, closeTo(13.94, 0.5)); |
| await tester.pump(const Duration(milliseconds: 42)); |
| expect(tester.getTopLeft(find.text('label')).dy, closeTo(18.22, 0.5)); |
| await tester.pump(const Duration(milliseconds: 42)); |
| expect(tester.getTopLeft(find.text('label')).dy, closeTo(19.69, 0.5)); |
| await tester.pump(const Duration(milliseconds: 41)); |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| }); |
| |
| group('alignLabelWithHint', () { |
| group('expands false', () { |
| testWidgets('multiline TextField no-strut', (WidgetTester tester) async { |
| const String text = 'text'; |
| final FocusNode focusNode = FocusNode(); |
| final TextEditingController controller = TextEditingController(); |
| Widget buildFrame(bool alignLabelWithHint) { |
| return MaterialApp( |
| home: Material( |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: TextField( |
| controller: controller, |
| focusNode: focusNode, |
| maxLines: 8, |
| decoration: InputDecoration( |
| labelText: 'label', |
| alignLabelWithHint: alignLabelWithHint, |
| hintText: 'hint', |
| ), |
| strutStyle: StrutStyle.disabled, |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| // alignLabelWithHint: false centers the label in the TextField. |
| await tester.pumpWidget(buildFrame(false)); |
| await tester.pumpAndSettle(); |
| expect(tester.getTopLeft(find.text('label')).dy, 76.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 92.0); |
| |
| // Entering text still happens at the top. |
| await tester.enterText(find.byType(TextField), text); |
| expect(tester.getTopLeft(find.text(text)).dy, 28.0); |
| controller.clear(); |
| focusNode.unfocus(); |
| |
| // alignLabelWithHint: true aligns the label with the hint. |
| await tester.pumpWidget(buildFrame(true)); |
| await tester.pumpAndSettle(); |
| expect(tester.getTopLeft(find.text('label')).dy, tester.getTopLeft(find.text('hint')).dy); |
| expect(tester.getBottomLeft(find.text('label')).dy, tester.getBottomLeft(find.text('hint')).dy); |
| |
| // Entering text still happens at the top. |
| await tester.enterText(find.byType(TextField), text); |
| expect(tester.getTopLeft(find.text(text)).dy, 28.0); |
| controller.clear(); |
| focusNode.unfocus(); |
| }); |
| |
| testWidgets('multiline TextField', (WidgetTester tester) async { |
| const String text = 'text'; |
| final FocusNode focusNode = FocusNode(); |
| final TextEditingController controller = TextEditingController(); |
| Widget buildFrame(bool alignLabelWithHint) { |
| return MaterialApp( |
| home: Material( |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: TextField( |
| controller: controller, |
| focusNode: focusNode, |
| maxLines: 8, |
| decoration: InputDecoration( |
| labelText: 'label', |
| alignLabelWithHint: alignLabelWithHint, |
| hintText: 'hint', |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| // alignLabelWithHint: false centers the label in the TextField. |
| await tester.pumpWidget(buildFrame(false)); |
| await tester.pumpAndSettle(); |
| expect(tester.getTopLeft(find.text('label')).dy, 76.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 92.0); |
| |
| // Entering text still happens at the top. |
| await tester.enterText(find.byType(InputDecorator), text); |
| expect(tester.getTopLeft(find.text(text)).dy, 28.0); |
| controller.clear(); |
| focusNode.unfocus(); |
| |
| // alignLabelWithHint: true aligns the label with the hint. |
| await tester.pumpWidget(buildFrame(true)); |
| await tester.pumpAndSettle(); |
| expect(tester.getTopLeft(find.text('label')).dy, tester.getTopLeft(find.text('hint')).dy); |
| expect(tester.getBottomLeft(find.text('label')).dy, tester.getBottomLeft(find.text('hint')).dy); |
| |
| // Entering text still happens at the top. |
| await tester.enterText(find.byType(InputDecorator), text); |
| expect(tester.getTopLeft(find.text(text)).dy, 28.0); |
| controller.clear(); |
| focusNode.unfocus(); |
| }); |
| }); |
| |
| group('expands true', () { |
| testWidgets('multiline TextField', (WidgetTester tester) async { |
| const String text = 'text'; |
| final FocusNode focusNode = FocusNode(); |
| final TextEditingController controller = TextEditingController(); |
| Widget buildFrame(bool alignLabelWithHint) { |
| return MaterialApp( |
| home: Material( |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: TextField( |
| controller: controller, |
| focusNode: focusNode, |
| maxLines: null, |
| expands: true, |
| decoration: InputDecoration( |
| labelText: 'label', |
| alignLabelWithHint: alignLabelWithHint, |
| hintText: 'hint', |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| // alignLabelWithHint: false centers the label in the TextField. |
| await tester.pumpWidget(buildFrame(false)); |
| await tester.pumpAndSettle(); |
| expect(tester.getTopLeft(find.text('label')).dy, 292.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 308.0); |
| |
| // Entering text still happens at the top. |
| await tester.enterText(find.byType(InputDecorator), text); |
| expect(tester.getTopLeft(find.text(text)).dy, 28.0); |
| controller.clear(); |
| focusNode.unfocus(); |
| |
| // alignLabelWithHint: true aligns the label with the hint at the top. |
| await tester.pumpWidget(buildFrame(true)); |
| await tester.pumpAndSettle(); |
| expect(tester.getTopLeft(find.text('label')).dy, 28.0); |
| expect(tester.getTopLeft(find.text('label')).dy, tester.getTopLeft(find.text('hint')).dy); |
| expect(tester.getBottomLeft(find.text('label')).dy, tester.getBottomLeft(find.text('hint')).dy); |
| |
| // Entering text still happens at the top. |
| await tester.enterText(find.byType(InputDecorator), text); |
| expect(tester.getTopLeft(find.text(text)).dy, 28.0); |
| controller.clear(); |
| focusNode.unfocus(); |
| }); |
| |
| testWidgets('multiline TextField with outline border', (WidgetTester tester) async { |
| const String text = 'text'; |
| final FocusNode focusNode = FocusNode(); |
| final TextEditingController controller = TextEditingController(); |
| Widget buildFrame(bool alignLabelWithHint) { |
| return MaterialApp( |
| home: Material( |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: TextField( |
| controller: controller, |
| focusNode: focusNode, |
| maxLines: null, |
| expands: true, |
| decoration: InputDecoration( |
| labelText: 'label', |
| alignLabelWithHint: alignLabelWithHint, |
| hintText: 'hint', |
| border: const OutlineInputBorder( |
| borderRadius: BorderRadius.zero, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| // alignLabelWithHint: false centers the label in the TextField. |
| await tester.pumpWidget(buildFrame(false)); |
| await tester.pumpAndSettle(); |
| expect(tester.getTopLeft(find.text('label')).dy, 292.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 308.0); |
| |
| // Entering text happens in the center as well. |
| await tester.enterText(find.byType(InputDecorator), text); |
| expect(tester.getTopLeft(find.text(text)).dy, 291.0); |
| controller.clear(); |
| focusNode.unfocus(); |
| |
| // alignLabelWithHint: true aligns keeps the label in the center because |
| // that's where the hint is. |
| await tester.pumpWidget(buildFrame(true)); |
| await tester.pumpAndSettle(); |
| expect(tester.getTopLeft(find.text('label')).dy, 291.0); |
| expect(tester.getTopLeft(find.text('label')).dy, tester.getTopLeft(find.text('hint')).dy); |
| expect(tester.getBottomLeft(find.text('label')).dy, tester.getBottomLeft(find.text('hint')).dy); |
| |
| // Entering text still happens in the center. |
| await tester.enterText(find.byType(InputDecorator), text); |
| expect(tester.getTopLeft(find.text(text)).dy, 291.0); |
| controller.clear(); |
| focusNode.unfocus(); |
| }); |
| }); |
| }); |
| |
| // Overall height for this InputDecorator is 40.0dps |
| // 12 - top padding |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| testWidgets('InputDecorator input/hint layout', (WidgetTester tester) async { |
| // The hint aligns with the input text |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| 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); |
| }); |
| |
| testWidgets('InputDecorator input/label/hint layout', (WidgetTester tester) async { |
| // Label is visible, hint is not (opacity 0.0). |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 56dps. When the |
| // label is "floating" (empty input or no focus) the layout is: |
| // |
| // 12 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| // |
| // When the label is not floating, it's vertically centered. |
| // |
| // 20 - top padding |
| // 16 - label (ahem font size 16dps) |
| // 20 - bottom padding (empty input text still appears here) |
| |
| |
| // The label is not floating so it's vertically centered. |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 36.0); |
| expect(getOpacity(tester, 'hint'), 0.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 1.0); |
| |
| // Label moves upwards, hint is visible (opacity 1.0). |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| isFocused: true, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| // The hint's opacity animates from 0.0 to 1.0. |
| // The animation's duration is 167ms. |
| { |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity50ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity100ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity100ms, inExclusiveRange(hintOpacity50ms, 1.0)); |
| } |
| |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(tester.getTopLeft(find.text('hint')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('hint')).dy, 44.0); |
| expect(getOpacity(tester, 'hint'), 1.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 2.0); |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isFocused: true, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| // The hint's opacity animates from 1.0 to 0.0. |
| // The animation's duration is 167ms. |
| { |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity50ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity100ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity100ms, inExclusiveRange(0.0, hintOpacity50ms)); |
| } |
| |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(tester.getTopLeft(find.text('hint')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('hint')).dy, 44.0); |
| expect(getOpacity(tester, 'hint'), 0.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 2.0); |
| }); |
| |
| testWidgets('InputDecorator input/label/hint dense layout', (WidgetTester tester) async { |
| // Label is visible, hint is not (opacity 0.0). |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| isDense: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 48dps. When the |
| // label is "floating" (empty input or no focus) the layout is: |
| // |
| // 8 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 8 - bottom padding |
| // |
| // When the label is not floating, it's vertically centered. |
| // |
| // 16 - top padding |
| // 16 - label (ahem font size 16dps) |
| // 16 - bottom padding (empty input text still appears here) |
| |
| // The label is not floating so it's vertically centered. |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 40.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 16.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 32.0); |
| expect(getOpacity(tester, 'hint'), 0.0); |
| expect(getBorderBottom(tester), 48.0); |
| expect(getBorderWeight(tester), 1.0); |
| |
| // Label is visible, hint is not (opacity 0.0). |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| isFocused: true, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| isDense: true, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 40.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 8.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 20.0); |
| expect(getOpacity(tester, 'hint'), 1.0); |
| expect(getBorderBottom(tester), 48.0); |
| expect(getBorderWeight(tester), 2.0); |
| }); |
| |
| testWidgets('InputDecorator with no input border', (WidgetTester tester) async { |
| // Label is visible, hint is not (opacity 0.0). |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| border: InputBorder.none, |
| ), |
| ), |
| ); |
| expect(getBorderWeight(tester), 0.0); |
| }); |
| |
| testWidgets('InputDecorator error/helper/counter layout', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| helperText: 'helper', |
| counterText: 'counter', |
| filled: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 76dps. When the label is |
| // floating the layout is: |
| // |
| // 12 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| // 8 - below the border padding |
| // 12 - help/error/counter text (ahem font size 12dps) |
| // |
| // When the label is not floating, it's vertically centered in the space |
| // above the subtext: |
| // |
| // 20 - top padding |
| // 16 - label (ahem font size 16dps) |
| // 20 - bottom padding (empty input text still appears here) |
| // 8 - below the border padding |
| // 12 - help/error/counter text (ahem font size 12dps) |
| |
| // isEmpty: true, the label is not floating |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 76.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 20.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 36.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 1.0); |
| expect(tester.getTopLeft(find.text('helper')), const Offset(12.0, 64.0)); |
| expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 64.0)); |
| |
| // If errorText is specified then the helperText isn't shown |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| errorText: 'error', |
| helperText: 'helper', |
| counterText: 'counter', |
| filled: true, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| // isEmpty: false, the label _is_ floating |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 76.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 28.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 44.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(getBorderBottom(tester), 56.0); |
| expect(getBorderWeight(tester), 1.0); |
| expect(tester.getTopLeft(find.text('error')), const Offset(12.0, 64.0)); |
| expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 64.0)); |
| expect(find.text('helper'), findsNothing); |
| |
| // Overall height for this dense layout InputDecorator is 68dps. When the |
| // label is floating the layout is: |
| // |
| // 8 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 8 - bottom padding |
| // 8 - below the border padding |
| // 12 - help/error/counter text (ahem font size 12dps) |
| // |
| // When the label is not floating, it's vertically centered in the space |
| // above the subtext: |
| // |
| // 16 - top padding |
| // 16 - label (ahem font size 16dps) |
| // 16 - bottom padding (empty input text still appears here) |
| // 8 - below the border padding |
| // 12 - help/error/counter text (ahem font size 12dps) |
| // The layout of the error/helper/counter subtext doesn't change for dense layout. |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| isDense: true, |
| labelText: 'label', |
| errorText: 'error', |
| helperText: 'helper', |
| counterText: 'counter', |
| filled: true, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| // isEmpty: false, the label _is_ floating |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 68.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 40.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 8.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 20.0); |
| expect(getBorderBottom(tester), 48.0); |
| expect(getBorderWeight(tester), 1.0); |
| expect(tester.getTopLeft(find.text('error')), const Offset(12.0, 56.0)); |
| expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 56.0)); |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| isDense: true, |
| labelText: 'label', |
| errorText: 'error', |
| helperText: 'helper', |
| counterText: 'counter', |
| filled: true, |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| // isEmpty: false, the label is not floating |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 68.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 40.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 16.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 32.0); |
| expect(getBorderBottom(tester), 48.0); |
| expect(getBorderWeight(tester), 1.0); |
| expect(tester.getTopLeft(find.text('error')), const Offset(12.0, 56.0)); |
| expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 56.0)); |
| }); |
| |
| testWidgets('InputDecorator counter text, widget, and null', (WidgetTester tester) async { |
| Widget buildFrame({ |
| InputCounterWidgetBuilder? buildCounter, |
| String? counterText, |
| Widget? counter, |
| int? maxLength, |
| }) { |
| return MaterialApp( |
| home: Scaffold( |
| body: Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| TextFormField( |
| buildCounter: buildCounter, |
| maxLength: maxLength, |
| decoration: InputDecoration( |
| counterText: counterText, |
| counter: counter, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| // When counter, counterText, and buildCounter are null, defaults to showing |
| // the built-in counter. |
| int? maxLength = 10; |
| await tester.pumpWidget(buildFrame(maxLength: maxLength)); |
| Finder counterFinder = find.byType(Text); |
| expect(counterFinder, findsOneWidget); |
| final Text counterWidget = tester.widget(counterFinder); |
| expect(counterWidget.data, '0/$maxLength'); |
| |
| // When counter, counterText, and buildCounter are set, shows the counter |
| // widget. |
| final Key counterKey = UniqueKey(); |
| final Key buildCounterKey = UniqueKey(); |
| const String counterText = 'I show instead of count'; |
| final Widget counter = Text('hello', key: counterKey); |
| Widget buildCounter( |
| BuildContext context, { |
| required int currentLength, |
| required int? maxLength, |
| required bool isFocused, |
| }) { |
| return Text( |
| '$currentLength of $maxLength', |
| key: buildCounterKey, |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame( |
| counterText: counterText, |
| counter: counter, |
| buildCounter: buildCounter, |
| maxLength: maxLength, |
| )); |
| counterFinder = find.byKey(counterKey); |
| expect(counterFinder, findsOneWidget); |
| expect(find.text(counterText), findsNothing); |
| expect(find.byKey(buildCounterKey), findsNothing); |
| |
| // When counter is null but counterText and buildCounter are set, shows the |
| // counterText. |
| await tester.pumpWidget(buildFrame( |
| counterText: counterText, |
| buildCounter: buildCounter, |
| maxLength: maxLength, |
| )); |
| expect(find.text(counterText), findsOneWidget); |
| counterFinder = find.byKey(counterKey); |
| expect(counterFinder, findsNothing); |
| expect(find.byKey(buildCounterKey), findsNothing); |
| |
| // When counter and counterText are null but buildCounter is set, shows the |
| // generated widget. |
| await tester.pumpWidget(buildFrame( |
| buildCounter: buildCounter, |
| maxLength: maxLength, |
| )); |
| expect(find.byKey(buildCounterKey), findsOneWidget); |
| expect(counterFinder, findsNothing); |
| expect(find.text(counterText), findsNothing); |
| |
| // When counterText is empty string and counter and buildCounter are null, |
| // shows nothing. |
| await tester.pumpWidget(buildFrame(counterText: '', maxLength: maxLength)); |
| expect(find.byType(Text), findsNothing); |
| |
| // When no maxLength, can still show a counter |
| maxLength = null; |
| await tester.pumpWidget(buildFrame( |
| buildCounter: buildCounter, |
| maxLength: maxLength, |
| )); |
| expect(find.byKey(buildCounterKey), findsOneWidget); |
| }); |
| |
| testWidgets('InputDecoration errorMaxLines', (WidgetTester tester) async { |
| const String kError1 = 'e0'; |
| const String kError2 = 'e0\ne1'; |
| const String kError3 = 'e0\ne1\ne2'; |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| helperText: 'helper', |
| errorText: kError3, |
| errorMaxLines: 3, |
| filled: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 100dps: |
| // |
| // 12 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| // 8 - below the border padding |
| // 36 - error text (3 lines, ahem font size 12dps) |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 100.0)); |
| expect(tester.getTopLeft(find.text(kError3)), const Offset(12.0, 64.0)); |
| expect(tester.getBottomLeft(find.text(kError3)), const Offset(12.0, 100.0)); |
| |
| // Overall height for this InputDecorator is 12 less than the first |
| // one, 88dps, because errorText only occupies two lines. |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| helperText: 'helper', |
| errorText: kError2, |
| errorMaxLines: 3, |
| filled: true, |
| ), |
| ), |
| ); |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 88.0)); |
| expect(tester.getTopLeft(find.text(kError2)), const Offset(12.0, 64.0)); |
| expect(tester.getBottomLeft(find.text(kError2)), const Offset(12.0, 88.0)); |
| |
| // Overall height for this InputDecorator is 24 less than the first |
| // one, 88dps, because errorText only occupies one line. |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| helperText: 'helper', |
| errorText: kError1, |
| errorMaxLines: 3, |
| filled: true, |
| ), |
| ), |
| ); |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 76.0)); |
| expect(tester.getTopLeft(find.text(kError1)), const Offset(12.0, 64.0)); |
| expect(tester.getBottomLeft(find.text(kError1)), const Offset(12.0, 76.0)); |
| }); |
| |
| testWidgets('InputDecoration helperMaxLines', (WidgetTester tester) async { |
| const String kHelper1 = 'e0'; |
| const String kHelper2 = 'e0\ne1'; |
| const String kHelper3 = 'e0\ne1\ne2'; |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| helperText: kHelper3, |
| helperMaxLines: 3, |
| filled: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 100dps: |
| // |
| // 12 - top padding |
| // 12 - floating label (ahem font size 16dps * 0.75 = 12) |
| // 4 - floating label / input text gap |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| // 8 - below the border padding |
| // 36 - helper text (3 lines, ahem font size 12dps) |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 100.0)); |
| expect(tester.getTopLeft(find.text(kHelper3)), const Offset(12.0, 64.0)); |
| expect(tester.getBottomLeft(find.text(kHelper3)), const Offset(12.0, 100.0)); |
| |
| // Overall height for this InputDecorator is 12 less than the first |
| // one, 88dps, because helperText only occupies two lines. |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| helperText: kHelper3, |
| helperMaxLines: 2, |
| filled: true, |
| ), |
| ), |
| ); |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 88.0)); |
| expect(tester.getTopLeft(find.text(kHelper3)), const Offset(12.0, 64.0)); |
| expect(tester.getBottomLeft(find.text(kHelper3)), const Offset(12.0, 88.0)); |
| |
| // Overall height for this InputDecorator is 12 less than the first |
| // one, 88dps, because helperText only occupies two lines. |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| helperText: kHelper2, |
| helperMaxLines: 3, |
| filled: true, |
| ), |
| ), |
| ); |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 88.0)); |
| expect(tester.getTopLeft(find.text(kHelper2)), const Offset(12.0, 64.0)); |
| expect(tester.getBottomLeft(find.text(kHelper2)), const Offset(12.0, 88.0)); |
| |
| // Overall height for this InputDecorator is 24 less than the first |
| // one, 88dps, because helperText only occupies one line. |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| labelText: 'label', |
| helperText: kHelper1, |
| helperMaxLines: 3, |
| filled: true, |
| ), |
| ), |
| ); |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 76.0)); |
| expect(tester.getTopLeft(find.text(kHelper1)), const Offset(12.0, 64.0)); |
| expect(tester.getBottomLeft(find.text(kHelper1)), const Offset(12.0, 76.0)); |
| }); |
| |
| testWidgets('InputDecorator prefix/suffix texts', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| prefixText: 'p', |
| suffixText: 's', |
| filled: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 40dps: |
| // 12 - top padding |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| // |
| // 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, 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, 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, 16.0); |
| expect(tester.getTopRight(find.text('s')).dx, 788.0); |
| |
| // layout is a row: [p text s] |
| expect(tester.getTopLeft(find.text('p')).dx, 12.0); |
| expect(tester.getTopRight(find.text('p')).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('text')).dx)); |
| expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('s')).dx)); |
| }); |
| |
| testWidgets('InputDecorator icon/prefix/suffix', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| prefixText: 'p', |
| suffixText: 's', |
| icon: Icon(Icons.android), |
| filled: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 40dps: |
| // 12 - top padding |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| |
| 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, 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, 12.0); |
| |
| // layout is a row: [icon, p text s] |
| expect(tester.getTopLeft(find.byType(Icon)).dx, 0.0); |
| expect(tester.getTopRight(find.byType(Icon)).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('p')).dx)); |
| expect(tester.getTopRight(find.text('p')).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('text')).dx)); |
| expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('s')).dx)); |
| }); |
| |
| testWidgets('InputDecorator iconColor/prefixIconColor/suffixIconColor', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Material( |
| child: TextField( |
| decoration: InputDecoration( |
| icon: Icon(Icons.cabin), |
| prefixIcon: Icon(Icons.sailing), |
| suffixIcon: Icon(Icons.close), |
| iconColor: Colors.amber, |
| prefixIconColor: Colors.green, |
| suffixIconColor: Colors.red, |
| filled: true, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(tester.widget<IconTheme>(find.widgetWithIcon(IconTheme,Icons.cabin).first).data.color, Colors.amber); |
| expect(tester.widget<IconTheme>(find.widgetWithIcon(IconTheme,Icons.sailing).first).data.color, Colors.green); |
| expect(tester.widget<IconTheme>(find.widgetWithIcon(IconTheme,Icons.close).first).data.color, Colors.red); |
| }); |
| |
| testWidgets('InputDecorator prefix/suffix widgets', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| const Key sKey = Key('s'); |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| prefix: Padding( |
| key: pKey, |
| padding: EdgeInsets.all(4.0), |
| child: Text('p'), |
| ), |
| suffix: Padding( |
| key: sKey, |
| padding: EdgeInsets.all(4.0), |
| child: Text('s'), |
| ), |
| filled: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 48dps because |
| // the prefix and the suffix widget is surrounded with padding: |
| // 12 - top padding |
| // 4 - top prefix/suffix padding |
| // 16 - input text (ahem font size 16dps) |
| // 4 - bottom prefix/suffix padding |
| // 12 - bottom padding |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); |
| expect(tester.getSize(find.text('text')).height, 16.0); |
| expect(tester.getSize(find.byKey(pKey)).height, 24.0); |
| expect(tester.getSize(find.text('p')).height, 16.0); |
| expect(tester.getSize(find.byKey(sKey)).height, 24.0); |
| expect(tester.getSize(find.text('s')).height, 16.0); |
| expect(tester.getTopLeft(find.text('text')).dy, 16.0); |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, 12.0); |
| expect(tester.getTopLeft(find.text('p')).dy, 16.0); |
| expect(tester.getTopLeft(find.byKey(sKey)).dy, 12.0); |
| expect(tester.getTopLeft(find.text('s')).dy, 16.0); |
| expect(tester.getTopRight(find.byKey(sKey)).dx, 788.0); |
| expect(tester.getTopRight(find.text('s')).dx, 784.0); |
| |
| // layout is a row: [prefix text suffix] |
| expect(tester.getTopLeft(find.byKey(pKey)).dx, 12.0); |
| expect(tester.getTopRight(find.byKey(pKey)).dx, tester.getTopLeft(find.text('text')).dx); |
| expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopRight(find.byKey(sKey)).dx)); |
| }); |
| |
| testWidgets('InputDecorator tall prefix', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| prefix: SizedBox( |
| key: pKey, |
| height: 100, |
| width: 10, |
| ), |
| filled: true, |
| ), |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| 'text', |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is ~127.2dps because |
| // the prefix is 100dps tall, but it aligns with the input's baseline, |
| // overlapping the input a bit. |
| // 12 - top padding |
| // 100 - total height of prefix |
| // -16 - input prefix overlap (distance input top to baseline, not exact) |
| // 20 - input text (ahem font size 16dps) |
| // 0 - bottom prefix/suffix padding |
| // 12 - bottom padding |
| |
| expect(tester.getSize(find.byType(InputDecorator)).width, 800.0); |
| expect(tester.getSize(find.byType(InputDecorator)).height, moreOrLessEquals(128.0, epsilon: .0001)); |
| expect(tester.getSize(find.text('text')).height, 20.0); |
| expect(tester.getSize(find.byKey(pKey)).height, 100.0); |
| expect(tester.getTopLeft(find.text('text')).dy, moreOrLessEquals(96, epsilon: .0001)); // 12 + 100 - 16 |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, 12.0); |
| |
| // layout is a row: [prefix text suffix] |
| expect(tester.getTopLeft(find.byKey(pKey)).dx, 12.0); |
| expect(tester.getTopRight(find.byKey(pKey)).dx, tester.getTopLeft(find.text('text')).dx); |
| }); |
| |
| testWidgets('InputDecorator tall prefix with border', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| border: OutlineInputBorder(), |
| prefix: SizedBox( |
| key: pKey, |
| height: 100, |
| width: 10, |
| ), |
| filled: true, |
| ), |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| 'text', |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is ~127.2dps because |
| // the prefix is 100dps tall, but it aligns with the input's baseline, |
| // overlapping the input a bit. |
| // 24 - top padding |
| // 100 - total height of prefix |
| // -16 - input prefix overlap (distance input top to baseline, not exact) |
| // 20 - input text (ahem font size 16dps) |
| // 0 - bottom prefix/suffix padding |
| // 16 - bottom padding |
| // When a border is present, the input text and prefix/suffix are centered |
| // within the input. Here, that will be content of height 106, including 2 |
| // extra pixels of space, centered within an input of height 144. That gives |
| // 19 pixels of space on each side of the content, so the prefix is |
| // positioned at 19, and the text is at 19+100-16=103. |
| |
| expect(tester.getSize(find.byType(InputDecorator)).width, 800.0); |
| expect(tester.getSize(find.byType(InputDecorator)).height, moreOrLessEquals(144, epsilon: .0001)); |
| expect(tester.getSize(find.text('text')).height, 20.0); |
| expect(tester.getSize(find.byKey(pKey)).height, 100.0); |
| expect(tester.getTopLeft(find.text('text')).dy, moreOrLessEquals(103, epsilon: .0001)); |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, 19.0); |
| |
| // layout is a row: [prefix text suffix] |
| expect(tester.getTopLeft(find.byKey(pKey)).dx, 12.0); |
| expect(tester.getTopRight(find.byKey(pKey)).dx, tester.getTopLeft(find.text('text')).dx); |
| }); |
| |
| testWidgets('InputDecorator prefixIcon/suffixIcon', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| prefixIcon: Icon(Icons.pages), |
| suffixIcon: Icon(Icons.satellite), |
| filled: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 48dps because the prefix icon's minimum size |
| // is 48x48 and the rest of the elements only require 40dps: |
| // 12 - top padding |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); |
| expect(tester.getSize(find.text('text')).height, 16.0); |
| expect(tester.getSize(find.byIcon(Icons.pages)).height, 48.0); |
| expect(tester.getSize(find.byIcon(Icons.satellite)).height, 48.0); |
| expect(tester.getTopLeft(find.text('text')).dy, 12.0); |
| expect(tester.getTopLeft(find.byIcon(Icons.pages)).dy, 0.0); |
| expect(tester.getTopLeft(find.byIcon(Icons.satellite)).dy, 0.0); |
| expect(tester.getTopRight(find.byIcon(Icons.satellite)).dx, 800.0); |
| |
| |
| // layout is a row: [icon text icon] |
| expect(tester.getTopLeft(find.byIcon(Icons.pages)).dx, 0.0); |
| expect(tester.getTopRight(find.byIcon(Icons.pages)).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('text')).dx)); |
| expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopLeft(find.byIcon(Icons.satellite)).dx)); |
| }); |
| |
| testWidgets('InputDecorator prefixIconConstraints/suffixIconConstraints', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| prefixIcon: Icon(Icons.pages), |
| prefixIconConstraints: BoxConstraints( |
| minWidth: 32, |
| minHeight: 32, |
| ), |
| suffixIcon: Icon(Icons.satellite), |
| suffixIconConstraints: BoxConstraints( |
| minWidth: 25, |
| minHeight: 25, |
| ), |
| isDense: true, // has to be true to go below 48px height |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 32px because the prefix icon |
| // is now a custom value |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 32.0)); |
| expect(tester.getSize(find.text('text')).height, 16.0); |
| expect(tester.getSize(find.byIcon(Icons.pages)).height, 32.0); |
| expect(tester.getSize(find.byIcon(Icons.satellite)).height, 25.0); |
| |
| // (InputDecorator height - Text widget height) / 2 |
| expect(tester.getTopLeft(find.text('text')).dy, (32.0 - 16.0) / 2); |
| // prefixIcon should take up the entire height of InputDecorator |
| expect(tester.getTopLeft(find.byIcon(Icons.pages)).dy, 0.0); |
| // (InputDecorator height - suffixIcon height) / 2 |
| expect(tester.getTopLeft(find.byIcon(Icons.satellite)).dy, (32.0 - 25.0) / 2); |
| expect(tester.getTopRight(find.byIcon(Icons.satellite)).dx, 800.0); |
| }); |
| |
| testWidgets('prefix/suffix icons are centered when smaller than 48 by 48', (WidgetTester tester) async { |
| const Key prefixKey = Key('prefix'); |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| decoration: const InputDecoration( |
| prefixIcon: Padding( |
| padding: EdgeInsets.all(16.0), |
| child: SizedBox(width: 8.0, height: 8.0, key: prefixKey), |
| ), |
| filled: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 48dps because the prefix icon's minimum size |
| // is 48x48 and the rest of the elements only require 40dps: |
| // 12 - top padding |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); |
| expect(tester.getSize(find.byKey(prefixKey)).height, 16.0); |
| expect(tester.getTopLeft(find.byKey(prefixKey)).dy, 16.0); |
| }); |
| |
| testWidgets('InputDecorator respects reduced theme visualDensity', (WidgetTester tester) async { |
| // Label is visible, hint is not (opacity 0.0). |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| visualDensity: VisualDensity.compact, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| // The label is not floating so it's vertically centered. |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 40.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 16.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 32.0); |
| expect(getOpacity(tester, 'hint'), 0.0); |
| expect(getBorderBottom(tester), 48.0); |
| expect(getBorderWeight(tester), 1.0); |
| |
| // Label moves upwards, hint is visible (opacity 1.0). |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| isFocused: true, |
| visualDensity: VisualDensity.compact, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| // The hint's opacity animates from 0.0 to 1.0. |
| // The animation's duration is 167ms. |
| { |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity50ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity100ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity100ms, inExclusiveRange(hintOpacity50ms, 1.0)); |
| } |
| |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 40.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(tester.getTopLeft(find.text('hint')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('hint')).dy, 40.0); |
| expect(getOpacity(tester, 'hint'), 1.0); |
| expect(getBorderBottom(tester), 48.0); |
| expect(getBorderWeight(tester), 2.0); |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isFocused: true, |
| visualDensity: VisualDensity.compact, |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| // The hint's opacity animates from 1.0 to 0.0. |
| // The animation's duration is 167ms. |
| { |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity50ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity100ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity100ms, inExclusiveRange(0.0, hintOpacity50ms)); |
| } |
| |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('text')).dy,40.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(tester.getTopLeft(find.text('hint')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('hint')).dy,40.0); |
| expect(getOpacity(tester, 'hint'), 0.0); |
| expect(getBorderBottom(tester), 48.0); |
| expect(getBorderWeight(tester), 2.0); |
| }); |
| |
| testWidgets('InputDecorator respects increased theme visualDensity', (WidgetTester tester) async { |
| // Label is visible, hint is not (opacity 0.0). |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| visualDensity: const VisualDensity(horizontal: 2.0, vertical: 2.0), |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| // The label is not floating so it's vertically centered. |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 64.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 32.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 48.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 24.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 40.0); |
| expect(getOpacity(tester, 'hint'), 0.0); |
| expect(getBorderBottom(tester), 64.0); |
| expect(getBorderWeight(tester), 1.0); |
| |
| // Label moves upwards, hint is visible (opacity 1.0). |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isEmpty: true, |
| isFocused: true, |
| visualDensity: const VisualDensity(horizontal: 2.0, vertical: 2.0), |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| // The hint's opacity animates from 0.0 to 1.0. |
| // The animation's duration is 167ms. |
| { |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity50ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity100ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity100ms, inExclusiveRange(hintOpacity50ms, 1.0)); |
| } |
| |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 64.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 32.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 48.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(tester.getTopLeft(find.text('hint')).dy, 32.0); |
| expect(tester.getBottomLeft(find.text('hint')).dy, 48.0); |
| expect(getOpacity(tester, 'hint'), 1.0); |
| expect(getBorderBottom(tester), 64.0); |
| expect(getBorderWeight(tester), 2.0); |
| |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| isFocused: true, |
| visualDensity: const VisualDensity(horizontal: 2.0, vertical: 2.0), |
| decoration: const InputDecoration( |
| labelText: 'label', |
| hintText: 'hint', |
| ), |
| ), |
| ); |
| |
| // The hint's opacity animates from 1.0 to 0.0. |
| // The animation's duration is 167ms. |
| { |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity50ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| final double hintOpacity100ms = getOpacity(tester, 'hint'); |
| expect(hintOpacity100ms, inExclusiveRange(0.0, hintOpacity50ms)); |
| } |
| |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 64.0)); |
| expect(tester.getTopLeft(find.text('text')).dy, 32.0); |
| expect(tester.getBottomLeft(find.text('text')).dy, 48.0); |
| expect(tester.getTopLeft(find.text('label')).dy, 12.0); |
| expect(tester.getBottomLeft(find.text('label')).dy, 24.0); |
| expect(tester.getTopLeft(find.text('hint')).dy, 32.0); |
| expect(tester.getBottomLeft(find.text('hint')).dy, 48.0); |
| expect(getOpacity(tester, 'hint'), 0.0); |
| expect(getBorderBottom(tester), 64.0); |
| expect(getBorderWeight(tester), 2.0); |
| }); |
| |
| testWidgets('prefix/suffix icons increase height of decoration when larger than 48 by 48', (WidgetTester tester) async { |
| const Key prefixKey = Key('prefix'); |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| decoration: const InputDecoration( |
| prefixIcon: SizedBox(width: 100.0, height: 100.0, key: prefixKey), |
| filled: true, |
| ), |
| ), |
| ); |
| |
| // Overall height for this InputDecorator is 100dps because the prefix icon's size |
| // is 100x100 and the rest of the elements only require 40dps: |
| // 12 - top padding |
| // 16 - input text (ahem font size 16dps) |
| // 12 - bottom padding |
| |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 100.0)); |
| expect(tester.getSize(find.byKey(prefixKey)).height, 100.0); |
| expect(tester.getTopLeft(find.byKey(prefixKey)).dy, 0.0); |
| }); |
| |
| group('constraints', () { |
| testWidgets('No InputDecorator constraints', (WidgetTester tester) async { |
| await tester.pumpWidget(buildInputDecorator( |
| useMaterial3: useMaterial3, |
| )); |
| |
| // Should fill the screen width and be default height |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(800, 48)); |
| }); |
| |
| testWidgets('InputDecoratorThemeData constraints', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| theme: ThemeData( |
| inputDecorationTheme: const InputDecorationTheme( |
| constraints: BoxConstraints(maxWidth: 300, maxHeight: 40), |
| ), |
| ), |
| ), |
| ); |
| |
| // Theme settings should make it 300x40 pixels |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(300, 40)); |
| }); |
| |
| testWidgets('InputDecorator constraints', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| theme: ThemeData( |
| inputDecorationTheme: const InputDecorationTheme( |
| constraints: BoxConstraints(maxWidth: 300, maxHeight: 40), |
| ), |
| ), |
| decoration: const InputDecoration( |
| constraints: BoxConstraints(maxWidth: 200, maxHeight: 32), |
| ), |
| ), |
| ); |
| |
| // InputDecoration.constraints should override the theme. It should be |
| // only 200x32 pixels |
| expect(tester.getSize(find.byType(InputDecorator)), const Size(200, 32)); |
| }); |
| }); |
| |
| group('textAlignVertical position', () { |
| group('simple case', () { |
| testWidgets('align top (default)', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, // so we have a tall input where align can vary |
| decoration: const InputDecoration( |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.top, // default when no border |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Same as the default case above. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(12.0, epsilon: .0001)); |
| }); |
| |
| testWidgets('align center', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.center, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Below the top aligned case. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(290.0, epsilon: .0001)); |
| }); |
| |
| testWidgets('align bottom', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.bottom, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Below the center aligned case. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(568.0, epsilon: .0001)); |
| }); |
| |
| testWidgets('align as a double', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| filled: true, |
| ), |
| textAlignVertical: const TextAlignVertical(y: 0.75), |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // In between the center and bottom aligned cases. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(498.5, epsilon: .0001)); |
| }); |
| |
| testWidgets('works with density and content padding', (WidgetTester tester) async { |
| const Key key = Key('child'); |
| const Key containerKey = Key('container'); |
| const double totalHeight = 100.0; |
| const double childHeight = 20.0; |
| const VisualDensity visualDensity = VisualDensity(vertical: VisualDensity.maximumDensity); |
| const EdgeInsets contentPadding = EdgeInsets.only(top: 6, bottom: 14); |
| |
| await tester.pumpWidget( |
| Center( |
| child: SizedBox( |
| key: containerKey, |
| height: totalHeight, |
| child: buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| border: InputBorder.none, |
| contentPadding: contentPadding, |
| ), |
| textAlignVertical: TextAlignVertical.center, |
| visualDensity: visualDensity, |
| child: const SizedBox(key: key, height: childHeight), |
| ), |
| ), |
| ), |
| ); |
| |
| // Vertical components: contentPadding.vertical, densityOffset.y, child |
| final double childVerticalSpaceAffordance = totalHeight |
| - visualDensity.baseSizeAdjustment.dy |
| - contentPadding.vertical; |
| |
| // TextAlignVertical.center is specified so `child` needs to be centered |
| // in the avaiable space. |
| final double childMargin = (childVerticalSpaceAffordance - childHeight) / 2; |
| final double childTop = visualDensity.baseSizeAdjustment.dy / 2.0 |
| + contentPadding.top |
| + childMargin; |
| |
| expect( |
| tester.getTopLeft(find.byKey(key)).dy, |
| tester.getTopLeft(find.byKey(containerKey)).dy + childTop, |
| ); |
| }); |
| }); |
| |
| group('outline border', () { |
| testWidgets('align top', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, // so we have a tall input where align can vary |
| decoration: const InputDecoration( |
| filled: true, |
| border: OutlineInputBorder(), |
| ), |
| textAlignVertical: TextAlignVertical.top, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Similar to the case without a border, but with a little extra room at |
| // the top to make room for the border. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(24.0, epsilon: .0001)); |
| }); |
| |
| testWidgets('align center (default)', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| filled: true, |
| border: OutlineInputBorder(), |
| ), |
| textAlignVertical: TextAlignVertical.center, // default when border |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Below the top aligned case. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(289.0, epsilon: .0001)); |
| }); |
| |
| testWidgets('align bottom', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| filled: true, |
| border: OutlineInputBorder(), |
| ), |
| textAlignVertical: TextAlignVertical.bottom, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Below the center aligned case. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(564.0, epsilon: .0001)); |
| }); |
| }); |
| |
| group('prefix', () { |
| testWidgets('InputDecorator tall prefix align top', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| prefix: SizedBox( |
| key: pKey, |
| height: 100, |
| width: 10, |
| ), |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.top, // default when no border |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Same as the default case above. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(96, epsilon: .0001)); |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, 12.0); |
| }); |
| |
| testWidgets('InputDecorator tall prefix align center', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| prefix: SizedBox( |
| key: pKey, |
| height: 100, |
| width: 10, |
| ), |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.center, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Same as the default case above. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(96.0, epsilon: .0001)); |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, 12.0); |
| }); |
| |
| testWidgets('InputDecorator tall prefix align bottom', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| decoration: const InputDecoration( |
| prefix: SizedBox( |
| key: pKey, |
| height: 100, |
| width: 10, |
| ), |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.bottom, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Top of the input + 100 prefix height - overlap |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(96.0, epsilon: .0001)); |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, 12.0); |
| }); |
| }); |
| |
| group('outline border and prefix', () { |
| testWidgets('InputDecorator tall prefix align center', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| border: OutlineInputBorder(), |
| prefix: SizedBox( |
| key: pKey, |
| height: 100, |
| width: 10, |
| ), |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.center, // default when border |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // In the middle of the expanded InputDecorator. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(331.0, epsilon: .0001)); |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, moreOrLessEquals(247.0, epsilon: .0001)); |
| }); |
| |
| testWidgets('InputDecorator tall prefix with border align top', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| border: OutlineInputBorder(), |
| prefix: SizedBox( |
| key: pKey, |
| height: 100, |
| width: 10, |
| ), |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.top, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Above the center example. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(108.0, epsilon: .0001)); |
| // The prefix is positioned at the top of the input, so this value is |
| // the same as the top aligned test without a prefix. |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, 24.0); |
| }); |
| |
| testWidgets('InputDecorator tall prefix with border align bottom', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| border: OutlineInputBorder(), |
| prefix: SizedBox( |
| key: pKey, |
| height: 100, |
| width: 10, |
| ), |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.bottom, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Below the center example. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(564.0, epsilon: .0001)); |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, moreOrLessEquals(480.0, epsilon: .0001)); |
| }); |
| |
| testWidgets('InputDecorator tall prefix with border align double', (WidgetTester tester) async { |
| const Key pKey = Key('p'); |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, |
| decoration: const InputDecoration( |
| border: OutlineInputBorder(), |
| prefix: SizedBox( |
| key: pKey, |
| height: 100, |
| width: 10, |
| ), |
| filled: true, |
| ), |
| textAlignVertical: const TextAlignVertical(y: 0.1), |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // Between the top and center examples. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(354.3, epsilon: .0001)); |
| expect(tester.getTopLeft(find.byKey(pKey)).dy, moreOrLessEquals(270.3, epsilon: .0001)); |
| }); |
| }); |
| |
| group('label', () { |
| testWidgets('align top (default)', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, // so we have a tall input where align can vary |
| decoration: const InputDecoration( |
| labelText: 'label', |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.top, // default |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // The label causes the text to start slightly lower than it would |
| // otherwise. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(28.0, epsilon: .0001)); |
| }); |
| |
| testWidgets('align center', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, // so we have a tall input where align can vary |
| decoration: const InputDecoration( |
| labelText: 'label', |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.center, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // The label reduces the amount of space available for text, so the |
| // center is slightly lower. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(298.0, epsilon: .0001)); |
| }); |
| |
| testWidgets('align bottom', (WidgetTester tester) async { |
| const String text = 'text'; |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| // isEmpty: false (default) |
| // isFocused: false (default) |
| expands: true, // so we have a tall input where align can vary |
| decoration: const InputDecoration( |
| labelText: 'label', |
| filled: true, |
| ), |
| textAlignVertical: TextAlignVertical.bottom, |
| // Set the fontSize so that everything works out to whole numbers. |
| child: const Text( |
| text, |
| style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0), |
| ), |
| ), |
| ); |
| |
| // The label reduces the amount of space available for text, but the |
| // bottom line is still in the same place. |
| expect(tester.getTopLeft(find.text(text)).dy, moreOrLessEquals(568.0, epsilon: .0001)); |
| }); |
| }); |
| }); |
| |
| group('OutlineInputBorder', () { |
| group('default alignment', () { |
| testWidgets('Centers when border', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| buildInputDecorator( |
| useMaterial3: useMaterial3, |
| decoration: const InputDecoration( |
| border: OutlineInputBorder(), |
|