Skip to content

Commit f4c25d1

Browse files
gspencergoogTytaniumDev
authored andcommitted
Add find.backButton finder and StandardComponentType enum to find components in tests. (flutter#149349)
## Description This adds `find.backButton()` in the Common Finders to allow finding different types of standard UI elements. It works by attaching a key made from an enum value in a new enum called `StandardComponentType` to all of the standard widgets that perform the associated function. I also substituted the finder in several places where it is useful in tests. This allows writing tests that want to find the "back" button without having to know exactly which icon the back button uses under what circumstances. To do it correctly is actually quite complicated, since there are several adaptations that occur (based on platform, and whether it is web or not). ## Tests - Added tests.
1 parent 53d1bc7 commit f4c25d1

File tree

14 files changed

+170
-28
lines changed

14 files changed

+170
-28
lines changed

dev/integration_tests/new_gallery/lib/studies/fortnightly/shared.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ class NavigationMenu extends StatelessWidget {
305305
Row(
306306
children: <Widget>[
307307
IconButton(
308+
key: StandardComponentType.closeButton.key,
308309
icon: const Icon(Icons.close),
309310
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
310311
onPressed: () => Navigator.pop(context),

packages/flutter/lib/src/cupertino/nav_bar.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1669,7 +1669,10 @@ class _BackChevron extends StatelessWidget {
16691669
break;
16701670
}
16711671

1672-
return iconWidget;
1672+
return KeyedSubtree(
1673+
key: StandardComponentType.backButton.key,
1674+
child: iconWidget,
1675+
);
16731676
}
16741677
}
16751678

packages/flutter/lib/src/material/action_buttons.dart

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ abstract class _ActionButton extends StatelessWidget {
2828
this.color,
2929
required this.icon,
3030
required this.onPressed,
31+
this.standardComponent,
3132
this.style,
3233
});
3334

@@ -56,6 +57,10 @@ abstract class _ActionButton extends StatelessWidget {
5657
/// Null by default.
5758
final ButtonStyle? style;
5859

60+
/// An enum value to use to identify this button as a type of
61+
/// [StandardComponentType].
62+
final StandardComponentType? standardComponent;
63+
5964
/// This returns the appropriate tooltip text for this action button.
6065
String _getTooltip(BuildContext context);
6166

@@ -67,6 +72,7 @@ abstract class _ActionButton extends StatelessWidget {
6772
Widget build(BuildContext context) {
6873
assert(debugCheckHasMaterialLocalizations(context));
6974
return IconButton(
75+
key: standardComponent?.key,
7076
icon: icon,
7177
style: style,
7278
color: color,
@@ -212,7 +218,10 @@ class BackButton extends _ActionButton {
212218
super.color,
213219
super.style,
214220
super.onPressed,
215-
}) : super(icon: const BackButtonIcon());
221+
}) : super(
222+
icon: const BackButtonIcon(),
223+
standardComponent: StandardComponentType.backButton,
224+
);
216225

217226
@override
218227
void _onPressedCallback(BuildContext context) => Navigator.maybePop(context);
@@ -281,7 +290,10 @@ class CloseButtonIcon extends StatelessWidget {
281290
class CloseButton extends _ActionButton {
282291
/// Creates a Material Design close icon button.
283292
const CloseButton({ super.key, super.color, super.onPressed, super.style })
284-
: super(icon: const CloseButtonIcon());
293+
: super(
294+
icon: const CloseButtonIcon(),
295+
standardComponent: StandardComponentType.closeButton,
296+
);
285297

286298
@override
287299
void _onPressedCallback(BuildContext context) => Navigator.maybePop(context);
@@ -347,7 +359,10 @@ class DrawerButton extends _ActionButton {
347359
super.color,
348360
super.style,
349361
super.onPressed,
350-
}) : super(icon: const DrawerButtonIcon());
362+
}) : super(
363+
icon: const DrawerButtonIcon(),
364+
standardComponent: StandardComponentType.drawerButton,
365+
);
351366

352367
@override
353368
void _onPressedCallback(BuildContext context) => Scaffold.of(context).openDrawer();

packages/flutter/lib/src/material/popup_menu.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,7 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
15481548
}
15491549

15501550
return IconButton(
1551+
key: StandardComponentType.moreButton.key,
15511552
icon: widget.icon ?? Icon(Icons.adaptive.more),
15521553
padding: widget.padding,
15531554
splashRadius: widget.splashRadius,

packages/flutter/lib/src/material/search_anchor.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'dart:ui';
88

99
import 'package:flutter/widgets.dart';
1010

11+
import 'back_button.dart';
1112
import 'button_style.dart';
1213
import 'color_scheme.dart';
1314
import 'colors.dart';
@@ -865,11 +866,9 @@ class _ViewContentState extends State<_ViewContent> {
865866

866867
@override
867868
Widget build(BuildContext context) {
868-
final Widget defaultLeading = IconButton(
869-
icon: const Icon(Icons.arrow_back),
870-
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
871-
onPressed: () { Navigator.of(context).pop(); },
869+
final Widget defaultLeading = BackButton(
872870
style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap),
871+
onPressed: () { Navigator.of(context).pop(); },
873872
);
874873

875874
final List<Widget> defaultTrailing = <Widget>[

packages/flutter/lib/src/material/snack_bar.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ class _SnackBarState extends State<SnackBar> {
678678

679679
final IconButton? iconButton = showCloseIcon
680680
? IconButton(
681+
key: StandardComponentType.closeButton.key,
681682
icon: const Icon(Icons.close),
682683
iconSize: 24.0,
683684
color: widget.closeIconColor ?? snackBarTheme.closeIconColor ?? defaults.closeIconColor,

packages/flutter/lib/src/material/text_selection_toolbar.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ class _TextSelectionToolbarOverflowableState extends State<_TextSelectionToolbar
220220
// The navButton that shows and hides the overflow menu is the
221221
// first child.
222222
_TextSelectionToolbarOverflowButton(
223+
key: _overflowOpen ? StandardComponentType.backButton.key : StandardComponentType.moreButton.key,
223224
icon: Icon(_overflowOpen ? Icons.arrow_back : Icons.more_vert),
224225
onPressed: () {
225226
setState(() {
@@ -731,6 +732,7 @@ class _TextSelectionToolbarContainer extends StatelessWidget {
731732
// forward and back controls.
732733
class _TextSelectionToolbarOverflowButton extends StatelessWidget {
733734
const _TextSelectionToolbarOverflowButton({
735+
super.key,
734736
required this.icon,
735737
this.onPressed,
736738
this.tooltip,
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/foundation.dart';
6+
7+
/// An enum identifying standard UI components.
8+
///
9+
/// This enum is used to attach a key to a widget identifying it as a standard
10+
/// UI component for testing and discovery purposes.
11+
///
12+
/// It is used by the testing infrastructure (e.g. the `find` object in the
13+
/// Flutter test framework) to positively identify and/or activate specific
14+
/// widgets as representing standard UI components, since many of these
15+
/// components vary slightly in the icons or tooltips that they use, and making
16+
/// an effective test matcher for them is fragile and error prone.
17+
///
18+
/// The keys don't have any effect on the functioning of the UI elements, they
19+
/// are just a means of identifying them. A widget won't be treated specially if
20+
/// it has this key, other than to be found by the testing infrastructure. If
21+
/// tests are not searching for them, then adding them to a widget serves no
22+
/// purpose.
23+
///
24+
/// Any widget with the [key] from a value here applied to it will be considered
25+
/// to be that type of standard UI component in tests.
26+
///
27+
/// Types included here are generally only those for which it can be difficult
28+
/// or fragile to create a reliable test matcher for. It is not (nor should it
29+
/// become) an exhaustive list of standard UI components.
30+
///
31+
/// These are typically used in tests via `find.backButton()` or
32+
/// `find.closeButton()`.
33+
enum StandardComponentType {
34+
/// Indicates the associated widget is a standard back button, typically used
35+
/// to navigate back to the previous screen.
36+
backButton,
37+
38+
/// Indicates the associated widget is a close button, typically used to
39+
/// dismiss a dialog or modal sheet.
40+
closeButton,
41+
42+
/// Indicates the associated widget is a "more" button, typically used to
43+
/// display a menu of additional options.
44+
moreButton,
45+
46+
/// Indicates the associated widget is a drawer button, typically used to open
47+
/// a drawer.
48+
drawerButton;
49+
50+
/// Returns a [ValueKey] for this [StandardComponentType].
51+
///
52+
/// Attach this key to a widget to indicate it is a standard UI component.
53+
ValueKey<StandardComponentType> get key => ValueKey<StandardComponentType>(this);
54+
}

packages/flutter/lib/widgets.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export 'src/widgets/slotted_render_object_widget.dart';
145145
export 'src/widgets/snapshot_widget.dart';
146146
export 'src/widgets/spacer.dart';
147147
export 'src/widgets/spell_check.dart';
148+
export 'src/widgets/standard_component_type.dart';
148149
export 'src/widgets/status_transitions.dart';
149150
export 'src/widgets/system_context_menu.dart';
150151
export 'src/widgets/table.dart';

packages/flutter/test/cupertino/nav_bar_transition_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ void main() {
463463
expect(
464464
flying(
465465
tester,
466-
find.byWidgetPredicate((Widget widget) => widget.key != null),
466+
find.byWidgetPredicate((Widget widget) => widget.key != null && widget.key is GlobalKey),
467467
),
468468
findsNothing,
469469
);

0 commit comments

Comments
 (0)