@@ -433,7 +433,7 @@ void _testEngineSemanticsOwner() {
433433 expectSemanticsTree ('''
434434<sem style="$rootSemanticStyle ">
435435 <sem-c>
436- <sem aria-label="Hello"></sem>
436+ <sem aria-label="Hello" role="text" ></sem>
437437 </sem-c>
438438</sem>''' );
439439
@@ -448,7 +448,7 @@ void _testEngineSemanticsOwner() {
448448 expectSemanticsTree ('''
449449<sem style="$rootSemanticStyle ">
450450 <sem-c>
451- <a aria-label="Hello" role="button" style="display: block;"></a>
451+ <a aria-label="Hello" style="display: block;"></a>
452452 </sem-c>
453453</sem>''' );
454454 expect (existingParent, tree[1 ]! .element.parent);
@@ -2111,6 +2111,89 @@ void _testTappable() {
21112111
21122112 semantics ().semanticsEnabled = false ;
21132113 });
2114+
2115+ // Regression test for: https://github.com/flutter/flutter/issues/134842
2116+ //
2117+ // If the click event is allowed to propagate through the hierarchy, then both
2118+ // the descendant and the parent will generate a SemanticsAction.tap, causing
2119+ // a double-tap to happen on the framework side.
2120+ test ('inner tappable overrides ancestor tappable' , () async {
2121+ semantics ()
2122+ ..debugOverrideTimestampFunction (() => _testTime)
2123+ ..semanticsEnabled = true ;
2124+
2125+ final List <CapturedAction > capturedActions = < CapturedAction > [];
2126+ EnginePlatformDispatcher .instance.onSemanticsActionEvent = (ui.SemanticsActionEvent event) {
2127+ capturedActions.add ((event.nodeId, event.type, event.arguments));
2128+ };
2129+
2130+ final SemanticsTester tester = SemanticsTester (semantics ());
2131+ tester.updateNode (
2132+ id: 0 ,
2133+ isFocusable: true ,
2134+ hasTap: true ,
2135+ hasEnabledState: true ,
2136+ isEnabled: true ,
2137+ isButton: true ,
2138+ rect: const ui.Rect .fromLTRB (0 , 0 , 100 , 50 ),
2139+ children: < SemanticsNodeUpdate > [
2140+ tester.updateNode (
2141+ id: 1 ,
2142+ isFocusable: true ,
2143+ hasTap: true ,
2144+ hasEnabledState: true ,
2145+ isEnabled: true ,
2146+ isButton: true ,
2147+ rect: const ui.Rect .fromLTRB (0 , 0 , 100 , 50 ),
2148+ ),
2149+ ],
2150+ );
2151+ tester.apply ();
2152+
2153+ expectSemanticsTree ('''
2154+ <sem flt-tappable role="button" style="$rootSemanticStyle ">
2155+ <sem-c>
2156+ <sem flt-tappable role="button"></sem>
2157+ </sem-c>
2158+ </sem>
2159+ ''' );
2160+
2161+ // Tap on the outer element
2162+ {
2163+ final DomElement element = tester.getSemanticsObject (0 ).element;
2164+ final DomRect rect = element.getBoundingClientRect ();
2165+
2166+ element.dispatchEvent (createDomMouseEvent ('click' , < Object ? , Object ? > {
2167+ 'clientX' : (rect.left + (rect.right - rect.left) / 2 ).floor (),
2168+ 'clientY' : (rect.top + (rect.bottom - rect.top) / 2 ).floor (),
2169+ }));
2170+
2171+ expect (capturedActions, < CapturedAction > [
2172+ (0 , ui.SemanticsAction .tap, null ),
2173+ ]);
2174+ }
2175+
2176+ // Tap on the inner element
2177+ {
2178+ capturedActions.clear ();
2179+ final DomElement element = tester.getSemanticsObject (1 ).element;
2180+ final DomRect rect = element.getBoundingClientRect ();
2181+
2182+ element.dispatchEvent (createDomMouseEvent ('click' , < Object ? , Object ? > {
2183+ 'bubbles' : true ,
2184+ 'clientX' : (rect.left + (rect.right - rect.left) / 2 ).floor (),
2185+ 'clientY' : (rect.top + (rect.bottom - rect.top) / 2 ).floor (),
2186+ }));
2187+
2188+ // The click on the inner element should not propagate to the parent to
2189+ // avoid sending a second SemanticsAction.tap action to the framework.
2190+ expect (capturedActions, < CapturedAction > [
2191+ (1 , ui.SemanticsAction .tap, null ),
2192+ ]);
2193+ }
2194+
2195+ semantics ().semanticsEnabled = false ;
2196+ });
21142197}
21152198
21162199void _testImage () {
0 commit comments