mirror of
https://github.com/flutter/flutter
synced 2024-08-24 18:36:03 +00:00
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.
This commit is contained in:
parent
9a6bda87d9
commit
50862bc04a
|
@ -553,7 +553,7 @@ class CommonSemanticsFinders {
|
|||
return _PredicateSemanticsFinder(
|
||||
predicate,
|
||||
describeMatch,
|
||||
_rootFromView(view),
|
||||
view,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -689,15 +689,6 @@ class CommonSemanticsFinders {
|
|||
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.
|
||||
|
@ -1065,16 +1056,44 @@ abstract class Finder extends FinderBase<Element> with _LegacyFinderMixin {
|
|||
|
||||
/// A base class for creating finders that search the semantics tree.
|
||||
abstract class SemanticsFinder extends FinderBase<SemanticsNode> {
|
||||
/// Creates a new [SemanticsFinder] that will search starting at the given
|
||||
/// `root`.
|
||||
SemanticsFinder(this.root);
|
||||
/// Creates a new [SemanticsFinder] that will search within the given [view] or
|
||||
/// within all views if [view] is null.
|
||||
SemanticsFinder(this.view);
|
||||
|
||||
/// The root of the semantics tree that this finder will search.
|
||||
final SemanticsNode root;
|
||||
/// The [FlutterView] whose semantics tree this finder will search.
|
||||
///
|
||||
/// If null, the finder will search within all views.
|
||||
final FlutterView? view;
|
||||
|
||||
/// Returns the root [SemanticsNode]s of all the semantics trees that this
|
||||
/// 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
|
||||
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 {
|
|||
|
||||
class _PredicateSemanticsFinder extends SemanticsFinder
|
||||
with MatchFinderMixin<SemanticsNode> {
|
||||
_PredicateSemanticsFinder(this.predicate, DescribeMatchCallback? describeMatch, super.root)
|
||||
_PredicateSemanticsFinder(this.predicate, DescribeMatchCallback? describeMatch, super.view)
|
||||
: _describeMatch = describeMatch;
|
||||
|
||||
final SemanticsNodePredicate predicate;
|
||||
|
|
|
@ -190,6 +190,21 @@ void main() {
|
|||
expect((find.text('View1Child1').hitTestable().evaluate().single.widget as Text).data, 'View1Child1');
|
||||
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}) {
|
||||
|
|
71
packages/flutter_test/test/semantics_finder_test.dart
Normal file
71
packages/flutter_test/test/semantics_finder_test.dart
Normal file
|
@ -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 '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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue