Unverified Commit 52a1a318 authored by pdblasi-google's avatar pdblasi-google Committed by GitHub

Adds API for performing semantics actions in tests (#132598)

* Added `performAction` to `SemanticsController` as well as specific methods for specific actions
* Added a `scrollable` finder to `find.semantics` as a convenience method for `findAny(<all scrollable actions>)`
* Updated `containsSemantics` and `matchSemantics` matchers to also work on `FinderBase<Semantics>`

Closes #112413
parent 11a2bc0b
...@@ -639,6 +639,26 @@ class CommonSemanticsFinders { ...@@ -639,6 +639,26 @@ class CommonSemanticsFinders {
); );
} }
/// Finds any [SemanticsNode]s that can scroll in at least one direction.
///
/// If `axis` is provided, then the search will be limited to scrollable nodes
/// that can scroll in the given axis. If `axis` is not provided, then both
/// horizontal and vertical scrollable nodes will be found.
///
/// {@macro flutter_test.finders.CommonSemanticsFinders.viewParameter}
SemanticsFinder scrollable({Axis? axis, FlutterView? view}) {
return byAnyAction(<SemanticsAction>[
if (axis == null || axis == Axis.vertical) ...<SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
if (axis == null || axis == Axis.horizontal) ...<SemanticsAction>[
SemanticsAction.scrollLeft,
SemanticsAction.scrollRight,
],
]);
}
bool _matchesPattern(String target, Pattern pattern) { bool _matchesPattern(String target, Pattern pattern) {
if (pattern is RegExp) { if (pattern is RegExp) {
return pattern.hasMatch(target); return pattern.hasMatch(target);
......
...@@ -600,7 +600,11 @@ AsyncMatcher matchesReferenceImage(ui.Image image) { ...@@ -600,7 +600,11 @@ AsyncMatcher matchesReferenceImage(ui.Image image) {
/// provided, then they are not part of the comparison. All of the boolean /// provided, then they are not part of the comparison. All of the boolean
/// flag and action fields must match, and default to false. /// flag and action fields must match, and default to false.
/// ///
/// To retrieve the semantics data of a widget, use [WidgetTester.getSemantics] /// To find a [SemanticsNode] directly, use [CommonFinders.semantics].
/// These methods will search the semantics tree directly and avoid the edge
/// cases that [SemanticsController.find] sometimes runs into.
///
/// To retrieve the semantics data of a widget, use [SemanticsController.find]
/// with a [Finder] that returns a single widget. Semantics must be enabled /// with a [Finder] that returns a single widget. Semantics must be enabled
/// in order to use this method. /// in order to use this method.
/// ///
...@@ -780,7 +784,11 @@ Matcher matchesSemantics({ ...@@ -780,7 +784,11 @@ Matcher matchesSemantics({
/// There are no default expected values, so no unspecified values will be /// There are no default expected values, so no unspecified values will be
/// validated. /// validated.
/// ///
/// To retrieve the semantics data of a widget, use [WidgetTester.getSemantics] /// To find a [SemanticsNode] directly, use [CommonFinders.semantics].
/// These methods will search the semantics tree directly and avoid the edge
/// cases that [SemanticsController.find] sometimes runs into.
///
/// To retrieve the semantics data of a widget, use [SemanticsController.find]
/// with a [Finder] that returns a single widget. Semantics must be enabled /// with a [Finder] that returns a single widget. Semantics must be enabled
/// in order to use this method. /// in order to use this method.
/// ///
...@@ -2502,7 +2510,13 @@ class _MatchesSemanticsData extends Matcher { ...@@ -2502,7 +2510,13 @@ class _MatchesSemanticsData extends Matcher {
return failWithDescription(matchState, 'No SemanticsData provided. ' return failWithDescription(matchState, 'No SemanticsData provided. '
'Maybe you forgot to enable semantics?'); 'Maybe you forgot to enable semantics?');
} }
final SemanticsData data = node is SemanticsNode ? node.getSemanticsData() : (node as SemanticsData);
final SemanticsData data = switch (node) {
SemanticsNode() => node.getSemanticsData(),
FinderBase<SemanticsNode>() => node.evaluate().single.getSemanticsData(),
_ => node as SemanticsData,
};
if (label != null && label != data.label) { if (label != null && label != data.label) {
return failWithDescription(matchState, 'label was: ${data.label}'); return failWithDescription(matchState, 'label was: ${data.label}');
} }
......
...@@ -988,6 +988,110 @@ void main() { ...@@ -988,6 +988,110 @@ void main() {
expect(failure.message, contains('Actual: _PredicateSemanticsFinder:<Found 2 SemanticsNodes with any of the following flags: [SemanticsFlag.isHeader, SemanticsFlag.isTextField]:')); expect(failure.message, contains('Actual: _PredicateSemanticsFinder:<Found 2 SemanticsNodes with any of the following flags: [SemanticsFlag.isHeader, SemanticsFlag.isTextField]:'));
}); });
}); });
group('scrollable', () {
testWidgets('can find node that can scroll up', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
await tester.pumpWidget(MaterialApp(
home: SingleChildScrollView(
controller: controller,
child: const SizedBox(width: 100, height: 1000),
),
));
expect(find.semantics.scrollable(), containsSemantics(
hasScrollUpAction: true,
hasScrollDownAction: false,
));
});
testWidgets('can find node that can scroll down', (WidgetTester tester) async {
final ScrollController controller = ScrollController(initialScrollOffset: 400);
await tester.pumpWidget(MaterialApp(
home: SingleChildScrollView(
controller: controller,
child: const SizedBox(width: 100, height: 1000),
),
));
expect(find.semantics.scrollable(), containsSemantics(
hasScrollUpAction: false,
hasScrollDownAction: true,
));
});
testWidgets('can find node that can scroll left', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
await tester.pumpWidget(MaterialApp(
home: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: controller,
child: const SizedBox(width: 1000, height: 100),
),
));
expect(find.semantics.scrollable(), containsSemantics(
hasScrollLeftAction: true,
hasScrollRightAction: false,
));
});
testWidgets('can find node that can scroll right', (WidgetTester tester) async {
final ScrollController controller = ScrollController(initialScrollOffset: 200);
await tester.pumpWidget(MaterialApp(
home: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: controller,
child: const SizedBox(width: 1000, height: 100),
),
));
expect(find.semantics.scrollable(), containsSemantics(
hasScrollLeftAction: false,
hasScrollRightAction: true,
));
});
testWidgets('can exclusively find node that scrolls horizontally', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Column(
children: <Widget>[
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(width: 1000, height: 100),
),
Expanded(
child: SingleChildScrollView(
child: SizedBox(width: 100, height: 1000),
),
),
],
)
));
expect(find.semantics.scrollable(axis: Axis.horizontal), findsOne);
});
testWidgets('can exclusively find node that scrolls vertically', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Column(
children: <Widget>[
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(width: 1000, height: 100),
),
Expanded(
child: SingleChildScrollView(
child: SizedBox(width: 100, height: 1000),
),
),
],
)
));
expect(find.semantics.scrollable(axis: Axis.vertical), findsOne);
});
});
}); });
group('FinderBase', () { group('FinderBase', () {
......
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