diff --git a/packages/@react-native/tester/js/examples/Touchable/TouchableExample.js b/packages/@react-native/tester/js/examples/Touchable/TouchableExample.js index 8f6f0949cd8..17321394a45 100644 --- a/packages/@react-native/tester/js/examples/Touchable/TouchableExample.js +++ b/packages/@react-native/tester/js/examples/Touchable/TouchableExample.js @@ -312,6 +312,107 @@ class TouchableHitSlop extends React.Component<{...}, $FlowFixMeState> { } } +class TouchableWithoutFeedbackHitSlop extends React.Component< + {...}, + $FlowFixMeState, +> { + state: any | {timesPressed: number} = { + timesPressed: 0, + }; + + onPress = () => { + this.setState({ + timesPressed: this.state.timesPressed + 1, + }); + }; + + render(): React.Node { + let log = ''; + if (this.state.timesPressed > 1) { + log = this.state.timesPressed + 'x onPress'; + } else if (this.state.timesPressed > 0) { + log = 'onPress'; + } + + return ( + + + + + + Press Outside This View + + + + + + + {log} + + + + ); + } +} + +class TouchableWithoutFeedbackStyleUpdate extends React.Component< + {...}, + $FlowFixMeState, +> { + state: any | {dynamicColor: string, timesPressed: number} = { + dynamicColor: '#007AFF', + timesPressed: 0, + }; + + onPress = () => { + const colors = ['#007AFF', '#FF6B35', '#4ECDC4', '#45B7D1', '#96CEB4']; + const nextColor = colors[(this.state.timesPressed + 1) % colors.length]; + this.setState({ + dynamicColor: nextColor, + timesPressed: this.state.timesPressed + 1, + }); + }; + + render(): React.Node { + const dynamicStyle = { + backgroundColor: this.state.dynamicColor, + padding: 10, + borderRadius: 8, + }; + + let log = ''; + if (this.state.timesPressed > 1) { + log = this.state.timesPressed + 'x style updated'; + } else if (this.state.timesPressed > 0) { + log = 'style updated'; + } + + return ( + + + + + + Press to Update Style! + + + + + + + {log} + + + + ); + } +} + function TouchableNativeMethodChecker< T: component(ref?: React.RefSetter, ...any), >(props: {Component: T, name: string}): React.Node { @@ -776,6 +877,23 @@ exports.examples = [ return ; }, }, + { + title: 'TouchableWithoutFeedback Hit Slop', + description: + ('TouchableWithoutFeedback accepts hitSlop prop which extends the touch area ' + + 'without changing the view bounds.': string), + render(): React.MixedElement { + return ; + }, + }, + { + title: 'TouchableWithoutFeedback Style Update', + description: + ('TouchableWithoutFeedback can update styles dynamically and should support fast refresh.': string), + render(): React.MixedElement { + return ; + }, + }, { title: 'Touchable Native Methods', description: diff --git a/packages/e2e-test-app-fabric/test/TouchableComponentTest.test.ts b/packages/e2e-test-app-fabric/test/TouchableComponentTest.test.ts index ba1f75fbcc4..a3e36c9c386 100644 --- a/packages/e2e-test-app-fabric/test/TouchableComponentTest.test.ts +++ b/packages/e2e-test-app-fabric/test/TouchableComponentTest.test.ts @@ -116,4 +116,42 @@ describe('Touchable Tests', () => { expect(dump2).toMatchSnapshot(); await searchBox(''); }); + test('TouchableWithoutFeedback should register press in clicked within hitSlop range', async () => { + await searchBox('TouchableWithoutFeedback Hit Slop'); + const component = await app.findElementByTestID( + 'touchable_without_feedback_hit_slop_button', + ); + await component.waitForDisplayed({timeout: 5000}); + const dump = await dumpVisualTree( + 'touchable_without_feedback_hit_slop_button', + ); + expect(dump).toMatchSnapshot(); + await component.click(); + const dump2 = await dumpVisualTree( + 'touchable_without_feedback_hit_slop_console', + ); + expect(dump2).toMatchSnapshot(); + await searchBox(''); + }); + test('TouchableWithoutFeedback should update style upon fast refresh', async () => { + await searchBox('TouchableWithoutFeedback Style Update'); + const component = await app.findElementByTestID( + 'touchable_without_feedback_style_update_button', + ); + await component.waitForDisplayed({timeout: 5000}); + const dump = await dumpVisualTree( + 'touchable_without_feedback_style_update_button', + ); + expect(dump).toMatchSnapshot(); + await component.click(); + const dump2 = await dumpVisualTree( + 'touchable_without_feedback_style_update_button', + ); + expect(dump2).toMatchSnapshot(); + const dump3 = await dumpVisualTree( + 'touchable_without_feedback_style_update_console', + ); + expect(dump3).toMatchSnapshot(); + await searchBox(''); + }); });