Commit 94d0b2ff authored by TL Lee's avatar TL Lee Committed by Will Larche

[Material] Add API for an unpainted input border (#21289)

* Introduce a rounded InputBorder with no paint

* Update documentation for API

* Change name of Widget to NoStrokeInputBorder and updated documentation

* [FilledInputBorder] PR feedback.

* [FilledInputBorder] typo correction.

* [FilledInputBorder] Removing news.

* [FilledInputBorder] PR feedback.

* [FilledInputBorder] Removing use of borderSide.

* [FilledInputBorder] Adding tests for hashcode.

* [FilledInputBorder] Comments.

* [TextFields] Input decoration feature parity.

* [TextFields] Making floating of placeholder optional.

* [TextFields] Cleanup

* [TextFields] Removing unused isAnimated.

* [TextFields] Cleanup.

* [TextFields] Correcting border.

* [TextFields] Correcting comment.

* [TextFields] Comment.

* [TextFields] Corrections for tests.

* [TextFields] Cleanup.

* [TextFields] Cleanup.

* [TextFields] Tests.

* [TextFields] Cleanup.

* [TextFields] Cleanup.

* [TextFields] Formatting.

* [TextFields] PR feedback.

* [TextFields] PR feedback.

* [TextFields] PR feedback.
parent 903c5f8d
...@@ -139,7 +139,7 @@ class UnderlineInputBorder extends InputBorder { ...@@ -139,7 +139,7 @@ class UnderlineInputBorder extends InputBorder {
/// and right corners have a circular radius of 4.0. The [borderRadius] /// and right corners have a circular radius of 4.0. The [borderRadius]
/// parameter must not be null. /// parameter must not be null.
const UnderlineInputBorder({ const UnderlineInputBorder({
BorderSide borderSide = BorderSide.none, BorderSide borderSide = const BorderSide(),
this.borderRadius = const BorderRadius.only( this.borderRadius = const BorderRadius.only(
topLeft: Radius.circular(4.0), topLeft: Radius.circular(4.0),
topRight: Radius.circular(4.0), topRight: Radius.circular(4.0),
...@@ -256,17 +256,26 @@ class UnderlineInputBorder extends InputBorder { ...@@ -256,17 +256,26 @@ class UnderlineInputBorder extends InputBorder {
class OutlineInputBorder extends InputBorder { class OutlineInputBorder extends InputBorder {
/// Creates a rounded rectangle outline border for an [InputDecorator]. /// Creates a rounded rectangle outline border for an [InputDecorator].
/// ///
/// The [borderSide] parameter defaults to [BorderSide.none] (it must not be /// If the [borderSide] parameter is [BorderSide.none], it will not draw a
/// null). Applications typically do not specify a [borderSide] parameter /// border. However, it will still define a shape (which you can see if
/// because the input decorator substitutes its own, using [copyWith], based /// [InputDecoration.filled] is true).
/// on the current theme and [InputDecorator.isFocused]. ///
/// If an application does not specify a [borderSide] parameter of
/// value [BorderSide.none], the input decorator substitutes its own, using
/// [copyWith], based on the current theme and [InputDecorator.isFocused].
/// ///
/// The [borderRadius] parameter defaults to a value where all four /// The [borderRadius] parameter defaults to a value where all four
/// corners have a circular radius of 4.0. The [borderRadius] parameter /// corners have a circular radius of 4.0. The [borderRadius] parameter
/// must not be null and the corner radii must be circular, i.e. their /// must not be null and the corner radii must be circular, i.e. their
/// [Radius.x] and [Radius.y] values must be the same. /// [Radius.x] and [Radius.y] values must be the same.
///
/// See also:
/// * [InputDecoration.hasFloatingPlaceholder], which should be set to false
/// when the [borderSide] is [BorderSide.none]. If let as true, the label
/// will extend beyond the container as if the border were still being
/// drawn.
const OutlineInputBorder({ const OutlineInputBorder({
BorderSide borderSide = BorderSide.none, BorderSide borderSide = const BorderSide(),
this.borderRadius = const BorderRadius.all(Radius.circular(4.0)), this.borderRadius = const BorderRadius.all(Radius.circular(4.0)),
this.gapPadding = 4.0, this.gapPadding = 4.0,
}) : assert(borderRadius != null), }) : assert(borderRadius != null),
......
...@@ -1488,7 +1488,9 @@ class InputDecorator extends StatefulWidget { ...@@ -1488,7 +1488,9 @@ class InputDecorator extends StatefulWidget {
/// Typically an [EditableText], [DropdownButton], or [InkWell]. /// Typically an [EditableText], [DropdownButton], or [InkWell].
final Widget child; final Widget child;
bool get _labelIsFloating => !isEmpty || isFocused; /// Whether the label needs to get out of the way of the input, either by
/// floating or disappearing.
bool get _labelShouldWithdraw => !isEmpty || isFocused;
@override @override
_InputDecoratorState createState() => _InputDecoratorState(); _InputDecoratorState createState() => _InputDecoratorState();
...@@ -1526,7 +1528,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1526,7 +1528,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
_floatingLabelController = AnimationController( _floatingLabelController = AnimationController(
duration: _kTransitionDuration, duration: _kTransitionDuration,
vsync: this, vsync: this,
value: widget._labelIsFloating ? 1.0 : 0.0, value: (widget.decoration.hasFloatingPlaceholder && widget._labelShouldWithdraw) ? 1.0 : 0.0,
); );
_floatingLabelController.addListener(_handleChange); _floatingLabelController.addListener(_handleChange);
...@@ -1573,9 +1575,10 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1573,9 +1575,10 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
if (widget.decoration != old.decoration) if (widget.decoration != old.decoration)
_effectiveDecoration = null; _effectiveDecoration = null;
if (widget._labelIsFloating != old._labelIsFloating) { if (widget._labelShouldWithdraw != old._labelShouldWithdraw && widget.decoration.hasFloatingPlaceholder) {
if (widget._labelIsFloating) if (widget._labelShouldWithdraw) {
_floatingLabelController.forward(); _floatingLabelController.forward();
}
else else
_floatingLabelController.reverse(); _floatingLabelController.reverse();
} }
...@@ -1641,7 +1644,11 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1641,7 +1644,11 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
// True if the label will be shown and the hint will not. // True if the label will be shown and the hint will not.
// If we're not focused, there's no value, and labelText was provided, // If we're not focused, there's no value, and labelText was provided,
// then the label appears where the hint would. // then the label appears where the hint would.
bool get _hasInlineLabel => !isFocused && isEmpty && decoration.labelText != null; bool get _hasInlineLabel => !widget._labelShouldWithdraw && decoration.labelText != null;
// If the label is a floating placeholder, it's always shown.
bool get _shouldShowLabel => _hasInlineLabel || decoration.hasFloatingPlaceholder;
// The base style for the inline label or hint when they're displayed "inline", // The base style for the inline label or hint when they're displayed "inline",
// i.e. when they appear in place of the empty text field. // i.e. when they appear in place of the empty text field.
...@@ -1671,6 +1678,10 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1671,6 +1678,10 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
} }
InputBorder _getDefaultBorder(ThemeData themeData) { InputBorder _getDefaultBorder(ThemeData themeData) {
if (decoration.border?.borderSide == BorderSide.none) {
return decoration.border;
}
Color borderColor; Color borderColor;
if (decoration.enabled) { if (decoration.enabled) {
borderColor = decoration.errorText == null borderColor = decoration.errorText == null
...@@ -1731,10 +1742,14 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1731,10 +1742,14 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
final TextStyle inlineLabelStyle = inlineStyle.merge(decoration.labelStyle); final TextStyle inlineLabelStyle = inlineStyle.merge(decoration.labelStyle);
final Widget label = decoration.labelText == null ? null : _Shaker( final Widget label = decoration.labelText == null ? null : _Shaker(
animation: _shakingLabelController.view, animation: _shakingLabelController.view,
child: AnimatedDefaultTextStyle( child: AnimatedOpacity(
duration: _kTransitionDuration, duration: _kTransitionDuration,
curve: _kTransitionCurve, curve: _kTransitionCurve,
style: widget._labelIsFloating opacity: _shouldShowLabel ? 1.0 : 0.0,
child: AnimatedDefaultTextStyle(
duration:_kTransitionDuration,
curve: _kTransitionCurve,
style: widget._labelShouldWithdraw
? _getFloatingLabelStyle(themeData) ? _getFloatingLabelStyle(themeData)
: inlineLabelStyle, : inlineLabelStyle,
child: Text( child: Text(
...@@ -1743,11 +1758,12 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1743,11 +1758,12 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
textAlign: textAlign, textAlign: textAlign,
), ),
), ),
),
); );
final Widget prefix = decoration.prefix == null && decoration.prefixText == null ? null : final Widget prefix = decoration.prefix == null && decoration.prefixText == null ? null :
_AffixText( _AffixText(
labelIsFloating: widget._labelIsFloating, labelIsFloating: widget._labelShouldWithdraw,
text: decoration.prefixText, text: decoration.prefixText,
style: decoration.prefixStyle ?? hintStyle, style: decoration.prefixStyle ?? hintStyle,
child: decoration.prefix, child: decoration.prefix,
...@@ -1755,7 +1771,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1755,7 +1771,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
final Widget suffix = decoration.suffix == null && decoration.suffixText == null ? null : final Widget suffix = decoration.suffix == null && decoration.suffixText == null ? null :
_AffixText( _AffixText(
labelIsFloating: widget._labelIsFloating, labelIsFloating: widget._labelShouldWithdraw,
text: decoration.suffixText, text: decoration.suffixText,
style: decoration.suffixStyle ?? hintStyle, style: decoration.suffixStyle ?? hintStyle,
child: decoration.suffix, child: decoration.suffix,
...@@ -1931,6 +1947,7 @@ class InputDecoration { ...@@ -1931,6 +1947,7 @@ class InputDecoration {
this.errorText, this.errorText,
this.errorStyle, this.errorStyle,
this.errorMaxLines, this.errorMaxLines,
this.hasFloatingPlaceholder = true,
this.isDense, this.isDense,
this.contentPadding, this.contentPadding,
this.prefixIcon, this.prefixIcon,
...@@ -1965,6 +1982,7 @@ class InputDecoration { ...@@ -1965,6 +1982,7 @@ class InputDecoration {
/// Sets the [isCollapsed] property to true. /// Sets the [isCollapsed] property to true.
const InputDecoration.collapsed({ const InputDecoration.collapsed({
@required this.hintText, @required this.hintText,
this.hasFloatingPlaceholder = true,
this.hintStyle, this.hintStyle,
this.filled = false, this.filled = false,
this.fillColor, this.fillColor,
...@@ -2088,6 +2106,16 @@ class InputDecoration { ...@@ -2088,6 +2106,16 @@ class InputDecoration {
/// of the [Text] widget used to display the error. /// of the [Text] widget used to display the error.
final int errorMaxLines; final int errorMaxLines;
/// Whether the label floats on focus.
///
/// If this is false, the placeholder disappears when the input has focus or
/// inputted text.
/// If this is true, the placeholder will rise to the top of the input when
/// the input has focus or inputted text.
///
/// Defaults to true.
final bool hasFloatingPlaceholder;
/// Whether the input [child] is part of a dense form (i.e., uses less vertical /// Whether the input [child] is part of a dense form (i.e., uses less vertical
/// space). /// space).
/// ///
...@@ -2424,6 +2452,7 @@ class InputDecoration { ...@@ -2424,6 +2452,7 @@ class InputDecoration {
String errorText, String errorText,
TextStyle errorStyle, TextStyle errorStyle,
int errorMaxLines, int errorMaxLines,
bool hasFloatingPlaceholder,
bool isDense, bool isDense,
EdgeInsetsGeometry contentPadding, EdgeInsetsGeometry contentPadding,
Widget prefixIcon, Widget prefixIcon,
...@@ -2458,6 +2487,7 @@ class InputDecoration { ...@@ -2458,6 +2487,7 @@ class InputDecoration {
errorText: errorText ?? this.errorText, errorText: errorText ?? this.errorText,
errorStyle: errorStyle ?? this.errorStyle, errorStyle: errorStyle ?? this.errorStyle,
errorMaxLines: errorMaxLines ?? this.errorMaxLines, errorMaxLines: errorMaxLines ?? this.errorMaxLines,
hasFloatingPlaceholder: hasFloatingPlaceholder ?? this.hasFloatingPlaceholder,
isDense: isDense ?? this.isDense, isDense: isDense ?? this.isDense,
contentPadding: contentPadding ?? this.contentPadding, contentPadding: contentPadding ?? this.contentPadding,
prefixIcon: prefixIcon ?? this.prefixIcon, prefixIcon: prefixIcon ?? this.prefixIcon,
...@@ -2495,6 +2525,7 @@ class InputDecoration { ...@@ -2495,6 +2525,7 @@ class InputDecoration {
hintStyle: hintStyle ?? theme.hintStyle, hintStyle: hintStyle ?? theme.hintStyle,
errorStyle: errorStyle ?? theme.errorStyle, errorStyle: errorStyle ?? theme.errorStyle,
errorMaxLines: errorMaxLines ?? theme.errorMaxLines, errorMaxLines: errorMaxLines ?? theme.errorMaxLines,
hasFloatingPlaceholder: hasFloatingPlaceholder ?? theme.hasFloatingPlaceholder,
isDense: isDense ?? theme.isDense, isDense: isDense ?? theme.isDense,
contentPadding: contentPadding ?? theme.contentPadding, contentPadding: contentPadding ?? theme.contentPadding,
prefixStyle: prefixStyle ?? theme.prefixStyle, prefixStyle: prefixStyle ?? theme.prefixStyle,
...@@ -2528,6 +2559,7 @@ class InputDecoration { ...@@ -2528,6 +2559,7 @@ class InputDecoration {
&& typedOther.errorText == errorText && typedOther.errorText == errorText
&& typedOther.errorStyle == errorStyle && typedOther.errorStyle == errorStyle
&& typedOther.errorMaxLines == errorMaxLines && typedOther.errorMaxLines == errorMaxLines
&& typedOther.hasFloatingPlaceholder == hasFloatingPlaceholder
&& typedOther.isDense == isDense && typedOther.isDense == isDense
&& typedOther.contentPadding == contentPadding && typedOther.contentPadding == contentPadding
&& typedOther.isCollapsed == isCollapsed && typedOther.isCollapsed == isCollapsed
...@@ -2568,6 +2600,7 @@ class InputDecoration { ...@@ -2568,6 +2600,7 @@ class InputDecoration {
errorText, errorText,
errorStyle, errorStyle,
errorMaxLines, errorMaxLines,
hasFloatingPlaceholder,
isDense, isDense,
hashValues( hashValues(
contentPadding, contentPadding,
...@@ -2619,6 +2652,8 @@ class InputDecoration { ...@@ -2619,6 +2652,8 @@ class InputDecoration {
description.add('errorStyle: "$errorStyle"'); description.add('errorStyle: "$errorStyle"');
if (errorMaxLines != null) if (errorMaxLines != null)
description.add('errorMaxLines: "$errorMaxLines"'); description.add('errorMaxLines: "$errorMaxLines"');
if (hasFloatingPlaceholder == false)
description.add('hasFloatingPlaceholder: false');
if (isDense ?? false) if (isDense ?? false)
description.add('isDense: $isDense'); description.add('isDense: $isDense');
if (contentPadding != null) if (contentPadding != null)
...@@ -2691,6 +2726,7 @@ class InputDecorationTheme extends Diagnosticable { ...@@ -2691,6 +2726,7 @@ class InputDecorationTheme extends Diagnosticable {
this.hintStyle, this.hintStyle,
this.errorStyle, this.errorStyle,
this.errorMaxLines, this.errorMaxLines,
this.hasFloatingPlaceholder = true,
this.isDense = false, this.isDense = false,
this.contentPadding, this.contentPadding,
this.isCollapsed = false, this.isCollapsed = false,
...@@ -2747,6 +2783,16 @@ class InputDecorationTheme extends Diagnosticable { ...@@ -2747,6 +2783,16 @@ class InputDecorationTheme extends Diagnosticable {
/// of the [Text] widget used to display the error. /// of the [Text] widget used to display the error.
final int errorMaxLines; final int errorMaxLines;
/// Whether the placeholder text floats to become a label on focus.
///
/// If this is false, the placeholder disappears when the input has focus or
/// inputted text.
/// If this is true, the placeholder will rise to the top of the input when
/// the input has focus or inputted text.
///
/// Defaults to true.
final bool hasFloatingPlaceholder;
/// Whether the input decorator's child is part of a dense form (i.e., uses /// Whether the input decorator's child is part of a dense form (i.e., uses
/// less vertical space). /// less vertical space).
/// ///
...@@ -2965,6 +3011,7 @@ class InputDecorationTheme extends Diagnosticable { ...@@ -2965,6 +3011,7 @@ class InputDecorationTheme extends Diagnosticable {
properties.add(DiagnosticsProperty<TextStyle>('hintStyle', hintStyle, defaultValue: defaultTheme.hintStyle)); properties.add(DiagnosticsProperty<TextStyle>('hintStyle', hintStyle, defaultValue: defaultTheme.hintStyle));
properties.add(DiagnosticsProperty<TextStyle>('errorStyle', errorStyle, defaultValue: defaultTheme.errorStyle)); properties.add(DiagnosticsProperty<TextStyle>('errorStyle', errorStyle, defaultValue: defaultTheme.errorStyle));
properties.add(DiagnosticsProperty<int>('errorMaxLines', errorMaxLines, defaultValue: defaultTheme.errorMaxLines)); properties.add(DiagnosticsProperty<int>('errorMaxLines', errorMaxLines, defaultValue: defaultTheme.errorMaxLines));
properties.add(DiagnosticsProperty<bool>('hasFloatingPlaceholder', hasFloatingPlaceholder, defaultValue: defaultTheme.hasFloatingPlaceholder));
properties.add(DiagnosticsProperty<bool>('isDense', isDense, defaultValue: defaultTheme.isDense)); properties.add(DiagnosticsProperty<bool>('isDense', isDense, defaultValue: defaultTheme.isDense));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: defaultTheme.contentPadding)); properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: defaultTheme.contentPadding));
properties.add(DiagnosticsProperty<bool>('isCollapsed', isCollapsed, defaultValue: defaultTheme.isCollapsed)); properties.add(DiagnosticsProperty<bool>('isCollapsed', isCollapsed, defaultValue: defaultTheme.isCollapsed));
......
...@@ -81,10 +81,10 @@ double getBorderWeight(WidgetTester tester) => getBorderSide(tester)?.width; ...@@ -81,10 +81,10 @@ double getBorderWeight(WidgetTester tester) => getBorderSide(tester)?.width;
Color getBorderColor(WidgetTester tester) => getBorderSide(tester)?.color; Color getBorderColor(WidgetTester tester) => getBorderSide(tester)?.color;
double getHintOpacity(WidgetTester tester) { double getOpacity(WidgetTester tester, String textValue) {
final FadeTransition opacityWidget = tester.widget<FadeTransition>( final FadeTransition opacityWidget = tester.widget<FadeTransition>(
find.ancestor( find.ancestor(
of: find.text('hint'), of: find.text(textValue),
matching: find.byType(FadeTransition), matching: find.byType(FadeTransition),
).first ).first
); );
...@@ -304,7 +304,7 @@ void main() { ...@@ -304,7 +304,7 @@ void main() {
expect(tester.getBottomLeft(find.text('text')).dy, 44.0); expect(tester.getBottomLeft(find.text('text')).dy, 44.0);
expect(tester.getTopLeft(find.text('label')).dy, 20.0); expect(tester.getTopLeft(find.text('label')).dy, 20.0);
expect(tester.getBottomLeft(find.text('label')).dy, 36.0); expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
expect(getHintOpacity(tester), 0.0); expect(getOpacity(tester, 'hint'), 0.0);
expect(getBorderBottom(tester), 56.0); expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0); expect(getBorderWeight(tester), 1.0);
...@@ -324,10 +324,10 @@ void main() { ...@@ -324,10 +324,10 @@ void main() {
// The animation's duration is 200ms. // The animation's duration is 200ms.
{ {
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
final double hintOpacity50ms = getHintOpacity(tester); final double hintOpacity50ms = getOpacity(tester, 'hint');
expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0)); expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0));
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
final double hintOpacity100ms = getHintOpacity(tester); final double hintOpacity100ms = getOpacity(tester, 'hint');
expect(hintOpacity100ms, inExclusiveRange(hintOpacity50ms, 1.0)); expect(hintOpacity100ms, inExclusiveRange(hintOpacity50ms, 1.0));
} }
...@@ -339,7 +339,7 @@ void main() { ...@@ -339,7 +339,7 @@ void main() {
expect(tester.getBottomLeft(find.text('label')).dy, 24.0); expect(tester.getBottomLeft(find.text('label')).dy, 24.0);
expect(tester.getTopLeft(find.text('hint')).dy, 28.0); expect(tester.getTopLeft(find.text('hint')).dy, 28.0);
expect(tester.getBottomLeft(find.text('hint')).dy, 44.0); expect(tester.getBottomLeft(find.text('hint')).dy, 44.0);
expect(getHintOpacity(tester), 1.0); expect(getOpacity(tester, 'hint'), 1.0);
expect(getBorderBottom(tester), 56.0); expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 2.0); expect(getBorderWeight(tester), 2.0);
...@@ -358,10 +358,10 @@ void main() { ...@@ -358,10 +358,10 @@ void main() {
// The animation's duration is 200ms. // The animation's duration is 200ms.
{ {
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
final double hintOpacity50ms = getHintOpacity(tester); final double hintOpacity50ms = getOpacity(tester, 'hint');
expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0)); expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0));
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
final double hintOpacity100ms = getHintOpacity(tester); final double hintOpacity100ms = getOpacity(tester, 'hint');
expect(hintOpacity100ms, inExclusiveRange(0.0, hintOpacity50ms)); expect(hintOpacity100ms, inExclusiveRange(0.0, hintOpacity50ms));
} }
...@@ -373,7 +373,7 @@ void main() { ...@@ -373,7 +373,7 @@ void main() {
expect(tester.getBottomLeft(find.text('label')).dy, 24.0); expect(tester.getBottomLeft(find.text('label')).dy, 24.0);
expect(tester.getTopLeft(find.text('hint')).dy, 28.0); expect(tester.getTopLeft(find.text('hint')).dy, 28.0);
expect(tester.getBottomLeft(find.text('hint')).dy, 44.0); expect(tester.getBottomLeft(find.text('hint')).dy, 44.0);
expect(getHintOpacity(tester), 0.0); expect(getOpacity(tester, 'hint'), 0.0);
expect(getBorderBottom(tester), 56.0); expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 2.0); expect(getBorderWeight(tester), 2.0);
}); });
...@@ -413,7 +413,7 @@ void main() { ...@@ -413,7 +413,7 @@ void main() {
expect(tester.getBottomLeft(find.text('text')).dy, 40.0); expect(tester.getBottomLeft(find.text('text')).dy, 40.0);
expect(tester.getTopLeft(find.text('label')).dy, 16.0); expect(tester.getTopLeft(find.text('label')).dy, 16.0);
expect(tester.getBottomLeft(find.text('label')).dy, 32.0); expect(tester.getBottomLeft(find.text('label')).dy, 32.0);
expect(getHintOpacity(tester), 0.0); expect(getOpacity(tester, 'hint'), 0.0);
expect(getBorderBottom(tester), 48.0); expect(getBorderBottom(tester), 48.0);
expect(getBorderWeight(tester), 1.0); expect(getBorderWeight(tester), 1.0);
...@@ -435,7 +435,7 @@ void main() { ...@@ -435,7 +435,7 @@ void main() {
expect(tester.getBottomLeft(find.text('text')).dy, 40.0); expect(tester.getBottomLeft(find.text('text')).dy, 40.0);
expect(tester.getTopLeft(find.text('label')).dy, 8.0); expect(tester.getTopLeft(find.text('label')).dy, 8.0);
expect(tester.getBottomLeft(find.text('label')).dy, 20.0); expect(tester.getBottomLeft(find.text('label')).dy, 20.0);
expect(getHintOpacity(tester), 1.0); expect(getOpacity(tester, 'hint'), 1.0);
expect(getBorderBottom(tester), 48.0); expect(getBorderBottom(tester), 48.0);
expect(getBorderWeight(tester), 2.0); expect(getBorderWeight(tester), 2.0);
}); });
...@@ -1167,7 +1167,7 @@ void main() { ...@@ -1167,7 +1167,7 @@ void main() {
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 16.0)); expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 16.0));
expect(tester.getSize(find.text('text')).height, 16.0); expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 0.0); expect(tester.getTopLeft(find.text('text')).dy, 0.0);
expect(getHintOpacity(tester), 0.0); expect(getOpacity(tester, 'hint'), 0.0);
expect(getBorderWeight(tester), 0.0); expect(getBorderWeight(tester), 0.0);
// The hint should appear // The hint should appear
...@@ -1272,6 +1272,58 @@ void main() { ...@@ -1272,6 +1272,58 @@ void main() {
expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 64.0)); expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 64.0));
}); });
testWidgets('InputDecoration outline shape with no border and no floating placeholder', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
// isFocused: false (default)
isEmpty: true,
decoration: const InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide.none),
hasFloatingPlaceholder: false,
labelText: 'label',
),
),
);
// Overall height for this InputDecorator is 56dps. Layout is:
// 20 - top padding
// 16 - label (ahem font size 16dps)
// 20 - bottom padding
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.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), 0.0);
});
testWidgets('InputDecoration outline shape with no border and no floating placeholder not empty', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
// isFocused: false (default)
decoration: const InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide.none),
hasFloatingPlaceholder: false,
labelText: 'label',
),
),
);
// Overall height for this InputDecorator is 56dps. Layout is:
// 20 - top padding
// 16 - label (ahem font size 16dps)
// 20 - bottom padding
// expect(tester.widget<Text>(find.text('prefix')).style.color, prefixStyle.color);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.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), 0.0);
// The label should not be seen.
expect(getOpacity(tester, 'label'), 0.0);
});
testWidgets('InputDecorationTheme outline border', (WidgetTester tester) async { testWidgets('InputDecorationTheme outline border', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
buildInputDecorator( buildInputDecorator(
...@@ -1878,4 +1930,46 @@ void main() { ...@@ -1878,4 +1930,46 @@ void main() {
await tester.pumpAndSettle(); // border changes are animated await tester.pumpAndSettle(); // border changes are animated
expect(getBorder(tester), disabledBorder); expect(getBorder(tester), disabledBorder);
}); });
test('InputBorder equality', () {
// OutlineInputBorder's equality is defined by the borderRadius, borderSide, & gapPadding
const OutlineInputBorder outlineInputBorder = OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(9.0)),
borderSide: BorderSide(color: Colors.blue),
gapPadding: 32.0,
);
expect(outlineInputBorder, const OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
borderRadius: BorderRadius.all(Radius.circular(9.0)),
gapPadding: 32.0,
));
expect(outlineInputBorder, isNot(const OutlineInputBorder()));
// UnderlineInputBorder's equality is defined only by the borderSide
const UnderlineInputBorder underlineInputBorder = UnderlineInputBorder(borderSide: BorderSide(color: Colors.blue));
expect(underlineInputBorder, const UnderlineInputBorder(borderSide: BorderSide(color: Colors.blue)));
expect(underlineInputBorder, isNot(const UnderlineInputBorder()));
});
test('InputBorder hashCodes', () {
// OutlineInputBorder's hashCode is defined by the borderRadius, borderSide, & gapPadding
const OutlineInputBorder outlineInputBorder = OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(9.0)),
borderSide: BorderSide(color: Colors.blue),
gapPadding: 32.0,
);
expect(outlineInputBorder.hashCode, const OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue),
borderRadius: BorderRadius.all(Radius.circular(9.0)),
gapPadding: 32.0,
).hashCode);
expect(outlineInputBorder.hashCode, isNot(const OutlineInputBorder().hashCode));
// UnderlineInputBorder's hashCode is defined only by the borderSide
const UnderlineInputBorder underlineInputBorder = UnderlineInputBorder(borderSide: BorderSide(color: Colors.blue));
expect(underlineInputBorder.hashCode, const UnderlineInputBorder(borderSide: BorderSide(color: Colors.blue)).hashCode);
expect(underlineInputBorder.hashCode, isNot(const UnderlineInputBorder().hashCode));
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment