diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 084b3b0a76..c85ef0caf7 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -1976,6 +1976,7 @@ class _TabBarState extends State { ), ), ); + wrappedTabs[index] = MergeSemantics(child: wrappedTabs[index]); if (!widget.isScrollable && effectiveTabAlignment == TabAlignment.fill) { wrappedTabs[index] = Expanded(child: wrappedTabs[index]); } diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index f4b5e0fe20..c8fedd4b4b 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -4300,6 +4300,9 @@ class SemanticsOwner extends ChangeNotifier { return null; } if (node.mergeAllDescendantsIntoThisNode) { + if (node._canPerformAction(action)) { + return node._actions[action]; + } SemanticsNode? result; node._visitDescendants((SemanticsNode child) { if (child._canPerformAction(action)) { diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 0e71d0a9ca..ba1bb8385d 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -2,8 +2,6 @@ // 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/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -156,6 +154,22 @@ void main() { expect(tester.renderObject(find.byType(CustomPaint)).debugNeedsPaint, true); }); + testWidgets('tab semantics role test', (WidgetTester tester) async { + // Regressing test for https://github.com/flutter/flutter/issues/169175 + // Creates an image semantics node with zero size. + await tester.pumpWidget( + boilerplate( + child: DefaultTabController( + length: 1, + child: TabBar( + tabs: [Tab(icon: Semantics(image: true, child: const SizedBox.shrink()))], + ), + ), + ), + ); + expect(find.byType(Tab), findsOneWidget); + }); + testWidgets('Tab sizing - icon', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart index f30ec10080..c5c103404f 100644 --- a/packages/flutter/test/semantics/semantics_test.dart +++ b/packages/flutter/test/semantics/semantics_test.dart @@ -918,6 +918,26 @@ void main() { expect(newNode.id, expectId); }); + test('performActionAt can hit test on merged semantics node', () { + bool tapped = false; + final SemanticsOwner owner = SemanticsOwner(onSemanticsUpdate: (SemanticsUpdate update) {}); + final SemanticsNode root = SemanticsNode.root(owner: owner) + ..rect = const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); + final SemanticsNode merged = SemanticsNode()..rect = const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); + final SemanticsConfiguration mergeConfig = + SemanticsConfiguration() + ..isSemanticBoundary = true + ..isMergingSemanticsOfDescendants = true + ..onTap = () => tapped = true; + final SemanticsConfiguration rootConfig = SemanticsConfiguration()..isSemanticBoundary = true; + + merged.updateWith(config: mergeConfig, childrenInInversePaintOrder: []); + root.updateWith(config: rootConfig, childrenInInversePaintOrder: [merged]); + + owner.performActionAt(const Offset(5, 5), SemanticsAction.tap); + expect(tapped, isTrue); + }); + test('Tags show up in debug properties', () { final SemanticsNode actionNode = SemanticsNode()..tags = {RenderViewport.useTwoPaneSemantics};