blob: 0b56a55e128db4784e565a56d852f372c4f3b1c3 [file] [log] [blame]
// 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.
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,
TextStyle? baseStyle,
TextAlignVertical? textAlignVertical,
VisualDensity? visualDensity,
bool fixTextFieldOutlineLabel = false,
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,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
),
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());
}
InputBorder? getBorder(WidgetTester tester) {
if (!tester.any(findBorderPainter()))
return null;
final CustomPaint customPaint = tester.widget(findBorderPainter());
final dynamic/*_InputBorderPainter*/ inputBorderPainter = customPaint.foregroundPainter;
final dynamic/*_InputBorderTween*/ inputBorderTween = inputBorderPainter.border;
final Animation<double> animation = inputBorderPainter.borderAnimation as Animation<double>;
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;
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() {
testWidgets('InputDecorator input/label layout', (WidgetTester tester) async {
// The label appears above the input text
await tester.pumpWidget(
buildInputDecorator(
// 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(
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(
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(
isEmpty: false,
// 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(
// 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(
isEmpty: true,
isFocused: false,
decoration: const InputDecoration(
labelText: 'label',
),
),
);
// The label animates downwards from it's initial position
// above the input text. The animation's duration is 200ms.
{
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(
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 200ms.
{
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(
isEmpty: true,
isFocused: false,
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), 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(
isEmpty: true,
isFocused: false,
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);
expect(getBorderColor(tester), Colors.transparent);
// alignLabelWithHint: true positions the label at the text baseline,
// aligned with the hint.
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
isFocused: false,
decoration: const InputDecoration(
labelText: 'label',
alignLabelWithHint: true,
hintText: 'hint',
),
),
);
await tester.pumpAndSettle();
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.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);
});
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: OutlineInputBorder(
borderSide: const BorderSide(width: 1, color: Colors.black, style: BorderStyle.solid),
borderRadius: BorderRadius.circular(0),
),
),
),
),
),
);
}
// 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(
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(
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(
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 200ms.
{
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(
isEmpty: false,
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 200ms.
{
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(
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(
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(
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(
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(
// 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(
// 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(
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.toString()}');
// 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.toString()} of ${maxLength.toString()}',
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(
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(
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(
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(
isEmpty: true,
// isFocused: false (default)
decoration: const InputDecoration(
labelText: 'label',
helperText: kHelper3,
helperMaxLines: 3,
errorText: null,
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(
isEmpty: true,
// isFocused: false (default)
decoration: const InputDecoration(
labelText: 'label',
helperText: kHelper3,
helperMaxLines: 2,
errorText: null,
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(
isEmpty: true,
// isFocused: false (default)
decoration: const InputDecoration(
labelText: 'label',
helperText: kHelper2,
helperMaxLines: 3,
errorText: null,
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(
isEmpty: true,
// isFocused: false (default)
decoration: const InputDecoration(
labelText: 'label',
helperText: kHelper1,
helperMaxLines: 3,
errorText: null,
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(
// 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(
// 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 prefix/suffix widgets', (WidgetTester tester) async {
const Key pKey = Key('p');
const Key sKey = Key('s');
await tester.pumpWidget(
buildInputDecorator(
// 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(
// 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(
// 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(
// 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(
// 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(
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(
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(
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 200ms.
{
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(
isEmpty: false,
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 200ms.
{
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(
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(
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 200ms.
{
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(
isEmpty: false,
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 200ms.
{
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(
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('textAlignVertical position', () {
group('simple case', () {
testWidgets('align top (default)', (WidgetTester tester) async {
const String text = 'text';
await tester.pumpWidget(
buildInputDecorator(
// 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(
// 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(
// 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(
// 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));
});
});
group('outline border', () {
testWidgets('align top', (WidgetTester tester) async {
const String text = 'text';
await tester.pumpWidget(
buildInputDecorator(
// 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(
// 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(
// 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(
// 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(
// 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(
// 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(
// 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(
// 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(
// 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(
// 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(
// 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(
// 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(
// 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(
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0));
expect(tester.getTopLeft(find.text('text')).dy, 19.0);
expect(tester.getBottomLeft(find.text('text')).dy, 35.0);
expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('Centers when border and label', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
decoration: const InputDecoration(
labelText: 'label',
border: OutlineInputBorder(),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0));
expect(tester.getTopLeft(find.text('text')).dy, 19.0);
expect(tester.getBottomLeft(find.text('text')).dy, 35.0);
expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('Centers when border and contentPadding', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 14.0,
8.0, 14.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0));
expect(tester.getTopLeft(find.text('text')).dy, 15.0);
expect(tester.getBottomLeft(find.text('text')).dy, 31.0);
expect(getBorderBottom(tester), 48.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('Centers when border and contentPadding and label', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
decoration: const InputDecoration(
labelText: 'label',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 14.0,
8.0, 14.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension));
expect(tester.getTopLeft(find.text('text')).dy, 15.0);
expect(tester.getBottomLeft(find.text('text')).dy, 31.0);
expect(getBorderBottom(tester), 48.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('Centers when border and lopsided contentPadding and label', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
decoration: const InputDecoration(
labelText: 'label',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 104.0,
8.0, 0.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 120.0));
expect(tester.getTopLeft(find.text('text')).dy, 51.0);
expect(tester.getBottomLeft(find.text('text')).dy, 67.0);
expect(getBorderBottom(tester), 120.0);
expect(getBorderWeight(tester), 1.0);
});
});
group('3 point interpolation alignment', () {
testWidgets('top align includes padding', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
expands: true,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 24.0,
8.0, 2.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 600.0));
// Aligned to the top including the 24px padding.
expect(tester.getTopLeft(find.text('text')).dy, 24.0);
expect(tester.getBottomLeft(find.text('text')).dy, 40.0);
expect(getBorderBottom(tester), 600.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('center align ignores padding', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
expands: true,
textAlignVertical: TextAlignVertical.center,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 24.0,
8.0, 2.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 600.0));
// Baseline is on the center of the 600px high input.
expect(tester.getTopLeft(find.text('text')).dy, 291.0);
expect(tester.getBottomLeft(find.text('text')).dy, 307.0);
expect(getBorderBottom(tester), 600.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('bottom align includes padding', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
expands: true,
textAlignVertical: TextAlignVertical.bottom,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 24.0,
8.0, 2.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 600.0));
// Includes bottom padding of 2px.
expect(tester.getTopLeft(find.text('text')).dy, 582.0);
expect(tester.getBottomLeft(find.text('text')).dy, 598.0);
expect(getBorderBottom(tester), 600.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('padding exceeds middle keeps top at middle', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
expands: true,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 504.0,
8.0, 0.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 600.0));
// Same position as the center example above.
expect(tester.getTopLeft(find.text('text')).dy, 291.0);
expect(tester.getBottomLeft(find.text('text')).dy, 307.0);
expect(getBorderBottom(tester), 600.0);
expect(getBorderWeight(tester), 1.0);
});
});
});
testWidgets('counter text has correct right margin - LTR, not dense', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
// isFocused: false (default)
decoration: const InputDecoration(
counterText: 'test',
filled: true,
),
),
);
// Margin for text decoration is 12 when filled
// (dx) - 12 = (text offset)x.
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 68.0));
final double dx = tester.getRect(find.byType(InputDecorator)).right;
expect(tester.getRect(find.text('test')).right, dx - 12.0);
});
testWidgets('counter text has correct right margin - RTL, not dense', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
textDirection: TextDirection.rtl,
// isEmpty: false (default)
// isFocused: false (default)
decoration: const InputDecoration(
counterText: 'test',
filled: true,
),
),
);
// Margin for text decoration is 12 when filled and top left offset is (0, 0)
// 0 + 12 = 12.
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 68.0));
expect(tester.getRect(find.text('test')).left, 12.0);
});
testWidgets('counter text has correct right margin - LTR, dense', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
// isFocused: false (default)
decoration: const InputDecoration(
counterText: 'test',
filled: true,
isDense: true,
),
),
);
// Margin for text decoration is 12 when filled
// (dx) - 12 = (text offset)x.
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 52.0));
final double dx = tester.getRect(find.byType(InputDecorator)).right;
expect(tester.getRect(find.text('test')).right, dx - 12.0);
});
testWidgets('counter text has correct right margin - RTL, dense', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
textDirection: TextDirection.rtl,
// isEmpty: false (default)
// isFocused: false (default)
decoration: const InputDecoration(
counterText: 'test',
filled: true,
isDense: true,
),
),
);
// Margin for text decoration is 12 when filled and top left offset is (0, 0)
// 0 + 12 = 12.
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 52.0));
expect(tester.getRect(find.text('test')).left, 12.0);
});
testWidgets('InputDecorator error/helper/counter RTL layout', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
// isFocused: false (default)
textDirection: TextDirection.rtl,
decoration: const InputDecoration(
labelText: 'label',
helperText: 'helper',
counterText: 'counter',
filled: true,
),
),
);
// Overall height for this InputDecorator is 76dps:
// 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 - [counter helper/error] (ahem font size 12dps)
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('counter')), const Offset(12.0, 64.0));
expect(tester.getTopRight(find.text('helper')), const Offset(788.0, 64.0));
// If both error and helper are specified, show the error
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
// isFocused: false (default)
textDirection: TextDirection.rtl,
decoration: const InputDecoration(
labelText: 'label',
helperText: 'helper',
errorText: 'error',
counterText: 'counter',
filled: true,
),
),
);
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('counter')), const Offset(12.0, 64.0));
expect(tester.getTopRight(find.text('error')), const Offset(788.0, 64.0));
expect(find.text('helper'), findsNothing);
});
testWidgets('InputDecorator prefix/suffix RTL', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
// isFocused: false (default)
textDirection: TextDirection.rtl,
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
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension)); // 40 bumped up to minimum.
expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getSize(find.text('p')).height, 16.0);
expect(tester.getSize(find.text('s')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 16.0);
expect(tester.getTopLeft(find.text('p')).dy, 16.0);
expect(tester.getTopLeft(find.text('s')).dy, 16.0);
// layout is a row: [s text p]
expect(tester.getTopLeft(find.text('s')).dx, 12.0);
expect(tester.getTopRight(find.text('s')).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('text')).dx));
expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('p')).dx));
});
testWidgets('InputDecorator contentPadding RTL layout', (WidgetTester tester) async {
// LTR: content left edge is contentPadding.start: 40.0
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
// isFocused: false (default)
textDirection: TextDirection.ltr,
decoration: const InputDecoration(
contentPadding: EdgeInsetsDirectional.only(start: 40.0, top: 12.0, bottom: 12.0),
labelText: 'label',
hintText: 'hint',
filled: true,
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0));
expect(tester.getTopLeft(find.text('text')).dx, 40.0);
expect(tester.getTopLeft(find.text('label')).dx, 40.0);
expect(tester.getTopLeft(find.text('hint')).dx, 40.0);
// RTL: content right edge is 800 - contentPadding.start: 760.0.
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
isFocused: true, // label is floating, still adjusted for contentPadding
textDirection: TextDirection.rtl,
decoration: const InputDecoration(
contentPadding: EdgeInsetsDirectional.only(start: 40.0, top: 12.0, bottom: 12.0),
labelText: 'label',
hintText: 'hint',
filled: true,
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0));
expect(tester.getTopRight(find.text('text')).dx, 760.0);
expect(tester.getTopRight(find.text('label')).dx, 760.0);
expect(tester.getTopRight(find.text('hint')).dx, 760.0);
});
testWidgets('InputDecorator prefix/suffix dense layout', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
isFocused: true,
decoration: const InputDecoration(
isDense: true,
prefixText: 'p',
suffixText: 's',
filled: true,
),
),
);
// Overall height for this InputDecorator is 32dps:
// 8 - top padding
// 16 - input text (ahem font size 16dps)
// 8 - bottom padding
//
// The only difference from normal layout for this case is that the
// padding above and below the prefix, input text, suffix, is 8 instead of 12.
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.text('p')).height, 16.0);
expect(tester.getSize(find.text('s')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 8.0);
expect(tester.getTopLeft(find.text('p')).dy, 8.0);
expect(tester.getTopLeft(find.text('p')).dx, 12.0);
expect(tester.getTopLeft(find.text('s')).dy, 8.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));
expect(getBorderBottom(tester), 32.0);
expect(getBorderWeight(tester), 2.0);
});
testWidgets('InputDecorator with empty InputDecoration', (WidgetTester tester) async {
await tester.pumpWidget(buildInputDecorator());
// 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)); // 40 bumped up to minimum.
expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 16.0);
expect(getBorderBottom(tester), kMinInteractiveDimension); // 40 bumped up to minimum.
expect(getBorderWeight(tester), 1.0);
});
testWidgets('contentPadding smaller than kMinInteractiveDimension', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/42449
const double verticalPadding = 1.0;
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default),
// isFocused: false (default)
decoration: const InputDecoration(
hintText: 'hint',
contentPadding: EdgeInsets.symmetric(vertical: verticalPadding),
isDense: true,
),
),
);
// The overall height is 18dps. This is shorter than
// kMinInteractiveDimension, but because isDense is true, the minimum is
// ignored.
// 16 - input text (ahem font size 16dps)
// 2 - total vertical padding
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 18.0));
expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 1.0);
expect(getOpacity(tester, 'hint'), 0.0);
expect(getBorderWeight(tester), 1.0);
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default),
// isFocused: false (default)
decoration: const InputDecoration.collapsed(
hintText: 'hint',
// InputDecoration.collapsed does not support contentPadding
),
),
);
// The overall height is 16dps. This is shorter than
// kMinInteractiveDimension, but because isCollapsed is true, the minimum is
// ignored. There is no padding at all, because isCollapsed doesn't support
// contentPadding.
// 16 - input text (ahem font size 16dps)
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 16.0));
expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 0.0);
expect(getOpacity(tester, 'hint'), 0.0);
expect(getBorderWeight(tester), 1.0);
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default),
// isFocused: false (default)
decoration: const InputDecoration(
hintText: 'hint',
contentPadding: EdgeInsets.symmetric(vertical: verticalPadding),
),
),
);
// The requested overall height is 18dps, however the minimum height is
// kMinInteractiveDimension because neither isDense or isCollapsed are true.
// 16 - input text (ahem font size 16dps)
// 2 - total vertical padding
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, kMinInteractiveDimension));
expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 16.0);
expect(getOpacity(tester, 'hint'), 0.0);
expect(getBorderWeight(tester), 0.0);
});
testWidgets('InputDecorator.collapsed', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default),
// isFocused: false (default)
decoration: const InputDecoration.collapsed(
hintText: 'hint',
),
),
);
// Overall height for this InputDecorator is 16dps. There is no minimum
// height when InputDecoration.collapsed is used.
// 16 - input text (ahem font size 16dps)
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 16.0));
expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 0.0);
expect(getOpacity(tester, 'hint'), 0.0);
expect(getBorderWeight(tester), 0.0);
// The hint should appear
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
isFocused: true,
decoration: const InputDecoration.collapsed(
hintText: 'hint',
),
),
);
await tester.pumpAndSettle();
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 16.0));
expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 0.0);
expect(tester.getSize(find.