From aea7d981d6ee7d5c0286eaf9afaaefd29cdafbe2 Mon Sep 17 00:00:00 2001
From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com>
Date: Wed, 25 Mar 2026 13:37:39 -0700
Subject: [PATCH 1/9] overflow: hidden should prevent hittesting
---
.../Fabric/Composition/CompositionViewComponentView.cpp | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp
index 8948a5bb4e1..71d6f2a2f89 100644
--- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp
+++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp
@@ -1174,15 +1174,17 @@ facebook::react::Tag ViewComponentView::hitTest(
facebook::react::Tag targetTag = -1;
+ bool isPointInside = ptLocal.x >= 0 && ptLocal.x <= m_layoutMetrics.frame.size.width && ptLocal.y >= 0 &&
+ ptLocal.y <= m_layoutMetrics.frame.size.height;
+
if ((ignorePointerEvents || m_props->pointerEvents == facebook::react::PointerEventsMode::Auto ||
m_props->pointerEvents == facebook::react::PointerEventsMode::BoxNone) &&
- anyHitTestHelper(targetTag, ptLocal, localPt))
+ (isPointInside || !viewProps()->getClipsContentToBounds()) && anyHitTestHelper(targetTag, ptLocal, localPt))
return targetTag;
if ((ignorePointerEvents || m_props->pointerEvents == facebook::react::PointerEventsMode::Auto ||
m_props->pointerEvents == facebook::react::PointerEventsMode::BoxOnly) &&
- ptLocal.x >= 0 && ptLocal.x <= m_layoutMetrics.frame.size.width && ptLocal.y >= 0 &&
- ptLocal.y <= m_layoutMetrics.frame.size.height) {
+ isPointInside) {
localPt = ptLocal;
return Tag();
}
From f1e98059d777a5cde6feb23047bbb8bab4c8583c Mon Sep 17 00:00:00 2001
From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com>
Date: Wed, 25 Mar 2026 13:46:14 -0700
Subject: [PATCH 2/9] Change files
---
...ative-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 change/react-native-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json
diff --git a/change/react-native-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json b/change/react-native-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json
new file mode 100644
index 00000000000..e7ea0fd6e89
--- /dev/null
+++ b/change/react-native-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "overflow: hidden should prevent hittesting",
+ "packageName": "react-native-windows",
+ "email": "30809111+acoates-ms@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
From 2436058442f998c79e2111735e18e547116e83c3 Mon Sep 17 00:00:00 2001
From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com>
Date: Wed, 25 Mar 2026 16:35:14 -0700
Subject: [PATCH 3/9] Add tests
---
.../js/examples-win/HitTest/HitTestExample.js | 101 ++++++++++++++++++
.../src/js/utils/RNTesterList.windows.js | 5 +
.../e2e-test-app-fabric/test/HitTest.test.ts | 69 ++++++++++++
3 files changed, 175 insertions(+)
create mode 100644 packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js
create mode 100644 packages/e2e-test-app-fabric/test/HitTest.test.ts
diff --git a/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js b/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js
new file mode 100644
index 00000000000..3e37880ec51
--- /dev/null
+++ b/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ * Licensed under the MIT License.
+ * @format
+ */
+
+'use strict';
+
+import React from 'react';
+
+import {View, Text, Pressable} from 'react-native';
+import {useState} from 'react';
+
+exports.displayName = 'HitTestExample';
+exports.title = 'Hit Testing';
+exports.category = 'Basic';
+exports.description = 'Test that overflow hidden affect hit testing';
+exports.examples = [
+ {
+ title: 'overflow visible affects hit testing\n',
+ render: function (): React.Node {
+ const [bgColor, setBgColor] = React.useState('red');
+
+ return (
+
+
+ Clicking the pressable should work even if it is outside the bounds
+ of its parent.
+
+
+
+ {
+ setBgColor(bgColor === 'red' ? 'green' : 'red');
+ }}>
+ Press me
+
+
+
+
+ );
+ },
+ },
+ {
+ title: 'overflow hidden affects hit testing\n',
+ render: function (): React.Node {
+ const [bgColor, setBgColor] = React.useState('red');
+
+ return (
+
+
+ Clicking within the visible view will trigger the pressable.
+ Clicking outside the bounds, where the pressable extends but is
+ clipped by its parent overflow:hidden, should not trigger the
+ pressable.
+
+
+
+ {
+ setBgColor(bgColor === 'red' ? 'green' : 'red');
+ }}>
+ Press me
+
+
+
+
+ );
+ },
+ },
+];
diff --git a/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js b/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js
index c9c15047b6c..c3df1a9fdef 100644
--- a/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js
+++ b/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js
@@ -207,6 +207,11 @@ const Components: Array = [
key: 'LegacyTextHitTestTest',
module: require('../examples-win/LegacyTests/TextHitTestPage'),
},
+ {
+ key: 'HitTestExample',
+ category: 'UI',
+ module: require('../examples-win/HitTest/HitTestExample'),
+ },
{
key: 'PerformanceComparisonExample',
category: 'Basic',
diff --git a/packages/e2e-test-app-fabric/test/HitTest.test.ts b/packages/e2e-test-app-fabric/test/HitTest.test.ts
new file mode 100644
index 00000000000..d71450d11f0
--- /dev/null
+++ b/packages/e2e-test-app-fabric/test/HitTest.test.ts
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ * Licensed under the MIT License.
+ *
+ * @format
+ */
+
+import { app } from '@react-native-windows/automation';
+import { dumpVisualTree } from '@react-native-windows/automation-commands';
+import { goToComponentExample } from './RNTesterNavigation';
+import { verifyNoErrorLogs } from './Helpers';
+
+beforeAll(async () => {
+ // If window is partially offscreen, tests will fail to click on certain elements
+ await app.setWindowPosition(0, 0);
+ await app.setWindowSize(1000, 1250);
+ await goToComponentExample('Hit Testing');
+});
+
+afterEach(async () => {
+ await verifyNoErrorLogs();
+});
+
+async function verifyElementAccessibiltyValue(element: string, value: string) {
+ const dump = await dumpVisualTree(element);
+ expect(dump!["Automation Tree"]["ValuePattern.Value"]).toBe(value);
+}
+
+describe('Hit Testing', () => {
+
+ test('Hit testing child outside the bounds of parents', async () => {
+ const target = await app.findElementByTestID('visible-overflow-element');
+
+ // View starts in red state
+ await verifyElementAccessibiltyValue('visible-overflow-element', 'red');
+
+ // The webdriverio package computes the offsets from the center point of the target.
+ // This is within the bounds of the child and the parent, so should hitTest even with overflow:visible
+ await target.click({ x: -50, y: -50, });
+
+ await verifyElementAccessibiltyValue('visible-overflow-element', 'green');
+
+ // The webdriverio package computes the offsets from the center point of the target.
+ // This is within the bounds of the child, but outside the parents bounds
+ await target.click({ x: 0, y: 0, });
+
+ // View should still be red, since the click should hit the pressable
+ await verifyElementAccessibiltyValue('visible-overflow-element', 'red');
+ });
+
+ test('Overflow hidden prevents hittesting child', async () => {
+ const target = await app.findElementByTestID('hidden-overflow-element');
+
+ // View starts in red state
+ await verifyElementAccessibiltyValue('hidden-overflow-element', 'red');
+
+ // This is within the bounds of the child and the parent, so should hitTest even with overflow:hidden
+ await target.click({ x: -50, y: -50, });
+
+ await verifyElementAccessibiltyValue('hidden-overflow-element', 'green');
+
+ // This is within the bounds of the child, but shouldn't hit test, since the parent is overflow:hidden
+ await target.click({ x: 0, y: 0, });
+
+ // View should still be green, since the click shouldn't hit the pressable
+ await verifyElementAccessibiltyValue('hidden-overflow-element', 'green');
+ });
+
+});
From ec32e6d60bd23da6e8d8acb4da876143608f9a0c Mon Sep 17 00:00:00 2001
From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com>
Date: Thu, 26 Mar 2026 11:04:29 -0700
Subject: [PATCH 4/9] snapshots + lint
---
.../e2e-test-app-fabric/test/HitTest.test.ts | 20 +-
.../__snapshots__/HomeUIADump.test.ts.snap | 301 ++++++++++------
.../PointerButtonComponentTest.test.ts.snap | 53 +++
.../PressableComponentTest.test.ts.snap | 4 +-
.../TextComponentTest.test.ts.snap | 54 +--
.../TouchableComponentTest.test.ts.snap | 8 +-
.../__snapshots__/snapshotPages.test.js.snap | 321 +++++++++++++++++-
7 files changed, 602 insertions(+), 159 deletions(-)
create mode 100644 packages/e2e-test-app-fabric/test/__snapshots__/PointerButtonComponentTest.test.ts.snap
diff --git a/packages/e2e-test-app-fabric/test/HitTest.test.ts b/packages/e2e-test-app-fabric/test/HitTest.test.ts
index d71450d11f0..042943091be 100644
--- a/packages/e2e-test-app-fabric/test/HitTest.test.ts
+++ b/packages/e2e-test-app-fabric/test/HitTest.test.ts
@@ -5,10 +5,10 @@
* @format
*/
-import { app } from '@react-native-windows/automation';
-import { dumpVisualTree } from '@react-native-windows/automation-commands';
-import { goToComponentExample } from './RNTesterNavigation';
-import { verifyNoErrorLogs } from './Helpers';
+import {app} from '@react-native-windows/automation';
+import {dumpVisualTree} from '@react-native-windows/automation-commands';
+import {goToComponentExample} from './RNTesterNavigation';
+import {verifyNoErrorLogs} from './Helpers';
beforeAll(async () => {
// If window is partially offscreen, tests will fail to click on certain elements
@@ -23,11 +23,10 @@ afterEach(async () => {
async function verifyElementAccessibiltyValue(element: string, value: string) {
const dump = await dumpVisualTree(element);
- expect(dump!["Automation Tree"]["ValuePattern.Value"]).toBe(value);
+ expect(dump!['Automation Tree']['ValuePattern.Value']).toBe(value);
}
describe('Hit Testing', () => {
-
test('Hit testing child outside the bounds of parents', async () => {
const target = await app.findElementByTestID('visible-overflow-element');
@@ -36,13 +35,13 @@ describe('Hit Testing', () => {
// The webdriverio package computes the offsets from the center point of the target.
// This is within the bounds of the child and the parent, so should hitTest even with overflow:visible
- await target.click({ x: -50, y: -50, });
+ await target.click({x: -50, y: -50});
await verifyElementAccessibiltyValue('visible-overflow-element', 'green');
// The webdriverio package computes the offsets from the center point of the target.
// This is within the bounds of the child, but outside the parents bounds
- await target.click({ x: 0, y: 0, });
+ await target.click({x: 0, y: 0});
// View should still be red, since the click should hit the pressable
await verifyElementAccessibiltyValue('visible-overflow-element', 'red');
@@ -55,15 +54,14 @@ describe('Hit Testing', () => {
await verifyElementAccessibiltyValue('hidden-overflow-element', 'red');
// This is within the bounds of the child and the parent, so should hitTest even with overflow:hidden
- await target.click({ x: -50, y: -50, });
+ await target.click({x: -50, y: -50});
await verifyElementAccessibiltyValue('hidden-overflow-element', 'green');
// This is within the bounds of the child, but shouldn't hit test, since the parent is overflow:hidden
- await target.click({ x: 0, y: 0, });
+ await target.click({x: 0, y: 0});
// View should still be green, since the click shouldn't hit the pressable
await verifyElementAccessibiltyValue('hidden-overflow-element', 'green');
});
-
});
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap
index 83cd2b99f8a..6060ab66a55 100644
--- a/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap
@@ -1469,87 +1469,6 @@ exports[`Home UIA Tree Dump Custom Native Accessibility Example 1`] = `
}
`;
-exports[`Home UIA Tree Dump Cxx TurboModule 1`] = `
-{
- "Automation Tree": {
- "AutomationId": "Cxx TurboModule",
- "ControlType": 50026,
- "IsKeyboardFocusable": true,
- "LocalizedControlType": "group",
- "Name": "Cxx TurboModule Usage of Cxx TurboModule",
- "__Children": [
- {
- "AutomationId": "",
- "ControlType": 50020,
- "LocalizedControlType": "text",
- "Name": "Cxx TurboModule",
- "TextRangePattern.GetText": "Cxx TurboModule",
- },
- {
- "AutomationId": "",
- "ControlType": 50020,
- "LocalizedControlType": "text",
- "Name": "Usage of Cxx TurboModule",
- "TextRangePattern.GetText": "Usage of Cxx TurboModule",
- },
- ],
- },
- "Component Tree": {
- "Type": "Microsoft.ReactNative.Composition.ViewComponentView",
- "_Props": {
- "AccessibilityLabel": "Cxx TurboModule Usage of Cxx TurboModule",
- "TestId": "Cxx TurboModule",
- },
- "__Children": [
- {
- "Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
- "_Props": {},
- },
- {
- "Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
- "_Props": {},
- },
- ],
- },
- "Visual Tree": {
- "Brush": {
- "Brush Type": "ColorBrush",
- "Color": "rgba(255, 255, 255, 255)",
- },
- "Comment": "Cxx TurboModule",
- "Offset": "0, 0, 0",
- "Size": "966, 78",
- "Visual Type": "SpriteVisual",
- "__Children": [
- {
- "Offset": "16, 16, 0",
- "Size": "141, 25",
- "Visual Type": "SpriteVisual",
- "__Children": [
- {
- "Offset": "0, 0, 0",
- "Size": "141, 25",
- "Visual Type": "SpriteVisual",
- },
- ],
- },
- {
- "Offset": "16, 45, 0",
- "Size": "934, 17",
- "Visual Type": "SpriteVisual",
- "__Children": [
- {
- "Offset": "0, 0, 0",
- "Size": "934, 17",
- "Visual Type": "SpriteVisual",
- },
- ],
- },
- ],
- },
-}
-`;
-
exports[`Home UIA Tree Dump DevSettings 1`] = `
{
"Automation Tree": {
@@ -2264,12 +2183,12 @@ exports[`Home UIA Tree Dump Filter 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
},
],
@@ -2522,6 +2441,87 @@ exports[`Home UIA Tree Dump Glyph UWP 1`] = `
}
`;
+exports[`Home UIA Tree Dump Hit Testing 1`] = `
+{
+ "Automation Tree": {
+ "AutomationId": "Hit Testing",
+ "ControlType": 50026,
+ "IsKeyboardFocusable": true,
+ "LocalizedControlType": "group",
+ "Name": "Hit Testing Test that overflow hidden affect hit testing",
+ "__Children": [
+ {
+ "AutomationId": "",
+ "ControlType": 50020,
+ "LocalizedControlType": "text",
+ "Name": "Hit Testing",
+ "TextRangePattern.GetText": "Hit Testing",
+ },
+ {
+ "AutomationId": "",
+ "ControlType": 50020,
+ "LocalizedControlType": "text",
+ "Name": "Test that overflow hidden affect hit testing",
+ "TextRangePattern.GetText": "Test that overflow hidden affect hit testing",
+ },
+ ],
+ },
+ "Component Tree": {
+ "Type": "Microsoft.ReactNative.Composition.ViewComponentView",
+ "_Props": {
+ "AccessibilityLabel": "Hit Testing Test that overflow hidden affect hit testing",
+ "TestId": "Hit Testing",
+ },
+ "__Children": [
+ {
+ "Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
+ "_Props": {},
+ },
+ {
+ "Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
+ "_Props": {},
+ },
+ ],
+ },
+ "Visual Tree": {
+ "Brush": {
+ "Brush Type": "ColorBrush",
+ "Color": "rgba(255, 255, 255, 255)",
+ },
+ "Comment": "Hit Testing",
+ "Offset": "0, 0, 0",
+ "Size": "966, 77",
+ "Visual Type": "SpriteVisual",
+ "__Children": [
+ {
+ "Offset": "16, 16, 0",
+ "Size": "85, 25",
+ "Visual Type": "SpriteVisual",
+ "__Children": [
+ {
+ "Offset": "0, 0, 0",
+ "Size": "85, 25",
+ "Visual Type": "SpriteVisual",
+ },
+ ],
+ },
+ {
+ "Offset": "16, 45, 0",
+ "Size": "934, 17",
+ "Visual Type": "SpriteVisual",
+ "__Children": [
+ {
+ "Offset": "0, 0, 0",
+ "Size": "934, 17",
+ "Visual Type": "SpriteVisual",
+ },
+ ],
+ },
+ ],
+ },
+}
+`;
+
exports[`Home UIA Tree Dump Image 1`] = `
{
"Automation Tree": {
@@ -2571,7 +2571,7 @@ exports[`Home UIA Tree Dump Image 1`] = `
},
"Comment": "Image",
"Offset": "0, 0, 0",
- "Size": "966, 77",
+ "Size": "966, 78",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -2669,12 +2669,12 @@ exports[`Home UIA Tree Dump Keyboard 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
},
],
@@ -2750,12 +2750,12 @@ exports[`Home UIA Tree Dump Keyboard 2`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
},
],
@@ -3057,7 +3057,7 @@ exports[`Home UIA Tree Dump Layout Events 1`] = `
},
"Comment": "Layout Events",
"Offset": "0, 0, 0",
- "Size": "966, 77",
+ "Size": "966, 78",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -3138,7 +3138,7 @@ exports[`Home UIA Tree Dump Legacy Native Module 1`] = `
},
"Comment": "Legacy Native Module",
"Offset": "0, 0, 0",
- "Size": "966, 78",
+ "Size": "966, 77",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -3398,12 +3398,12 @@ exports[`Home UIA Tree Dump LegacyLoginTest 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
},
],
@@ -3479,12 +3479,12 @@ exports[`Home UIA Tree Dump LegacySelectableTextTest 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
},
],
@@ -4029,7 +4029,7 @@ exports[`Home UIA Tree Dump Moving Light Example 1`] = `
},
"Comment": "Moving Light Example",
"Offset": "0, 0, 0",
- "Size": "966, 78",
+ "Size": "966, 77",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -4127,12 +4127,12 @@ exports[`Home UIA Tree Dump Native Animated Example 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
},
],
@@ -4191,7 +4191,7 @@ exports[`Home UIA Tree Dump New App Screen 1`] = `
},
"Comment": "New App Screen",
"Offset": "0, 0, 0",
- "Size": "966, 77",
+ "Size": "966, 78",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -4289,12 +4289,12 @@ exports[`Home UIA Tree Dump PanResponder Sample 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
},
],
@@ -4547,6 +4547,87 @@ exports[`Home UIA Tree Dump PlatformColor 1`] = `
}
`;
+exports[`Home UIA Tree Dump Pointer Button 1`] = `
+{
+ "Automation Tree": {
+ "AutomationId": "Pointer Button",
+ "ControlType": 50026,
+ "IsKeyboardFocusable": true,
+ "LocalizedControlType": "group",
+ "Name": "Pointer Button Tests that PointerEvent.button and PointerEvent.buttons are correctly populated.",
+ "__Children": [
+ {
+ "AutomationId": "",
+ "ControlType": 50020,
+ "LocalizedControlType": "text",
+ "Name": "Pointer Button",
+ "TextRangePattern.GetText": "Pointer Button",
+ },
+ {
+ "AutomationId": "",
+ "ControlType": 50020,
+ "LocalizedControlType": "text",
+ "Name": "Tests that PointerEvent.button and PointerEvent.buttons are correctly populated.",
+ "TextRangePattern.GetText": "Tests that PointerEvent.button and PointerEvent.buttons are correctly populated.",
+ },
+ ],
+ },
+ "Component Tree": {
+ "Type": "Microsoft.ReactNative.Composition.ViewComponentView",
+ "_Props": {
+ "AccessibilityLabel": "Pointer Button Tests that PointerEvent.button and PointerEvent.buttons are correctly populated.",
+ "TestId": "Pointer Button",
+ },
+ "__Children": [
+ {
+ "Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
+ "_Props": {},
+ },
+ {
+ "Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
+ "_Props": {},
+ },
+ ],
+ },
+ "Visual Tree": {
+ "Brush": {
+ "Brush Type": "ColorBrush",
+ "Color": "rgba(255, 255, 255, 255)",
+ },
+ "Comment": "Pointer Button",
+ "Offset": "0, 0, 0",
+ "Size": "966, 78",
+ "Visual Type": "SpriteVisual",
+ "__Children": [
+ {
+ "Offset": "16, 16, 0",
+ "Size": "115, 25",
+ "Visual Type": "SpriteVisual",
+ "__Children": [
+ {
+ "Offset": "0, 0, 0",
+ "Size": "115, 25",
+ "Visual Type": "SpriteVisual",
+ },
+ ],
+ },
+ {
+ "Offset": "16, 45, 0",
+ "Size": "934, 17",
+ "Visual Type": "SpriteVisual",
+ "__Children": [
+ {
+ "Offset": "0, 0, 0",
+ "Size": "934, 17",
+ "Visual Type": "SpriteVisual",
+ },
+ ],
+ },
+ ],
+ },
+}
+`;
+
exports[`Home UIA Tree Dump Pointer Events 1`] = `
{
"Automation Tree": {
@@ -4937,12 +5018,12 @@ exports[`Home UIA Tree Dump ScrollView 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
},
],
@@ -5018,12 +5099,12 @@ exports[`Home UIA Tree Dump ScrollViewAnimated 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
},
],
@@ -5597,7 +5678,7 @@ exports[`Home UIA Tree Dump Text 1`] = `
},
"Comment": "Text",
"Offset": "0, 0, 0",
- "Size": "966, 78",
+ "Size": "966, 77",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -5678,7 +5759,7 @@ exports[`Home UIA Tree Dump TextInput 1`] = `
},
"Comment": "TextInput",
"Offset": "0, 0, 0",
- "Size": "966, 77",
+ "Size": "966, 78",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -6169,12 +6250,12 @@ exports[`Home UIA Tree Dump TransparentHitTestExample 1`] = `
"__Children": [
{
"Offset": "16, 16, 0",
- "Size": "214, 25",
+ "Size": "214, 24",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "214, 25",
+ "Size": "214, 24",
"Visual Type": "SpriteVisual",
},
],
@@ -6412,12 +6493,12 @@ exports[`Home UIA Tree Dump View 1`] = `
"__Children": [
{
"Offset": "16, 16, 0",
- "Size": "38, 24",
+ "Size": "38, 25",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "38, 24",
+ "Size": "38, 25",
"Visual Type": "SpriteVisual",
},
],
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/PointerButtonComponentTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/PointerButtonComponentTest.test.ts.snap
new file mode 100644
index 00000000000..9ea6e8adb43
--- /dev/null
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/PointerButtonComponentTest.test.ts.snap
@@ -0,0 +1,53 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Pointer Button Tests onPointerDown reports correct button property on left click 1`] = `
+{
+ "Automation Tree": {
+ "AutomationId": "pointer-button-target",
+ "ControlType": 50026,
+ "LocalizedControlType": "group",
+ },
+ "Component Tree": {
+ "Type": "Microsoft.ReactNative.Composition.ViewComponentView",
+ "_Props": {
+ "TestId": "pointer-button-target",
+ },
+ },
+ "Visual Tree": {
+ "Brush": {
+ "Brush Type": "ColorBrush",
+ "Color": "rgba(255, 0, 255, 255)",
+ },
+ "Comment": "pointer-button-target",
+ "Offset": "0, 0, 0",
+ "Size": "200, 200",
+ "Visual Type": "SpriteVisual",
+ },
+}
+`;
+
+exports[`Pointer Button Tests onPointerUp reports correct button property on left click 1`] = `
+{
+ "Automation Tree": {
+ "AutomationId": "pointer-up-button-target",
+ "ControlType": 50026,
+ "LocalizedControlType": "group",
+ },
+ "Component Tree": {
+ "Type": "Microsoft.ReactNative.Composition.ViewComponentView",
+ "_Props": {
+ "TestId": "pointer-up-button-target",
+ },
+ },
+ "Visual Tree": {
+ "Brush": {
+ "Brush Type": "ColorBrush",
+ "Color": "rgba(255, 0, 255, 255)",
+ },
+ "Comment": "pointer-up-button-target",
+ "Offset": "0, 0, 0",
+ "Size": "200, 200",
+ "Visual Type": "SpriteVisual",
+ },
+}
+`;
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/PressableComponentTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/PressableComponentTest.test.ts.snap
index 5e8c7116993..4d9b837e37b 100644
--- a/packages/e2e-test-app-fabric/test/__snapshots__/PressableComponentTest.test.ts.snap
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/PressableComponentTest.test.ts.snap
@@ -1961,8 +1961,8 @@ exports[`Pressable Tests Text can have pressable behavior 1`] = `
{
"Automation Tree": {
"AutomationId": "tappable_text",
- "ControlType": 50020,
- "LocalizedControlType": "text",
+ "ControlType": 50005,
+ "LocalizedControlType": "link",
"Name": "Text has built-in onPress handling",
"TextRangePattern.GetText": "Text has built-in onPress handling",
},
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/TextComponentTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/TextComponentTest.test.ts.snap
index be1ba4318a3..9a870819a63 100644
--- a/packages/e2e-test-app-fabric/test/__snapshots__/TextComponentTest.test.ts.snap
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/TextComponentTest.test.ts.snap
@@ -234,7 +234,7 @@ exports[`Text Tests Text can be restricted to one line 1`] = `
"Visual Tree": {
"Comment": "text-one-line",
"Offset": "0, 0, 0",
- "Size": "300, 19",
+ "Size": "300, 20",
"Visual Type": "SpriteVisual",
},
}
@@ -258,7 +258,7 @@ exports[`Text Tests Text can be selectable 1`] = `
"Visual Tree": {
"Comment": "text-selectable",
"Offset": "0, 0, 0",
- "Size": "916, 19",
+ "Size": "916, 20",
"Visual Type": "SpriteVisual",
},
}
@@ -396,12 +396,12 @@ exports[`Text Tests Text can have advanced borders 1`] = `
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "916, 39",
+ "Size": "916, 40",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "916, 39",
+ "Size": "916, 40",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -437,7 +437,7 @@ exports[`Text Tests Text can have advanced borders 1`] = `
"Color": "rgba(255, 0, 0, 255)",
},
"Offset": "-10, 20, 0",
- "Size": "10, 13",
+ "Size": "10, 14",
"Visual Type": "SpriteVisual",
},
{
@@ -473,7 +473,7 @@ exports[`Text Tests Text can have advanced borders 1`] = `
"Color": "rgba(255, 0, 0, 255)",
},
"Offset": "0, 22, 0",
- "Size": "20, 9",
+ "Size": "20, 10",
"Visual Type": "SpriteVisual",
},
],
@@ -482,12 +482,12 @@ exports[`Text Tests Text can have advanced borders 1`] = `
},
{
"Offset": "0, 38, 0",
- "Size": "916, 40",
+ "Size": "916, 39",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "916, 40",
+ "Size": "916, 39",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -523,7 +523,7 @@ exports[`Text Tests Text can have advanced borders 1`] = `
"Color": "rgba(0, 0, 255, 255)",
},
"Offset": "-10, 20, 0",
- "Size": "10, 14",
+ "Size": "10, 13",
"Visual Type": "SpriteVisual",
},
{
@@ -559,7 +559,7 @@ exports[`Text Tests Text can have advanced borders 1`] = `
"Color": "rgba(0, 0, 255, 255)",
},
"Offset": "0, 22, 0",
- "Size": "20, 10",
+ "Size": "20, 9",
"Visual Type": "SpriteVisual",
},
],
@@ -661,8 +661,8 @@ exports[`Text Tests Text can have an outer color 1`] = `
{
"Automation Tree": {
"AutomationId": "text-outer-color",
- "ControlType": 50020,
- "LocalizedControlType": "text",
+ "ControlType": 50005,
+ "LocalizedControlType": "link",
"Name": "(Normal text,(R)red(G)green(B)blue(C)cyan(M)magenta(Y)yellow(K)black(and bold(and tiny bold italic blue(and tiny normal blue))))",
"TextRangePattern.GetText": "(Normal text,(R)red(G)green(B)blue(C)cyan(M)magenta(Y)yellow(K)black(and bold(and tiny bold italic blue(and tiny normal blue))))",
},
@@ -675,7 +675,7 @@ exports[`Text Tests Text can have an outer color 1`] = `
"Visual Tree": {
"Comment": "text-outer-color",
"Offset": "0, 0, 0",
- "Size": "916, 20",
+ "Size": "916, 19",
"Visual Type": "SpriteVisual",
},
}
@@ -740,7 +740,7 @@ exports[`Text Tests Text can have borders 1`] = `
"Visual Tree": {
"Comment": "text-border",
"Offset": "0, 0, 0",
- "Size": "916, 384",
+ "Size": "916, 385",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -853,12 +853,12 @@ exports[`Text Tests Text can have borders 1`] = `
},
{
"Offset": "0, 365, 0",
- "Size": "916, 20",
+ "Size": "916, 19",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "916, 20",
+ "Size": "916, 19",
"Visual Type": "SpriteVisual",
},
],
@@ -886,7 +886,7 @@ exports[`Text Tests Text can have decoration lines: Solid Line Through 1`] = `
"Visual Tree": {
"Comment": "text-decoration-solid-linethru",
"Offset": "0, 0, 0",
- "Size": "916, 20",
+ "Size": "916, 19",
"Visual Type": "SpriteVisual",
},
}
@@ -910,7 +910,7 @@ exports[`Text Tests Text can have decoration lines: Underline 1`] = `
"Visual Tree": {
"Comment": "text-decoration-underline",
"Offset": "0, 0, 0",
- "Size": "916, 20",
+ "Size": "916, 19",
"Visual Type": "SpriteVisual",
},
}
@@ -953,7 +953,7 @@ exports[`Text Tests Text can have inline views/images 1`] = `
"Visual Tree": {
"Comment": "text-view",
"Offset": "0, 0, 0",
- "Size": "916, 27",
+ "Size": "916, 26",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -1044,17 +1044,17 @@ exports[`Text Tests Text can have nested views 1`] = `
"Visual Tree": {
"Comment": "text-nested-view",
"Offset": "0, 0, 0",
- "Size": "916, 41",
+ "Size": "916, 40",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "916, 19",
+ "Size": "916, 20",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "916, 19",
+ "Size": "916, 20",
"Visual Type": "SpriteVisual",
},
],
@@ -1230,17 +1230,17 @@ exports[`Text Tests Texts can clip inline View/Images 1`] = `
"Visual Tree": {
"Comment": "text-view-images-clipped",
"Offset": "0, 0, 0",
- "Size": "916, 222",
+ "Size": "916, 223",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "916, 20",
+ "Size": "916, 19",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "916, 20",
+ "Size": "916, 19",
"Visual Type": "SpriteVisual",
},
],
@@ -1300,12 +1300,12 @@ exports[`Text Tests Texts can clip inline View/Images 1`] = `
},
{
"Offset": "0, 103, 0",
- "Size": "916, 19",
+ "Size": "916, 20",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "916, 19",
+ "Size": "916, 20",
"Visual Type": "SpriteVisual",
},
],
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/TouchableComponentTest.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/TouchableComponentTest.test.ts.snap
index e5670892632..dacfdc72efa 100644
--- a/packages/e2e-test-app-fabric/test/__snapshots__/TouchableComponentTest.test.ts.snap
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/TouchableComponentTest.test.ts.snap
@@ -4,8 +4,8 @@ exports[`Touchable Tests Text components can be tappable 1`] = `
{
"Automation Tree": {
"AutomationId": "tappable_text",
- "ControlType": 50020,
- "LocalizedControlType": "text",
+ "ControlType": 50005,
+ "LocalizedControlType": "link",
"Name": "Text has built-in onPress handling",
"TextRangePattern.GetText": "Text has built-in onPress handling",
},
@@ -120,10 +120,10 @@ exports[`Touchable Tests Touchables can be defined in a set using accessibilityP
},
{
"AutomationId": "",
- "ControlType": 50020,
+ "ControlType": 50005,
"IsKeyboardFocusable": true,
"LiveSetting": "Assertive",
- "LocalizedControlType": "text",
+ "LocalizedControlType": "link",
"Name": "TouchableWithoutFeedback (Control 3 in Set of 3)",
"TextRangePattern.GetText": "TouchableWithoutFeedback (Control 3 in Set of 3)",
},
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap
index d9fdfc834f3..2371b90f71a 100644
--- a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap
@@ -26040,6 +26040,174 @@ exports[`snapshotAllPages Glyph UWP 1`] = `
`;
+exports[`snapshotAllPages Hit Testing 1`] = `
+
+
+ Clicking the pressable should work even if it is outside the bounds of its parent.
+
+
+
+
+
+ Press me
+
+
+
+
+
+`;
+
+exports[`snapshotAllPages Hit Testing 2`] = `
+
+
+ Clicking within the visible view will trigger the pressable. Clicking outside the bounds, where the pressable extends but is clipped by its parent overflow:hidden, should not trigger the pressable.
+
+
+
+
+
+ Press me
+
+
+
+
+
+`;
+
exports[`snapshotAllPages Keyboard 1`] = `
`;
+exports[`snapshotAllPages Pointer Button 1`] = `
+
+
+ Click the box to test pointer events
+
+
+
+`;
+
+exports[`snapshotAllPages Pointer Button 2`] = `
+
+
+ Click the box to test pointer up events
+
+
+
+`;
+
exports[`snapshotAllPages Pointer Events 1`] = `
Move fast and be normal
,
+
+ Move fast and be italic, but just be longer so that you don't fit on a single line and make sure text is not truncated.
+ ,
]
`;
@@ -75303,6 +75539,81 @@ exports[`snapshotAllPages Text 46`] = `
`;
exports[`snapshotAllPages Text 47`] = `
+
+
+ Link Text
+
+
+
+ Nested Link
+
+
+
+ Before
+
+
+ Nested Link
+
+ After
+
+
+
+ Nested Link 1
+
+ -
+
+ Nested Link 2
+
+
+
+`;
+
+exports[`snapshotAllPages Text 48`] = `
`;
-exports[`snapshotAllPages Text 48`] = `
+exports[`snapshotAllPages Text 49`] = `
`;
-exports[`snapshotAllPages Text 49`] = `
+exports[`snapshotAllPages Text 50`] = `
`;
-exports[`snapshotAllPages Text 50`] = `
+exports[`snapshotAllPages Text 51`] = `
@@ -75560,7 +75871,7 @@ exports[`snapshotAllPages Text 50`] = `
`;
-exports[`snapshotAllPages Text 51`] = `
+exports[`snapshotAllPages Text 52`] = `
`;
-exports[`snapshotAllPages Text 52`] = `
+exports[`snapshotAllPages Text 53`] = `
Date: Wed, 25 Mar 2026 13:37:39 -0700
Subject: [PATCH 5/9] overflow: hidden should prevent hittesting
---
.../Fabric/Composition/CompositionViewComponentView.cpp | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp
index 8948a5bb4e1..71d6f2a2f89 100644
--- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp
+++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp
@@ -1174,15 +1174,17 @@ facebook::react::Tag ViewComponentView::hitTest(
facebook::react::Tag targetTag = -1;
+ bool isPointInside = ptLocal.x >= 0 && ptLocal.x <= m_layoutMetrics.frame.size.width && ptLocal.y >= 0 &&
+ ptLocal.y <= m_layoutMetrics.frame.size.height;
+
if ((ignorePointerEvents || m_props->pointerEvents == facebook::react::PointerEventsMode::Auto ||
m_props->pointerEvents == facebook::react::PointerEventsMode::BoxNone) &&
- anyHitTestHelper(targetTag, ptLocal, localPt))
+ (isPointInside || !viewProps()->getClipsContentToBounds()) && anyHitTestHelper(targetTag, ptLocal, localPt))
return targetTag;
if ((ignorePointerEvents || m_props->pointerEvents == facebook::react::PointerEventsMode::Auto ||
m_props->pointerEvents == facebook::react::PointerEventsMode::BoxOnly) &&
- ptLocal.x >= 0 && ptLocal.x <= m_layoutMetrics.frame.size.width && ptLocal.y >= 0 &&
- ptLocal.y <= m_layoutMetrics.frame.size.height) {
+ isPointInside) {
localPt = ptLocal;
return Tag();
}
From 98b7b2cd6e9064e6d2b46e85fdda2dd388207323 Mon Sep 17 00:00:00 2001
From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com>
Date: Wed, 25 Mar 2026 13:46:14 -0700
Subject: [PATCH 6/9] Change files
---
...ative-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 change/react-native-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json
diff --git a/change/react-native-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json b/change/react-native-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json
new file mode 100644
index 00000000000..e7ea0fd6e89
--- /dev/null
+++ b/change/react-native-windows-52161979-b14c-4bbe-b376-b292bbd03a4a.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "overflow: hidden should prevent hittesting",
+ "packageName": "react-native-windows",
+ "email": "30809111+acoates-ms@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
From 9c2fb746373cb22a418a0787d2998b111c2f0c6c Mon Sep 17 00:00:00 2001
From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com>
Date: Wed, 25 Mar 2026 16:35:14 -0700
Subject: [PATCH 7/9] Add tests
---
.../js/examples-win/HitTest/HitTestExample.js | 101 ++++++++++++++++++
.../src/js/utils/RNTesterList.windows.js | 5 +
.../e2e-test-app-fabric/test/HitTest.test.ts | 69 ++++++++++++
3 files changed, 175 insertions(+)
create mode 100644 packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js
create mode 100644 packages/e2e-test-app-fabric/test/HitTest.test.ts
diff --git a/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js b/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js
new file mode 100644
index 00000000000..3e37880ec51
--- /dev/null
+++ b/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ * Licensed under the MIT License.
+ * @format
+ */
+
+'use strict';
+
+import React from 'react';
+
+import {View, Text, Pressable} from 'react-native';
+import {useState} from 'react';
+
+exports.displayName = 'HitTestExample';
+exports.title = 'Hit Testing';
+exports.category = 'Basic';
+exports.description = 'Test that overflow hidden affect hit testing';
+exports.examples = [
+ {
+ title: 'overflow visible affects hit testing\n',
+ render: function (): React.Node {
+ const [bgColor, setBgColor] = React.useState('red');
+
+ return (
+
+
+ Clicking the pressable should work even if it is outside the bounds
+ of its parent.
+
+
+
+ {
+ setBgColor(bgColor === 'red' ? 'green' : 'red');
+ }}>
+ Press me
+
+
+
+
+ );
+ },
+ },
+ {
+ title: 'overflow hidden affects hit testing\n',
+ render: function (): React.Node {
+ const [bgColor, setBgColor] = React.useState('red');
+
+ return (
+
+
+ Clicking within the visible view will trigger the pressable.
+ Clicking outside the bounds, where the pressable extends but is
+ clipped by its parent overflow:hidden, should not trigger the
+ pressable.
+
+
+
+ {
+ setBgColor(bgColor === 'red' ? 'green' : 'red');
+ }}>
+ Press me
+
+
+
+
+ );
+ },
+ },
+];
diff --git a/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js b/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js
index 30164d87def..9fb20e81174 100644
--- a/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js
+++ b/packages/@react-native-windows/tester/src/js/utils/RNTesterList.windows.js
@@ -212,6 +212,11 @@ const Components: Array = [
key: 'LegacyTextHitTestTest',
module: require('../examples-win/LegacyTests/TextHitTestPage'),
},
+ {
+ key: 'HitTestExample',
+ category: 'UI',
+ module: require('../examples-win/HitTest/HitTestExample'),
+ },
{
key: 'PerformanceComparisonExample',
category: 'Basic',
diff --git a/packages/e2e-test-app-fabric/test/HitTest.test.ts b/packages/e2e-test-app-fabric/test/HitTest.test.ts
new file mode 100644
index 00000000000..d71450d11f0
--- /dev/null
+++ b/packages/e2e-test-app-fabric/test/HitTest.test.ts
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) Microsoft Corporation.
+ * Licensed under the MIT License.
+ *
+ * @format
+ */
+
+import { app } from '@react-native-windows/automation';
+import { dumpVisualTree } from '@react-native-windows/automation-commands';
+import { goToComponentExample } from './RNTesterNavigation';
+import { verifyNoErrorLogs } from './Helpers';
+
+beforeAll(async () => {
+ // If window is partially offscreen, tests will fail to click on certain elements
+ await app.setWindowPosition(0, 0);
+ await app.setWindowSize(1000, 1250);
+ await goToComponentExample('Hit Testing');
+});
+
+afterEach(async () => {
+ await verifyNoErrorLogs();
+});
+
+async function verifyElementAccessibiltyValue(element: string, value: string) {
+ const dump = await dumpVisualTree(element);
+ expect(dump!["Automation Tree"]["ValuePattern.Value"]).toBe(value);
+}
+
+describe('Hit Testing', () => {
+
+ test('Hit testing child outside the bounds of parents', async () => {
+ const target = await app.findElementByTestID('visible-overflow-element');
+
+ // View starts in red state
+ await verifyElementAccessibiltyValue('visible-overflow-element', 'red');
+
+ // The webdriverio package computes the offsets from the center point of the target.
+ // This is within the bounds of the child and the parent, so should hitTest even with overflow:visible
+ await target.click({ x: -50, y: -50, });
+
+ await verifyElementAccessibiltyValue('visible-overflow-element', 'green');
+
+ // The webdriverio package computes the offsets from the center point of the target.
+ // This is within the bounds of the child, but outside the parents bounds
+ await target.click({ x: 0, y: 0, });
+
+ // View should still be red, since the click should hit the pressable
+ await verifyElementAccessibiltyValue('visible-overflow-element', 'red');
+ });
+
+ test('Overflow hidden prevents hittesting child', async () => {
+ const target = await app.findElementByTestID('hidden-overflow-element');
+
+ // View starts in red state
+ await verifyElementAccessibiltyValue('hidden-overflow-element', 'red');
+
+ // This is within the bounds of the child and the parent, so should hitTest even with overflow:hidden
+ await target.click({ x: -50, y: -50, });
+
+ await verifyElementAccessibiltyValue('hidden-overflow-element', 'green');
+
+ // This is within the bounds of the child, but shouldn't hit test, since the parent is overflow:hidden
+ await target.click({ x: 0, y: 0, });
+
+ // View should still be green, since the click shouldn't hit the pressable
+ await verifyElementAccessibiltyValue('hidden-overflow-element', 'green');
+ });
+
+});
From fdc6d2a1e3d281ff7b6ace55d32933762235a72e Mon Sep 17 00:00:00 2001
From: Andrew Coates <30809111+acoates-ms@users.noreply.github.com>
Date: Thu, 26 Mar 2026 11:04:29 -0700
Subject: [PATCH 8/9] snapshots + lint
---
.../e2e-test-app-fabric/test/HitTest.test.ts | 20 +--
.../__snapshots__/HomeUIADump.test.ts.snap | 115 ++++++++++--
.../__snapshots__/snapshotPages.test.js.snap | 168 ++++++++++++++++++
3 files changed, 275 insertions(+), 28 deletions(-)
diff --git a/packages/e2e-test-app-fabric/test/HitTest.test.ts b/packages/e2e-test-app-fabric/test/HitTest.test.ts
index d71450d11f0..042943091be 100644
--- a/packages/e2e-test-app-fabric/test/HitTest.test.ts
+++ b/packages/e2e-test-app-fabric/test/HitTest.test.ts
@@ -5,10 +5,10 @@
* @format
*/
-import { app } from '@react-native-windows/automation';
-import { dumpVisualTree } from '@react-native-windows/automation-commands';
-import { goToComponentExample } from './RNTesterNavigation';
-import { verifyNoErrorLogs } from './Helpers';
+import {app} from '@react-native-windows/automation';
+import {dumpVisualTree} from '@react-native-windows/automation-commands';
+import {goToComponentExample} from './RNTesterNavigation';
+import {verifyNoErrorLogs} from './Helpers';
beforeAll(async () => {
// If window is partially offscreen, tests will fail to click on certain elements
@@ -23,11 +23,10 @@ afterEach(async () => {
async function verifyElementAccessibiltyValue(element: string, value: string) {
const dump = await dumpVisualTree(element);
- expect(dump!["Automation Tree"]["ValuePattern.Value"]).toBe(value);
+ expect(dump!['Automation Tree']['ValuePattern.Value']).toBe(value);
}
describe('Hit Testing', () => {
-
test('Hit testing child outside the bounds of parents', async () => {
const target = await app.findElementByTestID('visible-overflow-element');
@@ -36,13 +35,13 @@ describe('Hit Testing', () => {
// The webdriverio package computes the offsets from the center point of the target.
// This is within the bounds of the child and the parent, so should hitTest even with overflow:visible
- await target.click({ x: -50, y: -50, });
+ await target.click({x: -50, y: -50});
await verifyElementAccessibiltyValue('visible-overflow-element', 'green');
// The webdriverio package computes the offsets from the center point of the target.
// This is within the bounds of the child, but outside the parents bounds
- await target.click({ x: 0, y: 0, });
+ await target.click({x: 0, y: 0});
// View should still be red, since the click should hit the pressable
await verifyElementAccessibiltyValue('visible-overflow-element', 'red');
@@ -55,15 +54,14 @@ describe('Hit Testing', () => {
await verifyElementAccessibiltyValue('hidden-overflow-element', 'red');
// This is within the bounds of the child and the parent, so should hitTest even with overflow:hidden
- await target.click({ x: -50, y: -50, });
+ await target.click({x: -50, y: -50});
await verifyElementAccessibiltyValue('hidden-overflow-element', 'green');
// This is within the bounds of the child, but shouldn't hit test, since the parent is overflow:hidden
- await target.click({ x: 0, y: 0, });
+ await target.click({x: 0, y: 0});
// View should still be green, since the click shouldn't hit the pressable
await verifyElementAccessibiltyValue('hidden-overflow-element', 'green');
});
-
});
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap b/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap
index 16457c7c5cf..6060ab66a55 100644
--- a/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/HomeUIADump.test.ts.snap
@@ -2441,6 +2441,87 @@ exports[`Home UIA Tree Dump Glyph UWP 1`] = `
}
`;
+exports[`Home UIA Tree Dump Hit Testing 1`] = `
+{
+ "Automation Tree": {
+ "AutomationId": "Hit Testing",
+ "ControlType": 50026,
+ "IsKeyboardFocusable": true,
+ "LocalizedControlType": "group",
+ "Name": "Hit Testing Test that overflow hidden affect hit testing",
+ "__Children": [
+ {
+ "AutomationId": "",
+ "ControlType": 50020,
+ "LocalizedControlType": "text",
+ "Name": "Hit Testing",
+ "TextRangePattern.GetText": "Hit Testing",
+ },
+ {
+ "AutomationId": "",
+ "ControlType": 50020,
+ "LocalizedControlType": "text",
+ "Name": "Test that overflow hidden affect hit testing",
+ "TextRangePattern.GetText": "Test that overflow hidden affect hit testing",
+ },
+ ],
+ },
+ "Component Tree": {
+ "Type": "Microsoft.ReactNative.Composition.ViewComponentView",
+ "_Props": {
+ "AccessibilityLabel": "Hit Testing Test that overflow hidden affect hit testing",
+ "TestId": "Hit Testing",
+ },
+ "__Children": [
+ {
+ "Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
+ "_Props": {},
+ },
+ {
+ "Type": "Microsoft.ReactNative.Composition.ParagraphComponentView",
+ "_Props": {},
+ },
+ ],
+ },
+ "Visual Tree": {
+ "Brush": {
+ "Brush Type": "ColorBrush",
+ "Color": "rgba(255, 255, 255, 255)",
+ },
+ "Comment": "Hit Testing",
+ "Offset": "0, 0, 0",
+ "Size": "966, 77",
+ "Visual Type": "SpriteVisual",
+ "__Children": [
+ {
+ "Offset": "16, 16, 0",
+ "Size": "85, 25",
+ "Visual Type": "SpriteVisual",
+ "__Children": [
+ {
+ "Offset": "0, 0, 0",
+ "Size": "85, 25",
+ "Visual Type": "SpriteVisual",
+ },
+ ],
+ },
+ {
+ "Offset": "16, 45, 0",
+ "Size": "934, 17",
+ "Visual Type": "SpriteVisual",
+ "__Children": [
+ {
+ "Offset": "0, 0, 0",
+ "Size": "934, 17",
+ "Visual Type": "SpriteVisual",
+ },
+ ],
+ },
+ ],
+ },
+}
+`;
+
exports[`Home UIA Tree Dump Image 1`] = `
{
"Automation Tree": {
@@ -2490,7 +2571,7 @@ exports[`Home UIA Tree Dump Image 1`] = `
},
"Comment": "Image",
"Offset": "0, 0, 0",
- "Size": "966, 77",
+ "Size": "966, 78",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -3317,12 +3398,12 @@ exports[`Home UIA Tree Dump LegacyLoginTest 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
},
],
@@ -3398,12 +3479,12 @@ exports[`Home UIA Tree Dump LegacySelectableTextTest 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
},
],
@@ -3948,7 +4029,7 @@ exports[`Home UIA Tree Dump Moving Light Example 1`] = `
},
"Comment": "Moving Light Example",
"Offset": "0, 0, 0",
- "Size": "966, 78",
+ "Size": "966, 77",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -4110,7 +4191,7 @@ exports[`Home UIA Tree Dump New App Screen 1`] = `
},
"Comment": "New App Screen",
"Offset": "0, 0, 0",
- "Size": "966, 77",
+ "Size": "966, 78",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -4937,12 +5018,12 @@ exports[`Home UIA Tree Dump ScrollView 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 17",
+ "Size": "934, 16",
"Visual Type": "SpriteVisual",
},
],
@@ -5018,12 +5099,12 @@ exports[`Home UIA Tree Dump ScrollViewAnimated 1`] = `
},
{
"Offset": "16, 45, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "934, 16",
+ "Size": "934, 17",
"Visual Type": "SpriteVisual",
},
],
@@ -5597,7 +5678,7 @@ exports[`Home UIA Tree Dump Text 1`] = `
},
"Comment": "Text",
"Offset": "0, 0, 0",
- "Size": "966, 78",
+ "Size": "966, 77",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -5678,7 +5759,7 @@ exports[`Home UIA Tree Dump TextInput 1`] = `
},
"Comment": "TextInput",
"Offset": "0, 0, 0",
- "Size": "966, 77",
+ "Size": "966, 78",
"Visual Type": "SpriteVisual",
"__Children": [
{
@@ -6169,12 +6250,12 @@ exports[`Home UIA Tree Dump TransparentHitTestExample 1`] = `
"__Children": [
{
"Offset": "16, 16, 0",
- "Size": "214, 25",
+ "Size": "214, 24",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "214, 25",
+ "Size": "214, 24",
"Visual Type": "SpriteVisual",
},
],
@@ -6412,12 +6493,12 @@ exports[`Home UIA Tree Dump View 1`] = `
"__Children": [
{
"Offset": "16, 16, 0",
- "Size": "38, 24",
+ "Size": "38, 25",
"Visual Type": "SpriteVisual",
"__Children": [
{
"Offset": "0, 0, 0",
- "Size": "38, 24",
+ "Size": "38, 25",
"Visual Type": "SpriteVisual",
},
],
diff --git a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap
index 731a03eea1e..2371b90f71a 100644
--- a/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap
+++ b/packages/e2e-test-app-fabric/test/__snapshots__/snapshotPages.test.js.snap
@@ -26040,6 +26040,174 @@ exports[`snapshotAllPages Glyph UWP 1`] = `
`;
+exports[`snapshotAllPages Hit Testing 1`] = `
+
+
+ Clicking the pressable should work even if it is outside the bounds of its parent.
+
+
+
+
+
+ Press me
+
+
+
+
+
+`;
+
+exports[`snapshotAllPages Hit Testing 2`] = `
+
+
+ Clicking within the visible view will trigger the pressable. Clicking outside the bounds, where the pressable extends but is clipped by its parent overflow:hidden, should not trigger the pressable.
+
+
+
+
+
+ Press me
+
+
+
+
+
+`;
+
exports[`snapshotAllPages Keyboard 1`] = `
Date: Fri, 27 Mar 2026 11:21:26 -0700
Subject: [PATCH 9/9] lint fix
---
.../js/examples-win/HitTest/HitTestExample.js | 157 +++++++++---------
1 file changed, 81 insertions(+), 76 deletions(-)
diff --git a/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js b/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js
index 3e37880ec51..68558983a4e 100644
--- a/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js
+++ b/packages/@react-native-windows/tester/src/js/examples-win/HitTest/HitTestExample.js
@@ -7,9 +7,84 @@
'use strict';
import React from 'react';
+import { View, Text, Pressable } from 'react-native';
-import {View, Text, Pressable} from 'react-native';
-import {useState} from 'react';
+function HitTestWithOverflowVisibile() {
+ const [bgColor, setBgColor] = React.useState('red');
+
+ return (
+
+
+ Clicking the pressable should work even if it is outside the bounds
+ of its parent.
+
+
+
+ {
+ setBgColor(bgColor === 'red' ? 'green' : 'red');
+ }}>
+ Press me
+
+
+
+
+ );
+}
+
+function HitTestWithOverflowHidden() {
+ const [bgColor, setBgColor] = React.useState('red');
+ return (
+
+
+ Clicking within the visible view will trigger the pressable.
+ Clicking outside the bounds, where the pressable extends but is
+ clipped by its parent overflow:hidden, should not trigger the
+ pressable.
+
+
+
+ {
+ setBgColor(bgColor === 'red' ? 'green' : 'red');
+ }}>
+ Press me
+
+
+
+
+ );
+}
exports.displayName = 'HitTestExample';
exports.title = 'Hit Testing';
@@ -18,84 +93,14 @@ exports.description = 'Test that overflow hidden affect hit testing';
exports.examples = [
{
title: 'overflow visible affects hit testing\n',
- render: function (): React.Node {
- const [bgColor, setBgColor] = React.useState('red');
-
- return (
-
-
- Clicking the pressable should work even if it is outside the bounds
- of its parent.
-
-
-
- {
- setBgColor(bgColor === 'red' ? 'green' : 'red');
- }}>
- Press me
-
-
-
-
- );
+ render: function () {
+ return ;
},
},
{
title: 'overflow hidden affects hit testing\n',
- render: function (): React.Node {
- const [bgColor, setBgColor] = React.useState('red');
-
- return (
-
-
- Clicking within the visible view will trigger the pressable.
- Clicking outside the bounds, where the pressable extends but is
- clipped by its parent overflow:hidden, should not trigger the
- pressable.
-
-
-
- {
- setBgColor(bgColor === 'red' ? 'green' : 'red');
- }}>
- Press me
-
-
-
-
- );
+ render: function () {
+ return ;
},
},
];