Unverified Commit 50862bc0 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Fix SemanticsFinder for multi-view (#143485)

Fixes https://github.com/flutter/flutter/issues/143405.

It was counter-intuitive that a SemanticsFinder without specifying a FlutterView would only search the nodes in the default view. This change makes it so that when no view is specified the semantics trees of all known FlutterViews are searched.
parent 9a6bda87
...@@ -553,7 +553,7 @@ class CommonSemanticsFinders { ...@@ -553,7 +553,7 @@ class CommonSemanticsFinders {
return _PredicateSemanticsFinder( return _PredicateSemanticsFinder(
predicate, predicate,
describeMatch, describeMatch,
_rootFromView(view), view,
); );
} }
...@@ -689,15 +689,6 @@ class CommonSemanticsFinders { ...@@ -689,15 +689,6 @@ class CommonSemanticsFinders {
return pattern == target; return pattern == target;
} }
} }
SemanticsNode _rootFromView(FlutterView? view) {
view ??= TestWidgetsFlutterBinding.instance.platformDispatcher.implicitView;
assert(view != null, 'The given view was not available. Ensure WidgetTester.view is available or pass in a specific view using WidgetTester.viewOf.');
final RenderView renderView = TestWidgetsFlutterBinding.instance.renderViews
.firstWhere((RenderView r) => r.flutterView == view);
return renderView.owner!.semanticsOwner!.rootSemanticsNode!;
}
} }
/// Provides lightweight syntax for getting frequently used text range finders. /// Provides lightweight syntax for getting frequently used text range finders.
...@@ -1065,16 +1056,44 @@ abstract class Finder extends FinderBase<Element> with _LegacyFinderMixin { ...@@ -1065,16 +1056,44 @@ abstract class Finder extends FinderBase<Element> with _LegacyFinderMixin {
/// A base class for creating finders that search the semantics tree. /// A base class for creating finders that search the semantics tree.
abstract class SemanticsFinder extends FinderBase<SemanticsNode> { abstract class SemanticsFinder extends FinderBase<SemanticsNode> {
/// Creates a new [SemanticsFinder] that will search starting at the given /// Creates a new [SemanticsFinder] that will search within the given [view] or
/// `root`. /// within all views if [view] is null.
SemanticsFinder(this.root); SemanticsFinder(this.view);
/// The [FlutterView] whose semantics tree this finder will search.
///
/// If null, the finder will search within all views.
final FlutterView? view;
/// The root of the semantics tree that this finder will search. /// Returns the root [SemanticsNode]s of all the semantics trees that this
final SemanticsNode root; /// finder will search.
Iterable<SemanticsNode> get roots {
if (view == null) {
return _allRoots;
}
final RenderView renderView = TestWidgetsFlutterBinding.instance.renderViews
.firstWhere((RenderView r) => r.flutterView == view);
return <SemanticsNode>[
renderView.owner!.semanticsOwner!.rootSemanticsNode!
];
}
@override @override
Iterable<SemanticsNode> get allCandidates { Iterable<SemanticsNode> get allCandidates {
return collectAllSemanticsNodesFrom(root); return roots.expand((SemanticsNode root) => collectAllSemanticsNodesFrom(root));
}
static Iterable<SemanticsNode> get _allRoots {
final List<SemanticsNode> roots = <SemanticsNode>[];
void collectSemanticsRoots(PipelineOwner owner) {
final SemanticsNode? root = owner.semanticsOwner?.rootSemanticsNode;
if (root != null) {
roots.add(root);
}
owner.visitChildren(collectSemanticsRoots);
}
collectSemanticsRoots(TestWidgetsFlutterBinding.instance.rootPipelineOwner);
return roots;
} }
} }
...@@ -1539,7 +1558,7 @@ class _ElementPredicateWidgetFinder extends MatchFinder { ...@@ -1539,7 +1558,7 @@ class _ElementPredicateWidgetFinder extends MatchFinder {
class _PredicateSemanticsFinder extends SemanticsFinder class _PredicateSemanticsFinder extends SemanticsFinder
with MatchFinderMixin<SemanticsNode> { with MatchFinderMixin<SemanticsNode> {
_PredicateSemanticsFinder(this.predicate, DescribeMatchCallback? describeMatch, super.root) _PredicateSemanticsFinder(this.predicate, DescribeMatchCallback? describeMatch, super.view)
: _describeMatch = describeMatch; : _describeMatch = describeMatch;
final SemanticsNodePredicate predicate; final SemanticsNodePredicate predicate;
......
...@@ -190,6 +190,21 @@ void main() { ...@@ -190,6 +190,21 @@ void main() {
expect((find.text('View1Child1').hitTestable().evaluate().single.widget as Text).data, 'View1Child1'); expect((find.text('View1Child1').hitTestable().evaluate().single.widget as Text).data, 'View1Child1');
expect((find.text('View2Child2').hitTestable().evaluate().single.widget as Text).data, 'View2Child2'); expect((find.text('View2Child2').hitTestable().evaluate().single.widget as Text).data, 'View2Child2');
}); });
testWidgets('simulatedAccessibilityTraversal - startNode and endNode in same view', (WidgetTester tester) async {
await pumpViews(tester: tester);
expect(
tester.semantics.simulatedAccessibilityTraversal(
startNode: find.semantics.byLabel('View2Child1'),
endNode: find.semantics.byLabel('View2Child3'),
).map((SemanticsNode node) => node.label),
<String>[
'View2Child1',
'View2Child2',
'View2Child3',
],
);
});
} }
Future<void> pumpViews({required WidgetTester tester}) { Future<void> pumpViews({required WidgetTester tester}) {
......
// 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:ui';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'multi_view_testing.dart';
void main() {
testWidgets('can find nodes in an view when no view is specified', (WidgetTester tester) async {
final List<FlutterView> views = <FlutterView>[
for (int i = 0; i < 3; i++)
FakeView(tester.view, viewId: i + 100)
];
await pumpViews(tester: tester, views: views);
expect(find.semantics.byLabel('View0Child0'), findsOne);
expect(find.semantics.byLabel('View1Child1'), findsOne);
expect(find.semantics.byLabel('View2Child2'), findsOne);
});
testWidgets('can find nodes only in specified view', (WidgetTester tester) async {
final List<FlutterView> views = <FlutterView>[
for (int i = 0; i < 3; i++)
FakeView(tester.view, viewId: i + 100)
];
await pumpViews(tester: tester, views: views);
expect(find.semantics.byLabel('View0Child0', view: views[0]), findsOne);
expect(find.semantics.byLabel('View0Child0', view: views[1]), findsNothing);
expect(find.semantics.byLabel('View0Child0', view: views[2]), findsNothing);
expect(find.semantics.byLabel('View1Child1', view: views[0]), findsNothing);
expect(find.semantics.byLabel('View1Child1', view: views[1]), findsOne);
expect(find.semantics.byLabel('View1Child1', view: views[2]), findsNothing);
expect(find.semantics.byLabel('View2Child2', view: views[0]), findsNothing);
expect(find.semantics.byLabel('View2Child2', view: views[1]), findsNothing);
expect(find.semantics.byLabel('View2Child2', view: views[2]), findsOne);
});
}
Future<void> pumpViews({required WidgetTester tester, required List<FlutterView> views}) {
final List<Widget> viewWidgets = <Widget>[
for (int i = 0; i < 3; i++)
View(
view: views[i],
child: Center(
child: Column(
children: <Widget>[
for (int c = 0; c < 5; c++)
Semantics(container: true, child: Text('View${i}Child$c')),
],
),
),
),
];
return tester.pumpWidget(
wrapWithView: false,
Directionality(
textDirection: TextDirection.ltr,
child: ViewCollection(
views: viewWidgets,
),
),
);
}
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