Unverified Commit 97cdc0ec authored by Renzo Olivares's avatar Renzo Olivares Committed by GitHub

InputDecoration.error should activate error state (#134001)

When passed an `error` widget, `InputDecoration` should activate its error state. Before this change the `errorBorder` would only activate if an `errorText` was provided. This change solves this issue by accounting for a provided `error` widget.
parent fa9c301a
......@@ -1971,6 +1971,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
TextAlign? get textAlign => widget.textAlign;
bool get isFocused => widget.isFocused;
bool get _hasError => decoration.errorText != null || decoration.error != null;
bool get isHovering => widget.isHovering && decoration.enabled;
bool get isEmpty => widget.isEmpty;
bool get _floatingLabelEnabled {
......@@ -2011,7 +2012,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
? Colors.transparent
: themeData.disabledColor;
}
if (decoration.errorText != null) {
if (_hasError) {
return themeData.colorScheme.error;
}
if (isFocused) {
......@@ -2107,7 +2108,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
TextStyle _getFloatingLabelStyle(ThemeData themeData, InputDecorationTheme defaults) {
TextStyle defaultTextStyle = MaterialStateProperty.resolveAs(defaults.floatingLabelStyle!, materialState);
if (decoration.errorText != null && decoration.errorStyle?.color != null) {
if (_hasError && decoration.errorStyle?.color != null) {
defaultTextStyle = defaultTextStyle.copyWith(color: decoration.errorStyle?.color);
}
defaultTextStyle = defaultTextStyle.merge(decoration.floatingLabelStyle ?? decoration.labelStyle);
......@@ -2137,7 +2138,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
if (!decoration.enabled) MaterialState.disabled,
if (isFocused) MaterialState.focused,
if (isHovering) MaterialState.hovered,
if (decoration.errorText != null) MaterialState.error,
if (_hasError) MaterialState.error,
};
}
......@@ -2205,14 +2206,13 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
),
);
final bool isError = decoration.errorText != null;
InputBorder? border;
if (!decoration.enabled) {
border = isError ? decoration.errorBorder : decoration.disabledBorder;
border = _hasError ? decoration.errorBorder : decoration.disabledBorder;
} else if (isFocused) {
border = isError ? decoration.focusedErrorBorder : decoration.focusedBorder;
border = _hasError ? decoration.focusedErrorBorder : decoration.focusedBorder;
} else {
border = isError ? decoration.errorBorder : decoration.enabledBorder;
border = _hasError ? decoration.errorBorder : decoration.enabledBorder;
}
border ??= _getDefaultBorder(themeData, defaults);
......
......@@ -945,7 +945,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
bool get _hasIntrinsicError => widget.maxLength != null && widget.maxLength! > 0 && _effectiveController.value.text.characters.length > widget.maxLength!;
bool get _hasError => widget.decoration?.errorText != null || _hasIntrinsicError;
bool get _hasError => widget.decoration?.errorText != null || widget.decoration?.error != null || _hasIntrinsicError;
Color get _errorColor => widget.decoration?.errorStyle?.color ?? Theme.of(context).colorScheme.error;
......
......@@ -1668,6 +1668,105 @@ void runAllTests({ required bool useMaterial3 }) {
expect(find.text('errorText'), findsOneWidget);
});
testWidgets('InputDecoration shows error border for errorText and error widget', (WidgetTester tester) async {
const InputBorder errorBorder = OutlineInputBorder(
borderSide: BorderSide(color: Colors.red, width: 1.5),
);
const InputBorder focusedErrorBorder = OutlineInputBorder(
borderSide: BorderSide(color: Colors.teal, width: 5.0),
);
await tester.pumpWidget(
buildInputDecorator(
useMaterial3: useMaterial3,
isFocused: true,
decoration: const InputDecoration(
errorText: 'error',
// enabled: true (default)
errorBorder: errorBorder,
focusedErrorBorder: focusedErrorBorder,
),
),
);
await tester.pumpAndSettle(); // Border changes are animated.
expect(getBorder(tester), focusedErrorBorder);
await tester.pumpWidget(
buildInputDecorator(
useMaterial3: useMaterial3,
// isFocused: false (default)
decoration: const InputDecoration(
errorText: 'error',
// enabled: true (default)
errorBorder: errorBorder,
focusedErrorBorder: focusedErrorBorder,
),
),
);
await tester.pumpAndSettle(); // Border changes are animated.
expect(getBorder(tester), errorBorder);
await tester.pumpWidget(
buildInputDecorator(
useMaterial3: useMaterial3,
// isFocused: false (default)
decoration: const InputDecoration(
errorText: 'error',
enabled: false,
errorBorder: errorBorder,
focusedErrorBorder: focusedErrorBorder,
),
),
);
await tester.pumpAndSettle(); // Border changes are animated.
expect(getBorder(tester), errorBorder);
await tester.pumpWidget(
buildInputDecorator(
useMaterial3: useMaterial3,
isFocused: true,
decoration: const InputDecoration(
error: Text('error'),
// enabled: true (default)
errorBorder: errorBorder,
focusedErrorBorder: focusedErrorBorder,
),
),
);
await tester.pumpAndSettle(); // Border changes are animated.
expect(getBorder(tester), focusedErrorBorder);
await tester.pumpWidget(
buildInputDecorator(
useMaterial3: useMaterial3,
// isFocused: false (default)
decoration: const InputDecoration(
error: Text('error'),
// enabled: true (default)
errorBorder: errorBorder,
focusedErrorBorder: focusedErrorBorder,
),
),
);
await tester.pumpAndSettle(); // Border changes are animated.
expect(getBorder(tester), errorBorder);
await tester.pumpWidget(
buildInputDecorator(
useMaterial3: useMaterial3,
// isFocused: false (default)
decoration: const InputDecoration(
error: Text('error'),
enabled: false,
errorBorder: errorBorder,
focusedErrorBorder: focusedErrorBorder,
),
),
);
await tester.pumpAndSettle(); // Border changes are animated.
expect(getBorder(tester), errorBorder);
});
testWidgetsWithLeakTracking('InputDecorator shows error widget', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
......
......@@ -787,6 +787,42 @@ void main() {
expect(state.widget.cursorColor, cursorColor);
});
testWidgets('Use error cursor color when an InputDecoration with an errorText or error widget is provided', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: TextField(
autofocus: true,
decoration: InputDecoration(
error: Text('error'),
errorStyle: TextStyle(color: Colors.teal),
),
),
),
),
);
await tester.pump();
EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
expect(state.widget.cursorColor, Colors.teal);
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: TextField(
autofocus: true,
decoration: InputDecoration(
errorText: 'error',
errorStyle: TextStyle(color: Colors.teal),
),
),
),
),
);
await tester.pump();
state = tester.state<EditableTextState>(find.byType(EditableText));
expect(state.widget.cursorColor, Colors.teal);
});
testWidgetsWithLeakTracking('sets cursorOpacityAnimates on EditableText correctly', (WidgetTester tester) async {
// True
......
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