diff --git a/dev/bots/check_code_samples.dart b/dev/bots/check_code_samples.dart index 577ef4e2281..d09dd68d73d 100644 --- a/dev/bots/check_code_samples.dart +++ b/dev/bots/check_code_samples.dart @@ -325,15 +325,6 @@ final Set _knownMissingTests = { 'examples/api/test/material/flexible_space_bar/flexible_space_bar.0_test.dart', 'examples/api/test/material/chip/deletable_chip_attributes.on_deleted.0_test.dart', 'examples/api/test/material/expansion_panel/expansion_panel_list.expansion_panel_list_radio.0_test.dart', - 'examples/api/test/material/input_decorator/input_decoration.1_test.dart', - 'examples/api/test/material/input_decorator/input_decoration.prefix_icon_constraints.0_test.dart', - 'examples/api/test/material/input_decorator/input_decoration.material_state.0_test.dart', - 'examples/api/test/material/input_decorator/input_decoration.2_test.dart', - 'examples/api/test/material/input_decorator/input_decoration.0_test.dart', - 'examples/api/test/material/input_decorator/input_decoration.label.0_test.dart', - 'examples/api/test/material/input_decorator/input_decoration.suffix_icon_constraints.0_test.dart', - 'examples/api/test/material/input_decorator/input_decoration.3_test.dart', - 'examples/api/test/material/input_decorator/input_decoration.material_state.1_test.dart', 'examples/api/test/material/text_form_field/text_form_field.1_test.dart', 'examples/api/test/material/scrollbar/scrollbar.1_test.dart', 'examples/api/test/material/search_anchor/search_anchor.0_test.dart', diff --git a/examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart b/examples/api/lib/material/input_decorator/input_decoration.widget_state.0.dart similarity index 81% rename from examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart rename to examples/api/lib/material/input_decorator/input_decoration.widget_state.0.dart index a361606fc16..c20700dd0ca 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.widget_state.0.dart @@ -32,13 +32,10 @@ class MaterialStateExample extends StatelessWidget { initialValue: 'abc', decoration: InputDecoration( prefixIcon: const Icon(Icons.person), - prefixIconColor: MaterialStateColor.resolveWith((Set states) { - if (states.contains(MaterialState.focused)) { + prefixIconColor: WidgetStateColor.resolveWith((Set states) { + if (states.contains(WidgetState.focused)) { return Colors.green; } - if (states.contains(MaterialState.error)) { - return Colors.red; - } return Colors.grey; }), ), diff --git a/examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart b/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart similarity index 69% rename from examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart rename to examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart index 69a478cf318..06401d60ec2 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart @@ -32,24 +32,31 @@ class MaterialStateExample extends StatelessWidget { return Theme( data: themeData.copyWith( inputDecorationTheme: themeData.inputDecorationTheme.copyWith( - prefixIconColor: MaterialStateColor.resolveWith( - (Set states) { - if (states.contains(MaterialState.focused)) { - return Colors.green; - } - if (states.contains(MaterialState.error)) { + prefixIconColor: WidgetStateColor.resolveWith( + (Set states) { + if (states.contains(WidgetState.error)) { return Colors.red; } + if (states.contains(WidgetState.focused)) { + return Colors.blue; + } return Colors.grey; }, ), ), ), child: TextFormField( - initialValue: 'abc', + initialValue: 'example.com', decoration: const InputDecoration( - prefixIcon: Icon(Icons.person), + prefixIcon: Icon(Icons.web), ), + autovalidateMode: AutovalidateMode.always, + validator: (String? text) { + if (text?.endsWith('.com') ?? false) { + return null; + } + return 'No .com tld'; + } ), ); } diff --git a/examples/api/test/material/input_decorator/input_decoration.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.0_test.dart new file mode 100644 index 00000000000..3abb7db2753 --- /dev/null +++ b/examples/api/test/material/input_decorator/input_decoration.0_test.dart @@ -0,0 +1,23 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/input_decorator/input_decoration.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('TextField is decorated', (WidgetTester tester) async { + await tester.pumpWidget( + const example.InputDecorationExampleApp(), + ); + expect(find.text('InputDecoration Sample'), findsOneWidget); + + expect(find.byType(TextField), findsOneWidget); + expect(find.text('Hint Text'), findsOneWidget); + expect(find.text('Helper Text'), findsOneWidget); + expect(find.text('0 characters'), findsOneWidget); + + expect(find.byIcon(Icons.send), findsOneWidget); + }); +} diff --git a/examples/api/test/material/input_decorator/input_decoration.1_test.dart b/examples/api/test/material/input_decorator/input_decoration.1_test.dart new file mode 100644 index 00000000000..f6192442477 --- /dev/null +++ b/examples/api/test/material/input_decorator/input_decoration.1_test.dart @@ -0,0 +1,28 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/input_decorator/input_decoration.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('TextField is decorated', (WidgetTester tester) async { + await tester.pumpWidget( + const example.InputDecorationExampleApp(), + ); + expect(find.text('InputDecoration Sample'), findsOneWidget); + + expect(find.byType(TextField), findsOneWidget); + expect(find.text('Hint Text'), findsOneWidget); + + expect( + tester.widget(find.byType(TextField)).decoration?.contentPadding, + EdgeInsets.zero, + ); + expect( + tester.widget(find.byType(TextField)).decoration?.border, + const OutlineInputBorder(), + ); + }); +} diff --git a/examples/api/test/material/input_decorator/input_decoration.2_test.dart b/examples/api/test/material/input_decorator/input_decoration.2_test.dart new file mode 100644 index 00000000000..cf45965d873 --- /dev/null +++ b/examples/api/test/material/input_decorator/input_decoration.2_test.dart @@ -0,0 +1,27 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/input_decorator/input_decoration.2.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('TextField is decorated', (WidgetTester tester) async { + await tester.pumpWidget( + const example.InputDecorationExampleApp(), + ); + expect(find.text('InputDecoration Sample'), findsOneWidget); + + expect(find.byType(TextField), findsOneWidget); + expect(find.text('Hint Text'), findsOneWidget); + expect(find.text('Error Text'), findsOneWidget); + + expect( + tester.widget(find.byType(TextField)) + .decoration + ?.border, + isNotNull, + ); + }); +} diff --git a/examples/api/test/material/input_decorator/input_decoration.3_test.dart b/examples/api/test/material/input_decorator/input_decoration.3_test.dart new file mode 100644 index 00000000000..9974ca8c880 --- /dev/null +++ b/examples/api/test/material/input_decorator/input_decoration.3_test.dart @@ -0,0 +1,41 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/input_decorator/input_decoration.3.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('TextFormField is decorated', (WidgetTester tester) async { + await tester.pumpWidget( + const example.InputDecorationExampleApp(), + ); + expect(find.text('InputDecoration Sample'), findsOneWidget); + + expect(find.byType(TextFormField), findsOneWidget); + expect(find.text('Prefix'), findsOneWidget); + expect(find.text('abc'), findsOneWidget); + expect(find.text('Suffix'), findsOneWidget); + expect( + tester.widget(find.byType(TextField)).decoration?.border, + const OutlineInputBorder(), + ); + }); + + testWidgets('Decorations are correctly ordered', (WidgetTester tester) async { + await tester.pumpWidget( + const example.InputDecorationExampleApp(), + ); + expect(find.text('InputDecoration Sample'), findsOneWidget); + + expect(find.byType(TextFormField), findsOneWidget); + + final double prefixX = tester.getCenter(find.text('Prefix')).dx; + final double contentX = tester.getCenter(find.text('abc')).dx; + final double suffixX = tester.getCenter(find.text('Suffix')).dx; + + expect(prefixX, lessThan(contentX)); + expect(contentX, lessThan(suffixX)); + }); +} diff --git a/examples/api/test/material/input_decorator/input_decoration.floating_label_style_error.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.floating_label_style_error.0_test.dart index 0ee58183e45..51a2012427c 100644 --- a/examples/api/test/material/input_decorator/input_decoration.floating_label_style_error.0_test.dart +++ b/examples/api/test/material/input_decorator/input_decoration.floating_label_style_error.0_test.dart @@ -16,7 +16,10 @@ void main() { await tester.tap(find.byType(TextFormField)); await tester.pumpAndSettle(); - final AnimatedDefaultTextStyle label = tester.firstWidget(find.ancestor(of: find.text('Name'), matching: find.byType(AnimatedDefaultTextStyle))); + final AnimatedDefaultTextStyle label = tester.firstWidget(find.ancestor( + of: find.text('Name'), + matching: find.byType(AnimatedDefaultTextStyle), + )); expect(label.style.color, theme.data.colorScheme.error); }); } diff --git a/examples/api/test/material/input_decorator/input_decoration.helper.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.helper.0_test.dart index 35c9e43f70c..ece9852df42 100644 --- a/examples/api/test/material/input_decorator/input_decoration.helper.0_test.dart +++ b/examples/api/test/material/input_decorator/input_decoration.helper.0_test.dart @@ -7,7 +7,7 @@ import 'package:flutter_api_samples/material/input_decorator/input_decoration.he import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('InputDecorator helper', (WidgetTester tester) async { + testWidgets('Shows multi element InputDecorator help decoration', (WidgetTester tester) async { await tester.pumpWidget( const example.HelperExampleApp(), ); diff --git a/examples/api/test/material/input_decorator/input_decoration.label.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.label.0_test.dart new file mode 100644 index 00000000000..7cd71bda33b --- /dev/null +++ b/examples/api/test/material/input_decorator/input_decoration.label.0_test.dart @@ -0,0 +1,20 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/input_decorator/input_decoration.label.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Decorates TextField in sample app with label', (WidgetTester tester) async { + await tester.pumpWidget( + const example.LabelExampleApp(), + ); + expect(find.text('InputDecoration.label Sample'), findsOneWidget); + + expect(find.byType(TextField), findsOneWidget); + expect(find.text('Username'), findsOneWidget); + expect(find.text('*'), findsOneWidget); + }); +} diff --git a/examples/api/test/material/input_decorator/input_decoration.label_style_error.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.label_style_error.0_test.dart index 640b8fd228a..f0840c26b53 100644 --- a/examples/api/test/material/input_decorator/input_decoration.label_style_error.0_test.dart +++ b/examples/api/test/material/input_decorator/input_decoration.label_style_error.0_test.dart @@ -13,7 +13,10 @@ void main() { ); final Theme theme = tester.firstWidget(find.byType(Theme)); - final AnimatedDefaultTextStyle label = tester.firstWidget(find.ancestor(of: find.text('Name'), matching: find.byType(AnimatedDefaultTextStyle))); + final AnimatedDefaultTextStyle label = tester.firstWidget(find.ancestor( + of: find.text('Name'), + matching: find.byType(AnimatedDefaultTextStyle), + )); expect(label.style.color, theme.data.colorScheme.error); }); } diff --git a/examples/api/test/material/input_decorator/input_decoration.prefix_icon_constraints.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.prefix_icon_constraints.0_test.dart new file mode 100644 index 00000000000..a83252be857 --- /dev/null +++ b/examples/api/test/material/input_decorator/input_decoration.prefix_icon_constraints.0_test.dart @@ -0,0 +1,71 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/input_decorator/input_decoration.prefix_icon_constraints.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Shows two TextFields decorated with prefix icon sizes matching their hint text', (WidgetTester tester) async { + await tester.pumpWidget( + const example.PrefixIconConstraintsExampleApp(), + ); + expect(find.text('InputDecoration Sample'), findsOneWidget); + + expect(find.byType(TextField), findsNWidgets(2)); + expect(find.byIcon(Icons.search), findsNWidgets(2)); + expect(find.text('Normal Icon Constraints'), findsOneWidget); + expect(find.text('Smaller Icon Constraints'), findsOneWidget); + + final Finder normalIcon = find.descendant( + of: find.ancestor( + of: find.text('Normal Icon Constraints'), + matching: find.byType(TextField), + ), + matching: find.byIcon(Icons.search), + ); + final Finder smallerIcon = find.descendant( + of: find.ancestor( + of: find.text('Smaller Icon Constraints'), + matching: find.byType(TextField), + ), + matching: find.byIcon(Icons.search), + ); + + expect( + tester.getSize(normalIcon).longestSide, + greaterThan(tester.getSize(smallerIcon).longestSide), + ); + }); + + testWidgets('prefixIcons are placed left of hintText', (WidgetTester tester) async { + await tester.pumpWidget( + const example.PrefixIconConstraintsExampleApp(), + ); + + final Finder normalIcon = find.descendant( + of: find.ancestor( + of: find.text('Normal Icon Constraints'), + matching: find.byType(TextField), + ), + matching: find.byIcon(Icons.search), + ); + final Finder smallerIcon = find.descendant( + of: find.ancestor( + of: find.text('Smaller Icon Constraints'), + matching: find.byType(TextField), + ), + matching: find.byIcon(Icons.search), + ); + + expect( + tester.getCenter(find.text('Normal Icon Constraints')).dx, + greaterThan(tester.getCenter(normalIcon).dx), + ); + expect( + tester.getCenter(find.text('Smaller Icon Constraints')).dx, + greaterThan(tester.getCenter(smallerIcon).dx), + ); + }); +} diff --git a/examples/api/test/material/input_decorator/input_decoration.suffix_icon_constraints.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.suffix_icon_constraints.0_test.dart new file mode 100644 index 00000000000..0a4c09a1782 --- /dev/null +++ b/examples/api/test/material/input_decorator/input_decoration.suffix_icon_constraints.0_test.dart @@ -0,0 +1,69 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/input_decorator/input_decoration.suffix_icon_constraints.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Shows two TextFields decorated with suffix icon sizes matching their hint text', (WidgetTester tester) async { + await tester.pumpWidget( + const example.SuffixIconConstraintsExampleApp(), + ); + expect(find.text('InputDecoration Sample'), findsOneWidget); + + expect(find.byType(TextField), findsNWidgets(2)); + expect(find.byIcon(Icons.search), findsNWidgets(2)); + expect(find.text('Normal Icon Constraints'), findsOneWidget); + expect(find.text('Smaller Icon Constraints'), findsOneWidget); + + final Finder normalIcon = find.descendant( + of: find.ancestor( + of: find.text('Normal Icon Constraints'), + matching: find.byType(TextField), + ), + matching: find.byIcon(Icons.search), + ); + final Finder smallerIcon = find.descendant( + of: find.ancestor( + of: find.text('Smaller Icon Constraints'), + matching: find.byType(TextField), + ), + matching: find.byIcon(Icons.search), + ); + + expect(tester.getSize(normalIcon).longestSide, + greaterThan(tester.getSize(smallerIcon).longestSide)); + }); + + testWidgets('suffixIcons are placed right of hintText', (WidgetTester tester) async { + await tester.pumpWidget( + const example.SuffixIconConstraintsExampleApp(), + ); + + final Finder normalIcon = find.descendant( + of: find.ancestor( + of: find.text('Normal Icon Constraints'), + matching: find.byType(TextField), + ), + matching: find.byIcon(Icons.search), + ); + final Finder smallerIcon = find.descendant( + of: find.ancestor( + of: find.text('Smaller Icon Constraints'), + matching: find.byType(TextField), + ), + matching: find.byIcon(Icons.search), + ); + + expect( + tester.getCenter(find.text('Normal Icon Constraints')).dx, + lessThan(tester.getCenter(normalIcon).dx), + ); + expect( + tester.getCenter(find.text('Smaller Icon Constraints')).dx, + lessThan(tester.getCenter(smallerIcon).dx), + ); + }); +} diff --git a/examples/api/test/material/input_decorator/input_decoration.widget_state.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.widget_state.0_test.dart new file mode 100644 index 00000000000..c1efad4151e --- /dev/null +++ b/examples/api/test/material/input_decorator/input_decoration.widget_state.0_test.dart @@ -0,0 +1,27 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/input_decorator/input_decoration.widget_state.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('TextFormField updates decorations depending on state', (WidgetTester tester) async { + await tester.pumpWidget( + const example.MaterialStateExampleApp(), + ); + expect(find.text('InputDecoration Sample'), findsOneWidget); + + expect(find.byType(TextFormField), findsOneWidget); + expect(find.text('abc'), findsOneWidget); + expect(find.byIcon(Icons.person), findsOneWidget); + + expect( + tester.widget(find.byType(TextField)).decoration?.prefixIconColor, + isA() + .having((WidgetStateColor color) => color.resolve({}), 'default', Colors.grey) + .having((WidgetStateColor color) => color.resolve({WidgetState.focused}), 'focused', Colors.green), + ); + }); +} diff --git a/examples/api/test/material/input_decorator/input_decoration.widget_state.1_test.dart b/examples/api/test/material/input_decorator/input_decoration.widget_state.1_test.dart new file mode 100644 index 00000000000..20a20ca8f33 --- /dev/null +++ b/examples/api/test/material/input_decorator/input_decoration.widget_state.1_test.dart @@ -0,0 +1,40 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/input_decorator/input_decoration.widget_state.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('TextFormField updates decorations depending on state', (WidgetTester tester) async { + await tester.pumpWidget( + const example.MaterialStateExampleApp(), + ); + expect(find.text('InputDecoration Sample'), findsOneWidget); + + expect(find.byType(TextFormField), findsOneWidget); + expect(find.text('example.com'), findsOneWidget); + expect(find.byIcon(Icons.web), findsOneWidget); + + expect( + tester.widget(find.byType(TextField)).decoration?.prefixIconColor, + isA() + .having((WidgetStateColor color) => color.resolve({}), 'default', Colors.grey) + .having((WidgetStateColor color) => color.resolve({WidgetState.focused}), 'focused', Colors.blue) + .having((WidgetStateColor color) => color.resolve({WidgetState.error}), 'error', Colors.red) + .having((WidgetStateColor color) => color.resolve({WidgetState.error, WidgetState.focused}), 'error', Colors.red), + ); + }); + + testWidgets('Validates field input', (WidgetTester tester) async { + await tester.pumpWidget( + const example.MaterialStateExampleApp(), + ); + + expect(find.text('No .com tld'), findsNothing); + await tester.enterText(find.byType(TextFormField), 'noUrl'); + await tester.pump(); + expect(find.text('No .com tld'), findsOneWidget); + }); +} diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index ad56f3e3ada..05a7ad38774 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -2517,10 +2517,9 @@ class _InputDecoratorState extends State with TickerProviderStat /// /// {@tool dartpad} /// This sample shows how to style a `TextField` with a prefixIcon that changes color -/// based on the `MaterialState`. The color defaults to gray, be blue while focused -/// and red if in an error state. +/// based on the `MaterialState`. The color defaults to gray and is green while focused. /// -/// ** See code in examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart ** +/// ** See code in examples/api/lib/material/input_decorator/input_decoration.widget_state.0.dart ** /// {@end-tool} /// /// {@tool dartpad} @@ -2528,7 +2527,7 @@ class _InputDecoratorState extends State with TickerProviderStat /// based on the `MaterialState` through the use of `ThemeData`. The color defaults /// to gray, be blue while focused and red if in an error state. /// -/// ** See code in examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart ** +/// ** See code in examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart ** /// {@end-tool} /// /// See also: